feat: connect Gong call sync to customer requests#717
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fbdaf6b487
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| values.Set("client_secret", clientSecret) | ||
| values.Set("code", code) | ||
| values.Set("redirect_uri", redirectURI) | ||
| req, err := http.NewRequestWithContext(ctx, http.MethodPost, gongAPIBaseURL()+"/oauth2/token", strings.NewReader(values.Encode())) |
There was a problem hiding this comment.
Exchange Gong OAuth codes at the documented token endpoint
Gong's OAuth setup docs show the authorization-code exchange going to /oauth2/generate-customer-token, not /oauth2/token (Gong docs). With the real Gong host, this callback will post the code to a non-existent token endpoint, treat the non-2xx response as an exchange failure, and leave every attempted Gong install in error, so workspaces cannot connect the integration outside mocked tests.
Useful? React with 👍 / 👎.
| type gongOAuthResponse struct { | ||
| AccessToken string `json:"access_token"` | ||
| RefreshToken string `json:"refresh_token"` | ||
| TokenType string `json:"token_type"` | ||
| Scope string `json:"scope"` | ||
| ExpiresIn int `json:"expires_in"` | ||
| TenantID string `json:"tenant_id"` | ||
| CompanyID string `json:"company_id"` | ||
| } |
There was a problem hiding this comment.
Store the customer API base URL from Gong OAuth
Gong OAuth returns a tenant-specific api_base_url_for_customer for subsequent API calls, but this response type drops that field, so completeGongInstall cannot persist it and the worker later posts all /v2/calls/* requests to the process-wide gongAPIBaseURL() instead. For real OAuth installs whose API host is customer-specific, sync jobs will call the wrong host and either fail or be treated as empty results; capture the returned base URL in the credential/metadata and use it when building Gong API requests.
Useful? React with 👍 / 👎.
fbdaf6b to
1b54628
Compare
There was a problem hiding this comment.
💡 Codex Review
This is a rename of an existing migration (0010_customer_requests_gong.sql) rather than a new forward migration, but packages/proto/migrations/README.md says "Never rename a migration that may have been applied anywhere" because the runner records applied migrations by filename. Any environment that already applied the old filename will treat this identical file as a new migration on deploy and record/run it again, so keep the original migration file and add a separate forward migration only for new schema changes.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if credential.AccessToken == "" { | ||
| return gongCredential{}, fmt.Errorf("active Gong credential is missing access token") | ||
| } | ||
| return credential, nil |
There was a problem hiding this comment.
Refresh expired Gong access tokens
When Gong's OAuth access token expires, this path still returns the stored accessToken and postGongJSON sends it unchanged; Gong's OAuth docs state the token expires periodically and should be refreshed with the provided refresh token (https://help.gong.io/docs/create-an-app-for-gong). In that scenario the next sync gets a 401/403, failGongJob marks the integration error, and future enqueues skip it, so real installs stop syncing after the first token lifetime unless the user reconnects; refresh and persist the new token before API calls or on authentication failure.
Useful? React with 👍 / 👎.
|
Controller disposition for current head Evidence:
Required before merge:
|
1b54628 to
e3367fb
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e3367fb21c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if _, err := tx.Exec(ctx, `update provider_job set status=$2, last_error=$3, next_run_at=$4, completed_at=case when $2='dead' then $5 else completed_at end, updated_at=$5 where id=$1::uuid`, job.ID, status, cause.Error(), nextRunAt, now); err != nil { | ||
| return err | ||
| } | ||
| if _, err := tx.Exec(ctx, `update workspace_integration set status='degraded', last_failure_at=$2, last_failure_message=$3, last_event_at=$2, updated_at=$2 where id=$1::uuid`, job.IntegrationID, now, cause.Error()); err != nil { |
There was a problem hiding this comment.
Mark exhausted Gong syncs as error
When a Gong sync keeps failing until providerJobFailureStatus returns dead, this statement still leaves the integration degraded. enqueueDueGongSync selects degraded integrations and only suppresses queued/running/failed jobs, so after max attempts the next poll creates a brand-new sync job for the same broken integration and repeats forever (for example with revoked scopes or a bad API host). Set the integration to error or otherwise exclude dead integrations once retries are exhausted.
Useful? React with 👍 / 👎.
| } | ||
| var id string | ||
| err := tx.QueryRow(ctx, `insert into customer (workspace_id, name, domain, source_provider, source_external_id, metadata, created_at, updated_at) values ($1::uuid,$2,$3,'gong',$4,$5::jsonb,now(),now()) on conflict (workspace_id, source_provider, source_external_id) do update set name=excluded.name, domain=coalesce(nullif(excluded.domain,''), customer.domain), metadata=customer.metadata || excluded.metadata, updated_at=now() returning id::text`, workspaceID, name, domain, externalID, metadataRaw).Scan(&id) | ||
| err := tx.QueryRow(ctx, `insert into customer (workspace_id, name, domain, source, created_at, updated_at) values ($1::uuid,$2,$3,'gong',now(),now()) returning id::text`, workspaceID, name, nullString(domain)).Scan(&id) |
There was a problem hiding this comment.
Upsert domainless Gong customers by account ID
When Gong supplies an account ID/name but no domain and the speaker email is unavailable, this path skips the domain lookup and performs an unconditional insert with a NULL domain. Every finding for the same Gong account then creates a separate customer row, whereas the previous Gong path used call.Account.ID as a stable external key; preserve a Gong account identifier or another stable key before inserting.
Useful? React with 👍 / 👎.
Summary
Verification
Blocked checks