From 819856fd10208ac93cd306b47c13cb990aca5bcd Mon Sep 17 00:00:00 2001 From: jkarage Date: Wed, 5 Mar 2025 16:41:54 +0300 Subject: [PATCH] feat: Add ZendeskChat Read Connector --- providers/zendeskchat/connector.go | 20 +++++++++ providers/zendeskchat/handlers.go | 43 +++++++++++++++++-- providers/zendeskchat/parse.go | 46 ++++++++++++++++++++ providers/zendeskchat/supports.go | 28 ++++++++++++- test/zendeskchat/read/main.go | 67 ++++++++++++++++++++++++++++++ 5 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 providers/zendeskchat/parse.go create mode 100644 test/zendeskchat/read/main.go diff --git a/providers/zendeskchat/connector.go b/providers/zendeskchat/connector.go index 0d026e068..0c7db05f6 100644 --- a/providers/zendeskchat/connector.go +++ b/providers/zendeskchat/connector.go @@ -4,7 +4,9 @@ import ( "github.com/amp-labs/connectors/common" "github.com/amp-labs/connectors/internal/components" "github.com/amp-labs/connectors/internal/components/operations" + "github.com/amp-labs/connectors/internal/components/reader" "github.com/amp-labs/connectors/internal/components/schema" + "github.com/amp-labs/connectors/internal/staticschema" "github.com/amp-labs/connectors/providers" ) @@ -20,6 +22,7 @@ type Connector struct { // Supported operations components.SchemaProvider + components.Reader } func NewConnector(params common.Parameters) (*Connector, error) { @@ -39,5 +42,22 @@ func constructor(base *components.Connector) (*Connector, error) { }, ) + registry, err := components.NewEndpointRegistry(supportedOperations()) + if err != nil { + return nil, err + } + + // Set the read provider for the connector + connector.Reader = reader.NewHTTPReader( + connector.HTTPClient().Client, + registry, + staticschema.RootModuleID, + operations.ReadHandlers{ + BuildRequest: connector.buildReadRequest, + ParseResponse: connector.parseReadResponse, + ErrorHandler: common.InterpretError, + }, + ) + return connector, nil } diff --git a/providers/zendeskchat/handlers.go b/providers/zendeskchat/handlers.go index 3db0aa983..8551b72f7 100644 --- a/providers/zendeskchat/handlers.go +++ b/providers/zendeskchat/handlers.go @@ -11,8 +11,10 @@ import ( ) const ( - bans = "bans" - account = "account" + bans = "bans" + account = "account" + chats = "chats" + defaultPageSize = 1000 ) func (c *Connector) buildSingleObjectMetadataRequest(ctx context.Context, objectName string) (*http.Request, error) { @@ -47,7 +49,7 @@ func (c *Connector) parseSingleObjectMetadataResponse( } func parseIndividualResponse(objectName string, response *common.JSONHTTPResponse) (map[string]any, error) { - field := responseFields(objectName) + field := responseField(objectName) if field != "" { resp, err := common.UnmarshalJSON[map[string]any](response) if err != nil { @@ -100,3 +102,38 @@ func parseListResponse(response *common.JSONHTTPResponse) (map[string]any, error return (*resp)[0], nil } + +func (c *Connector) buildReadRequest(ctx context.Context, params common.ReadParams) (*http.Request, error) { + var ( + url *urlbuilder.URL + err error + ) + + url, err = urlbuilder.New(c.ProviderInfo().BaseURL, restAPIVersion, params.ObjectName) + if err != nil { + return nil, err + } + + if params.NextPage != "" { + url, err = urlbuilder.New(params.NextPage.String()) + if err != nil { + return nil, err + } + } + + return http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil) +} + +func (c *Connector) parseReadResponse( + ctx context.Context, + params common.ReadParams, + response *common.JSONHTTPResponse, +) (*common.ReadResult, error) { + return common.ParseResult( + response, + common.GetRecordsUnderJSONPath(responseField(params.ObjectName)), + nextRecordsURL(params.ObjectName), + common.GetMarshaledData, + params.Fields, + ) +} diff --git a/providers/zendeskchat/parse.go b/providers/zendeskchat/parse.go new file mode 100644 index 000000000..82e28d896 --- /dev/null +++ b/providers/zendeskchat/parse.go @@ -0,0 +1,46 @@ +package zendeskchat + +import ( + "github.com/amp-labs/connectors/common" + "github.com/amp-labs/connectors/internal/jsonquery" + "github.com/spyzhov/ajson" +) + +func nextRecordsURL(objectName string) common.NextPageFunc { + return func(node *ajson.Node) (string, error) { + switch objectName { + case chats: + nextURL, err := jsonquery.New(node).StringOptional("next_url") + if err != nil { + return "", err + } + + if nextURL != nil { + return *nextURL, nil + } + + return "", nil + + default: + counts, err := jsonquery.New(node).IntegerOptional("count") + if err != nil { + return "", err + } + + if counts == nil || *counts < defaultPageSize { + return "", nil + } + + nextURL, err := jsonquery.New(node).StringOptional("next_page") + if err != nil { + return "", err + } + + if nextURL != nil { + return *nextURL, nil + } + + return "", nil + } + } +} diff --git a/providers/zendeskchat/supports.go b/providers/zendeskchat/supports.go index 93eaecb93..3c6874141 100644 --- a/providers/zendeskchat/supports.go +++ b/providers/zendeskchat/supports.go @@ -1,6 +1,14 @@ package zendeskchat -func responseFields(objectName string) string { +import ( + "fmt" + "strings" + + "github.com/amp-labs/connectors/internal/components" + "github.com/amp-labs/connectors/internal/staticschema" +) + +func responseField(objectName string) string { switch objectName { case "chats", "incremental/chats": return "chats" @@ -16,3 +24,21 @@ func responseFields(objectName string) string { return "" } } + +func supportedOperations() components.EndpointRegistryInput { + readSupport := []string{ + "account", "agents", "bans", "bans/ip", "chats", "departments", "goals", + "incremental/agent_events", "incremental/agent_timeline", + "incremental/chats", "incremental/conversions", "incremental/department_events", + "roles", "routing_settings/agents", "shortcuts", "skills", "triggers", + } + + return components.EndpointRegistryInput{ + staticschema.RootModuleID: { + { + Endpoint: fmt.Sprintf("{%s}", strings.Join(readSupport, ",")), + Support: components.ReadSupport, + }, + }, + } +} diff --git a/test/zendeskchat/read/main.go b/test/zendeskchat/read/main.go new file mode 100644 index 000000000..e93d7945c --- /dev/null +++ b/test/zendeskchat/read/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + "os" + "os/signal" + "syscall" + + "github.com/amp-labs/connectors" + "github.com/amp-labs/connectors/common" + zc "github.com/amp-labs/connectors/providers/zendeskchat" + "github.com/amp-labs/connectors/test/utils" + "github.com/amp-labs/connectors/test/zendeskchat" +) + +func main() { + // Handle Ctrl-C gracefully. + ctx, done := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer done() + + // Set up slog logging. + utils.SetupLogging() + + conn := zendeskchat.GetConnector(ctx) + + if err := testRead(ctx, conn, "chats", []string{"id", "session", "timestamp"}); err != nil { + slog.Error(err.Error()) + } + + if err := testRead(ctx, conn, "agents", []string{"id", "first_name", "last_login"}); err != nil { + slog.Error(err.Error()) + } + + if err := testRead(ctx, conn, "triggers", []string{"name", "enabled", "id"}); err != nil { + slog.Error(err.Error()) + } + +} + +func testRead(ctx context.Context, conn *zc.Connector, objectName string, fields []string) error { + params := common.ReadParams{ + ObjectName: objectName, + Fields: connectors.Fields(fields...), + } + + res, err := conn.Read(ctx, params) + if err != nil { + return fmt.Errorf("failed to read %s: %w", objectName, err) + } + + jsonStr, err := json.MarshalIndent(res, "", " ") + if err != nil { + return fmt.Errorf("error marshalling JSON: %w", err) + } + + if _, err := os.Stdout.Write(jsonStr); err != nil { + return fmt.Errorf("error writing to stdout: %w", err) + } + if _, err := os.Stdout.WriteString("\n"); err != nil { + return fmt.Errorf("error writing to stdout: %w", err) + } + + return nil +}