From d2f149283d372f1748ccffd7deae4f5e699bdca0 Mon Sep 17 00:00:00 2001 From: Daniel Aharon Date: Wed, 13 Nov 2019 14:17:37 +0200 Subject: [PATCH] Issue #122 custom_fields support for tickets --- fixture/GET/ticket_custom_field.json | 57 ++++++++++++++++++ zendesk/ticket.go | 86 ++++++++++++++++++++-------- zendesk/ticket_test.go | 64 +++++++++++++++++++++ 3 files changed, 183 insertions(+), 24 deletions(-) create mode 100644 fixture/GET/ticket_custom_field.json diff --git a/fixture/GET/ticket_custom_field.json b/fixture/GET/ticket_custom_field.json new file mode 100644 index 00000000..ab868164 --- /dev/null +++ b/fixture/GET/ticket_custom_field.json @@ -0,0 +1,57 @@ +{ + "ticket": { + "url": "https://d3v-terraform-provider.zendesk.com/api/v2/tickets/4.json", + "id": 4, + "external_id": null, + "via": { + "channel": "web", + "source": { + "from": {}, + "to": {}, + "rel": null + } + }, + "created_at": "2019-06-03T02:34:52Z", + "updated_at": "2019-06-03T02:35:05Z", + "type": null, + "subject": "Ticket containing a custom field", + "raw_subject": "Ticket containing a custom field", + "description": "Testing custom field value in a ticket.", + "priority": null, + "status": "solved", + "recipient": null, + "requester_id": 377922500012, + "submitter_id": 377922500012, + "assignee_id": 377922500012, + "organization_id": 360363695492, + "group_id": 360004077472, + "collaborator_ids": [], + "follower_ids": [], + "email_cc_ids": [], + "forum_topic_id": null, + "problem_id": null, + "has_incidents": false, + "is_public": true, + "due_at": null, + "tags": [], + "custom_fields": [ + { + "id": 360005657120, + "value": "Custom field value for testing" + }, + { + "id": 360005657121, + "value": ["list", "of", "values"] + } + ], + "satisfaction_rating": null, + "sharing_agreement_ids": [], + "fields": [], + "followup_ids": [], + "ticket_form_id": 360000389592, + "brand_id": 360002256672, + "satisfaction_probability": null, + "allow_channelback": false, + "allow_attachments": true + } +} diff --git a/zendesk/ticket.go b/zendesk/ticket.go index 8470d0b0..f0bf1622 100644 --- a/zendesk/ticket.go +++ b/zendesk/ticket.go @@ -9,33 +9,71 @@ import ( "time" ) +type CustomField struct { + ID int64 `json:"id"` + // Valid types are string or []string. + Value interface{} `json:"value"` +} + +// Custom Unmarshal function required because a custom field's value can be +// a string or array of strings. +func (cf *CustomField) UnmarshalJSON(data []byte) error { + var temp map[string]interface{} + if err := json.Unmarshal(data, &temp); err != nil { + return err + } + + cf.ID = int64(temp["id"].(float64)) + + switch v := temp["value"].(type) { + case string: + cf.Value = v + case []interface{}: + var list []string + + for _, v := range temp["value"].([]interface{}) { + if s, ok := v.(string); ok { + list = append(list, s) + } else { + return fmt.Errorf("%T is an invalid type for custom field value", v) + } + } + + cf.Value = list + default: + return fmt.Errorf("%T is an invalid type for custom field value", v) + } + + return nil +} + type Ticket struct { - ID int64 `json:"id,omitempty"` - URL string `json:"url,omitempty"` - ExternalID string `json:"external_id,omitempty"` - Type string `json:"type,omitempty"` - Subject string `json:"subject,omitempty"` - RawSubject string `json:"raw_subject,omitempty"` - Description string `json:"description,omitempty"` - Priority string `json:"priority,omitempty"` - Status string `json:"status,omitempty"` - Recipient string `json:"recipient,omitempty"` - RequesterID int64 `json:"requester_id,omitempty"` - SubmitterID int64 `json:"submitter_id,omitempty"` - AssigneeID int64 `json:"assignee_id,omitempty"` - OrganizationID int64 `json:"organization_id,omitempty"` - GroupID int64 `json:"group_id,omitempty"` - CollaboratorIDs []int64 `json:"collaborator_ids,omitempty"` - FollowerIDs []int64 `json:"follower_ids,omitempty"` - EmailCCIDs []int64 `json:"email_cc_ids,omitempty"` - ForumTopicID int64 `json:"forum_topic_id,omitempty"` - ProblemID int64 `json:"problem_id,omitempty"` - HasIncidents bool `json:"has_incidents,omitempty"` - DueAt time.Time `json:"due_at,omitempty"` - Tags []string `json:"tags,omitempty"` + ID int64 `json:"id,omitempty"` + URL string `json:"url,omitempty"` + ExternalID string `json:"external_id,omitempty"` + Type string `json:"type,omitempty"` + Subject string `json:"subject,omitempty"` + RawSubject string `json:"raw_subject,omitempty"` + Description string `json:"description,omitempty"` + Priority string `json:"priority,omitempty"` + Status string `json:"status,omitempty"` + Recipient string `json:"recipient,omitempty"` + RequesterID int64 `json:"requester_id,omitempty"` + SubmitterID int64 `json:"submitter_id,omitempty"` + AssigneeID int64 `json:"assignee_id,omitempty"` + OrganizationID int64 `json:"organization_id,omitempty"` + GroupID int64 `json:"group_id,omitempty"` + CollaboratorIDs []int64 `json:"collaborator_ids,omitempty"` + FollowerIDs []int64 `json:"follower_ids,omitempty"` + EmailCCIDs []int64 `json:"email_cc_ids,omitempty"` + ForumTopicID int64 `json:"forum_topic_id,omitempty"` + ProblemID int64 `json:"problem_id,omitempty"` + HasIncidents bool `json:"has_incidents,omitempty"` + DueAt time.Time `json:"due_at,omitempty"` + Tags []string `json:"tags,omitempty"` + CustomFields []CustomField `json:"custom_fields,omitempty"` // TODO: Via #123 - // TODO: CustomFields #122 SatisfactionRating struct { ID int64 `json:"id"` diff --git a/zendesk/ticket_test.go b/zendesk/ticket_test.go index f89203ad..54a18b8d 100644 --- a/zendesk/ticket_test.go +++ b/zendesk/ticket_test.go @@ -1,7 +1,9 @@ package zendesk import ( + "encoding/json" "net/http" + "sort" "testing" ) @@ -44,6 +46,68 @@ func TestGetTicket(t *testing.T) { } } +// Test the CustomField unmarshalling fails on an invalid value. +// In this case a float64 as CustomField.Value should cause an error. +func TestGetTicketWithInvalidCustomField(t *testing.T) { + // Test with a number value. + invalidCustomFieldJson := `{ "id": 360005657120, "value": 123.456 }` + var customField CustomField + err := json.Unmarshal([]byte(invalidCustomFieldJson), &customField) + if err == nil { + t.Fatalf("Expected an error when parsing a custom field of type number.") + } + + // Test with an array of numbers. + invalidCustomFieldJson = `{ "id": 360005657120, "value": [123, 456] }` + err = json.Unmarshal([]byte(invalidCustomFieldJson), &customField) + if err == nil { + t.Fatalf("Expected an error when parsing a custom field of type [number, ...].") + } +} + +func TestGetTicketWithCustomFields(t *testing.T) { + mockAPI := newMockAPI(http.MethodGet, "ticket_custom_field.json") + client := newTestClient(mockAPI) + defer mockAPI.Close() + + ticket, err := client.GetTicket(ctx, 4) + if err != nil { + t.Fatalf("Failed to get ticket: %s", err) + } + + expectedID := int64(4) + if ticket.ID != expectedID { + t.Fatalf("Returned ticket does not have the expected ID %d. Ticket id is %d", expectedID, ticket.ID) + } + if ticket.CustomFields == nil || len(ticket.CustomFields) == 0 { + t.Fatalf("Returned ticket does not have the expected custom fields.") + } + for _, cf := range ticket.CustomFields { + switch cf.Value.(type) { + case string: + expectedCustomFieldValue := "Custom field value for testing" + if cf.Value != expectedCustomFieldValue { + t.Fatalf("Returned custom field value is not the expected value %s", cf.Value) + } + case []string: + expectedCustomFieldValue := []string{"list", "of", "values"} + sort.Strings(expectedCustomFieldValue) + // FIXME: This comparison of array contents was necessary because reflect.DeepEqual(cf.Value.([]string), expectedCustomFieldValue) would not work. + if len(cf.Value.([]string)) != len(expectedCustomFieldValue) { + t.Fatalf("Expected length comparison failed") + } + for _, v := range cf.Value.([]string) { + i := sort.SearchStrings(expectedCustomFieldValue, v) + if i >= len(expectedCustomFieldValue) || expectedCustomFieldValue[i] != v { + t.Fatalf("Expected to find %s in custom fields", v) + } + } + default: + t.Fatalf("Invalid value type in custom field: %v.", cf) + } + } +} + func TestGetMultipleTicket(t *testing.T) { mockAPI := newMockAPI(http.MethodGet, "ticket_show_many.json") client := newTestClient(mockAPI)