diff --git a/core/handlers/ticket_opened.go b/core/handlers/ticket_opened.go index 6a62f5494..a720e33d3 100644 --- a/core/handlers/ticket_opened.go +++ b/core/handlers/ticket_opened.go @@ -28,6 +28,15 @@ func handleTicketOpened(ctx context.Context, tx *sqlx.Tx, rp *redis.Pool, oa *mo return errors.Errorf("unable to find ticketer with UUID: %s", event.Ticket.Ticketer.UUID) } + var topicID models.TopicID + if event.Ticket.Topic != nil { + topic := oa.TopicByUUID(event.Ticket.Topic.UUID) + if topic == nil { + return errors.Errorf("unable to find topic with UUID: %s", event.Ticket.Topic.UUID) + } + topicID = topic.ID() + } + var assigneeID models.UserID if event.Ticket.Assignee != nil { assignee := oa.UserByEmail(event.Ticket.Assignee.Email) @@ -43,6 +52,7 @@ func handleTicketOpened(ctx context.Context, tx *sqlx.Tx, rp *redis.Pool, oa *mo scene.ContactID(), ticketer.ID(), event.Ticket.ExternalID, + topicID, event.Ticket.Subject, event.Ticket.Body, assigneeID, diff --git a/core/handlers/ticket_opened_test.go b/core/handlers/ticket_opened_test.go index 9c4a6b726..831ceaee3 100644 --- a/core/handlers/ticket_opened_test.go +++ b/core/handlers/ticket_opened_test.go @@ -45,7 +45,7 @@ func TestTicketOpened(t *testing.T) { })) // an existing ticket - cathyTicket := models.NewTicket(flows.TicketUUID(uuids.New()), testdata.Org1.ID, testdata.Cathy.ID, testdata.Mailgun.ID, "748363", "Old Question", "Who?", models.NilUserID, nil) + cathyTicket := models.NewTicket(flows.TicketUUID(uuids.New()), testdata.Org1.ID, testdata.Cathy.ID, testdata.Mailgun.ID, "748363", testdata.DefaultTopic.ID, "Old Question", "Who?", models.NilUserID, nil) err := models.InsertTickets(ctx, db, []*models.Ticket{cathyTicket}) require.NoError(t, err) diff --git a/core/models/assets.go b/core/models/assets.go index 45a989a2e..82f881743 100644 --- a/core/models/assets.go +++ b/core/models/assets.go @@ -60,8 +60,10 @@ type OrgAssets struct { ticketers []assets.Ticketer ticketersByID map[TicketerID]*Ticketer ticketersByUUID map[assets.TicketerUUID]*Ticketer - topics []assets.Topic - topicsByUUID map[assets.TopicUUID]*Topic + + topics []assets.Topic + topicsByID map[TopicID]*Topic + topicsByUUID map[assets.TopicUUID]*Topic resthooks []assets.Resthook templates []assets.Template @@ -327,12 +329,14 @@ func NewOrgAssets(ctx context.Context, db *sqlx.DB, orgID OrgID, prev *OrgAssets if err != nil { return nil, errors.Wrapf(err, "error loading topic assets for org %d", orgID) } - oa.topicsByUUID = make(map[assets.TopicUUID]*Topic) + oa.topicsByID = make(map[TopicID]*Topic, len(oa.topics)) + oa.topicsByUUID = make(map[assets.TopicUUID]*Topic, len(oa.topics)) for _, t := range oa.topics { oa.topicsByUUID[t.UUID()] = t.(*Topic) } } else { oa.topics = prev.topics + oa.topicsByID = prev.topicsByID oa.topicsByUUID = prev.topicsByUUID } @@ -650,6 +654,10 @@ func (a *OrgAssets) Topics() ([]assets.Topic, error) { return a.topics, nil } +func (a *OrgAssets) TopicByID(id TopicID) *Topic { + return a.topicsByID[id] +} + func (a *OrgAssets) TopicByUUID(uuid assets.TopicUUID) *Topic { return a.topicsByUUID[uuid] } diff --git a/core/models/contacts.go b/core/models/contacts.go index fc73178a2..e3e0e085c 100644 --- a/core/models/contacts.go +++ b/core/models/contacts.go @@ -322,7 +322,7 @@ func LoadContacts(ctx context.Context, db Queryer, org *OrgAssets, ids []Contact for _, t := range e.Tickets { ticketer := org.TicketerByID(t.TicketerID) if ticketer != nil { - tickets = append(tickets, NewTicket(t.UUID, org.OrgID(), contact.ID(), ticketer.ID(), t.ExternalID, t.Subject, t.Body, t.AssigneeID, nil)) + tickets = append(tickets, NewTicket(t.UUID, org.OrgID(), contact.ID(), ticketer.ID(), t.ExternalID, t.TopicID, t.Subject, t.Body, t.AssigneeID, nil)) } } contact.tickets = tickets @@ -466,6 +466,7 @@ type contactEnvelope struct { UUID flows.TicketUUID `json:"uuid"` TicketerID TicketerID `json:"ticketer_id"` ExternalID string `json:"external_id"` + TopicID TopicID `json:"topic_id"` Subject string `json:"subject"` Body string `json:"body"` AssigneeID UserID `json:"assignee_id"` diff --git a/core/models/tickets.go b/core/models/tickets.go index cdd922cb9..214f07bc2 100644 --- a/core/models/tickets.go +++ b/core/models/tickets.go @@ -74,6 +74,7 @@ type Ticket struct { TicketerID TicketerID `db:"ticketer_id"` ExternalID null.String `db:"external_id"` Status TicketStatus `db:"status"` + TopicID TopicID `db:"topic_id"` Subject string `db:"subject"` Body string `db:"body"` AssigneeID UserID `db:"assignee_id"` @@ -86,7 +87,7 @@ type Ticket struct { } // NewTicket creates a new open ticket -func NewTicket(uuid flows.TicketUUID, orgID OrgID, contactID ContactID, ticketerID TicketerID, externalID, subject, body string, assigneeID UserID, config map[string]interface{}) *Ticket { +func NewTicket(uuid flows.TicketUUID, orgID OrgID, contactID ContactID, ticketerID TicketerID, externalID string, topicID TopicID, subject, body string, assigneeID UserID, config map[string]interface{}) *Ticket { t := &Ticket{} t.t.UUID = uuid t.t.OrgID = orgID @@ -94,6 +95,7 @@ func NewTicket(uuid flows.TicketUUID, orgID OrgID, contactID ContactID, ticketer t.t.TicketerID = ticketerID t.t.ExternalID = null.String(externalID) t.t.Status = TicketStatusOpen + t.t.TopicID = topicID t.t.Subject = subject t.t.Body = body t.t.AssigneeID = assigneeID @@ -108,6 +110,7 @@ func (t *Ticket) ContactID() ContactID { return t.t.ContactID } func (t *Ticket) TicketerID() TicketerID { return t.t.TicketerID } func (t *Ticket) ExternalID() null.String { return t.t.ExternalID } func (t *Ticket) Status() TicketStatus { return t.t.Status } +func (t *Ticket) TopicID() TopicID { return t.t.TopicID } func (t *Ticket) Subject() string { return t.t.Subject } func (t *Ticket) Body() string { return t.t.Body } func (t *Ticket) AssigneeID() UserID { return t.t.AssigneeID } @@ -122,22 +125,30 @@ func (t *Ticket) FlowTicket(oa *OrgAssets) (*flows.Ticket, error) { return nil, errors.New("unable to load ticketer with id %d") } - var flowUser *flows.User + var topic *flows.Topic + if t.TopicID() != NilTopicID { + dbTopic := oa.TopicByID(t.TopicID()) + if dbTopic != nil { + topic = oa.SessionAssets().Topics().Get(dbTopic.UUID()) + } + } + + var assignee *flows.User if t.AssigneeID() != NilUserID { user := oa.UserByID(t.AssigneeID()) if user != nil { - flowUser = oa.SessionAssets().Users().Get(user.Email()) + assignee = oa.SessionAssets().Users().Get(user.Email()) } } return flows.NewTicket( t.UUID(), oa.SessionAssets().Ticketers().Get(modelTicketer.UUID()), - nil, // TODO + topic, t.Subject(), t.Body(), string(t.ExternalID()), - flowUser, + assignee, ), nil } diff --git a/core/models/tickets_test.go b/core/models/tickets_test.go index d8da4b2ae..0e65b4ccd 100644 --- a/core/models/tickets_test.go +++ b/core/models/tickets_test.go @@ -68,6 +68,7 @@ func TestTickets(t *testing.T) { testdata.Cathy.ID, testdata.Mailgun.ID, "EX12345", + testdata.DefaultTopic.ID, "New Ticket", "Where are my cookies?", testdata.Admin.ID, @@ -81,6 +82,7 @@ func TestTickets(t *testing.T) { testdata.Bob.ID, testdata.Zendesk.ID, "EX7869", + testdata.DefaultTopic.ID, "New Zen Ticket", "Where are my trousers?", models.NilUserID, @@ -92,6 +94,7 @@ func TestTickets(t *testing.T) { testdata.Alexandria.ID, testdata.Zendesk.ID, "EX6677", + models.NilTopicID, "Other Org Ticket", "Where are my pants?", testdata.Org2Admin.ID, @@ -103,6 +106,7 @@ func TestTickets(t *testing.T) { assert.Equal(t, testdata.Cathy.ID, ticket1.ContactID()) assert.Equal(t, testdata.Mailgun.ID, ticket1.TicketerID()) assert.Equal(t, null.String("EX12345"), ticket1.ExternalID()) + assert.Equal(t, testdata.DefaultTopic.ID, ticket1.TopicID()) assert.Equal(t, "New Ticket", ticket1.Subject()) assert.Equal(t, "Cathy", ticket1.Config("contact-display")) assert.Equal(t, testdata.Admin.ID, ticket1.AssigneeID()) diff --git a/core/models/topics.go b/core/models/topics.go index bcc1e77e0..238ebcdaa 100644 --- a/core/models/topics.go +++ b/core/models/topics.go @@ -17,6 +17,8 @@ import ( type TopicID null.Int +const NilTopicID = TopicID(0) + type Topic struct { t struct { ID TopicID `json:"id"` diff --git a/core/models/topics_test.go b/core/models/topics_test.go index cd1e4b65e..a7cd86a2a 100644 --- a/core/models/topics_test.go +++ b/core/models/topics_test.go @@ -6,6 +6,7 @@ import ( "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -20,5 +21,6 @@ func TestTopics(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, len(topics)) + assert.Equal(t, testdata.DefaultTopic.UUID, topics[0].UUID()) assert.Equal(t, "General", topics[0].Name()) } diff --git a/services/tickets/intern/service_test.go b/services/tickets/intern/service_test.go index 9fd47152c..40614ba31 100644 --- a/services/tickets/intern/service_test.go +++ b/services/tickets/intern/service_test.go @@ -50,7 +50,7 @@ func TestOpenAndForward(t *testing.T) { assert.Equal(t, "", ticket.ExternalID()) assert.Equal(t, 0, len(logger.Logs)) - dbTicket := models.NewTicket(ticket.UUID(), testdata.Org1.ID, testdata.Cathy.ID, testdata.Internal.ID, "", "Need help", "Where are my cookies?", models.NilUserID, nil) + dbTicket := models.NewTicket(ticket.UUID(), testdata.Org1.ID, testdata.Cathy.ID, testdata.Internal.ID, "", testdata.DefaultTopic.ID, "Need help", "Where are my cookies?", models.NilUserID, nil) logger = &flows.HTTPLogger{} err = svc.Forward( @@ -77,8 +77,8 @@ func TestCloseAndReopen(t *testing.T) { require.NoError(t, err) logger := &flows.HTTPLogger{} - ticket1 := models.NewTicket("88bfa1dc-be33-45c2-b469-294ecb0eba90", testdata.Org1.ID, testdata.Cathy.ID, testdata.Internal.ID, "12", "New ticket", "Where my cookies?", models.NilUserID, nil) - ticket2 := models.NewTicket("645eee60-7e84-4a9e-ade3-4fce01ae28f1", testdata.Org1.ID, testdata.Bob.ID, testdata.Internal.ID, "14", "Second ticket", "Where my shoes?", models.NilUserID, nil) + ticket1 := models.NewTicket("88bfa1dc-be33-45c2-b469-294ecb0eba90", testdata.Org1.ID, testdata.Cathy.ID, testdata.Internal.ID, "12", testdata.DefaultTopic.ID, "New ticket", "Where my cookies?", models.NilUserID, nil) + ticket2 := models.NewTicket("645eee60-7e84-4a9e-ade3-4fce01ae28f1", testdata.Org1.ID, testdata.Bob.ID, testdata.Internal.ID, "14", testdata.DefaultTopic.ID, "Second ticket", "Where my shoes?", models.NilUserID, nil) err = svc.Close([]*models.Ticket{ticket1, ticket2}, logger.Log) diff --git a/services/tickets/mailgun/service_test.go b/services/tickets/mailgun/service_test.go index 83f2a983c..ef162cfa1 100644 --- a/services/tickets/mailgun/service_test.go +++ b/services/tickets/mailgun/service_test.go @@ -94,7 +94,7 @@ func TestOpenAndForward(t *testing.T) { assert.Equal(t, 1, len(logger.Logs)) test.AssertSnapshot(t, "open_ticket", logger.Logs[0].Request) - dbTicket := models.NewTicket(ticket.UUID(), testdata.Org1.ID, testdata.Cathy.ID, testdata.Mailgun.ID, "", "Need help", "Where are my cookies?", models.NilUserID, map[string]interface{}{ + dbTicket := models.NewTicket(ticket.UUID(), testdata.Org1.ID, testdata.Cathy.ID, testdata.Mailgun.ID, "", testdata.DefaultTopic.ID, "Need help", "Where are my cookies?", models.NilUserID, map[string]interface{}{ "contact-uuid": string(testdata.Cathy.UUID), "contact-display": "Cathy", }) @@ -154,8 +154,8 @@ func TestCloseAndReopen(t *testing.T) { require.NoError(t, err) logger := &flows.HTTPLogger{} - ticket1 := models.NewTicket("88bfa1dc-be33-45c2-b469-294ecb0eba90", testdata.Org1.ID, testdata.Cathy.ID, testdata.Zendesk.ID, "12", "New ticket", "Where my cookies?", models.NilUserID, nil) - ticket2 := models.NewTicket("645eee60-7e84-4a9e-ade3-4fce01ae28f1", testdata.Org1.ID, testdata.Bob.ID, testdata.Zendesk.ID, "14", "Second ticket", "Where my shoes?", models.NilUserID, nil) + ticket1 := models.NewTicket("88bfa1dc-be33-45c2-b469-294ecb0eba90", testdata.Org1.ID, testdata.Cathy.ID, testdata.Zendesk.ID, "12", testdata.DefaultTopic.ID, "New ticket", "Where my cookies?", models.NilUserID, nil) + ticket2 := models.NewTicket("645eee60-7e84-4a9e-ade3-4fce01ae28f1", testdata.Org1.ID, testdata.Bob.ID, testdata.Zendesk.ID, "14", testdata.DefaultTopic.ID, "Second ticket", "Where my shoes?", models.NilUserID, nil) err = svc.Close([]*models.Ticket{ticket1, ticket2}, logger.Log) diff --git a/services/tickets/rocketchat/service_test.go b/services/tickets/rocketchat/service_test.go index f95dec3db..dd3100555 100644 --- a/services/tickets/rocketchat/service_test.go +++ b/services/tickets/rocketchat/service_test.go @@ -84,7 +84,7 @@ func TestOpenAndForward(t *testing.T) { assert.Equal(t, 1, len(logger.Logs)) test.AssertSnapshot(t, "open_ticket", logger.Logs[0].Request) - dbTicket := models.NewTicket(ticket.UUID(), testdata.Org1.ID, testdata.Cathy.ID, testdata.RocketChat.ID, "", "Need help", "Where are my cookies?", models.NilUserID, map[string]interface{}{ + dbTicket := models.NewTicket(ticket.UUID(), testdata.Org1.ID, testdata.Cathy.ID, testdata.RocketChat.ID, "", testdata.DefaultTopic.ID, "Need help", "Where are my cookies?", models.NilUserID, map[string]interface{}{ "contact-uuid": string(testdata.Cathy.UUID), "contact-display": "Cathy", }) @@ -132,8 +132,8 @@ func TestCloseAndReopen(t *testing.T) { ) require.NoError(t, err) - ticket1 := models.NewTicket("88bfa1dc-be33-45c2-b469-294ecb0eba90", testdata.Org1.ID, testdata.Cathy.ID, testdata.RocketChat.ID, "X5gwXeaxbnGDaq8Q3", "New ticket", "Where my cookies?", models.NilUserID, nil) - ticket2 := models.NewTicket("645eee60-7e84-4a9e-ade3-4fce01ae28f1", testdata.Org1.ID, testdata.Bob.ID, testdata.RocketChat.ID, "cq7AokJHKkGhAMoBK", "Second ticket", "Where my shoes?", models.NilUserID, nil) + ticket1 := models.NewTicket("88bfa1dc-be33-45c2-b469-294ecb0eba90", testdata.Org1.ID, testdata.Cathy.ID, testdata.RocketChat.ID, "X5gwXeaxbnGDaq8Q3", testdata.DefaultTopic.ID, "New ticket", "Where my cookies?", models.NilUserID, nil) + ticket2 := models.NewTicket("645eee60-7e84-4a9e-ade3-4fce01ae28f1", testdata.Org1.ID, testdata.Bob.ID, testdata.RocketChat.ID, "cq7AokJHKkGhAMoBK", testdata.DefaultTopic.ID, "Second ticket", "Where my shoes?", models.NilUserID, nil) logger := &flows.HTTPLogger{} err = svc.Close([]*models.Ticket{ticket1, ticket2}, logger.Log) diff --git a/services/tickets/utils_test.go b/services/tickets/utils_test.go index 4d995bbba..09b964ef6 100644 --- a/services/tickets/utils_test.go +++ b/services/tickets/utils_test.go @@ -165,7 +165,8 @@ func TestCloseTicket(t *testing.T) { testdata.Cathy.ID, testdata.Mailgun.ID, "EX12345", - "New Ticket", + testdata.DefaultTopic.ID, + "", "Where are my cookies?", models.NilUserID, map[string]interface{}{ @@ -187,5 +188,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:34Z"}`}) + []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"}`}) } diff --git a/services/tickets/zendesk/service_test.go b/services/tickets/zendesk/service_test.go index 7c1693bc1..966b63caf 100644 --- a/services/tickets/zendesk/service_test.go +++ b/services/tickets/zendesk/service_test.go @@ -100,7 +100,7 @@ func TestOpenAndForward(t *testing.T) { assert.Equal(t, 1, len(logger.Logs)) test.AssertSnapshot(t, "open_ticket", logger.Logs[0].Request) - dbTicket := models.NewTicket(ticket.UUID(), testdata.Org1.ID, testdata.Cathy.ID, testdata.Zendesk.ID, "", "Need help", "Where are my cookies?", models.NilUserID, map[string]interface{}{ + dbTicket := models.NewTicket(ticket.UUID(), testdata.Org1.ID, testdata.Cathy.ID, testdata.Zendesk.ID, "", testdata.DefaultTopic.ID, "Need help", "Where are my cookies?", models.NilUserID, map[string]interface{}{ "contact-uuid": string(testdata.Cathy.UUID), "contact-display": "Cathy", }) @@ -161,8 +161,8 @@ func TestCloseAndReopen(t *testing.T) { require.NoError(t, err) logger := &flows.HTTPLogger{} - ticket1 := models.NewTicket("88bfa1dc-be33-45c2-b469-294ecb0eba90", testdata.Org1.ID, testdata.Cathy.ID, testdata.Zendesk.ID, "12", "New ticket", "Where my cookies?", models.NilUserID, nil) - ticket2 := models.NewTicket("645eee60-7e84-4a9e-ade3-4fce01ae28f1", testdata.Org1.ID, testdata.Bob.ID, testdata.Zendesk.ID, "14", "Second ticket", "Where my shoes?", models.NilUserID, nil) + ticket1 := models.NewTicket("88bfa1dc-be33-45c2-b469-294ecb0eba90", testdata.Org1.ID, testdata.Cathy.ID, testdata.Zendesk.ID, "12", testdata.DefaultTopic.ID, "New ticket", "Where my cookies?", models.NilUserID, nil) + ticket2 := models.NewTicket("645eee60-7e84-4a9e-ade3-4fce01ae28f1", testdata.Org1.ID, testdata.Bob.ID, testdata.Zendesk.ID, "14", testdata.DefaultTopic.ID, "Second ticket", "Where my shoes?", models.NilUserID, nil) err = svc.Close([]*models.Ticket{ticket1, ticket2}, logger.Log) diff --git a/testsuite/testdata/constants.go b/testsuite/testdata/constants.go index c151d4e62..c658efd78 100644 --- a/testsuite/testdata/constants.go +++ b/testsuite/testdata/constants.go @@ -71,6 +71,8 @@ var TestersGroup = &Group{10001, "5e9d8fab-5e7e-4f51-b533-261af5dea70d"} var ReportingLabel = &Label{10000, "ebc4dedc-91c4-4ed4-9dd6-daa05ea82698"} var TestingLabel = &Label{10001, "a6338cdc-7938-4437-8b05-2d5d785e3a08"} +var DefaultTopic = &Topic{1, "ffc903f7-8cbb-443f-9627-87106842d1aa"} + var Internal = &Ticketer{1, "8bd48029-6ca1-46a8-aa14-68f7213b82b3"} var Mailgun = &Ticketer{2, "f9c9447f-a291-4f3c-8c79-c089bbd4e713"} var Zendesk = &Ticketer{3, "4ee6d4f3-f92b-439b-9718-8da90c05490b"} diff --git a/testsuite/testdata/tickets.go b/testsuite/testdata/tickets.go index c6dba78c4..c0ad84827 100644 --- a/testsuite/testdata/tickets.go +++ b/testsuite/testdata/tickets.go @@ -13,6 +13,11 @@ import ( "github.com/jmoiron/sqlx" ) +type Topic struct { + ID models.TopicID + UUID assets.TopicUUID +} + type Ticket struct { ID models.TicketID UUID flows.TicketUUID