diff --git a/core/models/contacts_test.go b/core/models/contacts_test.go index d9b93b6bb..764069862 100644 --- a/core/models/contacts_test.go +++ b/core/models/contacts_test.go @@ -26,9 +26,9 @@ func TestContacts(t *testing.T) { defer testsuite.Reset(testsuite.ResetAll) testdata.InsertContactURN(db, testdata.Org1, testdata.Bob, "whatsapp:250788373373", 999) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.SupportTopic, "Where are my shoes?", "1234", testdata.Agent) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.SalesTopic, "Where are my pants?", "2345", nil) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Bob, testdata.Mailgun, testdata.DefaultTopic, "His name is Bob", "", testdata.Editor) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.SupportTopic, "Where are my shoes?", "1234", time.Now(), testdata.Agent) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.SalesTopic, "Where are my pants?", "2345", time.Now(), nil) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Bob, testdata.Mailgun, testdata.DefaultTopic, "His name is Bob", "", time.Now(), testdata.Editor) // delete mailgun ticketer db.MustExec(`UPDATE tickets_ticketer SET is_active = false WHERE id = $1`, testdata.Mailgun.ID) diff --git a/core/models/counts.go b/core/models/counts.go index 6f2e95b7c..238e89eb0 100644 --- a/core/models/counts.go +++ b/core/models/counts.go @@ -48,18 +48,18 @@ type dailyTiming struct { const sqlInsertDailyTiming = `INSERT INTO %s(count_type, scope, day, count, seconds, is_squashed) VALUES(:count_type, :scope, :day, :count, :seconds, FALSE)` -func insertDailyTiming(ctx context.Context, tx Queryer, table string, countType TicketDailyTimingType, tz *time.Location, scope string, count int, seconds int64) error { +func insertDailyTiming(ctx context.Context, tx Queryer, table string, countType TicketDailyTimingType, tz *time.Location, scope string, duration time.Duration) error { day := dates.ExtractDate(dates.Now().In(tz)) timing := &dailyTiming{ dailyCount: dailyCount{ scopedCount: scopedCount{ CountType: string(countType), Scope: scope, - Count: count, + Count: 1, }, Day: day, }, - Seconds: 0, + Seconds: int64(duration / time.Second), } _, err := tx.NamedExecContext(ctx, fmt.Sprintf(sqlInsertDailyTiming, table), timing) diff --git a/core/models/msgs.go b/core/models/msgs.go index 359053afa..b1a5e1d97 100644 --- a/core/models/msgs.go +++ b/core/models/msgs.go @@ -1157,9 +1157,9 @@ func CreateBroadcastMessages(ctx context.Context, rt *runtime.Runtime, oa *OrgAs } func (b *BroadcastBatch) updateTicket(ctx context.Context, db Queryer, oa *OrgAssets) error { - err := updateTicketLastActivity(ctx, db, []TicketID{b.TicketID()}, dates.Now()) + firstReplySeconds, err := TicketRecordReplied(ctx, db, b.TicketID(), dates.Now()) if err != nil { - return errors.Wrapf(err, "error updating broadcast ticket") + return err } // record reply counts for org, user and team @@ -1179,12 +1179,8 @@ func (b *BroadcastBatch) updateTicket(ctx context.Context, db Queryer, oa *OrgAs return err } - seconds, err := TicketRecordReplied(ctx, db, b.TicketID(), dates.Now()) - if err != nil { - return err - } - if seconds >= 0 { - if err := insertTicketDailyTiming(ctx, db, TicketDailyTimingFirstReply, oa.Org().Timezone(), scopeOrg(oa), 1, seconds); err != nil { + if firstReplySeconds >= 0 { + if err := insertTicketDailyTiming(ctx, db, TicketDailyTimingFirstReply, oa.Org().Timezone(), scopeOrg(oa), firstReplySeconds); err != nil { return err } } diff --git a/core/models/msgs_test.go b/core/models/msgs_test.go index ace219b91..13185b753 100644 --- a/core/models/msgs_test.go +++ b/core/models/msgs_test.go @@ -595,7 +595,7 @@ func TestNonPersistentBroadcasts(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Bob, testdata.Mailgun, testdata.DefaultTopic, "", "", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Bob, testdata.Mailgun, testdata.DefaultTopic, "", "", time.Now(), nil) modelTicket := ticket.Load(db) translations := map[envs.Language]*models.BroadcastTranslation{envs.Language("eng"): {Text: "Hi there"}} diff --git a/core/models/notifications_test.go b/core/models/notifications_test.go index 934e32f1c..f6e985786 100644 --- a/core/models/notifications_test.go +++ b/core/models/notifications_test.go @@ -165,7 +165,7 @@ func assertNotifications(t *testing.T, ctx context.Context, db *sqlx.DB, after t } func openTicket(t *testing.T, ctx context.Context, db *sqlx.DB, openedBy *testdata.User, assignee *testdata.User) (*models.Ticket, *models.TicketEvent) { - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.SupportTopic, "Where my pants", "", assignee) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.SupportTopic, "Where my pants", "", time.Now(), assignee) modelTicket := ticket.Load(db) openedEvent := models.NewTicketOpenedEvent(modelTicket, openedBy.SafeID(), assignee.SafeID()) diff --git a/core/models/ticket_events_test.go b/core/models/ticket_events_test.go index 34ca21ca4..eab35dd73 100644 --- a/core/models/ticket_events_test.go +++ b/core/models/ticket_events_test.go @@ -2,6 +2,7 @@ package models_test import ( "testing" + "time" "github.com/nyaruka/gocommon/dbutil/assertdb" "github.com/nyaruka/mailroom/core/models" @@ -17,7 +18,7 @@ func TestTicketEvents(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Have you seen my cookies?", "17", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Have you seen my cookies?", "17", time.Now(), nil) modelTicket := ticket.Load(db) e1 := models.NewTicketOpenedEvent(modelTicket, testdata.Admin.ID, testdata.Agent.ID) diff --git a/core/models/tickets.go b/core/models/tickets.go index 78f984521..852334160 100644 --- a/core/models/tickets.go +++ b/core/models/tickets.go @@ -124,6 +124,7 @@ func (t *Ticket) Status() TicketStatus { return t.t.Status } func (t *Ticket) TopicID() TopicID { return t.t.TopicID } func (t *Ticket) Body() string { return t.t.Body } func (t *Ticket) AssigneeID() UserID { return t.t.AssigneeID } +func (t *Ticket) RepliedOn() *time.Time { return t.t.RepliedOn } func (t *Ticket) LastActivityOn() time.Time { return t.t.LastActivityOn } func (t *Ticket) Config(key string) string { return t.t.Config.GetString(key, "") @@ -699,14 +700,16 @@ func recalcGroupsForTicketChanges(ctx context.Context, db Queryer, oa *OrgAssets } const sqlUpdateTicketRepliedOn = ` - UPDATE tickets_ticket - SET replied_on = $2 - WHERE id = $1 AND replied_on IS NOT NULL -RETURNING EXTRACT(EPOCH FROM (replied_on - opened_on))` - -// TicketRecordReplied records a ticket as being replied to, returning the number of seconds between the reply -// and the ticket being opened, if this is -func TicketRecordReplied(ctx context.Context, db Queryer, ticketID TicketID, when time.Time) (int64, error) { + UPDATE tickets_ticket t1 + SET last_activity_on = $2, replied_on = LEAST(t1.replied_on, $2) + FROM tickets_ticket t2 + WHERE t1.id = t2.id AND t1.id = $1 +RETURNING CASE WHEN t2.replied_on IS NULL THEN EXTRACT(EPOCH FROM (t1.replied_on - t1.opened_on)) ELSE NULL END` + +// TicketRecordReplied records a ticket as being replied to, updating last_activity_on. If this is the first reply +// to this ticket then replied_on is updated and the function returns the number of seconds between that and when +// the ticket was opened. +func TicketRecordReplied(ctx context.Context, db Queryer, ticketID TicketID, when time.Time) (time.Duration, error) { rows, err := db.QueryxContext(ctx, sqlUpdateTicketRepliedOn, ticketID, when) if err != nil && err != sql.ErrNoRows { return -1, err @@ -719,12 +722,16 @@ func TicketRecordReplied(ctx context.Context, db Queryer, ticketID TicketID, whe return -1, nil } - var seconds int64 + var seconds *float64 if err := rows.Scan(&seconds); err != nil { return -1, err } - return seconds, nil + if seconds != nil { + return time.Duration(*seconds * float64(time.Second)), nil + } + + return time.Duration(-1), nil } // Ticketer is our type for a ticketer asset @@ -915,6 +922,6 @@ func insertTicketDailyCounts(ctx context.Context, tx Queryer, countType TicketDa return insertDailyCounts(ctx, tx, "tickets_ticketdailycount", countType, tz, scopeCounts) } -func insertTicketDailyTiming(ctx context.Context, tx Queryer, countType TicketDailyTimingType, tz *time.Location, scope string, count int, seconds int64) error { - return insertDailyTiming(ctx, tx, "tickets_ticketdailytiming", countType, tz, scope, count, seconds) +func insertTicketDailyTiming(ctx context.Context, tx Queryer, countType TicketDailyTimingType, tz *time.Location, scope string, duration time.Duration) error { + return insertDailyTiming(ctx, tx, "tickets_ticketdailytiming", countType, tz, scope, duration) } diff --git a/core/models/tickets_test.go b/core/models/tickets_test.go index fe90bfcf0..e4541f013 100644 --- a/core/models/tickets_test.go +++ b/core/models/tickets_test.go @@ -150,7 +150,7 @@ func TestUpdateTicketConfig(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Where my shoes", "123", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Where my shoes", "123", time.Now(), nil) modelTicket := ticket.Load(db) // empty configs are null @@ -171,19 +171,19 @@ func TestUpdateTicketLastActivity(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) - now := time.Date(2021, 6, 22, 15, 59, 30, 123456789, time.UTC) + now := time.Date(2021, 6, 22, 15, 59, 30, 123456000, time.UTC) defer dates.SetNowSource(dates.DefaultNowSource) dates.SetNowSource(dates.NewFixedNowSource(now)) - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Where my shoes", "123", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Where my shoes", "123", time.Now(), nil) modelTicket := ticket.Load(db) models.UpdateTicketLastActivity(ctx, db, []*models.Ticket{modelTicket}) assert.Equal(t, now, modelTicket.LastActivityOn()) - assertdb.Query(t, db, `SELECT count(*) FROM tickets_ticket WHERE id = $1 AND last_activity_on = $2`, ticket.ID, modelTicket.LastActivityOn()).Returns(1) + assertdb.Query(t, db, `SELECT last_activity_on FROM tickets_ticket WHERE id = $1`, ticket.ID).Returns(modelTicket.LastActivityOn()) } @@ -198,14 +198,14 @@ func TestTicketsAssign(t *testing.T) { ticket1 := testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Where my shoes", "123", nil) modelTicket1 := ticket1.Load(db) - ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Where my pants", "234", nil) + ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Where my pants", "234", time.Now(), 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) + ticket3 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Where my glasses", "", time.Now(), testdata.Admin) modelTicket3 := ticket3.Load(db) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "", "", nil) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "", "", time.Now(), nil) 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) @@ -239,10 +239,10 @@ func TestTicketsAddNote(t *testing.T) { ticket1 := testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Where my shoes", "123", nil) modelTicket1 := ticket1.Load(db) - ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Where my pants", "234", testdata.Agent) + ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Where my pants", "234", time.Now(), testdata.Agent) modelTicket2 := ticket2.Load(db) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "", "", nil) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "", "", time.Now(), nil) evts, err := models.TicketsAddNote(ctx, db, oa, testdata.Admin.ID, []*models.Ticket{modelTicket1, modelTicket2}, "spam") require.NoError(t, err) @@ -267,13 +267,13 @@ func TestTicketsChangeTopic(t *testing.T) { ticket1 := testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.SalesTopic, "Where my shoes", "123", nil) modelTicket1 := ticket1.Load(db) - ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.SupportTopic, "Where my pants", "234", nil) + ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.SupportTopic, "Where my pants", "234", time.Now(), nil) modelTicket2 := ticket2.Load(db) - ticket3 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Where my pants", "345", nil) + ticket3 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Where my pants", "345", time.Now(), nil) modelTicket3 := ticket3.Load(db) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "", "", nil) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "", "", time.Now(), nil) evts, err := models.TicketsChangeTopic(ctx, db, oa, testdata.Admin.ID, []*models.Ticket{modelTicket1, modelTicket2, modelTicket3}, testdata.SupportTopic.ID) require.NoError(t, err) @@ -304,7 +304,7 @@ func TestCloseTickets(t *testing.T) { oa, err := models.GetOrgAssetsWithRefresh(ctx, rt, testdata.Org1.ID, models.RefreshTicketers|models.RefreshGroups) require.NoError(t, err) - ticket1 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Where my shoes", "123", nil) + ticket1 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Where my shoes", "123", time.Now(), nil) modelTicket1 := ticket1.Load(db) ticket2 := testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Where my pants", "234", nil) @@ -345,7 +345,7 @@ func TestCloseTickets(t *testing.T) { assertdb.Query(t, db, `SELECT count(*) FROM tickets_ticketevent WHERE ticket_id = $1 AND event_type = 'C'`, ticket2.ID).Returns(0) // can close tickets without a user - ticket3 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Where my shoes", "123", nil) + ticket3 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Where my shoes", "123", time.Now(), nil) modelTicket3 := ticket3.Load(db) evts, err = models.CloseTickets(ctx, rt, oa, models.NilUserID, []*models.Ticket{modelTicket3}, false, false, logger) @@ -377,7 +377,7 @@ func TestReopenTickets(t *testing.T) { ticket1 := testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Where my shoes", "123", nil) modelTicket1 := ticket1.Load(db) - ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Where my pants", "234", nil) + ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Where my pants", "234", time.Now(), nil) modelTicket2 := ticket2.Load(db) logger := &models.HTTPLogger{} @@ -410,6 +410,42 @@ func TestReopenTickets(t *testing.T) { assertTicketDailyCount(t, db, models.TicketDailyCountOpening, fmt.Sprintf("o:%d", testdata.Org1.ID), 0) } +func TestTicketRecordReply(t *testing.T) { + ctx, _, db, _ := testsuite.Get() + + defer testsuite.Reset(testsuite.ResetData) + + openedOn := time.Date(2022, 5, 18, 14, 21, 0, 0, time.UTC) + repliedOn := time.Date(2022, 5, 18, 15, 0, 0, 0, time.UTC) + + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Where my shoes", "123", openedOn, nil) + + timing, err := models.TicketRecordReplied(ctx, db, ticket.ID, repliedOn) + assert.NoError(t, err) + assert.Equal(t, 2340*time.Second, timing) + + modelTicket := ticket.Load(db) + assert.Equal(t, repliedOn, *modelTicket.RepliedOn()) + assert.Equal(t, repliedOn, modelTicket.LastActivityOn()) + + assertdb.Query(t, db, `SELECT replied_on FROM tickets_ticket WHERE id = $1`, ticket.ID).Returns(repliedOn) + assertdb.Query(t, db, `SELECT last_activity_on FROM tickets_ticket WHERE id = $1`, ticket.ID).Returns(repliedOn) + + repliedAgainOn := time.Date(2022, 5, 18, 15, 5, 0, 0, time.UTC) + + // if we call it again, it won't change replied_on again but it will update last_activity_on + timing, err = models.TicketRecordReplied(ctx, db, ticket.ID, repliedAgainOn) + assert.NoError(t, err) + assert.Equal(t, time.Duration(-1), timing) + + modelTicket = ticket.Load(db) + assert.Equal(t, repliedOn, *modelTicket.RepliedOn()) + assert.Equal(t, repliedAgainOn, modelTicket.LastActivityOn()) + + assertdb.Query(t, db, `SELECT replied_on FROM tickets_ticket WHERE id = $1`, ticket.ID).Returns(repliedOn) + assertdb.Query(t, db, `SELECT last_activity_on FROM tickets_ticket WHERE id = $1`, ticket.ID).Returns(repliedAgainOn) +} + 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/core/tasks/handler/handler_test.go b/core/tasks/handler/handler_test.go index 29ef05c97..2e3497850 100644 --- a/core/tasks/handler/handler_test.go +++ b/core/tasks/handler/handler_test.go @@ -38,8 +38,8 @@ func TestMsgEvents(t *testing.T) { // give Cathy and Bob some tickets... openTickets := map[*testdata.Contact][]*testdata.Ticket{ testdata.Cathy: { - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Ok", "", nil), - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Ok", "", nil), + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Ok", "", time.Now(), nil), + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Ok", "", time.Now(), nil), }, } closedTickets := map[*testdata.Contact][]*testdata.Ticket{ diff --git a/core/tasks/msgs/send_broadcast_test.go b/core/tasks/msgs/send_broadcast_test.go index cddefc88c..116f390ec 100644 --- a/core/tasks/msgs/send_broadcast_test.go +++ b/core/tasks/msgs/send_broadcast_test.go @@ -135,7 +135,7 @@ func TestBroadcastTask(t *testing.T) { // insert a broadcast so we can check it is being set to sent legacyID := testdata.InsertBroadcast(db, testdata.Org1, "base", map[envs.Language]string{"base": "hi @(PROPER(contact.name)) legacy"}, models.NilScheduleID, nil, nil) - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "", "", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "", "", time.Now(), nil) modelTicket := ticket.Load(db) evaluated := map[envs.Language]*models.BroadcastTranslation{ @@ -272,10 +272,12 @@ func TestBroadcastTask(t *testing.T) { Returns(1, "%d: broadcast not marked as sent", i) } - // if we had a ticket, make sure its last_activity_on was updated + // if we had a ticket, make sure its replied_on and last_activity_on were updated if tc.TicketID != models.NilTicketID { assertdb.Query(t, db, `SELECT count(*) FROM tickets_ticket WHERE id = $1 AND last_activity_on > $2`, tc.TicketID, modelTicket.LastActivityOn()). Returns(1, "%d: ticket last_activity_on not updated", i) + assertdb.Query(t, db, `SELECT count(*) FROM tickets_ticket WHERE id = $1 AND replied_on IS NOT NULL`, tc.TicketID). + Returns(1, "%d: ticket replied_on not updated", i) } lastNow = time.Now() @@ -284,4 +286,6 @@ func TestBroadcastTask(t *testing.T) { 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) + + assertdb.Query(t, db, `SELECT SUM(count) FROM tickets_ticketdailytiming WHERE count_type = 'R' AND scope = CONCAT('o:', $1::text)`, testdata.Org1.ID).Returns(1) } diff --git a/services/tickets/mailgun/web_test.go b/services/tickets/mailgun/web_test.go index 98d544972..f831263f4 100644 --- a/services/tickets/mailgun/web_test.go +++ b/services/tickets/mailgun/web_test.go @@ -2,6 +2,7 @@ package mailgun import ( "testing" + "time" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" @@ -14,7 +15,7 @@ func TestReceive(t *testing.T) { defer testsuite.Reset(testsuite.ResetData | testsuite.ResetStorage) // create a mailgun ticket for Cathy - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Have you seen my cookies?", "", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Have you seen my cookies?", "", time.Now(), nil) web.RunWebTests(t, ctx, rt, "testdata/receive.json", map[string]string{"cathy_ticket_uuid": string(ticket.UUID)}) } diff --git a/services/tickets/rocketchat/web_test.go b/services/tickets/rocketchat/web_test.go index 595ea4ca6..b37567263 100644 --- a/services/tickets/rocketchat/web_test.go +++ b/services/tickets/rocketchat/web_test.go @@ -2,6 +2,7 @@ package rocketchat_test import ( "testing" + "time" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" @@ -14,7 +15,7 @@ func TestEventCallback(t *testing.T) { defer testsuite.Reset(testsuite.ResetData | testsuite.ResetStorage) // create a rocketchat ticket for Cathy - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.RocketChat, testdata.DefaultTopic, "Have you seen my cookies?", "1234", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.RocketChat, testdata.DefaultTopic, "Have you seen my cookies?", "1234", time.Now(), nil) web.RunWebTests(t, ctx, rt, "testdata/event_callback.json", map[string]string{"cathy_ticket_uuid": string(ticket.UUID)}) } diff --git a/services/tickets/utils_test.go b/services/tickets/utils_test.go index 2f77afed0..f917ad80f 100644 --- a/services/tickets/utils_test.go +++ b/services/tickets/utils_test.go @@ -52,8 +52,8 @@ func TestFromTicketUUID(t *testing.T) { defer testsuite.Reset(testsuite.ResetAll) // create some tickets - ticket1 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Have you seen my cookies?", "", nil) - ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my shoes?", "", nil) + ticket1 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Have you seen my cookies?", "", time.Now(), nil) + ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my shoes?", "", time.Now(), nil) // break mailgun configuration db.MustExec(`UPDATE tickets_ticketer SET config = '{"foo":"bar"}'::jsonb WHERE id = $1`, testdata.Mailgun.ID) @@ -123,7 +123,7 @@ func TestSendReply(t *testing.T) { image := &tickets.File{URL: "http://coolfiles.com/a.jpg", ContentType: "image/jpeg", Body: imageBody} // create a ticket - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Have you seen my cookies?", "", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Have you seen my cookies?", "", time.Now(), nil) modelTicket := ticket.Load(db) msg, err := tickets.SendReply(ctx, rt, modelTicket, "I'll get back to you", []*tickets.File{image}) diff --git a/services/tickets/zendesk/web_test.go b/services/tickets/zendesk/web_test.go index 1a189734f..c597b0da5 100644 --- a/services/tickets/zendesk/web_test.go +++ b/services/tickets/zendesk/web_test.go @@ -2,6 +2,7 @@ package zendesk import ( "testing" + "time" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" @@ -14,7 +15,7 @@ func TestChannelback(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) // create a zendesk ticket for Cathy - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my cookies?", "1234", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my cookies?", "1234", time.Now(), nil) web.RunWebTests(t, ctx, rt, "testdata/channelback.json", map[string]string{"cathy_ticket_uuid": string(ticket.UUID)}) } @@ -25,7 +26,7 @@ func TestEventCallback(t *testing.T) { defer testsuite.Reset(testsuite.ResetAll) // tests include destroying ticketer // create a zendesk ticket for Cathy - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my cookies?", "1234", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my cookies?", "1234", time.Now(), nil) web.RunWebTests(t, ctx, rt, "testdata/event_callback.json", map[string]string{"cathy_ticket_uuid": string(ticket.UUID)}) } @@ -36,7 +37,7 @@ func TestTarget(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) // create a zendesk ticket for Cathy - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my cookies?", "1234", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my cookies?", "1234", time.Now(), nil) web.RunWebTests(t, ctx, rt, "testdata/target.json", map[string]string{"cathy_ticket_uuid": string(ticket.UUID)}) } diff --git a/testsuite/testdata/tickets.go b/testsuite/testdata/tickets.go index 806409c64..e2173a3c7 100644 --- a/testsuite/testdata/tickets.go +++ b/testsuite/testdata/tickets.go @@ -40,16 +40,16 @@ type Ticketer struct { } // InsertOpenTicket inserts an open ticket -func InsertOpenTicket(db *sqlx.DB, org *Org, contact *Contact, ticketer *Ticketer, topic *Topic, body, externalID string, assignee *User) *Ticket { - return insertTicket(db, org, contact, ticketer, models.TicketStatusOpen, topic, body, externalID, assignee) +func InsertOpenTicket(db *sqlx.DB, org *Org, contact *Contact, ticketer *Ticketer, topic *Topic, body, externalID string, openedOn time.Time, assignee *User) *Ticket { + return insertTicket(db, org, contact, ticketer, models.TicketStatusOpen, topic, body, externalID, openedOn, assignee) } // InsertClosedTicket inserts a closed ticket func InsertClosedTicket(db *sqlx.DB, org *Org, contact *Contact, ticketer *Ticketer, topic *Topic, body, externalID string, assignee *User) *Ticket { - return insertTicket(db, org, contact, ticketer, models.TicketStatusClosed, topic, body, externalID, assignee) + return insertTicket(db, org, contact, ticketer, models.TicketStatusClosed, topic, body, externalID, dates.Now(), assignee) } -func insertTicket(db *sqlx.DB, org *Org, contact *Contact, ticketer *Ticketer, status models.TicketStatus, topic *Topic, body, externalID string, assignee *User) *Ticket { +func insertTicket(db *sqlx.DB, org *Org, contact *Contact, ticketer *Ticketer, status models.TicketStatus, topic *Topic, body, externalID string, openedOn time.Time, assignee *User) *Ticket { uuid := flows.TicketUUID(uuids.New()) var closedOn *time.Time if status == models.TicketStatusClosed { @@ -60,7 +60,7 @@ func insertTicket(db *sqlx.DB, org *Org, contact *Contact, ticketer *Ticketer, s var id models.TicketID must(db.Get(&id, `INSERT INTO tickets_ticket(uuid, org_id, contact_id, ticketer_id, status, topic_id, body, external_id, opened_on, modified_on, closed_on, last_activity_on, assignee_id) - VALUES($1, $2, $3, $4, $5, $6, $7, $8, NOW(), NOW(), $9, NOW(), $10) RETURNING id`, uuid, org.ID, contact.ID, ticketer.ID, status, topic.ID, body, externalID, closedOn, assignee.SafeID(), + VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), $10, NOW(), $11) RETURNING id`, uuid, org.ID, contact.ID, ticketer.ID, status, topic.ID, body, externalID, openedOn, closedOn, assignee.SafeID(), )) return &Ticket{id, uuid} } diff --git a/web/ticket/base_test.go b/web/ticket/base_test.go index 714ef258e..c25ae085d 100644 --- a/web/ticket/base_test.go +++ b/web/ticket/base_test.go @@ -2,6 +2,7 @@ package ticket import ( "testing" + "time" _ "github.com/nyaruka/mailroom/services/tickets/mailgun" _ "github.com/nyaruka/mailroom/services/tickets/zendesk" @@ -15,8 +16,8 @@ func TestTicketAssign(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Have you seen my cookies?", "17", testdata.Admin) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Have you seen my cookies?", "21", testdata.Agent) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Have you seen my cookies?", "17", time.Now(), testdata.Admin) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Have you seen my cookies?", "21", time.Now(), testdata.Agent) testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Have you seen my cookies?", "34", nil) testdata.InsertClosedTicket(db, testdata.Org1, testdata.Bob, testdata.Internal, testdata.DefaultTopic, "", "", nil) @@ -28,8 +29,8 @@ func TestTicketAddNote(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Have you seen my cookies?", "17", testdata.Admin) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Have you seen my cookies?", "21", testdata.Agent) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Have you seen my cookies?", "17", time.Now(), testdata.Admin) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Have you seen my cookies?", "21", time.Now(), testdata.Agent) testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Have you seen my cookies?", "34", nil) web.RunWebTests(t, ctx, rt, "testdata/add_note.json", nil) @@ -40,8 +41,8 @@ func TestTicketChangeTopic(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Have you seen my cookies?", "17", testdata.Admin) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.SupportTopic, "Have you seen my cookies?", "21", testdata.Agent) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Have you seen my cookies?", "17", time.Now(), testdata.Admin) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.SupportTopic, "Have you seen my cookies?", "21", time.Now(), testdata.Agent) testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.SalesTopic, "Have you seen my cookies?", "34", nil) web.RunWebTests(t, ctx, rt, "testdata/change_topic.json", nil) @@ -53,10 +54,10 @@ func TestTicketClose(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) // create 2 open tickets and 1 closed one for Cathy across two different ticketers - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Have you seen my cookies?", "17", testdata.Admin) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my cookies?", "21", nil) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Have you seen my cookies?", "17", time.Now(), testdata.Admin) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my cookies?", "21", time.Now(), nil) testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my cookies?", "34", testdata.Editor) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my cookies?", "21", nil) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my cookies?", "21", time.Now(), nil) web.RunWebTests(t, ctx, rt, "testdata/close.json", nil) } @@ -69,7 +70,7 @@ func TestTicketReopen(t *testing.T) { // create 2 closed tickets and 1 open one for Cathy testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Have you seen my cookies?", "17", testdata.Admin) testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my cookies?", "21", nil) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my cookies?", "34", testdata.Editor) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Have you seen my cookies?", "34", time.Now(), testdata.Editor) web.RunWebTests(t, ctx, rt, "testdata/reopen.json", nil) }