Repository: amp-labs/connectors
Pull request #1387 (2025-02-21 13:32:14)

[CON-1247] feat(gong): add read support for transcripts object
### Testing 
<img width="1121" alt="image" src="https://github.com/user-attachments/assets/b4224421-1037-4ce3-93f5-ef236e4e30b9" />



Patch:
diff --git a/providers/gong/connector.go b/providers/gong/connector.go
index 8c51e742..ba256521 100644
--- a/providers/gong/connector.go
+++ b/providers/gong/connector.go
@@ -6,6 +6,7 @@ import (
 	"github.com/amp-labs/connectors/common/paramsbuilder"
 	"github.com/amp-labs/connectors/common/urlbuilder"
 	"github.com/amp-labs/connectors/providers"
+	"github.com/amp-labs/connectors/providers/gong/metadata"
 )
 
 const ApiVersion = "v2"
@@ -48,6 +49,15 @@ func (c *Connector) getURL(arg string) (*urlbuilder.URL, error) {
 	return urlbuilder.New(c.BaseURL, ApiVersion, arg)
 }
 
+func (c *Connector) getReadURL(objectName string) (*urlbuilder.URL, error) {
+	path, err := metadata.Schemas.LookupURLPath(c.Module.ID, objectName)
+	if err != nil {
+		return nil, err
+	}
+
+	return urlbuilder.New(c.BaseURL, path)
+}
+
 func (c *Connector) setBaseURL(newURL string) {
 	c.BaseURL = newURL
 	c.Client.HTTPClient.Base = newURL
diff --git a/providers/gong/metadata/schemas.json b/providers/gong/metadata/schemas.json
index 04cfc73e..c0361e5f 100644
--- a/providers/gong/metadata/schemas.json
+++ b/providers/gong/metadata/schemas.json
@@ -31,6 +31,15 @@
             "workspaceId": "workspaceId"
           }
         },
+        "transcripts": {
+          "displayName": "Transcripts",
+          "path": "/calls/transcript",
+          "responseKey": "callTranscripts",
+          "fields": {
+            "callId": "callId",
+            "transcript": "transcript"
+          }
+        },
         "users": {
           "displayName": "Users",
           "path": "/users",
diff --git a/providers/gong/objectNames.go b/providers/gong/objectNames.go
index 31ee7353..02dcbe41 100644
--- a/providers/gong/objectNames.go
+++ b/providers/gong/objectNames.go
@@ -6,7 +6,8 @@ import (
 )
 
 const (
-	objectNameCalls = "calls"
+	objectNameCalls      = "calls"
+	objectNameTranscript = "transcripts"
 )
 
 // Supported object names can be found under schemas.json.
@@ -15,3 +16,15 @@ var supportedObjectsByRead = metadata.Schemas.ObjectNames() //nolint:gochecknogl
 var supportedObjectsByWrite = datautils.NewSet( //nolint:gochecknoglobals
 	objectNameCalls,
 )
+
+var postReadObjects = datautils.NewSet( //nolint:gochecknoglobals
+	objectNameTranscript,
+)
+
+var ObjectNameToResponseField = datautils.NewDefaultMap(map[string]string{ //nolint:gochecknoglobals
+	objectNameTranscript: "callTranscripts",
+},
+	func(objectName string) (fieldName string) {
+		return objectName
+	},
+)
diff --git a/providers/gong/params.go b/providers/gong/params.go
index d2ec940e..970da520 100644
--- a/providers/gong/params.go
+++ b/providers/gong/params.go
@@ -7,6 +7,8 @@ import (
 
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/common/paramsbuilder"
+	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/datautils"
 	"golang.org/x/oauth2"
 )
 
@@ -36,3 +38,33 @@ func WithAuthenticatedClient(client common.AuthenticatedHTTPClient) Option {
 		params.WithAuthenticatedClient(client)
 	}
 }
