Skip to content

feat: request access Semrush api integration - #3039

Open
vivesing wants to merge 5 commits into
mainfrom
feat/semrush-onboarding
Open

feat: request access Semrush api integration#3039
vivesing wants to merge 5 commits into
mainfrom
feat/semrush-onboarding

Conversation

@vivesing

@vivesing vivesing commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Extends POST /v2/orgs/:spaceCatId/semrush-onboarding from a Slack-notification-only endpoint into one that actually provisions the caller's Semrush workspace access via Semrush's new Adobe IMS Workspace Provisioning API. Slack is now used only as a failure alert (manual follow-up), not a success notification.

✅ Semrush api to add user is available on prod
POST https://www.semrush.com/enterprise/users/api/v1/adobe-ims/workspace-members

Changes

Controller

  • src/controllers/onboarding.js
    • Resolves the caller's Adobe IMS access token via the shared resolveSemrushImsToken helper (x-promise-token header → IMS exchange). Returns 401 if the header is missing/invalid.
    • Calls provisionWorkspaceMember(env, imsToken) to grant the caller admin access to their org's Semrush workspace.
    • Success: returns 200 { provisioned: true, workspaceId, role } — no Slack call.
    • Failure: logs the error, sends a best-effort Slack alert (email/workspace/org/reason) so a CSM can follow up manually, then returns the mapped upstream status.
    • Removed the old per-org Slack notification cooldown — no longer needed since success no longer pings Slack, and Semrush's own 409 Conflict already covers repeat-call de-duplication.

New support module

  • src/support/onboarding/workspace-provisioning.js (new)
    • provisionWorkspaceMember(env, imsToken)POST {SEMRUSH_PROJECTS_BASE_URL}/enterprise/users/api/v1/adobe-ims/workspace-members, sending the IMS token on both the Authorization: Bearer header and the { token } JSON body.
    • Throws SerenityTransportError carrying the upstream status (400/401/403/409/422/500) on a non-2xx response, or 502 on a netw

Slack notifier (repurposed) - **`src/support/onboarding/slack-no

  • Renamed notifyOnboardingnotifyProvisioningFailure.
  • Message changed from a "new onboarding request" ping to a :warning: failure alert, with an optional reason line.

Shared transport

  • **`src/support/serenity/rest-trans
    • Exported the previously-private baseUrl() helper (resolves/validates SEMRUSH_PROJECTS_BASE_URL) for reuse by the new onboarding module.

OpenAPI

  • docs/openapi/onboarding-api.yaml
    • Added required x-promise-token header parameter.
    • Updated summary/description to rust notification).
    • Replaced the 200 schema with OnboardingProvisioningResponse; added 401/409/422 responses;
      updated 400/403/502 descriptions pented error table.
  • docs/openapi/schemas.yaml
    • Replaced OnboardingNotificationProvisioningResponse (provisioned, workspaceId, role).
  • docs/index.html — regenerated via npm run docs:build.

