Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CON-1527]feat(zendesk): Human-readable custom fields #1411

Open
wants to merge 1 commit into
base: cobalt0s/zendesk-metadata-v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions providers/keap/metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
8 changes: 3 additions & 5 deletions providers/keap/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
28 changes: 27 additions & 1 deletion providers/zendesksupport/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
70 changes: 70 additions & 0 deletions providers/zendesksupport/metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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 {
Expand Down
14 changes: 14 additions & 0 deletions providers/zendesksupport/objectNames.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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,
),
}
10 changes: 10 additions & 0 deletions providers/zendesksupport/parse.go
Original file line number Diff line number Diff line change
@@ -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"
)

Expand Down Expand Up @@ -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)
}
}
Loading