+
+func buildReadParams(url *urlbuilder.URL, config common.ReadParams) {
+	if len(config.NextPage) != 0 { // not the first page, add a cursor
+		url.WithQueryParam("cursor", config.NextPage.String())
+	}
+
+	if !config.Since.IsZero() {
+		// This time format is RFC3339 but in UTC only.
+		// See calls or users object for query parameter requirements.
+		// https://gong.app.gong.io/settings/api/documentation#get-/v2/calls
+		url.WithQueryParam("fromDateTime", datautils.Time.FormatRFC3339inUTC(config.Since))
+	}
+}
+
+func buildReadBody(config common.ReadParams) map[string]any {
+	filter := make(map[string]any)
+
+	if !config.Since.IsZero() {
+		filter["fromDateTime"] = datautils.Time.FormatRFC3339inUTC(config.Since)
+	}
+
+	body := map[string]any{
+		"filter": filter,
+	}
+	if len(config.NextPage) != 0 {
+		body["cursor"] = config.NextPage.String()
+	}
+
+	return body
+}
diff --git a/providers/gong/read.go b/providers/gong/read.go
index 899b18a5..5c636132 100644
--- a/providers/gong/read.go
+++ b/providers/gong/read.go
@@ -5,7 +5,7 @@ import (
 	"errors"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/internal/datautils"
+	"github.com/amp-labs/connectors/providers/gong/metadata"
 )
 
 func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common.ReadResult, error) {
@@ -17,23 +17,21 @@ func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common
 		return nil, common.ErrOperationNotSupportedForObject
 	}
 
-	url, err := c.getURL(config.ObjectName)
+	url, err := c.getReadURL(config.ObjectName)
 	if err != nil {
 		return nil, err
 	}
 
-	if len(config.NextPage) != 0 { // not the first page, add a cursor
-		url.WithQueryParam("cursor", config.NextPage.String())
-	}
+	var res *common.JSONHTTPResponse
 