Tests

  • test/controllers/onboarding.test.js — rewritten: missing-promise-token → 401; successful provisioning → no Slack call; Semrush failure → Slack alert sent + mapped status; unexpected/network errors → 500/502 + alert; email fallback/unknown cases.
  • **test/support/onboarding/slack-ned for thenotifyProvisioningFailure` rename and the new failure-alert message shape.
  • **`test/support/onboarding/workspa (new) — request shape (header +body), 2xx parsing, non-2xx → typed error, network failure → 502, missing base URL → 503.
  • **`test/it/shared/tests/onboardingor accuracy (behavior unchanged:404/403 org-gate coverage).

Not in scope / follow-ups

  • SEMRUSH_USERS_BASE_URL vs SEMRUSH_PROJECTS_BASE_URL: currently reading SEMRUSH_PROJECTS_BASE_URL directly (adobe-hackathon.semrush.com), per confirmation that this will be repointed to www.semrush.com.
  • IT-level success-path coverage forends on the Semrush

@github-actions

Copy link
Copy Markdown

This PR will trigger a minor release when merged.

@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@MysticatBot MysticatBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @vivesing,

Verdict: Request changes - two blocking issues around response contract and missing timeout.
Complexity: HIGH - large diff; API surface + external integration.
Changes: Extends the Semrush onboarding endpoint from a Slack-notification-only flow into actual workspace provisioning via Semrush's Adobe IMS Workspace Provisioning API (12 files).

Must fix before merge

  1. [Important] 2xx with unparseable body returns undefined fields, violating the OpenAPI contract that declares workspaceId and role as required - src/support/onboarding/workspace-provisioning.js:65 (details inline)
  2. [Important] No request timeout on outbound fetch to Semrush, inconsistent with the codebase's 15s timeout pattern - src/support/onboarding/workspace-provisioning.js:49 (details inline)
  3. [Important] 409 (user already a member) triggers a Slack failure alert and error response, but this is a success from the user's perspective - src/controllers/onboarding.js:74 (details inline)
Non-blocking (4): minor issues and suggestions
  • suggestion: Map known upstream status codes (409/422/403) to caller-facing messages instead of the generic "Failed to provision Semrush workspace access" for all failures - src/controllers/onboarding.js:95
  • suggestion: Upstream error status codes pass through verbatim to the caller; consider mapping to a bounded set so a future Semrush 429 does not leak through undocumented - src/controllers/onboarding.js:66
  • nit: OpenAPI spec does not document the 503 that surfaces when SEMRUSH_PROJECTS_BASE_URL is unset - docs/openapi/onboarding-api.yaml
  • nit: Lockfile includes peer-flag churn on ~25 packages without a corresponding package.json change or PR-body explanation - package-lock.json

Note: Recommend a human read before merge - possible divergence from the documented design (heuristic, flagged by the conventions reviewer). The workspace provisioning flow is not documented in the referenced Semrush IMS integration proposal.


Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 2m 27s | Cost: $9.03 | Commit: 572806729bd6ef941bd0c62c57b5a28bc4e91725
If this code review was useful, please react with 👍. Otherwise, react with 👎.

Comment thread src/support/onboarding/workspace-provisioning.js
Comment thread src/support/onboarding/workspace-provisioning.js
Comment thread src/controllers/onboarding.js
@MysticatBot MysticatBot added ai-reviewed Reviewed by AI complexity:high AI-assessed PR complexity: HIGH needs-human-review AI reviewer recommends a human read before merge labels Aug 13, 2026
@vivesing vivesing changed the title feat: request access Semrush api integration [Draft] feat: request access Semrush api integration Aug 13, 2026
@vivesing

Copy link
Copy Markdown
Contributor Author

Fixes applied

  1. Contract violation on 2xx with missing fields (workspace-provisioning.js) — after the !response.ok check, now validates body?.workspace_id and body?.role are non-empty strings; throws SerenityTransportError(502, 'workspace-members returned an invalid response', body) if either is missing, instead of returning undefined fields.
  2. No fetch timeout — added signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS) to the fetch call. Exported DEFAULT_TIMEOUT_MS (15s) from rest-transport.js for reuse rather than duplicating the constant. The existing catch block's e.code || e.name already classifies the resulting TimeoutError/AbortError as a 502.
  3. 409 treated as failure (controllers/onboarding.js) — the catch block now special-cases status === 409 before the generic failure path: returns 200 { provisioned: true, alreadyMember: true, workspaceId, role } with no Slack alert. workspaceId/role prefer the values Semrush's 409 body echoes back, falling back to the org's stored workspace ID and a default 'admin' role (per the PDF's invariant that a member is always admin).

Updated OpenAPI (OnboardingProvisioningResponse gained optional alreadyMember; removed the now-unreachable 409 response doc; 502 doc mentions timeout/invalid-response causes) and added corresponding unit tests in both workspace-provisioning.test.js and onboarding.test.js (13 new/changed test cases total, all passing).

@vivesing vivesing changed the title [Draft] feat: request access Semrush api integration feat: request access Semrush api integration Aug 13, 2026
@vivesing
vivesing requested a review from MysticatBot August 13, 2026 09:25

@MysticatBot MysticatBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @vivesing,

Verdict: Approve - all prior blocking findings addressed, no new blocking issues.
Complexity: HIGH - large diff; API surface + dependency churn.
Changes: Extends the Semrush onboarding endpoint from Slack-notification-only into actual workspace provisioning via Semrush's Adobe IMS Workspace Provisioning API (12 files).
Note: Recommend a human read before merge - this change modifies a shared API contract (OpenAPI spec). The bot review is a complement to, not a replacement for, a human read here.

Non-blocking (1): minor issues and suggestions
  • nit: JSDoc @returns declares email and organizationId as string but runtime uses optional chaining (body?.email) and can return undefined when Semrush omits them - src/support/onboarding/workspace-provisioning.js:76

Previously flagged, now resolved

  • 2xx with unparseable body now validated: throws 502 on missing workspace_id/role
  • Fetch timeout added via AbortSignal.timeout(DEFAULT_TIMEOUT_MS), reusing the shared 15s constant
  • 409 from Semrush now treated as success: returns 200 with alreadyMember flag, no Slack alert

Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 0m 5s | Cost: $7.03 | Commit: 63b9140112ae93227ae389e697d6a97ddbb43213
If this code review was useful, please react with 👍. Otherwise, react with 👎.

@igor-grubic

Copy link
Copy Markdown
Contributor

Code review — feat/semrush-onboarding

Findings from an automated correctness/quality pass, most important first.

🔴 Wrong gateway origin (User Manager path on Project Engine host)

src/support/onboarding/workspace-provisioning.js:44 — The UM path /enterprise/users/api/v1/adobe-ims/workspace-members is built against baseUrl(env) (SEMRUSH_PROJECTS_BASE_URL, the Project Engine origin) instead of usersBaseUrl(env) (SEMRUSH_USERS_BASE_URL). Every other User-Manager call in rest-transport.js uses usersRoot precisely so local/IT/E2E can point UM at a separate host (PE mock :8443, UM mock :8444). This only passes today because deployed envs share one host, and no IT test exercises the provisioning path.
Fix: export usersBaseUrl from rest-transport.js and use it here.

🟠 Contract / schema violations

  • src/controllers/onboarding.js:96 — The 409 "already-member" branch can return workspaceId: null, but schemas.yaml now declares workspaceId as a required non-null string. Triggers when the 409 body lacks workspace_id and the org has no workspace id. Untested.
  • src/controllers/onboarding.js:107 (503 path) — A deploy misconfig (SEMRUSH_PROJECTS_BASE_URL unset/invalid) makes baseUrl(env) throw a 503 outside the try, which fires a customer-specific Slack "provisioning failed" alert (wrong audience — it's a service config error) and returns a 503 not documented in onboarding-api.yaml (only 400/401/403/404/422/500/502).
  • docs/openapi/onboarding-api.yaml:11x-promise-token is declared required: true, but the implementation accepts a direct IMS bearer via the getImsUserTokenStrict fallback in resolveSemrushImsToken. Doc/impl mismatch.

🟡 Behavior regressions & alerting quality

  • src/controllers/onboarding.js:107 — The per-org 30s Slack cooldown (ONBOARDING_COOLDOWN_MS) that guarded against retry-loop flooding was removed, and the alert now fires on every non-409 failure — including caller-fixable ones (401 bad token, 422 no capacity), which are not manual-follow-up cases.
  • src/controllers/onboarding.js:104 — The Slack alert falls back to profile.email, which per resolveCallerImsUserId is the IMS user-id alias (e.g. ABC123@AdobeID), not a human address a CSM can act on. A human address should come from trial_email / preferred_username.
  • src/support/onboarding/workspace-provisioning.js:64 — A request timeout is mapped to HTTP 502, but the shared transport (createTimeoutFetch) maps Semrush timeouts to 504. Inconsistent classification for the same condition.

⚪ Cleanup

  • src/support/onboarding/workspace-provisioning.js:91provisionWorkspaceMember returns email and organizationId that no caller reads and that (unlike workspace_id/role) are never validated despite the JSDoc declaring them required strings. Drop or validate them.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-reviewed Reviewed by AI complexity:high AI-assessed PR complexity: HIGH needs-human-review AI reviewer recommends a human read before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants