Skip to content

Commit

Permalink
Load user assets with their teams and record reply daily counts for o…
Browse files Browse the repository at this point in the history
…rg, user and team
  • Loading branch information
rowanseymour committed May 10, 2022
1 parent 3a362e6 commit cb5465e
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 23 deletions.
39 changes: 30 additions & 9 deletions core/models/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -697,22 +697,24 @@ type BroadcastTranslation struct {
// Broadcast represents a broadcast that needs to be sent
type Broadcast struct {
b struct {
BroadcastID BroadcastID `json:"broadcast_id,omitempty" db:"id"`
BroadcastID BroadcastID `json:"broadcast_id,omitempty" db:"id"`
Translations map[envs.Language]*BroadcastTranslation `json:"translations"`
Text hstore.Hstore ` db:"text"`
Text hstore.Hstore ` db:"text"`
TemplateState TemplateState `json:"template_state"`
BaseLanguage envs.Language `json:"base_language" db:"base_language"`
BaseLanguage envs.Language `json:"base_language" db:"base_language"`
URNs []urns.URN `json:"urns,omitempty"`
ContactIDs []ContactID `json:"contact_ids,omitempty"`
GroupIDs []GroupID `json:"group_ids,omitempty"`
OrgID OrgID `json:"org_id" db:"org_id"`
ParentID BroadcastID `json:"parent_id,omitempty" db:"parent_id"`
TicketID TicketID `json:"ticket_id,omitempty" db:"ticket_id"`
OrgID OrgID `json:"org_id" db:"org_id"`
CreatedByID UserID `json:"created_by_id,omitempty" db:"created_by_id"`
ParentID BroadcastID `json:"parent_id,omitempty" db:"parent_id"`
TicketID TicketID `json:"ticket_id,omitempty" db:"ticket_id"`
}
}

func (b *Broadcast) ID() BroadcastID { return b.b.BroadcastID }
func (b *Broadcast) OrgID() OrgID { return b.b.OrgID }
func (b *Broadcast) CreatedByID() UserID { return b.b.CreatedByID }
func (b *Broadcast) ContactIDs() []ContactID { return b.b.ContactIDs }
func (b *Broadcast) GroupIDs() []GroupID { return b.b.GroupIDs }
func (b *Broadcast) URNs() []urns.URN { return b.b.URNs }
Expand All @@ -727,7 +729,7 @@ func (b *Broadcast) UnmarshalJSON(data []byte) error { return json.Unmarshal(dat
// NewBroadcast creates a new broadcast with the passed in parameters
func NewBroadcast(
orgID OrgID, id BroadcastID, translations map[envs.Language]*BroadcastTranslation,
state TemplateState, baseLanguage envs.Language, urns []urns.URN, contactIDs []ContactID, groupIDs []GroupID, ticketID TicketID) *Broadcast {
state TemplateState, baseLanguage envs.Language, urns []urns.URN, contactIDs []ContactID, groupIDs []GroupID, ticketID TicketID, createdByID UserID) *Broadcast {

bcast := &Broadcast{}
bcast.b.OrgID = orgID
Expand All @@ -739,6 +741,7 @@ func NewBroadcast(
bcast.b.ContactIDs = contactIDs
bcast.b.GroupIDs = groupIDs
bcast.b.TicketID = ticketID
bcast.b.CreatedByID = createdByID

return bcast
}
Expand All @@ -755,8 +758,8 @@ func InsertChildBroadcast(ctx context.Context, db Queryer, parent *Broadcast) (*
parent.b.ContactIDs,
parent.b.GroupIDs,
parent.b.TicketID,
parent.b.CreatedByID,
)
// populate our parent id
child.b.ParentID = parent.ID()

// populate text from our translations
Expand Down Expand Up @@ -894,7 +897,7 @@ func NewBroadcastFromEvent(ctx context.Context, tx Queryer, oa *OrgAssets, event
}
}

return NewBroadcast(oa.OrgID(), NilBroadcastID, translations, TemplateStateEvaluated, event.BaseLanguage, event.URNs, contactIDs, groupIDs, NilTicketID), nil
return NewBroadcast(oa.OrgID(), NilBroadcastID, translations, TemplateStateEvaluated, event.BaseLanguage, event.URNs, contactIDs, groupIDs, NilTicketID, NilUserID), nil
}

func (b *Broadcast) CreateBatch(contactIDs []ContactID) *BroadcastBatch {
Expand All @@ -904,6 +907,7 @@ func (b *Broadcast) CreateBatch(contactIDs []ContactID) *BroadcastBatch {
batch.b.Translations = b.b.Translations
batch.b.TemplateState = b.b.TemplateState
batch.b.OrgID = b.b.OrgID
batch.b.CreatedByID = b.b.CreatedByID
batch.b.TicketID = b.b.TicketID
batch.b.ContactIDs = contactIDs
return batch
Expand All @@ -920,6 +924,7 @@ type BroadcastBatch struct {
ContactIDs []ContactID `json:"contact_ids,omitempty"`
IsLast bool `json:"is_last"`
OrgID OrgID `json:"org_id"`
CreatedByID UserID `json:"created_by_id"`
TicketID TicketID `json:"ticket_id"`
}
}
Expand All @@ -929,6 +934,7 @@ func (b *BroadcastBatch) ContactIDs() []ContactID { return b.b.Conta
func (b *BroadcastBatch) URNs() map[ContactID]urns.URN { return b.b.URNs }
func (b *BroadcastBatch) SetURNs(urns map[ContactID]urns.URN) { b.b.URNs = urns }
func (b *BroadcastBatch) OrgID() OrgID { return b.b.OrgID }
func (b *BroadcastBatch) CreatedByID() UserID { return b.b.CreatedByID }
func (b *BroadcastBatch) TicketID() TicketID { return b.b.TicketID }
func (b *BroadcastBatch) Translations() map[envs.Language]*BroadcastTranslation {
return b.b.Translations
Expand Down Expand Up @@ -1146,6 +1152,21 @@ func CreateBroadcastMessages(ctx context.Context, rt *runtime.Runtime, oa *OrgAs
if err != nil {
return nil, errors.Wrapf(err, "error updating broadcast ticket")
}

// record reply counts for org, user and team
replyCounts := map[string]int{scopeOrg(oa): 1}

if bcast.CreatedByID() != NilUserID {
user := oa.UserByID(bcast.CreatedByID())
if user != nil {
replyCounts[scopeUser(oa, user)] = 1
if user.Team() != nil {
replyCounts[scopeTeam(user.Team())] = 1
}
}
}

insertTicketDailyCounts(ctx, rt.DB, TicketDailyCountReply, oa.Org().Timezone(), replyCounts)
}

return msgs, nil
Expand Down
1 change: 1 addition & 0 deletions core/models/msgs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ func TestNonPersistentBroadcasts(t *testing.T) {
[]models.ContactID{testdata.Alexandria.ID, testdata.Bob.ID, testdata.Cathy.ID},
[]models.GroupID{testdata.DoctorsGroup.ID},
ticket.ID,
models.NilUserID,
)

assert.Equal(t, models.NilBroadcastID, bcast.ID())
Expand Down
43 changes: 43 additions & 0 deletions core/models/teams.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package models

import (
"database/sql/driver"

"github.com/nyaruka/null"
)

const (
// NilTeamID is the id 0 considered as nil user id
NilTeamID = TeamID(0)
)

// TeamID is our type for team ids, which can be null
type TeamID null.Int

type TeamUUID string

type Team struct {
ID TeamID `json:"id"`
UUID TeamUUID `json:"uuid"`
Name string `json:"name"`
}

// MarshalJSON marshals into JSON. 0 values will become null
func (i TeamID) MarshalJSON() ([]byte, error) {
return null.Int(i).MarshalJSON()
}

// UnmarshalJSON unmarshals from JSON. null values become 0
func (i *TeamID) UnmarshalJSON(b []byte) error {
return null.UnmarshalInt(b, (*null.Int)(i))
}

// Value returns the db value, null is returned for 0
func (i TeamID) Value() (driver.Value, error) {
return null.Int(i).Value()
}

// Scan scans from the db value. null values become 0
func (i *TeamID) Scan(value interface{}) error {
return null.ScanInt(value, (*null.Int)(i))
}
18 changes: 14 additions & 4 deletions core/models/tickets.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,10 @@ func InsertTickets(ctx context.Context, tx Queryer, oa *OrgAssets, tickets []*Ti
ts[i] = &t.t

if t.AssigneeID() != NilUserID {
assignmentCounts[scopeUser(oa, t.AssigneeID())]++
assignee := oa.UserByID(t.AssigneeID())
if assignee != nil {
assignmentCounts[scopeUser(oa, assignee)]++
}
}
}

Expand Down Expand Up @@ -449,7 +452,10 @@ func TicketsAssign(ctx context.Context, db Queryer, oa *OrgAssets, userID UserID

// if this is an initial assignment record count for user
if ticket.AssigneeID() == NilUserID && assigneeID != NilUserID {
assignmentCounts[scopeUser(oa, assigneeID)]++
assignee := oa.UserByID(assigneeID)
if assignee != nil {
assignmentCounts[scopeUser(oa, assignee)]++
}
}

ids = append(ids, ticket.ID())
Expand Down Expand Up @@ -899,6 +905,10 @@ func scopeOrg(oa *OrgAssets) string {
return fmt.Sprintf("o:%d", oa.OrgID())
}

func scopeUser(oa *OrgAssets, uid UserID) string {
return fmt.Sprintf("o:%d:u:%d", oa.OrgID(), uid)
func scopeTeam(t *Team) string {
return fmt.Sprintf("t:%d", t.ID)
}

func scopeUser(oa *OrgAssets, u *User) string {
return fmt.Sprintf("o:%d:u:%d", oa.OrgID(), u.ID())
}
11 changes: 10 additions & 1 deletion core/models/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type User struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Role UserRole `json:"role"`
Team *Team `json:"team"`
}
}

Expand All @@ -85,6 +86,11 @@ func (u *User) Name() string {
return strings.Join(names, " ")
}

// Team returns the user's ticketing team if any
func (u *User) Team() *Team {
return u.u.Team
}

var _ assets.User = (*User)(nil)

const selectOrgUsersSQL = `
Expand All @@ -93,7 +99,8 @@ SELECT ROW_TO_JSON(r) FROM (SELECT
u.email AS "email",
u.first_name as "first_name",
u.last_name as "last_name",
o.role AS "role"
o.role AS "role",
row_to_json(team_struct) AS team
FROM
auth_user u
INNER JOIN (
Expand All @@ -107,6 +114,8 @@ INNER JOIN (
UNION
SELECT user_id, 'S' AS "role" FROM orgs_org_surveyors WHERE org_id = $1
) o ON o.user_id = u.id
LEFT JOIN orgs_usersettings s ON s.user_id = u.id
LEFT JOIN LATERAL (SELECT id, uuid, name FROM tickets_team WHERE tickets_team.id = s.team_id) AS team_struct ON True
WHERE
u.is_active = TRUE
ORDER BY
Expand Down
15 changes: 10 additions & 5 deletions core/models/users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,21 @@ func TestLoadUsers(t *testing.T) {
users, err := oa.Users()
require.NoError(t, err)

partners := &models.Team{testdata.Partners.ID, testdata.Partners.UUID, "Partners"}
office := &models.Team{testdata.Office.ID, testdata.Office.UUID, "Office"}

expectedUsers := []struct {
id models.UserID
email string
name string
role models.UserRole
team *models.Team
}{
{testdata.Admin.ID, testdata.Admin.Email, "Andy Admin", models.UserRoleAdministrator},
{testdata.Agent.ID, testdata.Agent.Email, "Ann D'Agent", models.UserRoleAgent},
{testdata.Editor.ID, testdata.Editor.Email, "Ed McEditor", models.UserRoleEditor},
{testdata.Surveyor.ID, testdata.Surveyor.Email, "Steve Surveys", models.UserRoleSurveyor},
{testdata.Viewer.ID, testdata.Viewer.Email, "Veronica Views", models.UserRoleViewer},
{id: testdata.Admin.ID, email: testdata.Admin.Email, name: "Andy Admin", role: models.UserRoleAdministrator, team: office},
{id: testdata.Agent.ID, email: testdata.Agent.Email, name: "Ann D'Agent", role: models.UserRoleAgent, team: partners},
{id: testdata.Editor.ID, email: testdata.Editor.Email, name: "Ed McEditor", role: models.UserRoleEditor, team: office},
{id: testdata.Surveyor.ID, email: testdata.Surveyor.Email, name: "Steve Surveys", role: models.UserRoleSurveyor, team: nil},
{id: testdata.Viewer.ID, email: testdata.Viewer.Email, name: "Veronica Views", role: models.UserRoleViewer, team: nil},
}

require.Equal(t, len(expectedUsers), len(users))
Expand All @@ -43,6 +47,7 @@ func TestLoadUsers(t *testing.T) {
assert.Equal(t, expected.id, modelUser.ID())
assert.Equal(t, expected.email, modelUser.Email())
assert.Equal(t, expected.role, modelUser.Role())
assert.Equal(t, expected.team, modelUser.Team())

assert.Equal(t, modelUser, oa.UserByID(expected.id))
assert.Equal(t, modelUser, oa.UserByEmail(expected.email))
Expand Down
13 changes: 10 additions & 3 deletions core/tasks/msgs/send_broadcast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ func TestBroadcastTask(t *testing.T) {
ContactIDs []models.ContactID
URNs []urns.URN
TicketID models.TicketID
CreatedByID models.UserID
Queue string
BatchCount int
MsgCount int
Expand All @@ -190,7 +191,8 @@ func TestBroadcastTask(t *testing.T) {
doctorsOnly,
cathyOnly,
nil,
ticket.ID,
models.NilTicketID,
testdata.Admin.ID,
queue.BatchQueue,
2,
121,
Expand All @@ -205,6 +207,7 @@ func TestBroadcastTask(t *testing.T) {
cathyOnly,
nil,
models.NilTicketID,
models.NilUserID,
queue.HandlerQueue,
1,
1,
Expand All @@ -218,7 +221,8 @@ func TestBroadcastTask(t *testing.T) {
nil,
cathyOnly,
nil,
models.NilTicketID,
ticket.ID,
testdata.Agent.ID,
queue.HandlerQueue,
1,
1,
Expand All @@ -231,7 +235,7 @@ func TestBroadcastTask(t *testing.T) {

for i, tc := range tcs {
// handle our start task
bcast := models.NewBroadcast(oa.OrgID(), tc.BroadcastID, tc.Translations, tc.TemplateState, tc.BaseLanguage, tc.URNs, tc.ContactIDs, tc.GroupIDs, tc.TicketID)
bcast := models.NewBroadcast(oa.OrgID(), tc.BroadcastID, tc.Translations, tc.TemplateState, tc.BaseLanguage, tc.URNs, tc.ContactIDs, tc.GroupIDs, tc.TicketID, tc.CreatedByID)
err = msgs.CreateBroadcastBatches(ctx, rt, bcast)
assert.NoError(t, err)

Expand Down Expand Up @@ -277,4 +281,7 @@ func TestBroadcastTask(t *testing.T) {
lastNow = time.Now()
time.Sleep(10 * time.Millisecond)
}

assertdb.Query(t, db, `SELECT SUM(count) FROM tickets_ticketdailycount WHERE count_type = 'R' AND scope = CONCAT('o:', $1::text)`, testdata.Org1.ID).Returns(1)
assertdb.Query(t, db, `SELECT SUM(count) FROM tickets_ticketdailycount WHERE count_type = 'R' AND scope = CONCAT('o:', $1::text, ':u:', $2::text)`, testdata.Org1.ID, testdata.Agent.ID).Returns(1)
}
Binary file modified mailroom_test.dump
Binary file not shown.
2 changes: 1 addition & 1 deletion services/tickets/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func SendReply(ctx context.Context, rt *runtime.Runtime, ticket *models.Ticket,
translations := map[envs.Language]*models.BroadcastTranslation{envs.Language("base"): base}

// we'll use a broadcast to send this message
bcast := models.NewBroadcast(oa.OrgID(), models.NilBroadcastID, translations, models.TemplateStateEvaluated, envs.Language("base"), nil, nil, nil, ticket.ID())
bcast := models.NewBroadcast(oa.OrgID(), models.NilBroadcastID, translations, models.TemplateStateEvaluated, envs.Language("base"), nil, nil, nil, ticket.ID(), models.NilUserID)
batch := bcast.CreateBatch([]models.ContactID{ticket.ContactID()})
msgs, err := models.CreateBroadcastMessages(ctx, rt, oa, batch)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions testsuite/testdata/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ var Mailgun = &Ticketer{2, "f9c9447f-a291-4f3c-8c79-c089bbd4e713"}
var Zendesk = &Ticketer{3, "4ee6d4f3-f92b-439b-9718-8da90c05490b"}
var RocketChat = &Ticketer{4, "6c50665f-b4ff-4e37-9625-bc464fe6a999"}

var Partners = &Team{1, "4321c30b-b596-46fa-adb4-4a46d37923f6"}
var Office = &Team{2, "f14c1762-d38b-4072-ae63-2705332a3719"}

var Luis = &Classifier{1, "097e026c-ae79-4740-af67-656dbedf0263"}
var Wit = &Classifier{2, "ff2a817c-040a-4eb2-8404-7d92e8b79dd0"}
var Bothub = &Classifier{3, "859b436d-3005-4e43-9ad5-3de5f26ede4c"}
Expand Down
5 changes: 5 additions & 0 deletions testsuite/testdata/tickets.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ type Ticket struct {
UUID flows.TicketUUID
}

type Team struct {
ID models.TeamID
UUID models.TeamUUID
}

func (k *Ticket) Load(db *sqlx.DB) *models.Ticket {
tickets, err := models.LoadTickets(context.Background(), db, []models.TicketID{k.ID})
must(err, len(tickets) == 1)
Expand Down

0 comments on commit cb5465e

Please sign in to comment.