From 0b6197d16993c291dd084157e2b9d0b2be8c8641 Mon Sep 17 00:00:00 2001 From: Rafael Soares Date: Wed, 2 Aug 2023 10:17:16 -0300 Subject: [PATCH] tickets/zendesk: service search or create user on open ticket --- services/tickets/zendesk/client.go | 23 +++++++++++----- services/tickets/zendesk/client_test.go | 16 +++++------- services/tickets/zendesk/service.go | 26 ++++++++++++++++++- services/tickets/zendesk/service_test.go | 9 +++++-- .../TestOpenAndForward_forward_message.snap | 2 +- .../testdata/TestOpenAndForward_open.snap | 5 +--- .../TestOpenAndForward_open_ticket.snap | 5 +--- 7 files changed, 58 insertions(+), 28 deletions(-) diff --git a/services/tickets/zendesk/client.go b/services/tickets/zendesk/client.go index a238af9b5..0c5d3aaac 100644 --- a/services/tickets/zendesk/client.go +++ b/services/tickets/zendesk/client.go @@ -312,19 +312,20 @@ func encodeIds(ids []int64) string { // User see https://developer.zendesk.com/api-reference/ticketing/users/users/#json-format type User struct { ID int64 `json:"id,omitempty"` - Name string `json:"name"` + Name string `json:"name,omitempty"` Organization struct { Name string `json:"name"` - } `json:"organization"` - ExternalID string `json:"external_id"` + } `json:"organization,omitempty"` + ExternalID string `json:"external_id,omitempty"` Identities []struct { Type string `json:"type"` Value string `json:"value"` } `json:"identities,omitempty"` - Verified bool `json:"verified"` - Role string `json:"role"` + Verified bool `json:"verified,omitempty"` + Role string `json:"role,omitempty"` } +// CreateUser creates a new user in zendesk func (c *RESTClient) CreateUser(user *User) (*User, *httpx.Trace, error) { payload := struct { User *User `json:"user"` @@ -342,12 +343,20 @@ func (c *RESTClient) CreateUser(user *User) (*User, *httpx.Trace, error) { return response.User, trace, nil } +type SearchUserResponse struct { + Users []User `json:"users"` +} + +// SearchUser returns the user with the given external ID or nil if it doesn't exist func (c *RESTClient) SearchUser(externalID string) (*User, *httpx.Trace, error) { endpoint := fmt.Sprintf("users/search?external_id=%s", externalID) - var response map[string][]User + var response SearchUserResponse trace, err := c.get(endpoint, nil, &response) if err != nil { return nil, trace, err } - return &response["users"][0], trace, nil + if len(response.Users) == 0 { + return nil, trace, nil + } + return &response.Users[0], trace, nil } diff --git a/services/tickets/zendesk/client_test.go b/services/tickets/zendesk/client_test.go index 28abaa2f2..2dd46e82a 100644 --- a/services/tickets/zendesk/client_test.go +++ b/services/tickets/zendesk/client_test.go @@ -316,14 +316,8 @@ func TestSearchUser(t *testing.T) { fmt.Sprintf("https://mocked_subdomain.zendesk.com/api/v2/users/search?external_id=%s", userUUID): { httpx.MockConnectionError, httpx.NewMockResponse(400, nil, `{"description": "Something went wrong", "error": "Unknown"}`), - httpx.NewMockResponse(201, nil, `{ - "users": [ - { - "id": 35436, - "name": "Dummy User" - } - ] - }`), + httpx.NewMockResponse(200, nil, `{"users": []}`), + httpx.NewMockResponse(200, nil, `{"users": [{"id": 35436,"name": "Dummy User"}]}`), }, })) @@ -335,8 +329,12 @@ func TestSearchUser(t *testing.T) { _, _, err = client.SearchUser(userUUID) assert.EqualError(t, err, "Something went wrong") + user, _, err := client.SearchUser(userUUID) + assert.NoError(t, err) + assert.Nil(t, user) + user, trace, err := client.SearchUser(userUUID) assert.NoError(t, err) assert.Equal(t, "Dummy User", user.Name) - assert.Equal(t, "HTTP/1.0 201 Created\r\nContent-Length: 87\r\n\r\n", string(trace.ResponseTrace)) + assert.Equal(t, "HTTP/1.0 200 OK\r\nContent-Length: 47\r\n\r\n", string(trace.ResponseTrace)) } diff --git a/services/tickets/zendesk/service.go b/services/tickets/zendesk/service.go index 8826f3b0c..c45413433 100644 --- a/services/tickets/zendesk/service.go +++ b/services/tickets/zendesk/service.go @@ -81,13 +81,37 @@ func NewService(rtCfg *runtime.Config, httpClient *http.Client, httpRetries *htt func (s *service) Open(session flows.Session, topic *flows.Topic, body string, assignee *flows.User, logHTTP flows.HTTPLogCallback) (*flows.Ticket, error) { ticket := flows.OpenTicket(s.ticketer, topic, body, assignee) contactDisplay := session.Contact().Format(session.Environment()) + contactUUID := string(session.Contact().UUID()) + + user, trace, err := s.restClient.SearchUser(contactUUID) + if trace != nil { + logHTTP(flows.NewHTTPLog(trace, flows.HTTPStatusFromCode, s.redactor)) + } + if err != nil && trace.Response.StatusCode != http.StatusNotFound { + return nil, err + } + if trace.Response.StatusCode == http.StatusNotFound || user == nil { + user := &User{ + Name: contactDisplay, + ExternalID: contactUUID, + Verified: true, + Role: "end-user", + } + _, trace, err = s.restClient.CreateUser(user) + if trace != nil { + logHTTP(flows.NewHTTPLog(trace, flows.HTTPStatusFromCode, s.redactor)) + } + if err != nil { + return nil, err + } + } msg := &ExternalResource{ ExternalID: string(ticket.UUID()), // there's no local msg so use ticket UUID instead ThreadID: string(ticket.UUID()), CreatedAt: dates.Now(), Author: Author{ - ExternalID: string(session.Contact().UUID()), + ExternalID: contactUUID, Name: contactDisplay, }, AllowChannelback: true, diff --git a/services/tickets/zendesk/service_test.go b/services/tickets/zendesk/service_test.go index 041678deb..b4ebb2b72 100644 --- a/services/tickets/zendesk/service_test.go +++ b/services/tickets/zendesk/service_test.go @@ -41,6 +41,11 @@ func TestOpenAndForward(t *testing.T) { uuids.SetGenerator(uuids.NewSeededGenerator(12345)) dates.SetNowSource(dates.NewSequentialNowSource(time.Date(2019, 10, 7, 15, 21, 30, 0, time.UTC))) httpx.SetRequestor(httpx.NewMockRequestor(map[string][]httpx.MockResponse{ + "https://nyaruka.zendesk.com/api/v2/users/search?external_id=5d76d86b-3bb9-4d5a-b822-c9d86f5d8e4f": { + httpx.NewMockResponse(200, nil, `{"users": [{"id": 35241, "name": "Dummy User"}], "count": 1, "next_page": "https://nyaruka.zendesk.com/api/v2/users.json?page=2"}`), + httpx.NewMockResponse(200, nil, `{"users": [{"id": 35241, "name": "Dummy User"}], "count": 1, "next_page": "https://nyaruka.zendesk.com/api/v2/users.json?page=2"}`), + httpx.NewMockResponse(200, nil, `{"users": [{"id": 35241, "name": "Dummy User"}], "count": 1, "next_page": "https://nyaruka.zendesk.com/api/v2/users.json?page=2"}`), + }, "https://nyaruka.zendesk.com/api/v2/any_channel/push.json": { httpx.MockConnectionError, httpx.NewMockResponse(201, nil, `{ @@ -123,7 +128,7 @@ func TestOpenAndForward(t *testing.T) { assert.Equal(t, "General", ticket.Topic().Name()) assert.Equal(t, fieldTicket, ticket.Body()) assert.Equal(t, "", ticket.ExternalID()) - assert.Equal(t, 1, len(logger.Logs)) + assert.Equal(t, 2, 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, "", testdata.DefaultTopic.ID, "Where are my cookies?", models.NilUserID, map[string]interface{}{ @@ -147,7 +152,7 @@ func TestOpenAndForward(t *testing.T) { logger = &flows.HTTPLogger{} _, err = svc.Open(session, defaultTopic, fieldTicket1, nil, logger.Log) assert.NoError(t, err) - assert.Equal(t, 1, len(logger.Logs)) + assert.Equal(t, 2, len(logger.Logs)) test.AssertSnapshot(t, "open", logger.Logs[0].Request) } diff --git a/services/tickets/zendesk/testdata/TestOpenAndForward_forward_message.snap b/services/tickets/zendesk/testdata/TestOpenAndForward_forward_message.snap index 08b2950f6..20a8482aa 100644 --- a/services/tickets/zendesk/testdata/TestOpenAndForward_forward_message.snap +++ b/services/tickets/zendesk/testdata/TestOpenAndForward_forward_message.snap @@ -6,4 +6,4 @@ Authorization: Bearer **************** Content-Type: application/json Accept-Encoding: gzip -{"instance_push_id":"1234-abcd","request_id":"sesame:1570461700000000000","external_resources":[{"external_id":"ca5607f0-cba8-4c94-9cd5-c4fbc24aa767","message":"It's urgent","thread_id":"59d74b86-3e2f-4a93-aece-b05d2fdcde0c","created_at":"2019-10-07T15:21:39Z","author":{"external_id":"6393abc0-283d-4c9b-a1b3-641a035c34bf","name":"Cathy"},"allow_channelback":true,"file_urls":["https:///api/v2/file/0123/attachment1.jpg"]}]} \ No newline at end of file +{"instance_push_id":"1234-abcd","request_id":"sesame:1570461704000000000","external_resources":[{"external_id":"ca5607f0-cba8-4c94-9cd5-c4fbc24aa767","message":"It's urgent","thread_id":"59d74b86-3e2f-4a93-aece-b05d2fdcde0c","created_at":"2019-10-07T15:21:43Z","author":{"external_id":"6393abc0-283d-4c9b-a1b3-641a035c34bf","name":"Cathy"},"allow_channelback":true,"file_urls":["https:///api/v2/file/0123/attachment1.jpg"]}]} \ No newline at end of file diff --git a/services/tickets/zendesk/testdata/TestOpenAndForward_open.snap b/services/tickets/zendesk/testdata/TestOpenAndForward_open.snap index 6a258e8aa..70db668c2 100644 --- a/services/tickets/zendesk/testdata/TestOpenAndForward_open.snap +++ b/services/tickets/zendesk/testdata/TestOpenAndForward_open.snap @@ -1,9 +1,6 @@ -PUT /api/v2/tickets/update_many.json?ids=10 HTTP/1.1 +GET /api/v2/users/search?external_id=5d76d86b-3bb9-4d5a-b822-c9d86f5d8e4f HTTP/1.1 Host: nyaruka.zendesk.com User-Agent: Go-http-client/1.1 -Content-Length: 28 Authorization: Bearer **************** -Content-Type: application/json Accept-Encoding: gzip -{"ticket":{"status":"open"}} \ No newline at end of file diff --git a/services/tickets/zendesk/testdata/TestOpenAndForward_open_ticket.snap b/services/tickets/zendesk/testdata/TestOpenAndForward_open_ticket.snap index 15ffdd99b..70db668c2 100644 --- a/services/tickets/zendesk/testdata/TestOpenAndForward_open_ticket.snap +++ b/services/tickets/zendesk/testdata/TestOpenAndForward_open_ticket.snap @@ -1,9 +1,6 @@ -POST /api/v2/any_channel/push.json HTTP/1.1 +GET /api/v2/users/search?external_id=5d76d86b-3bb9-4d5a-b822-c9d86f5d8e4f HTTP/1.1 Host: nyaruka.zendesk.com User-Agent: Go-http-client/1.1 -Content-Length: 708 Authorization: Bearer **************** -Content-Type: application/json Accept-Encoding: gzip -{"instance_push_id":"1234-abcd","request_id":"sesame:1570461696000000000","external_resources":[{"external_id":"59d74b86-3e2f-4a93-aece-b05d2fdcde0c","message":"Cookies","thread_id":"59d74b86-3e2f-4a93-aece-b05d2fdcde0c","created_at":"2019-10-07T15:21:35Z","author":{"external_id":"5d76d86b-3bb9-4d5a-b822-c9d86f5d8e4f","name":"Ryan Lewis"},"allow_channelback":true,"fields":[{"id":"message","value":"Cookies"},{"id":"priority","value":"high"},{"id":"subject","value":"Where are my cookies?"},{"id":"description","value":"I want to know where is my cookie."},{"id":"21938362","value":"hd_3000"},{"id":"tags","value":["TAG_01","TAG_02"]},{"id":"external_id","value":"59d74b86-3e2f-4a93-aece-b05d2fdcde0c"}]}]} \ No newline at end of file