diff --git a/core/models/tickets.go b/core/models/tickets.go index db6771da5..0467057c3 100644 --- a/core/models/tickets.go +++ b/core/models/tickets.go @@ -345,18 +345,30 @@ func InsertTickets(ctx context.Context, tx Queryer, oa *OrgAssets, tickets []*Ti return nil } + openingCounts := map[string]int{scopeOrg(oa): len(tickets)} // all new tickets are open + assignmentCounts := make(map[string]int) + ts := make([]interface{}, len(tickets)) - for i := range tickets { - ts[i] = &tickets[i].t + for i, t := range tickets { + ts[i] = &t.t + + if t.AssigneeID() != NilUserID { + assignmentCounts[scopeUser(oa, t.AssigneeID())]++ + } } if err := BulkQuery(ctx, "inserted tickets", tx, sqlInsertTicket, ts); err != nil { return err } - return insertTicketDailyCounts(ctx, tx, TicketDailyCountOpening, oa.Org().Timezone(), map[string]int{ - fmt.Sprintf("o:%d", oa.OrgID()): len(tickets), - }) + if err := insertTicketDailyCounts(ctx, tx, TicketDailyCountOpening, oa.Org().Timezone(), openingCounts); err != nil { + return err + } + if err := insertTicketDailyCounts(ctx, tx, TicketDailyCountAssignment, oa.Org().Timezone(), assignmentCounts); err != nil { + return err + } + + return nil } const sqlInsertTicketDailyCount = ` @@ -430,8 +442,16 @@ func TicketsAssign(ctx context.Context, db Queryer, oa *OrgAssets, userID UserID eventsByTicket := make(map[*Ticket]*TicketEvent, len(tickets)) now := dates.Now() + assignmentCounts := make(map[string]int) + for _, ticket := range tickets { if ticket.AssigneeID() != assigneeID { + + // if this is an initial assignment record count for user + if ticket.AssigneeID() == NilUserID && assigneeID != NilUserID { + assignmentCounts[scopeUser(oa, assigneeID)]++ + } + ids = append(ids, ticket.ID()) t := &ticket.t t.AssigneeID = assigneeID @@ -460,6 +480,11 @@ func TicketsAssign(ctx context.Context, db Queryer, oa *OrgAssets, userID UserID return nil, errors.Wrap(err, "error inserting notifications") } + err = insertTicketDailyCounts(ctx, db, TicketDailyCountAssignment, oa.Org().Timezone(), assignmentCounts) + if err != nil { + return nil, errors.Wrap(err, "error inserting assignment counts") + } + return eventsByTicket, nil } @@ -869,3 +894,11 @@ func (i TicketerID) Value() (driver.Value, error) { func (i *TicketerID) Scan(value interface{}) error { return null.ScanInt(value, (*null.Int)(i)) } + +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) +} diff --git a/core/models/tickets_test.go b/core/models/tickets_test.go index f0fd516f9..fe90bfcf0 100644 --- a/core/models/tickets_test.go +++ b/core/models/tickets_test.go @@ -1,9 +1,11 @@ package models_test import ( + "fmt" "testing" "time" + "github.com/jmoiron/sqlx" "github.com/nyaruka/gocommon/dates" "github.com/nyaruka/gocommon/dbutil/assertdb" "github.com/nyaruka/gocommon/httpx" @@ -90,13 +92,13 @@ func TestTickets(t *testing.T) { ) ticket3 := models.NewTicket( "28ef8ddc-b221-42f3-aeae-ee406fc9d716", - testdata.Org2.ID, + testdata.Org1.ID, testdata.Alexandria.ID, testdata.Zendesk.ID, "EX6677", testdata.SupportTopic.ID, "Where are my pants?", - testdata.Org2Admin.ID, + testdata.Admin.ID, nil, ) @@ -116,8 +118,11 @@ func TestTickets(t *testing.T) { // check all tickets were created assertdb.Query(t, db, `SELECT count(*) FROM tickets_ticket WHERE status = 'O' AND closed_on IS NULL`).Returns(3) - // check the opened count was added - assertdb.Query(t, db, `SELECT SUM(count) FROM tickets_ticketdailycount WHERE count_type = 'O' AND scope = CONCAT('o:', $1::text)`, testdata.Org1.ID).Returns(3) + // check counts were added + assertTicketDailyCount(t, db, models.TicketDailyCountOpening, fmt.Sprintf("o:%d", testdata.Org1.ID), 3) + assertTicketDailyCount(t, db, models.TicketDailyCountOpening, fmt.Sprintf("o:%d", testdata.Org2.ID), 0) + assertTicketDailyCount(t, db, models.TicketDailyCountAssignment, fmt.Sprintf("o:%d:u:%d", testdata.Org1.ID, testdata.Admin.ID), 2) + assertTicketDailyCount(t, db, models.TicketDailyCountAssignment, fmt.Sprintf("o:%d:u:%d", testdata.Org1.ID, testdata.Editor.ID), 0) // can lookup a ticket by UUID tk1, err := models.LookupTicketByUUID(ctx, db, "2ef57efc-d85f-4291-b330-e4afe68af5fe") @@ -196,22 +201,31 @@ func TestTicketsAssign(t *testing.T) { ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Where my pants", "234", nil) modelTicket2 := ticket2.Load(db) + // create ticket already assigned to a user + ticket3 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Where my glasses", "", testdata.Admin) + modelTicket3 := ticket3.Load(db) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "", "", nil) - evts, err := models.TicketsAssign(ctx, db, oa, testdata.Admin.ID, []*models.Ticket{modelTicket1, modelTicket2}, testdata.Agent.ID, "please handle these") + evts, err := models.TicketsAssign(ctx, db, oa, testdata.Admin.ID, []*models.Ticket{modelTicket1, modelTicket2, modelTicket3}, testdata.Agent.ID, "please handle these") require.NoError(t, err) - assert.Equal(t, 2, len(evts)) + assert.Equal(t, 3, len(evts)) assert.Equal(t, models.TicketEventTypeAssigned, evts[modelTicket1].EventType()) assert.Equal(t, models.TicketEventTypeAssigned, evts[modelTicket2].EventType()) + assert.Equal(t, models.TicketEventTypeAssigned, evts[modelTicket3].EventType()) // check tickets are now assigned assertdb.Query(t, db, `SELECT assignee_id FROM tickets_ticket WHERE id = $1`, ticket1.ID).Columns(map[string]interface{}{"assignee_id": int64(testdata.Agent.ID)}) assertdb.Query(t, db, `SELECT assignee_id FROM tickets_ticket WHERE id = $1`, ticket2.ID).Columns(map[string]interface{}{"assignee_id": int64(testdata.Agent.ID)}) + assertdb.Query(t, db, `SELECT assignee_id FROM tickets_ticket WHERE id = $1`, ticket3.ID).Columns(map[string]interface{}{"assignee_id": int64(testdata.Agent.ID)}) - // and there are new assigned events - assertdb.Query(t, db, `SELECT count(*) FROM tickets_ticketevent WHERE event_type = 'A' AND note = 'please handle these'`).Returns(2) - + // and there are new assigned events with notifications + assertdb.Query(t, db, `SELECT count(*) FROM tickets_ticketevent WHERE event_type = 'A' AND note = 'please handle these'`).Returns(3) assertdb.Query(t, db, `SELECT count(*) FROM notifications_notification WHERE user_id = $1 AND notification_type = 'tickets:activity'`, testdata.Agent.ID).Returns(1) + + // and daily counts (we only count first assignments of a ticket) + assertTicketDailyCount(t, db, models.TicketDailyCountAssignment, fmt.Sprintf("o:%d:u:%d", testdata.Org1.ID, testdata.Agent.ID), 2) + assertTicketDailyCount(t, db, models.TicketDailyCountAssignment, fmt.Sprintf("o:%d:u:%d", testdata.Org1.ID, testdata.Admin.ID), 0) } func TestTicketsAddNote(t *testing.T) { @@ -391,4 +405,11 @@ func TestReopenTickets(t *testing.T) { assert.Equal(t, 2, len(cathy.Groups().All())) assert.Equal(t, "Doctors", cathy.Groups().All()[0].Name()) assert.Equal(t, "Open Tickets", cathy.Groups().All()[1].Name()) + + // reopening doesn't change opening daily counts + assertTicketDailyCount(t, db, models.TicketDailyCountOpening, fmt.Sprintf("o:%d", testdata.Org1.ID), 0) +} + +func assertTicketDailyCount(t *testing.T, db *sqlx.DB, countType models.TicketDailyCountType, scope string, expected int) { + assertdb.Query(t, db, `SELECT COALESCE(SUM(count), 0) FROM tickets_ticketdailycount WHERE count_type = $1 AND scope = $2`, countType, scope).Returns(expected) } diff --git a/services/tickets/utils_test.go b/services/tickets/utils_test.go index fd8fd1e8b..2f77afed0 100644 --- a/services/tickets/utils_test.go +++ b/services/tickets/utils_test.go @@ -186,5 +186,5 @@ func TestCloseTicket(t *testing.T) { require.NoError(t, err) testsuite.AssertContactTasks(t, 1, testdata.Cathy.ID, - []string{`{"type":"ticket_closed","org_id":1,"task":{"id":1,"org_id":1,"contact_id":10000,"ticket_id":1,"event_type":"C","created_on":"2021-06-08T16:40:32Z"},"queued_on":"2021-06-08T16:40:35Z"}`}) + []string{`{"type":"ticket_closed","org_id":1,"task":{"id":1,"org_id":1,"contact_id":10000,"ticket_id":1,"event_type":"C","created_on":"2021-06-08T16:40:34Z"},"queued_on":"2021-06-08T16:40:37Z"}`}) }