-	if !config.Since.IsZero() {
-		// This time format is RFC3339 but in UTC only.
-		// See calls or users object for query parameter requirements.
-		// https://gong.app.gong.io/settings/api/documentation#get-/v2/calls
-		url.WithQueryParam("fromDateTime", datautils.Time.FormatRFC3339inUTC(config.Since))
+	if postReadObjects.Has(config.ObjectName) {
+		body := buildReadBody(config)
+		res, err = c.Client.Post(ctx, url.String(), body)
+	} else {
+		buildReadParams(url, config)
+		res, err = c.Client.Get(ctx, url.String())
 	}
 
-	res, err := c.Client.Get(ctx, url.String())
 	if err != nil {
 		if errors.Is(err, common.ErrNotFound) {
 			return &common.ReadResult{
@@ -47,8 +45,10 @@ func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common
 		return nil, err
 	}
 
+	responseFieldName := metadata.Schemas.LookupArrayFieldName(c.Module.ID, config.ObjectName)
+
 	return common.ParseResult(res,
-		common.GetRecordsUnderJSONPath(config.ObjectName),
+		common.GetRecordsUnderJSONPath(responseFieldName),
 		getNextRecordsURL,
 		common.GetMarshaledData,
 		config.Fields,
diff --git a/scripts/openapi/gong/metadata/main.go b/scripts/openapi/gong/metadata/main.go
index 043ea6c5..d06d2da0 100644
--- a/scripts/openapi/gong/metadata/main.go
+++ b/scripts/openapi/gong/metadata/main.go
@@ -6,6 +6,7 @@ import (
 	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/internal/staticschema"
+	"github.com/amp-labs/connectors/providers/gong"
 	"github.com/amp-labs/connectors/providers/gong/metadata"
 	"github.com/amp-labs/connectors/providers/gong/openapi"
 	"github.com/amp-labs/connectors/tools/fileconv/api3"
@@ -48,6 +49,14 @@ var (
 		// Requires query parameters: flowOwnerEmail.
 		"/v2/flows",
 	}
+
+	postReadAllowedEndPoints = []string{ // nolint:gochecknoglobals
+		"/v2/calls/transcript",
+	}
+
+	postObjectEndpoints = map[string]string{ // nolint:gochecknoglobals
+		"/v2/calls/transcript": "transcripts",
+	}
 )
 
 func main() {
@@ -63,12 +72,22 @@ func main() {
 	)
 	goutils.MustBeNil(err)
 
-	objects, err := explorer.ReadObjectsGet(
+	getReadObjects, err := explorer.ReadObjectsGet(
 		api3.NewDenyPathStrategy(ignoreEndpoints),
 		nil, nil, api3.IdenticalObjectLocator,
 	)
+
+	goutils.MustBeNil(err)
+
+	postReadObjects, err := explorer.ReadObjectsPost(
+		api3.NewAllowPathStrategy(postReadAllowedEndPoints),
+		postObjectEndpoints, nil, api3.CustomMappingObjectCheck(gong.ObjectNameToResponseField),
+	)
+
 	goutils.MustBeNil(err)
 
+	objects := postReadObjects.Combine(getReadObjects)
+
 	schemas := staticschema.NewMetadata[staticschema.FieldMetadataMapV1]()
 	registry := datautils.NamedLists[string]{}
 


Test patch:
diff --git a/providers/gong/read_test.go b/providers/gong/read_test.go
index 82942135..813f3a14 100644
--- a/providers/gong/read_test.go
+++ b/providers/gong/read_test.go
@@ -20,6 +20,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 
 	fakeServerResp := testutils.DataFromFile(t, "read.json")
 	fakeServerResp2 := testutils.DataFromFile(t, "read_cursor.json")
+	responseTranscripts := testutils.DataFromFile(t, "read_transcripts.json")
 
 	tests := []testroutines.Read{
 		{
@@ -196,6 +197,30 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 			}.Server(),
 			ExpectedErrs: []error{jsonquery.ErrNotArray},
 		},
+
+		{
+			Name:  "Successful read transcripts using POST",
+			Input: common.ReadParams{ObjectName: "transcripts", Fields: connectors.Fields("callid")},
+			Server: mockserver.Fixed{
+				Setup:  mockserver.ContentJSON(),
+				Always: mockserver.Response(http.StatusOK, responseTranscripts),
+			}.Server(),
+			Comparator: testroutines.ComparatorSubsetRead,
+			Expected: &common.ReadResult{
+				Rows: 1,
+				Data: []common.ReadResultRow{{
+					Fields: map[string]any{
+						"callid": "7782342274025937895",
+					},
+					Raw: map[string]any{
+						"callid": "7782342274025937895",
+					},
+				}},
+				NextPage: "eyJhbGciOiJIUzI1NiJ9.eyJjYWxsSWQiM1M30.6qKwpOcvnuweTZmFRzYdtjs_YwJphJU4QIwWFM", // nolint:lll
+				Done:     false,
+			},
+			ExpectedErrs: nil,
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/providers/gong/test/read_transcripts.json b/providers/gong/test/read_transcripts.json
new file mode 100644
index 00000000..03f89405
--- /dev/null
+++ b/providers/gong/test/read_transcripts.json
@@ -0,0 +1,27 @@
+{
+    "requestId": "4al018gzaztcr8nbukw",
+    "records": {
+      "totalRecords": 263,
+      "currentPageSize": 100,
+      "currentPageNumber": 0,
+      "cursor": "eyJhbGciOiJIUzI1NiJ9.eyJjYWxsSWQiM1M30.6qKwpOcvnuweTZmFRzYdtjs_YwJphJU4QIwWFM"
+    },
+    "callTranscripts": [
+      {
+        "callid": "7782342274025937895",
+        "transcript": [
+          {
+            "speakerId": "6432345678555530067",
+            "topic": "Objections",
+            "sentences": [
+              {
+                "start": 460230,
+                "end": 462343,
+                "text": "No wait, I think we should check that out first."
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
\ No newline at end of file
diff --git a/test/gong/read/read.go b/test/gong/read/calls/read.go
similarity index 94%
rename from test/gong/read/read.go
rename to test/gong/read/calls/read.go
index 6ac15b3f..1ca43ddf 100644
--- a/test/gong/read/read.go
+++ b/test/gong/read/calls/read.go
@@ -25,7 +25,7 @@ func main() {
 
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: "calls",
-		Fields:     connectors.Fields("url"),
+		Fields:     connectors.Fields("id"),
 	})
 	if err != nil {
 		utils.Fail("error reading from Gong", "error", err)
diff --git a/test/gong/read/transcripts/read.go b/test/gong/read/transcripts/read.go
new file mode 100644
index 00000000..94d9d1d5
--- /dev/null
+++ b/test/gong/read/transcripts/read.go
@@ -0,0 +1,36 @@
+package main
+
+import (
+	"context"
+	"log/slog"
+	"os"
+	"os/signal"
+	"syscall"
+
+	"github.com/amp-labs/connectors"
+	"github.com/amp-labs/connectors/common"
+	connTest "github.com/amp-labs/connectors/test/gong"
+	"github.com/amp-labs/connectors/test/utils"
+)
+
+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 := connTest.GetGongConnector(ctx)
+
+	res, err := conn.Read(ctx, common.ReadParams{
+		ObjectName: "transcripts",
+		Fields:     connectors.Fields("callId"),
+	})
+	if err != nil {
+		utils.Fail("error reading from Gong", "error", err)
+	}
+
+	slog.Info("Reading transcripts..")
+	utils.DumpJSON(res, os.Stdout)
+}