diff --git a/common/types.go b/common/types.go index 5a9d5b6a2..b8891cc5e 100644 --- a/common/types.go +++ b/common/types.go @@ -113,6 +113,9 @@ var ( // ErrPayloadNotURLForm is returned when payload is not string key-value pair // which could be encoded for POST with content type of application/x-www-form-urlencoded. ErrPayloadNotURLForm = errors.New("payload cannot be url-form encoded") + + // ErrResolvingCustomFields is returned when custom fields cannot be retrieved for Read or ListObjectMetadata. + ErrResolvingCustomFields = errors.New("cannot resolve custom fields") ) // ReadParams defines how we are reading data from a SaaS API. @@ -333,6 +336,12 @@ type ObjectMetadata struct { FieldsMap map[string]string } +// AddFieldMetadata updates Fields and FieldsMap fields ensuring data consistency. +func (m ObjectMetadata) AddFieldMetadata(fieldName string, fieldMetadata FieldMetadata) { + m.Fields[fieldName] = fieldMetadata + m.FieldsMap[fieldName] = fieldMetadata.DisplayName +} + // NewObjectMetadata constructs ObjectMetadata. // This will automatically infer fields map from field metadata map. This construct exists for such convenience. func NewObjectMetadata(displayName string, fields map[string]FieldMetadata) *ObjectMetadata { diff --git a/providers/keap/metadata_test.go b/providers/keap/metadata_test.go index f43f0cf7d..2cd18061a 100644 --- a/providers/keap/metadata_test.go +++ b/providers/keap/metadata_test.go @@ -196,8 +196,8 @@ func TestListObjectMetadataV2(t *testing.T) { // nolint:funlen,gocognit,cyclop func metadataExpectAbsentFields(serverURL string, actual, expected *common.ListObjectMetadataResult) bool { const contacts = "contacts" - if !errors.Is(actual.Errors[contacts], ErrResolvingCustomFields) { - slog.Info("missing metadata error", "errors", ErrResolvingCustomFields) + if !errors.Is(actual.Errors[contacts], common.ErrResolvingCustomFields) { + slog.Info("missing metadata error", "errors", common.ErrResolvingCustomFields) return false } diff --git a/providers/keap/read.go b/providers/keap/read.go index 114282eda..f8697516f 100644 --- a/providers/keap/read.go +++ b/providers/keap/read.go @@ -11,8 +11,6 @@ import ( "github.com/amp-labs/connectors/providers/keap/metadata" ) -var ErrResolvingCustomFields = errors.New("cannot resolve custom fields") - func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common.ReadResult, error) { if err := config.ValidateParams(true); err != nil { return nil, err @@ -94,17 +92,17 @@ func (c *Connector) requestCustomFields( url, err := c.getURL(modulePath, objectName, "model") if err != nil { - return nil, errors.Join(ErrResolvingCustomFields, err) + return nil, errors.Join(common.ErrResolvingCustomFields, err) } res, err := c.Client.Get(ctx, url.String()) if err != nil { - return nil, errors.Join(ErrResolvingCustomFields, err) + return nil, errors.Join(common.ErrResolvingCustomFields, err) } fieldsResponse, err := common.UnmarshalJSON[modelCustomFieldsResponse](res) if err != nil { - return nil, errors.Join(ErrResolvingCustomFields, err) + return nil, errors.Join(common.ErrResolvingCustomFields, err) } fields := make(map[int]modelCustomField) diff --git a/providers/zendesksupport/metadata.go b/providers/zendesksupport/metadata.go index d5c93df24..eda09c72c 100644 --- a/providers/zendesksupport/metadata.go +++ b/providers/zendesksupport/metadata.go @@ -10,5 +10,31 @@ import ( func (c *Connector) ListObjectMetadata( ctx context.Context, objectNames []string, ) (*common.ListObjectMetadataResult, error) { - return metadata.Schemas.Select(c.Module.ID, objectNames) + metadataResult, err := metadata.Schemas.Select(c.Module.ID, objectNames) + if err != nil { + return nil, err + } + + for _, objectName := range objectNames { + fields, err := c.requestCustomFields(ctx, objectName) + if err != nil { + metadataResult.Errors[objectName] = err + + continue + } + + // Attach fields to the object metadata. + objectMetadata := metadataResult.Result[objectName] + for _, field := range fields { + objectMetadata.AddFieldMetadata(field.Title, common.FieldMetadata{ + DisplayName: field.TitleInPortal, + ValueType: field.GetValueType(), + ProviderType: field.Type, + ReadOnly: false, + Values: field.getValues(), + }) + } + } + + return metadataResult, nil } diff --git a/providers/zendesksupport/metadata_test.go b/providers/zendesksupport/metadata_test.go index 1d8834981..cba061577 100644 --- a/providers/zendesksupport/metadata_test.go +++ b/providers/zendesksupport/metadata_test.go @@ -7,13 +7,17 @@ import ( "github.com/amp-labs/connectors" "github.com/amp-labs/connectors/common" + "github.com/amp-labs/connectors/test/utils/mockutils/mockcond" "github.com/amp-labs/connectors/test/utils/mockutils/mockserver" "github.com/amp-labs/connectors/test/utils/testroutines" + "github.com/amp-labs/connectors/test/utils/testutils" ) func TestListObjectMetadataZendeskSupportModule(t *testing.T) { // nolint:funlen,gocognit,cyclop t.Parallel() + responseTicketsCustomFields := testutils.DataFromFile(t, "read/custom_fields/ticket_fields.json") + tests := []testroutines.Metadata{ { Name: "At least one object name must be queried", @@ -93,6 +97,72 @@ func TestListObjectMetadataZendeskSupportModule(t *testing.T) { // nolint:funlen }, ExpectedErrs: nil, }, + { + Name: "Successfully describe ticket custom fields", + Input: []string{"tickets"}, + Server: mockserver.Conditional{ + Setup: mockserver.ContentJSON(), + If: mockcond.PathSuffix("/api/v2/ticket_fields"), + Then: mockserver.Response(http.StatusOK, responseTicketsCustomFields), + }.Server(), + Comparator: testroutines.ComparatorSubsetMetadata, + Expected: &common.ListObjectMetadataResult{ + Result: map[string]common.ObjectMetadata{ + "tickets": { + DisplayName: "Tickets", + Fields: map[string]common.FieldMetadata{ + "comment": { + DisplayName: "comment", + ValueType: "other", + ProviderType: "object", + ReadOnly: false, + }, + "priority": { + DisplayName: "priority", + ValueType: "singleSelect", + ProviderType: "string", + ReadOnly: false, + Values: []common.FieldValue{{ + Value: "urgent", + DisplayValue: "urgent", + }, { + Value: "high", + DisplayValue: "high", + }, { + Value: "normal", + DisplayValue: "normal", + }, { + Value: "low", + DisplayValue: "low", + }}, + }, + // Custom field + "Customer Type": { + DisplayName: "Customer Type", + ValueType: "singleSelect", + ProviderType: "tagger", + ReadOnly: false, + Values: []common.FieldValue{{ + Value: "vip_customer", + DisplayValue: "VIP Customer", + }, { + Value: "standard_customer", + DisplayValue: "Standard Customer", + }}, + }, + }, + FieldsMap: map[string]string{ + "comment": "comment", + // Custom fields + "Customer Type": "Customer Type", + "Topic": "Topic", + }, + }, + }, + Errors: make(map[string]error), + }, + ExpectedErrs: nil, + }, } for _, tt := range tests { diff --git a/providers/zendesksupport/objectNames.go b/providers/zendesksupport/objectNames.go index 44e426002..8e1ad783f 100644 --- a/providers/zendesksupport/objectNames.go +++ b/providers/zendesksupport/objectNames.go @@ -6,6 +6,11 @@ import ( "github.com/amp-labs/connectors/providers/zendesksupport/metadata" ) +const ( + objectNameTickets = "tickets" + objectNameRequests = "requests" +) + // Supported object names can be found under schemas.json. var supportedObjectsByRead = metadata.Schemas.ObjectNames() //nolint:gochecknoglobals @@ -28,3 +33,12 @@ var writeURLExceptions = map[common.ModuleID]datautils.Map[string, string]{ //no }, ModuleHelpCenter: {}, } + +var objectsWithCustomFields = map[common.ModuleID]datautils.StringSet{ // nolint:gochecknoglobals + ModuleTicketing: datautils.NewStringSet( + // https://developer.zendesk.com/api-reference/ticketing/tickets/tickets/#json-format + objectNameTickets, + // https://developer.zendesk.com/api-reference/ticketing/tickets/ticket-requests/#json-format + objectNameRequests, + ), +} diff --git a/providers/zendesksupport/parse.go b/providers/zendesksupport/parse.go index 5e6aac595..ac5321d17 100644 --- a/providers/zendesksupport/parse.go +++ b/providers/zendesksupport/parse.go @@ -1,7 +1,9 @@ package zendesksupport import ( + "github.com/amp-labs/connectors/common" "github.com/amp-labs/connectors/internal/jsonquery" + "github.com/amp-labs/connectors/providers/zendesksupport/metadata" "github.com/spyzhov/ajson" ) @@ -49,3 +51,11 @@ func getNextRecordsURL(node *ajson.Node) (string, error) { return jsonquery.New(node).StrWithDefault("after_url", "") } + +func makeGetRecords(moduleID common.ModuleID, objectName string) common.NodeRecordsFunc { + return func(node *ajson.Node) ([]*ajson.Node, error) { + responseFieldName := metadata.Schemas.LookupArrayFieldName(moduleID, objectName) + + return jsonquery.New(node).ArrayRequired(responseFieldName) + } +} diff --git a/providers/zendesksupport/read.go b/providers/zendesksupport/read.go index 577989bff..64d55d84d 100644 --- a/providers/zendesksupport/read.go +++ b/providers/zendesksupport/read.go @@ -2,12 +2,15 @@ package zendesksupport import ( "context" + "errors" "strconv" "time" "github.com/amp-labs/connectors/common" "github.com/amp-labs/connectors/common/urlbuilder" + "github.com/amp-labs/connectors/internal/jsonquery" "github.com/amp-labs/connectors/providers/zendesksupport/metadata" + "github.com/spyzhov/ajson" ) func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common.ReadResult, error) { @@ -29,13 +32,16 @@ func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common return nil, err } - responseFieldName := metadata.Schemas.LookupArrayFieldName(c.Module.ID, config.ObjectName) + customFields, err := c.requestCustomFields(ctx, config.ObjectName) + if err != nil { + return nil, err + } return common.ParseResult( rsp, - common.GetRecordsUnderJSONPath(responseFieldName), + makeGetRecords(c.Module.ID, config.ObjectName), getNextRecordsURL, - common.GetMarshaledData, + common.MakeMarshaledDataFunc(c.attachReadCustomFields(customFields)), config.Fields, ) } @@ -89,3 +95,190 @@ func formatStartTime(config common.ReadParams) string { return strconv.FormatInt(unixTime, 10) } + +// requestCustomFields makes and API call to get model describing custom fields. +// For not applicable objects the empty mapping is returned. +// The mapping is between "custom field id" and struct containing "human-readable field name". +// +// Custom fields are always associated with "ticket_fields" regardless of the object type. +func (c *Connector) requestCustomFields( + ctx context.Context, objectName string, +) (map[int64]ticketField, error) { + if !objectsWithCustomFields[c.Module.ID].Has(objectName) { + // This object doesn't have custom fields, we are done. + return map[int64]ticketField{}, nil + } + + url, err := c.getReadURL("ticket_fields") + if err != nil { + return nil, errors.Join(common.ErrResolvingCustomFields, err) + } + + res, err := c.Client.Get(ctx, url.String()) + if err != nil { + return nil, errors.Join(common.ErrResolvingCustomFields, err) + } + + fieldsResponse, err := common.UnmarshalJSON[ticketFieldsResponse](res) + if err != nil { + return nil, errors.Join(common.ErrResolvingCustomFields, err) + } + + fields := make(map[int64]ticketField) + for _, field := range fieldsResponse.TicketFields { + fields[field.ID] = field + } + + return fields, nil +} + +// nolint:tagliatelle +// https://developer.zendesk.com/api-reference/ticketing/tickets/ticket_fields/#json-format +type ticketFieldsResponse struct { + TicketFields []ticketField `json:"ticket_fields"` +} + +// nolint:tagliatelle +type ticketField struct { + // Automatically assigned when created + ID int64 `json:"id"` + // System or custom field type. + Type string `json:"type"` + // The title of the ticket field for end users in Help Center + TitleInPortal string `json:"title_in_portal"` + // Title is usually similar to the TitleInPortal. + Title string `json:"title"` + + // Presented for a system ticket field of type "tickettype", "priority" or "status". + SystemFieldOptions []systemFieldOption `json:"system_field_options,omitempty"` + // List of customized ticket statuses. Only presented for a system ticket field of type "custom_status". + CustomStatuses []customStatus `json:"custom_statuses,omitempty"` + // Required and presented for a custom ticket field of type "multiselect" or "tagger". + CustomFieldOptions []customFieldOption `json:"custom_field_options,omitempty"` +} + +func (f ticketField) GetValueType() common.ValueType { + switch f.Type { + case "subject", "description": + return common.ValueTypeString + case + // custom_field_options: + "tagger", + // system_field_options: + "tickettype", "priority", "status", + // custom_statuses: + "custom_status": + return common.ValueTypeSingleSelect + case + // custom_field_options: + "multiselect": + return common.ValueTypeMultiSelect + default: + // group, assignee + return common.ValueTypeOther + } +} + +func (f ticketField) getValues() []common.FieldValue { + result := make([]common.FieldValue, 0) + + for _, option := range f.SystemFieldOptions { + result = append(result, common.FieldValue{ + Value: option.Value, + DisplayValue: option.Name, + }) + } + + for _, option := range f.CustomFieldOptions { + result = append(result, common.FieldValue{ + Value: option.Value, + DisplayValue: option.Name, + }) + } + + for _, status := range f.CustomStatuses { + result = append(result, common.FieldValue{ + Value: strconv.FormatInt(status.ID, 10), + DisplayValue: status.AgentLabel, + }) + } + + return result +} + +// nolint:tagliatelle +// https://developer.zendesk.com/api-reference/ticketing/tickets/custom_ticket_statuses/#json-format +type customStatus struct { + ID int64 `json:"id"` + // The label displayed to agents. Maximum length is 48 characters + AgentLabel string `json:"agent_label"` +} + +// https://developer.zendesk.com/api-reference/ticketing/tickets/ticket_fields/#list-ticket-field-options +type customFieldOption struct { + ID int64 `json:"id"` + Name string `json:"name"` + Value string `json:"value"` +} + +// https://developer.zendesk.com/api-reference/ticketing/tickets/ticket_fields/#list-ticket-field-options +type systemFieldOption struct { + Name string `json:"name"` + Value string `json:"value"` +} + +// nolint:tagliatelle,lll +// https://developer.zendesk.com/documentation/ticketing/managing-tickets/creating-and-updating-tickets/#setting-custom-field-values +type readCustomFieldsResponse struct { + CustomFields []readCustomField `json:"custom_fields"` +} + +type readCustomField struct { + ID int64 `json:"id"` + Value any `json:"value"` +} + +// Before parsing the records, if any custom fields are present (without a human-readable name), +// this will call the correct API to extend & replace the custom field with human-readable information. +// Object will then be enhanced using model. +func (c *Connector) attachReadCustomFields( + customFields map[int64]ticketField, +) common.RawReadRecordFunc { + return func(node *ajson.Node) (map[string]any, error) { + if len(customFields) == 0 { + // No custom fields, no-op, return as is. + return jsonquery.Convertor.ObjectToMap(node) + } + + return enhanceObjectsWithCustomFieldNames(node, customFields) + } +} + +// In general this does the usual JSON parsing. +// However, those objects that contain "custom_fields" are processed as follows: +// * Locate custom fields in JSON read response. +// * Replace ids with human-readable names, which is provided as argument. +// * Place fields at the top level of the object. +func enhanceObjectsWithCustomFieldNames( + node *ajson.Node, + fields map[int64]ticketField, +) (map[string]any, error) { + object, err := jsonquery.Convertor.ObjectToMap(node) + if err != nil { + return nil, err + } + + customFieldsResponse, err := jsonquery.ParseNode[readCustomFieldsResponse](node) + if err != nil { + return nil, err + } + + // Replace identifiers with human-readable field names which were found by making a call to "/model". + for _, field := range customFieldsResponse.CustomFields { + if model, ok := fields[field.ID]; ok { + object[model.Title] = field.Value + } + } + + return object, nil +} diff --git a/providers/zendesksupport/read_test.go b/providers/zendesksupport/read_test.go index 799b4baf6..7939a3f5c 100644 --- a/providers/zendesksupport/read_test.go +++ b/providers/zendesksupport/read_test.go @@ -137,32 +137,54 @@ func TestIncrementalReadZendeskSupportModule(t *testing.T) { //nolint:funlen,goc t.Parallel() responseTickets := testutils.DataFromFile(t, "read/incremental/tickets.json") + responseTicketsCustomFields := testutils.DataFromFile(t, "read/custom_fields/ticket_fields.json") responseUsersFirstPage := testutils.DataFromFile(t, "read/incremental/users-1-first-page.json") responseUsersLastPage := testutils.DataFromFile(t, "read/incremental/users-2-last-page.json") responseOrganizations := testutils.DataFromFile(t, "read/incremental/organizations.json") tests := []testroutines.Read{ { - Name: "Incremental Tickets no since", + Name: "Incremental Tickets no since with custom fields", Input: common.ReadParams{ ObjectName: "tickets", - Fields: connectors.Fields("id"), + Fields: connectors.Fields("id", "Customer Type", "Topic"), }, - Server: mockserver.Conditional{ + Server: mockserver.Switch{ Setup: mockserver.ContentJSON(), - If: mockcond.And{ - mockcond.QueryParam("per_page", "100"), - mockcond.QueryParam("start_time", "0"), - mockcond.PathSuffix("/api/v2/incremental/tickets/cursor"), - }, - Then: mockserver.Response(http.StatusOK, responseTickets), + Cases: []mockserver.Case{{ + If: mockcond.And{ + mockcond.QueryParam("per_page", "100"), + mockcond.QueryParam("start_time", "0"), + mockcond.PathSuffix("/api/v2/incremental/tickets/cursor"), + }, + Then: mockserver.Response(http.StatusOK, responseTickets), + }, { + If: mockcond.PathSuffix("/api/v2/ticket_fields"), + Then: mockserver.Response(http.StatusOK, responseTicketsCustomFields), + }}, }.Server(), Comparator: testroutines.ComparatorSubsetRead, Expected: &common.ReadResult{ Rows: 1, Data: []common.ReadResultRow{{ - Fields: map[string]any{"id": float64(5)}, - Raw: map[string]any{"priority": "normal"}, + Fields: map[string]any{ + "id": float64(5), + "customer type": "standard_customer", + "topic": "inquiry", + }, + Raw: map[string]any{ + "priority": "normal", + "custom_fields": []any{ + map[string]any{ + "id": float64(26363655924371), + "value": "standard_customer", + }, + map[string]any{ + "id": float64(26363685850259), + "value": "inquiry", + }, + }, + }, }}, NextPage: "", Done: true, @@ -176,14 +198,19 @@ func TestIncrementalReadZendeskSupportModule(t *testing.T) { //nolint:funlen,goc Fields: connectors.Fields("id"), Since: time.Unix(1726674883, 0), }, - Server: mockserver.Conditional{ + Server: mockserver.Switch{ Setup: mockserver.ContentJSON(), - If: mockcond.And{ - mockcond.QueryParam("per_page", "100"), - mockcond.QueryParam("start_time", "1726674883"), - mockcond.PathSuffix("/api/v2/incremental/tickets/cursor"), - }, - Then: mockserver.Response(http.StatusOK, responseTickets), + Cases: []mockserver.Case{{ + If: mockcond.And{ + mockcond.QueryParam("per_page", "100"), + mockcond.QueryParam("start_time", "1726674883"), + mockcond.PathSuffix("/api/v2/incremental/tickets/cursor"), + }, + Then: mockserver.Response(http.StatusOK, responseTickets), + }, { + If: mockcond.PathSuffix("/api/v2/ticket_fields"), + Then: mockserver.Response(http.StatusOK, responseTicketsCustomFields), + }}, }.Server(), Comparator: testroutines.ComparatorPagination, Expected: &common.ReadResult{Rows: 1, NextPage: "", Done: true}, diff --git a/providers/zendesksupport/test/read/custom_fields/ticket_fields.json b/providers/zendesksupport/test/read/custom_fields/ticket_fields.json new file mode 100644 index 000000000..0b3029845 --- /dev/null +++ b/providers/zendesksupport/test/read/custom_fields/ticket_fields.json @@ -0,0 +1,415 @@ +{ + "ticket_fields": [ + { + "url": "https://d3v-ampersand.zendesk.com/api/v2/ticket_fields/26363596559123.json", + "id": 26363596559123, + "type": "subject", + "title": "Subject", + "raw_title": "Subject", + "description": "", + "raw_description": "", + "position": 1, + "active": true, + "required": false, + "collapsed_for_agents": false, + "regexp_for_validation": null, + "title_in_portal": "Subject", + "raw_title_in_portal": "Subject", + "visible_in_portal": true, + "editable_in_portal": true, + "required_in_portal": true, + "tag": null, + "created_at": "2024-02-15T16:23:02Z", + "updated_at": "2024-02-15T16:23:02Z", + "removable": false, + "key": null, + "agent_description": null + }, + { + "url": "https://d3v-ampersand.zendesk.com/api/v2/ticket_fields/26363643077907.json", + "id": 26363643077907, + "type": "description", + "title": "Description", + "raw_title": "Description", + "description": "Please enter the details of your request. A member of our support staff will respond as soon as possible.", + "raw_description": "Please enter the details of your request. A member of our support staff will respond as soon as possible.", + "position": 2, + "active": true, + "required": false, + "collapsed_for_agents": false, + "regexp_for_validation": null, + "title_in_portal": "Description", + "raw_title_in_portal": "Description", + "visible_in_portal": true, + "editable_in_portal": true, + "required_in_portal": true, + "tag": null, + "created_at": "2024-02-15T16:23:02Z", + "updated_at": "2024-02-15T16:23:02Z", + "removable": false, + "key": null, + "agent_description": null + }, + { + "url": "https://d3v-ampersand.zendesk.com/api/v2/ticket_fields/26363596563731.json", + "id": 26363596563731, + "type": "status", + "title": "Status", + "raw_title": "Status", + "description": "Request status", + "raw_description": "Request status", + "position": 3, + "active": true, + "required": false, + "collapsed_for_agents": false, + "regexp_for_validation": null, + "title_in_portal": "Status", + "raw_title_in_portal": "Status", + "visible_in_portal": false, + "editable_in_portal": false, + "required_in_portal": false, + "tag": null, + "created_at": "2024-02-15T16:23:02Z", + "updated_at": "2024-02-15T16:23:02Z", + "removable": false, + "key": null, + "agent_description": null, + "system_field_options": [ + { + "name": "Open", + "value": "open" + }, + { + "name": "Pending", + "value": "pending" + }, + { + "name": "Solved", + "value": "solved" + } + ], + "sub_type_id": 0 + }, + { + "url": "https://d3v-ampersand.zendesk.com/api/v2/ticket_fields/26363596567827.json", + "id": 26363596567827, + "type": "tickettype", + "title": "Type", + "raw_title": "Type", + "description": "Request type", + "raw_description": "Request type", + "position": 4, + "active": true, + "required": false, + "collapsed_for_agents": false, + "regexp_for_validation": null, + "title_in_portal": "Type", + "raw_title_in_portal": "Type", + "visible_in_portal": false, + "editable_in_portal": false, + "required_in_portal": false, + "tag": null, + "created_at": "2024-02-15T16:23:02Z", + "updated_at": "2024-02-15T16:23:02Z", + "removable": true, + "key": null, + "agent_description": null, + "system_field_options": [ + { + "name": "Question", + "value": "question" + }, + { + "name": "Incident", + "value": "incident" + }, + { + "name": "Problem", + "value": "problem" + }, + { + "name": "Task", + "value": "task" + } + ] + }, + { + "url": "https://d3v-ampersand.zendesk.com/api/v2/ticket_fields/26363643088147.json", + "id": 26363643088147, + "type": "priority", + "title": "Priority", + "raw_title": "Priority", + "description": "Request priority", + "raw_description": "Request priority", + "position": 5, + "active": true, + "required": false, + "collapsed_for_agents": false, + "regexp_for_validation": null, + "title_in_portal": "Priority", + "raw_title_in_portal": "Priority", + "visible_in_portal": true, + "editable_in_portal": false, + "required_in_portal": false, + "tag": null, + "created_at": "2024-02-15T16:23:02Z", + "updated_at": "2024-02-15T16:23:02Z", + "removable": true, + "key": null, + "agent_description": null, + "system_field_options": [ + { + "name": "Low", + "value": "low" + }, + { + "name": "Normal", + "value": "normal" + }, + { + "name": "High", + "value": "high" + }, + { + "name": "Urgent", + "value": "urgent" + } + ], + "sub_type_id": 0 + }, + { + "url": "https://d3v-ampersand.zendesk.com/api/v2/ticket_fields/26363596569875.json", + "id": 26363596569875, + "type": "group", + "title": "Group", + "raw_title": "Group", + "description": "Request group", + "raw_description": "Request group", + "position": 6, + "active": true, + "required": false, + "collapsed_for_agents": false, + "regexp_for_validation": null, + "title_in_portal": "Group", + "raw_title_in_portal": "Group", + "visible_in_portal": false, + "editable_in_portal": false, + "required_in_portal": false, + "tag": null, + "created_at": "2024-02-15T16:23:02Z", + "updated_at": "2024-02-15T16:23:02Z", + "removable": false, + "key": null, + "agent_description": null + }, + { + "url": "https://d3v-ampersand.zendesk.com/api/v2/ticket_fields/26363643096979.json", + "id": 26363643096979, + "type": "assignee", + "title": "Assignee", + "raw_title": "Assignee", + "description": "Agent assigned to your request", + "raw_description": "Agent assigned to your request", + "position": 7, + "active": true, + "required": true, + "collapsed_for_agents": false, + "regexp_for_validation": null, + "title_in_portal": "Assignee", + "raw_title_in_portal": "Assignee", + "visible_in_portal": true, + "editable_in_portal": false, + "required_in_portal": false, + "tag": null, + "created_at": "2024-02-15T16:23:02Z", + "updated_at": "2024-02-15T16:23:02Z", + "removable": false, + "key": null, + "agent_description": null + }, + { + "url": "https://d3v-ampersand.zendesk.com/api/v2/ticket_fields/26363596566419.json", + "id": 26363596566419, + "type": "custom_status", + "title": "Ticket status", + "raw_title": "Ticket status", + "description": "Request ticket status", + "raw_description": "Request ticket status", + "position": 8, + "active": true, + "required": false, + "collapsed_for_agents": false, + "regexp_for_validation": null, + "title_in_portal": "Ticket status", + "raw_title_in_portal": "Ticket status", + "visible_in_portal": false, + "editable_in_portal": false, + "required_in_portal": false, + "tag": null, + "created_at": "2024-02-15T16:23:02Z", + "updated_at": "2024-02-15T16:23:02Z", + "removable": false, + "key": null, + "agent_description": null, + "custom_statuses": [ + { + "url": "https://d3v-ampersand.zendesk.com/api/v2/custom_statuses/26363596805011.json", + "id": 26363596805011, + "status_category": "new", + "agent_label": "New", + "end_user_label": "Open", + "description": "Ticket is awaiting assignment to an agent", + "end_user_description": "We are working on a response for you", + "active": true, + "default": true, + "created_at": "2024-02-15T16:23:04Z", + "updated_at": "2024-02-15T16:23:04Z" + }, + { + "url": "https://d3v-ampersand.zendesk.com/api/v2/custom_statuses/26363643424147.json", + "id": 26363643424147, + "status_category": "open", + "agent_label": "Open", + "end_user_label": "Open", + "description": "Staff is working on the ticket", + "end_user_description": "We are working on a response for you", + "active": true, + "default": true, + "created_at": "2024-02-15T16:23:04Z", + "updated_at": "2024-02-15T16:23:04Z" + }, + { + "url": "https://d3v-ampersand.zendesk.com/api/v2/custom_statuses/26363643430931.json", + "id": 26363643430931, + "status_category": "pending", + "agent_label": "Pending", + "end_user_label": "Awaiting your reply", + "description": "Staff is waiting for the requester to reply", + "end_user_description": "We are waiting for you to respond", + "active": true, + "default": true, + "created_at": "2024-02-15T16:23:04Z", + "updated_at": "2024-02-15T16:23:04Z" + }, + { + "url": "https://d3v-ampersand.zendesk.com/api/v2/custom_statuses/26363643436691.json", + "id": 26363643436691, + "status_category": "hold", + "agent_label": "On-hold", + "end_user_label": "Open", + "description": "Staff is waiting for a third party", + "end_user_description": "We are working on a response for you", + "active": false, + "default": true, + "created_at": "2024-02-15T16:23:04Z", + "updated_at": "2024-02-15T16:23:04Z" + }, + { + "url": "https://d3v-ampersand.zendesk.com/api/v2/custom_statuses/26363643446547.json", + "id": 26363643446547, + "status_category": "solved", + "agent_label": "Solved", + "end_user_label": "Solved", + "description": "The ticket has been solved", + "end_user_description": "This request has been solved", + "active": true, + "default": true, + "created_at": "2024-02-15T16:23:04Z", + "updated_at": "2024-02-15T16:23:04Z" + } + ] + }, + { + "url": "https://d3v-ampersand.zendesk.com/api/v2/ticket_fields/26363655924371.json", + "id": 26363655924371, + "type": "tagger", + "title": "Customer Type", + "raw_title": "Customer Type", + "description": "Customer Type", + "raw_description": "Customer Type", + "position": 9999, + "active": true, + "required": false, + "collapsed_for_agents": false, + "regexp_for_validation": null, + "title_in_portal": "Customer Type", + "raw_title_in_portal": "Customer Type", + "visible_in_portal": false, + "editable_in_portal": false, + "required_in_portal": false, + "tag": null, + "created_at": "2024-02-15T16:24:00Z", + "updated_at": "2024-02-15T16:24:00Z", + "removable": true, + "key": null, + "agent_description": null, + "custom_field_options": [ + { + "id": 26363655921683, + "name": "VIP Customer", + "raw_name": "VIP Customer", + "value": "vip_customer", + "default": false + }, + { + "id": 26363655921811, + "name": "Standard Customer", + "raw_name": "Standard Customer", + "value": "standard_customer", + "default": false + } + ] + }, + { + "url": "https://d3v-ampersand.zendesk.com/api/v2/ticket_fields/26363685850259.json", + "id": 26363685850259, + "type": "tagger", + "title": "Topic", + "raw_title": "Topic", + "description": "Topic", + "raw_description": "Topic", + "position": 9999, + "active": true, + "required": false, + "collapsed_for_agents": false, + "regexp_for_validation": null, + "title_in_portal": "Topic", + "raw_title_in_portal": "Topic", + "visible_in_portal": false, + "editable_in_portal": false, + "required_in_portal": false, + "tag": null, + "created_at": "2024-02-15T16:24:00Z", + "updated_at": "2024-02-15T16:24:00Z", + "removable": true, + "key": null, + "agent_description": null, + "custom_field_options": [ + { + "id": 26363685849363, + "name": "Issue", + "raw_name": "Issue", + "value": "issue", + "default": false + }, + { + "id": 26363685849491, + "name": "Inquiry", + "raw_name": "Inquiry", + "value": "inquiry", + "default": false + }, + { + "id": 26363685849619, + "name": "Other", + "raw_name": "Other", + "value": "other", + "default": false + } + ] + } + ], + "next_page": null, + "previous_page": null, + "count": 10 +} diff --git a/test/zendesksupport/read/tickets/main.go b/test/zendesksupport/read/tickets/main.go index 4e8be8a7f..130dac839 100644 --- a/test/zendesksupport/read/tickets/main.go +++ b/test/zendesksupport/read/tickets/main.go @@ -30,7 +30,7 @@ func main() { res, err := conn.Read(ctx, common.ReadParams{ ObjectName: objectName, - Fields: connectors.Fields("description"), + Fields: connectors.Fields("description", "Customer Type", "Topic"), // Since: time.Now().Add(-1 * time.Hour * 24 * 180), // Pagination returned when since was not specified: // NextPage: "https://d3v-ampersand.zendesk.com/api/v2/incremental/tickets/cursor.json?cursor=MTcwODAxNDI2MS4wfHw1fA%3D%3D&per_page=1", // nolint:lll