diff --git a/core/models/contacts.go b/core/models/contacts.go index e3e0e085c..9f4167897 100644 --- a/core/models/contacts.go +++ b/core/models/contacts.go @@ -536,6 +536,7 @@ LEFT JOIN ( 'body', t.body, 'external_id', t.external_id, 'ticketer_id', t.ticketer_id, + 'topic_id', t.topic_id, 'assignee_id', t.assignee_id ) ORDER BY t.opened_on ASC, t.id ASC ) as tickets diff --git a/core/models/contacts_test.go b/core/models/contacts_test.go index f915acbd2..76dbedbcf 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() testdata.InsertContactURN(db, testdata.Org1, testdata.Bob, "whatsapp:250788373373", 999) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "Problem!", "Where are my shoes?", "1234", testdata.Agent) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "Another Problem!", "Where are my pants?", "2345", nil) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Bob, testdata.Mailgun, "Urgent", "His name is Bob", "", testdata.Editor) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.SupportTopic, "Problem!", "Where are my shoes?", "1234", testdata.Agent) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.SalesTopic, "Another Problem!", "Where are my pants?", "2345", nil) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Bob, testdata.Mailgun, testdata.DefaultTopic, "Urgent", "His name is Bob", "", testdata.Editor) // delete mailgun ticketer db.MustExec(`UPDATE tickets_ticketer SET is_active = false WHERE id = $1`, testdata.Mailgun.ID) @@ -64,8 +64,10 @@ func TestContacts(t *testing.T) { cathyTickets := cathy.Tickets().All() assert.Equal(t, "Problem!", cathyTickets[0].Subject()) + assert.Equal(t, "Support", cathyTickets[0].Topic().Name()) assert.Equal(t, "agent1@nyaruka.com", cathyTickets[0].Assignee().Email()) assert.Equal(t, "Another Problem!", cathyTickets[1].Subject()) + assert.Equal(t, "Sales", cathyTickets[1].Topic().Name()) assert.Nil(t, cathyTickets[1].Assignee()) assert.Equal(t, "Yobe", cathy.Fields()["state"].QueryValue()) diff --git a/core/models/msgs_test.go b/core/models/msgs_test.go index af67c3e04..6403c8bae 100644 --- a/core/models/msgs_test.go +++ b/core/models/msgs_test.go @@ -284,7 +284,7 @@ func TestNonPersistentBroadcasts(t *testing.T) { db.MustExec(`DELETE FROM tickets_ticket`) }() - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Bob, testdata.Mailgun, "Problem", "", "", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Bob, testdata.Mailgun, testdata.DefaultTopic, "Problem", "", "", nil) modelTicket := ticket.Load(db) translations := map[envs.Language]*models.BroadcastTranslation{envs.Language("eng"): {Text: "Hi there"}} diff --git a/core/models/ticket_events.go b/core/models/ticket_events.go index a18331d51..4ef24d6b7 100644 --- a/core/models/ticket_events.go +++ b/core/models/ticket_events.go @@ -13,11 +13,12 @@ type TicketEventID int type TicketEventType string const ( - TicketEventTypeOpened TicketEventType = "O" - TicketEventTypeAssigned TicketEventType = "A" - TicketEventTypeNote TicketEventType = "N" - TicketEventTypeClosed TicketEventType = "C" - TicketEventTypeReopened TicketEventType = "R" + TicketEventTypeOpened TicketEventType = "O" + TicketEventTypeAssigned TicketEventType = "A" + TicketEventTypeNoteAdded TicketEventType = "N" + TicketEventTypeTopicChanged TicketEventType = "T" + TicketEventTypeClosed TicketEventType = "C" + TicketEventTypeReopened TicketEventType = "R" ) type TicketEvent struct { @@ -28,6 +29,7 @@ type TicketEvent struct { TicketID TicketID `json:"ticket_id" db:"ticket_id"` EventType TicketEventType `json:"event_type" db:"event_type"` Note null.String `json:"note,omitempty" db:"note"` + TopicID TopicID `json:"topic_id,omitempty" db:"topic_id"` AssigneeID UserID `json:"assignee_id,omitempty" db:"assignee_id"` CreatedByID UserID `json:"created_by_id,omitempty" db:"created_by_id"` CreatedOn time.Time `json:"created_on" db:"created_on"` @@ -35,26 +37,30 @@ type TicketEvent struct { } func NewTicketOpenedEvent(t *Ticket, userID UserID, assigneeID UserID) *TicketEvent { - return newTicketEvent(t, userID, TicketEventTypeOpened, "", assigneeID) + return newTicketEvent(t, userID, TicketEventTypeOpened, "", NilTopicID, assigneeID) } func NewTicketAssignedEvent(t *Ticket, userID UserID, assigneeID UserID, note string) *TicketEvent { - return newTicketEvent(t, userID, TicketEventTypeAssigned, note, assigneeID) + return newTicketEvent(t, userID, TicketEventTypeAssigned, note, NilTopicID, assigneeID) } -func NewTicketNoteEvent(t *Ticket, userID UserID, note string) *TicketEvent { - return newTicketEvent(t, userID, TicketEventTypeNote, note, NilUserID) +func NewTicketNoteAddedEvent(t *Ticket, userID UserID, note string) *TicketEvent { + return newTicketEvent(t, userID, TicketEventTypeNoteAdded, note, NilTopicID, NilUserID) +} + +func NewTicketTopicChangedEvent(t *Ticket, userID UserID, topicID TopicID) *TicketEvent { + return newTicketEvent(t, userID, TicketEventTypeTopicChanged, "", topicID, NilUserID) } func NewTicketClosedEvent(t *Ticket, userID UserID) *TicketEvent { - return newTicketEvent(t, userID, TicketEventTypeClosed, "", NilUserID) + return newTicketEvent(t, userID, TicketEventTypeClosed, "", NilTopicID, NilUserID) } func NewTicketReopenedEvent(t *Ticket, userID UserID) *TicketEvent { - return newTicketEvent(t, userID, TicketEventTypeReopened, "", NilUserID) + return newTicketEvent(t, userID, TicketEventTypeReopened, "", NilTopicID, NilUserID) } -func newTicketEvent(t *Ticket, userID UserID, eventType TicketEventType, note string, assigneeID UserID) *TicketEvent { +func newTicketEvent(t *Ticket, userID UserID, eventType TicketEventType, note string, topicID TopicID, assigneeID UserID) *TicketEvent { event := &TicketEvent{} e := &event.e e.OrgID = t.OrgID() @@ -62,6 +68,7 @@ func newTicketEvent(t *Ticket, userID UserID, eventType TicketEventType, note st e.TicketID = t.ID() e.EventType = eventType e.Note = null.String(note) + e.TopicID = topicID e.AssigneeID = assigneeID e.CreatedOn = dates.Now() e.CreatedByID = userID @@ -74,6 +81,7 @@ func (e *TicketEvent) ContactID() ContactID { return e.e.ContactID } func (e *TicketEvent) TicketID() TicketID { return e.e.TicketID } func (e *TicketEvent) EventType() TicketEventType { return e.e.EventType } func (e *TicketEvent) Note() null.String { return e.e.Note } +func (e *TicketEvent) TopicID() TopicID { return e.e.TopicID } func (e *TicketEvent) AssigneeID() UserID { return e.e.AssigneeID } func (e *TicketEvent) CreatedByID() UserID { return e.e.CreatedByID } @@ -89,8 +97,8 @@ func (e *TicketEvent) UnmarshalJSON(b []byte) error { const insertTicketEventsSQL = ` INSERT INTO - tickets_ticketevent(org_id, contact_id, ticket_id, event_type, note, assignee_id, created_on, created_by_id) - VALUES(:org_id, :contact_id, :ticket_id, :event_type, :note, :assignee_id, :created_on, :created_by_id) + tickets_ticketevent(org_id, contact_id, ticket_id, event_type, note, topic_id, assignee_id, created_on, created_by_id) + VALUES(:org_id, :contact_id, :ticket_id, :event_type, :note, :topic_id, :assignee_id, :created_on, :created_by_id) RETURNING id ` diff --git a/core/models/ticket_events_test.go b/core/models/ticket_events_test.go index c9de363cd..eddf33070 100644 --- a/core/models/ticket_events_test.go +++ b/core/models/ticket_events_test.go @@ -19,7 +19,7 @@ func TestTicketEvents(t *testing.T) { db.MustExec(`DELETE FROM tickets_ticket`) }() - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Need help", "Have you seen my cookies?", "17", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Need help", "Have you seen my cookies?", "17", nil) modelTicket := ticket.Load(db) e1 := models.NewTicketOpenedEvent(modelTicket, testdata.Admin.ID, testdata.Agent.ID) @@ -36,8 +36,8 @@ func TestTicketEvents(t *testing.T) { assert.Equal(t, null.String("please handle"), e2.Note()) assert.Equal(t, testdata.Admin.ID, e2.CreatedByID()) - e3 := models.NewTicketNoteEvent(modelTicket, testdata.Agent.ID, "please handle") - assert.Equal(t, models.TicketEventTypeNote, e3.EventType()) + e3 := models.NewTicketNoteAddedEvent(modelTicket, testdata.Agent.ID, "please handle") + assert.Equal(t, models.TicketEventTypeNoteAdded, e3.EventType()) assert.Equal(t, null.String("please handle"), e3.Note()) assert.Equal(t, testdata.Agent.ID, e3.CreatedByID()) @@ -49,6 +49,11 @@ func TestTicketEvents(t *testing.T) { assert.Equal(t, models.TicketEventTypeReopened, e5.EventType()) assert.Equal(t, testdata.Editor.ID, e5.CreatedByID()) + e6 := models.NewTicketTopicChangedEvent(modelTicket, testdata.Agent.ID, testdata.SupportTopic.ID) + assert.Equal(t, models.TicketEventTypeTopicChanged, e6.EventType()) + assert.Equal(t, testdata.SupportTopic.ID, e6.TopicID()) + assert.Equal(t, testdata.Agent.ID, e6.CreatedByID()) + err := models.InsertTicketEvents(ctx, db, []*models.TicketEvent{e1, e2, e3, e4, e5}) require.NoError(t, err) diff --git a/core/models/tickets.go b/core/models/tickets.go index c473bb13a..38b0eebdc 100644 --- a/core/models/tickets.go +++ b/core/models/tickets.go @@ -380,7 +380,7 @@ func updateTicketLastActivity(ctx context.Context, db Queryer, ids []TicketID, n return Exec(ctx, "update ticket last activity", db, `UPDATE tickets_ticket SET last_activity_on = $2 WHERE id = ANY($1)`, pq.Array(ids), now) } -const assignTicketSQL = ` +const ticketsAssignSQL = ` UPDATE tickets_ticket SET @@ -391,8 +391,8 @@ WHERE id = ANY($1) ` -// AssignTickets assigns the passed in tickets -func AssignTickets(ctx context.Context, db Queryer, oa *OrgAssets, userID UserID, tickets []*Ticket, assigneeID UserID, note string) (map[*Ticket]*TicketEvent, error) { +// TicketsAssign assigns the passed in tickets +func TicketsAssign(ctx context.Context, db Queryer, oa *OrgAssets, userID UserID, tickets []*Ticket, assigneeID UserID, note string) (map[*Ticket]*TicketEvent, error) { ids := make([]TicketID, 0, len(tickets)) events := make([]*TicketEvent, 0, len(tickets)) eventsByTicket := make(map[*Ticket]*TicketEvent, len(tickets)) @@ -413,7 +413,7 @@ func AssignTickets(ctx context.Context, db Queryer, oa *OrgAssets, userID UserID } // mark the tickets as assigned in the db - err := Exec(ctx, "assign tickets", db, assignTicketSQL, pq.Array(ids), assigneeID, now) + err := Exec(ctx, "assign tickets", db, ticketsAssignSQL, pq.Array(ids), assigneeID, now) if err != nil { return nil, errors.Wrapf(err, "error updating tickets") } @@ -426,13 +426,13 @@ func AssignTickets(ctx context.Context, db Queryer, oa *OrgAssets, userID UserID return eventsByTicket, nil } -// NoteTickets adds a note to the passed in tickets -func NoteTickets(ctx context.Context, db Queryer, oa *OrgAssets, userID UserID, tickets []*Ticket, note string) (map[*Ticket]*TicketEvent, error) { +// TicketsAddNote adds a note to the passed in tickets +func TicketsAddNote(ctx context.Context, db Queryer, oa *OrgAssets, userID UserID, tickets []*Ticket, note string) (map[*Ticket]*TicketEvent, error) { events := make([]*TicketEvent, 0, len(tickets)) eventsByTicket := make(map[*Ticket]*TicketEvent, len(tickets)) for _, ticket := range tickets { - e := NewTicketNoteEvent(ticket, userID, note) + e := NewTicketNoteAddedEvent(ticket, userID, note) events = append(events, e) eventsByTicket[ticket] = e } @@ -450,6 +450,52 @@ func NoteTickets(ctx context.Context, db Queryer, oa *OrgAssets, userID UserID, return eventsByTicket, nil } +const ticketsChangeTopicSQL = ` +UPDATE + tickets_ticket +SET + topic_id = $2, + modified_on = $3, + last_activity_on = $3 +WHERE + id = ANY($1) +` + +// TicketsChangeTopic changes the topic of the passed in tickets +func TicketsChangeTopic(ctx context.Context, db Queryer, oa *OrgAssets, userID UserID, tickets []*Ticket, topicID TopicID) (map[*Ticket]*TicketEvent, error) { + ids := make([]TicketID, 0, len(tickets)) + events := make([]*TicketEvent, 0, len(tickets)) + eventsByTicket := make(map[*Ticket]*TicketEvent, len(tickets)) + now := dates.Now() + + for _, ticket := range tickets { + if ticket.TopicID() != topicID { + ids = append(ids, ticket.ID()) + t := &ticket.t + t.TopicID = topicID + t.ModifiedOn = now + t.LastActivityOn = now + + e := NewTicketTopicChangedEvent(ticket, userID, topicID) + events = append(events, e) + eventsByTicket[ticket] = e + } + } + + // mark the tickets as assigned in the db + err := Exec(ctx, "change tickets topic", db, ticketsChangeTopicSQL, pq.Array(ids), topicID, now) + if err != nil { + return nil, errors.Wrapf(err, "error updating tickets") + } + + err = InsertTicketEvents(ctx, db, events) + if err != nil { + return nil, errors.Wrapf(err, "error inserting ticket events") + } + + return eventsByTicket, nil +} + const closeTicketSQL = ` UPDATE tickets_ticket diff --git a/core/models/tickets_test.go b/core/models/tickets_test.go index b0e4a8956..381848ddf 100644 --- a/core/models/tickets_test.go +++ b/core/models/tickets_test.go @@ -144,7 +144,7 @@ func TestUpdateTicketConfig(t *testing.T) { defer deleteTickets(db) - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Problem", "Where my shoes", "123", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Problem", "Where my shoes", "123", nil) modelTicket := ticket.Load(db) // empty configs are null @@ -170,7 +170,7 @@ func TestUpdateTicketLastActivity(t *testing.T) { defer dates.SetNowSource(dates.DefaultNowSource) dates.SetNowSource(dates.NewFixedNowSource(now)) - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Problem", "Where my shoes", "123", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Problem", "Where my shoes", "123", nil) modelTicket := ticket.Load(db) models.UpdateTicketLastActivity(ctx, db, []*models.Ticket{modelTicket}) @@ -189,15 +189,15 @@ func TestAssignTickets(t *testing.T) { oa, err := models.GetOrgAssetsWithRefresh(ctx, db, testdata.Org1.ID, models.RefreshTicketers) require.NoError(t, err) - ticket1 := testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Problem", "Where my shoes", "123", nil) + ticket1 := testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Problem", "Where my shoes", "123", nil) modelTicket1 := ticket1.Load(db) - ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "Old Problem", "Where my pants", "234", nil) + ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Old Problem", "Where my pants", "234", nil) modelTicket2 := ticket2.Load(db) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Ignore", "", "", nil) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Ignore", "", "", nil) - evts, err := models.AssignTickets(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}, testdata.Agent.ID, "please handle these") require.NoError(t, err) assert.Equal(t, 2, len(evts)) assert.Equal(t, models.TicketEventTypeAssigned, evts[modelTicket1].EventType()) @@ -211,7 +211,7 @@ func TestAssignTickets(t *testing.T) { testsuite.AssertQuery(t, db, `SELECT count(*) FROM tickets_ticketevent WHERE event_type = 'A' AND note = 'please handle these'`).Returns(2) } -func TestNoteTickets(t *testing.T) { +func TestTicketsAddNote(t *testing.T) { ctx, _, db, _ := testsuite.Get() defer deleteTickets(db) @@ -219,19 +219,19 @@ func TestNoteTickets(t *testing.T) { oa, err := models.GetOrgAssetsWithRefresh(ctx, db, testdata.Org1.ID, models.RefreshTicketers) require.NoError(t, err) - ticket1 := testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Problem", "Where my shoes", "123", nil) + ticket1 := testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Problem", "Where my shoes", "123", nil) modelTicket1 := ticket1.Load(db) - ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "Old Problem", "Where my pants", "234", nil) + ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Old Problem", "Where my pants", "234", nil) modelTicket2 := ticket2.Load(db) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Ignore", "", "", nil) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Ignore", "", "", nil) - evts, err := models.NoteTickets(ctx, db, oa, testdata.Admin.ID, []*models.Ticket{modelTicket1, modelTicket2}, "spam") + evts, err := models.TicketsAddNote(ctx, db, oa, testdata.Admin.ID, []*models.Ticket{modelTicket1, modelTicket2}, "spam") require.NoError(t, err) assert.Equal(t, 2, len(evts)) - assert.Equal(t, models.TicketEventTypeNote, evts[modelTicket1].EventType()) - assert.Equal(t, models.TicketEventTypeNote, evts[modelTicket2].EventType()) + assert.Equal(t, models.TicketEventTypeNoteAdded, evts[modelTicket1].EventType()) + assert.Equal(t, models.TicketEventTypeNoteAdded, evts[modelTicket2].EventType()) // check there are new note events testsuite.AssertQuery(t, db, `SELECT count(*) FROM tickets_ticketevent WHERE event_type = 'N' AND note = 'spam'`).Returns(2) @@ -255,10 +255,10 @@ func TestCloseTickets(t *testing.T) { oa, err := models.GetOrgAssetsWithRefresh(ctx, db, testdata.Org1.ID, models.RefreshTicketers) require.NoError(t, err) - ticket1 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Problem", "Where my shoes", "123", nil) + ticket1 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Problem", "Where my shoes", "123", nil) modelTicket1 := ticket1.Load(db) - ticket2 := testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "Old Problem", "Where my pants", "234", nil) + ticket2 := testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Old Problem", "Where my pants", "234", nil) modelTicket2 := ticket2.Load(db) logger := &models.HTTPLogger{} @@ -283,7 +283,7 @@ func TestCloseTickets(t *testing.T) { testsuite.AssertQuery(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, "Problem", "Where my shoes", "123", nil) + ticket3 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Problem", "Where my shoes", "123", nil) modelTicket3 := ticket3.Load(db) evts, err = models.CloseTickets(ctx, db, oa, models.NilUserID, []*models.Ticket{modelTicket3}, false, logger) @@ -312,10 +312,10 @@ func TestReopenTickets(t *testing.T) { oa, err := models.GetOrgAssetsWithRefresh(ctx, db, testdata.Org1.ID, models.RefreshTicketers) require.NoError(t, err) - ticket1 := testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Problem", "Where my shoes", "123", nil) + ticket1 := testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Problem", "Where my shoes", "123", nil) modelTicket1 := ticket1.Load(db) - ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "Old Problem", "Where my pants", "234", nil) + ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Old Problem", "Where my pants", "234", nil) modelTicket2 := ticket2.Load(db) logger := &models.HTTPLogger{} diff --git a/core/tasks/broadcasts/worker_test.go b/core/tasks/broadcasts/worker_test.go index 475a75c76..db33ad52a 100644 --- a/core/tasks/broadcasts/worker_test.go +++ b/core/tasks/broadcasts/worker_test.go @@ -129,7 +129,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, "Problem", "", "", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Problem", "", "", nil) modelTicket := ticket.Load(db) evaluated := map[envs.Language]*models.BroadcastTranslation{ diff --git a/core/tasks/handler/handler_test.go b/core/tasks/handler/handler_test.go index bd8b1c75b..a6c6f12ca 100644 --- a/core/tasks/handler/handler_test.go +++ b/core/tasks/handler/handler_test.go @@ -34,16 +34,16 @@ 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, "Hi there", "Ok", "", nil), - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "Hi again", "Ok", "", nil), + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Hi there", "Ok", "", nil), + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Hi again", "Ok", "", nil), }, } closedTickets := map[*testdata.Contact][]*testdata.Ticket{ testdata.Cathy: { - testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Old", "", "", nil), + testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Old", "", "", nil), }, testdata.Bob: { - testdata.InsertClosedTicket(db, testdata.Org1, testdata.Bob, testdata.Mailgun, "Hi there", "Ok", "", nil), + testdata.InsertClosedTicket(db, testdata.Org1, testdata.Bob, testdata.Mailgun, testdata.DefaultTopic, "Hi there", "Ok", "", nil), }, } @@ -309,7 +309,7 @@ func TestTicketEvents(t *testing.T) { // add a ticket closed trigger testdata.InsertTicketClosedTrigger(rt.DB, testdata.Org1, testdata.Favorites) - ticket := testdata.InsertClosedTicket(rt.DB, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Problem", "Where are my shoes?", "", nil) + ticket := testdata.InsertClosedTicket(rt.DB, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Problem", "Where are my shoes?", "", nil) modelTicket := ticket.Load(db) event := models.NewTicketClosedEvent(modelTicket, testdata.Admin.ID) diff --git a/mailroom_test.dump b/mailroom_test.dump index eab87ce7f..2ee9b53cf 100644 Binary files a/mailroom_test.dump and b/mailroom_test.dump differ diff --git a/services/tickets/mailgun/web_test.go b/services/tickets/mailgun/web_test.go index ec8d1cb02..c66062cc2 100644 --- a/services/tickets/mailgun/web_test.go +++ b/services/tickets/mailgun/web_test.go @@ -18,7 +18,7 @@ func TestReceive(t *testing.T) { }() // create a mailgun ticket for Cathy - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Need help", "Have you seen my cookies?", "", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Need help", "Have you seen my cookies?", "", nil) web.RunWebTests(t, "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 20cc9f3f0..9979c64a1 100644 --- a/services/tickets/rocketchat/web_test.go +++ b/services/tickets/rocketchat/web_test.go @@ -13,7 +13,7 @@ func TestEventCallback(t *testing.T) { db := testsuite.DB() // create a rocketchat ticket for Cathy - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.RocketChat, "Need help", "Have you seen my cookies?", "1234", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.RocketChat, testdata.DefaultTopic, "Need help", "Have you seen my cookies?", "1234", nil) web.RunWebTests(t, "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 09b964ef6..d5ccf01aa 100644 --- a/services/tickets/utils_test.go +++ b/services/tickets/utils_test.go @@ -50,8 +50,8 @@ func TestFromTicketUUID(t *testing.T) { ctx, _, db, _ := testsuite.Reset() // create some tickets - ticket1 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Need help", "Have you seen my cookies?", "", nil) - ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "Need help", "Have you seen my shoes?", "", nil) + ticket1 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Need help", "Have you seen my cookies?", "", nil) + ticket2 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Need help", "Have you seen my shoes?", "", nil) // break mailgun configuration db.MustExec(`UPDATE tickets_ticketer SET config = '{"foo":"bar"}'::jsonb WHERE id = $1`, testdata.Mailgun.ID) @@ -125,7 +125,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, "Need help", "Have you seen my cookies?", "", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Need help", "Have you seen my cookies?", "", 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 c00c0a17c..a7ae2b161 100644 --- a/services/tickets/zendesk/web_test.go +++ b/services/tickets/zendesk/web_test.go @@ -12,7 +12,7 @@ func TestChannelback(t *testing.T) { _, _, db, _ := testsuite.Reset() // create a zendesk ticket for Cathy - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "Need help", "Have you seen my cookies?", "1234", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Need help", "Have you seen my cookies?", "1234", nil) web.RunWebTests(t, "testdata/channelback.json", map[string]string{"cathy_ticket_uuid": string(ticket.UUID)}) } @@ -21,7 +21,7 @@ func TestEventCallback(t *testing.T) { _, _, db, _ := testsuite.Reset() // create a zendesk ticket for Cathy - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "Need help", "Have you seen my cookies?", "1234", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Need help", "Have you seen my cookies?", "1234", nil) web.RunWebTests(t, "testdata/event_callback.json", map[string]string{"cathy_ticket_uuid": string(ticket.UUID)}) } @@ -30,7 +30,7 @@ func TestTarget(t *testing.T) { _, _, db, _ := testsuite.Reset() // create a zendesk ticket for Cathy - ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "Need help", "Have you seen my cookies?", "1234", nil) + ticket := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Need help", "Have you seen my cookies?", "1234", nil) web.RunWebTests(t, "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 c0ad84827..f418665c0 100644 --- a/testsuite/testdata/tickets.go +++ b/testsuite/testdata/tickets.go @@ -35,16 +35,16 @@ type Ticketer struct { } // InsertOpenTicket inserts an open ticket -func InsertOpenTicket(db *sqlx.DB, org *Org, contact *Contact, ticketer *Ticketer, subject, body, externalID string, assignee *User) *Ticket { - return insertTicket(db, org, contact, ticketer, models.TicketStatusOpen, subject, body, externalID, assignee) +func InsertOpenTicket(db *sqlx.DB, org *Org, contact *Contact, ticketer *Ticketer, topic *Topic, subject, body, externalID string, assignee *User) *Ticket { + return insertTicket(db, org, contact, ticketer, models.TicketStatusOpen, topic, subject, body, externalID, assignee) } // InsertClosedTicket inserts a closed ticket -func InsertClosedTicket(db *sqlx.DB, org *Org, contact *Contact, ticketer *Ticketer, subject, body, externalID string, assignee *User) *Ticket { - return insertTicket(db, org, contact, ticketer, models.TicketStatusClosed, subject, body, externalID, assignee) +func InsertClosedTicket(db *sqlx.DB, org *Org, contact *Contact, ticketer *Ticketer, topic *Topic, subject, body, externalID string, assignee *User) *Ticket { + return insertTicket(db, org, contact, ticketer, models.TicketStatusClosed, topic, subject, body, externalID, assignee) } -func insertTicket(db *sqlx.DB, org *Org, contact *Contact, ticketer *Ticketer, status models.TicketStatus, subject, body, externalID string, assignee *User) *Ticket { +func insertTicket(db *sqlx.DB, org *Org, contact *Contact, ticketer *Ticketer, status models.TicketStatus, topic *Topic, subject, body, externalID string, assignee *User) *Ticket { uuid := flows.TicketUUID(uuids.New()) var closedOn *time.Time if status == models.TicketStatusClosed { @@ -58,8 +58,8 @@ 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, subject, 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, subject, body, externalID, closedOn, assigneeID, + `INSERT INTO tickets_ticket(uuid, org_id, contact_id, ticketer_id, status, topic_id, subject, body, external_id, opened_on, modified_on, closed_on, last_activity_on, assignee_id) + VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW(), $10, NOW(), $11) RETURNING id`, uuid, org.ID, contact.ID, ticketer.ID, status, topic.ID, subject, body, externalID, closedOn, assigneeID, )) return &Ticket{id, uuid} } diff --git a/web/ticket/add_note.go b/web/ticket/add_note.go index e9bb003e4..aaf4b053f 100644 --- a/web/ticket/add_note.go +++ b/web/ticket/add_note.go @@ -12,13 +12,13 @@ import ( ) func init() { - web.RegisterJSONRoute(http.MethodPost, "/mr/ticket/add_note", web.RequireAuthToken(web.WithHTTPLogs(handleAddNote))) + web.RegisterJSONRoute(http.MethodPost, "/mr/ticket/add_note", web.RequireAuthToken(handleAddNote)) } type addNoteRequest struct { bulkTicketRequest - Note string `json:"note"` + Note string `json:"note" validate:"required"` } // Adds the given text note to the tickets with the given ids @@ -30,7 +30,7 @@ type addNoteRequest struct { // "note": "spam" // } // -func handleAddNote(ctx context.Context, rt *runtime.Runtime, r *http.Request, l *models.HTTPLogger) (interface{}, int, error) { +func handleAddNote(ctx context.Context, rt *runtime.Runtime, r *http.Request) (interface{}, int, error) { request := &addNoteRequest{} if err := utils.UnmarshalAndValidateWithLimit(r.Body, request, web.MaxRequestBytes); err != nil { return errors.Wrapf(err, "request failed validation"), http.StatusBadRequest, nil @@ -47,7 +47,7 @@ func handleAddNote(ctx context.Context, rt *runtime.Runtime, r *http.Request, l return nil, http.StatusBadRequest, errors.Wrapf(err, "error loading tickets for org: %d", request.OrgID) } - evts, err := models.NoteTickets(ctx, rt.DB, oa, request.UserID, tickets, request.Note) + evts, err := models.TicketsAddNote(ctx, rt.DB, oa, request.UserID, tickets, request.Note) if err != nil { return nil, http.StatusInternalServerError, errors.Wrap(err, "error adding notes to tickets") } diff --git a/web/ticket/assign.go b/web/ticket/assign.go index 271ca7af8..3af56d6d5 100644 --- a/web/ticket/assign.go +++ b/web/ticket/assign.go @@ -12,7 +12,7 @@ import ( ) func init() { - web.RegisterJSONRoute(http.MethodPost, "/mr/ticket/assign", web.RequireAuthToken(web.WithHTTPLogs(handleAssign))) + web.RegisterJSONRoute(http.MethodPost, "/mr/ticket/assign", web.RequireAuthToken(handleAssign)) } type assignRequest struct { @@ -32,7 +32,7 @@ type assignRequest struct { // "note": "please look at these" // } // -func handleAssign(ctx context.Context, rt *runtime.Runtime, r *http.Request, l *models.HTTPLogger) (interface{}, int, error) { +func handleAssign(ctx context.Context, rt *runtime.Runtime, r *http.Request) (interface{}, int, error) { request := &assignRequest{} if err := utils.UnmarshalAndValidateWithLimit(r.Body, request, web.MaxRequestBytes); err != nil { return errors.Wrapf(err, "request failed validation"), http.StatusBadRequest, nil @@ -49,7 +49,7 @@ func handleAssign(ctx context.Context, rt *runtime.Runtime, r *http.Request, l * return nil, http.StatusBadRequest, errors.Wrapf(err, "error loading tickets for org: %d", request.OrgID) } - evts, err := models.AssignTickets(ctx, rt.DB, oa, request.UserID, tickets, request.AssigneeID, request.Note) + evts, err := models.TicketsAssign(ctx, rt.DB, oa, request.UserID, tickets, request.AssigneeID, request.Note) if err != nil { return nil, http.StatusInternalServerError, errors.Wrap(err, "error assigning tickets") } diff --git a/web/ticket/base_test.go b/web/ticket/base_test.go index d5f4be33a..3fbe09168 100644 --- a/web/ticket/base_test.go +++ b/web/ticket/base_test.go @@ -13,11 +13,10 @@ import ( func TestTicketAssign(t *testing.T) { _, _, db, _ := testsuite.Reset() - // create 2 open tickets and 1 closed one for Cathy across two different ticketers - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Need help", "Have you seen my cookies?", "17", testdata.Admin) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "More help", "Have you seen my cookies?", "21", testdata.Agent) - testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "Old question", "Have you seen my cookies?", "34", nil) - testdata.InsertClosedTicket(db, testdata.Org1, testdata.Bob, testdata.Zendesk, "Problem", "", "", nil) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Need help", "Have you seen my cookies?", "17", testdata.Admin) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "More help", "Have you seen my cookies?", "21", testdata.Agent) + testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Old question", "Have you seen my cookies?", "34", nil) + testdata.InsertClosedTicket(db, testdata.Org1, testdata.Bob, testdata.Internal, testdata.DefaultTopic, "Problem", "", "", nil) web.RunWebTests(t, "testdata/assign.json", nil) } @@ -25,21 +24,30 @@ func TestTicketAssign(t *testing.T) { func TestTicketAddNote(t *testing.T) { _, _, db, _ := testsuite.Reset() - // create 2 open tickets and 1 closed one for Cathy across two different ticketers - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Need help", "Have you seen my cookies?", "17", testdata.Admin) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "More help", "Have you seen my cookies?", "21", testdata.Agent) - testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "Old question", "Have you seen my cookies?", "34", nil) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Need help", "Have you seen my cookies?", "17", testdata.Admin) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "More help", "Have you seen my cookies?", "21", testdata.Agent) + testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Old question", "Have you seen my cookies?", "34", nil) web.RunWebTests(t, "testdata/add_note.json", nil) } +func TestTicketChangeTopic(t *testing.T) { + _, _, db, _ := testsuite.Reset() + + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.DefaultTopic, "Need help", "Have you seen my cookies?", "17", testdata.Admin) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.SupportTopic, "More help", "Have you seen my cookies?", "21", testdata.Agent) + testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Internal, testdata.SalesTopic, "Old question", "Have you seen my cookies?", "34", nil) + + web.RunWebTests(t, "testdata/change_topic.json", nil) +} + func TestTicketClose(t *testing.T) { _, _, db, _ := testsuite.Reset() // create 2 open tickets and 1 closed one for Cathy across two different ticketers - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Need help", "Have you seen my cookies?", "17", testdata.Admin) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "More help", "Have you seen my cookies?", "21", nil) - testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "Old question", "Have you seen my cookies?", "34", testdata.Editor) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Need help", "Have you seen my cookies?", "17", testdata.Admin) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "More help", "Have you seen my cookies?", "21", nil) + testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Old question", "Have you seen my cookies?", "34", testdata.Editor) web.RunWebTests(t, "testdata/close.json", nil) } @@ -48,9 +56,9 @@ func TestTicketReopen(t *testing.T) { _, _, db, _ := testsuite.Reset() // create 2 closed tickets and 1 open one for Cathy - testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, "Need help", "Have you seen my cookies?", "17", testdata.Admin) - testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "More help", "Have you seen my cookies?", "21", nil) - testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, "Old question", "Have you seen my cookies?", "34", testdata.Editor) + testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Need help", "Have you seen my cookies?", "17", testdata.Admin) + testdata.InsertClosedTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "More help", "Have you seen my cookies?", "21", nil) + testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Zendesk, testdata.DefaultTopic, "Old question", "Have you seen my cookies?", "34", testdata.Editor) web.RunWebTests(t, "testdata/reopen.json", nil) } diff --git a/web/ticket/change_topic.go b/web/ticket/change_topic.go new file mode 100644 index 000000000..29532e4ff --- /dev/null +++ b/web/ticket/change_topic.go @@ -0,0 +1,56 @@ +package ticket + +import ( + "context" + "net/http" + + "github.com/nyaruka/goflow/utils" + "github.com/nyaruka/mailroom/core/models" + "github.com/nyaruka/mailroom/runtime" + "github.com/nyaruka/mailroom/web" + "github.com/pkg/errors" +) + +func init() { + web.RegisterJSONRoute(http.MethodPost, "/mr/ticket/change_topic", web.RequireAuthToken(handleChangeTopic)) +} + +type changeTopicRequest struct { + bulkTicketRequest + + TopicID models.TopicID `json:"topic_id" validate:"required"` +} + +// Changes the topic of the tickets with the given ids +// +// { +// "org_id": 123, +// "user_id": 234, +// "ticket_ids": [1234, 2345], +// "topic_id": 345 +// } +// +func handleChangeTopic(ctx context.Context, rt *runtime.Runtime, r *http.Request) (interface{}, int, error) { + request := &changeTopicRequest{} + if err := utils.UnmarshalAndValidateWithLimit(r.Body, request, web.MaxRequestBytes); err != nil { + return errors.Wrapf(err, "request failed validation"), http.StatusBadRequest, nil + } + + // grab our org assets + oa, err := models.GetOrgAssets(ctx, rt.DB, request.OrgID) + if err != nil { + return nil, http.StatusInternalServerError, errors.Wrapf(err, "unable to load org assets") + } + + tickets, err := models.LoadTickets(ctx, rt.DB, request.TicketIDs) + if err != nil { + return nil, http.StatusBadRequest, errors.Wrapf(err, "error loading tickets for org: %d", request.OrgID) + } + + evts, err := models.TicketsChangeTopic(ctx, rt.DB, oa, request.UserID, tickets, request.TopicID) + if err != nil { + return nil, http.StatusInternalServerError, errors.Wrap(err, "error changing topic of tickets") + } + + return newBulkResponse(evts), http.StatusOK, nil +} diff --git a/web/ticket/testdata/add_note.json b/web/ticket/testdata/add_note.json index 1624cad09..d9424c8b4 100644 --- a/web/ticket/testdata/add_note.json +++ b/web/ticket/testdata/add_note.json @@ -1,4 +1,21 @@ [ + { + "label": "error if topic not specified", + "method": "POST", + "path": "/mr/ticket/add_note", + "body": { + "org_id": 1, + "user_id": 3, + "ticket_ids": [ + 1, + 3 + ] + }, + "status": 400, + "response": { + "error": "request failed validation: field 'note' is required" + } + }, { "label": "adds a note to the given tickets", "method": "POST", diff --git a/web/ticket/testdata/change_topic.json b/web/ticket/testdata/change_topic.json new file mode 100644 index 000000000..c9f89a283 --- /dev/null +++ b/web/ticket/testdata/change_topic.json @@ -0,0 +1,50 @@ +[ + { + "label": "error if topic not specified", + "method": "POST", + "path": "/mr/ticket/change_topic", + "body": { + "org_id": 1, + "user_id": 3, + "ticket_ids": [ + 1, + 3 + ] + }, + "status": 400, + "response": { + "error": "request failed validation: field 'topic_id' is required" + } + }, + { + "label": "changes the topic of the given tickets", + "method": "POST", + "path": "/mr/ticket/change_topic", + "body": { + "org_id": 1, + "user_id": 3, + "ticket_ids": [ + 1, + 3 + ], + "topic_id": 3 + }, + "status": 200, + "response": { + "changed_ids": [ + 1, + 3 + ] + }, + "db_assertions": [ + { + "query": "SELECT count(*) FROM tickets_ticket WHERE topic_id = 3", + "count": 3 + }, + { + "query": "SELECT count(*) FROM tickets_ticketevent WHERE event_type = 'T' AND created_by_id = 3 AND topic_id = 3", + "count": 2 + } + ] + } +] \ No newline at end of file