Skip to content

[APPS-2777] Fix: use app_builder_url from API response (fixes custom domain orgs)#449

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 5 commits into
masterfrom
tiffany.trinh/apps-2777-app-builder-url
Jul 9, 2026
Merged

[APPS-2777] Fix: use app_builder_url from API response (fixes custom domain orgs)#449
gh-worker-dd-mergequeue-cf854d[bot] merged 5 commits into
masterfrom
tiffany.trinh/apps-2777-app-builder-url

Conversation

@tyffical

@tyffical tyffical commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Motivation

  • The plugin constructed the App Builder URL by hardcoding https://app.${site}/..., which produces the wrong host for orgs with custom domains (e.g. dd.datad0g.com) — users on those orgs got a broken link printed after upload/release.
  • The backend now returns app_builder_url directly in the upload and release API responses, resolved from the org's actual configured web domain (see backend fix below), so the plugin no longer needs to guess the domain.
  • Jira: APPS-2777

Changes

What changed File
Upload log uses response.app_builder_url (server-provided, edit view + ?viewMode=preview) instead of constructing https://app.${site}/app-builder/apps/${id} packages/plugins/apps/src/upload.ts
Release log uses releaseResponse.app_builder_url (server-provided, published view, no query string) instead of a hardcoded message with no URL packages/plugins/apps/src/upload.ts
When app_builder_url is missing from either response, surfaces a warnings entry instead of going silent (upload) or printing a blank line (release, which previously had no guard at all) packages/plugins/apps/src/upload.ts
Warnings are aggregated and logged by handle-upload.ts without failing the build packages/plugins/apps/src/vite/handle-upload.ts
The warning names the app by its display name (context.name), not context.identifier — the latter is a SHA-256 hash in the default case (see identifier.ts's buildIdentifier) and not something a human would recognize or search for packages/plugins/apps/src/upload.ts
The warning also includes app_builder_id for unambiguous lookup — disambiguates same-named apps, and is usable directly by anyone who already knows their org's domain packages/plugins/apps/src/upload.ts
Added/updated tests: ?viewMode=preview present on upload / absent on release (default + custom-domain fixtures), warning text and app_builder_id inclusion when the URL is missing, and the edge case where app_builder_id is also absent (mirrors the backend's TestAppBuilderURL_EmptyAppBuilderID) packages/plugins/apps/src/upload.test.ts
response/releaseResponse typed via doAuthenticatedRequest<T>()'s generic instead of any, matching vite/dev-server.ts's existing pattern — catches typos/renames on app_builder_url/app_builder_id/version_id at compile time. Caught by automated PR review packages/plugins/apps/src/upload.ts

QA Instructions

Automated

yarn workspace @dd/apps-plugin run typecheck
# Expected: no type errors ✅ VERIFIED

yarn eslint packages/plugins/apps/src/upload.ts packages/plugins/apps/src/upload.test.ts
# Expected: no lint errors ✅ VERIFIED

yarn test:unit --testPathPatterns=upload.test
# Expected: 26/26 tests passing ✅ VERIFIED

yarn test:unit
# Expected: full repo suite unaffected — 77/77 suites, 1850/1851 tests (1 pre-existing unrelated skip) ✅ VERIFIED

CI on this PR: Unit tests ✅ VERIFIED, Linting ✅ VERIFIED, End to End ✅ VERIFIED.

Manual QA

This is a CLI/build-time plugin, not a hosted service, so there's no "test URL" to browse to. Three passes instead, each isolating a different part of the path — real plugin code vs. real network:

Pass Plugin code Network What it confirms
1. Real scaffold, fully live Real project from npm init @datadog/apps, local build linked in Live staging test drive (dd-auth credentials) The exact documented customer flow, end-to-end: real scaffold → real build → real backend → correct URL
2. Plugin wiring Real @datadog/vite-plugin, real vite build Mocked (nock) The missing-URL warning reaches the console through the real logger, not just unit tests
3. Backend only curl, and separately the raw uploadArchive() function Live staging test drive Original data point, before pass 1 existed

1. Real scaffold, fully live — the High Code Apps "Getting started" guide's documented flow, run for real: scaffold a project the way any customer would, link this branch's build into it instead of the npm-published version, and publish exactly as documented. Exact commands, in order (npm 10.9.2, node v22.13.1):

Steps 1-5 — scaffold, build, and link this branch's plugin in. The doc's own literal scaffolding syntax — npm init @datadog/apps test-app --template vite-react -y — failed here: npm's own init/-y flag handling mangles the args before they reach create-apps ("Unknown Syntax Error: Command not found"). npm create ... -- forces npm to pass everything after -- through untouched, which works — that's the one deviation from the doc's exact text below:

# 1. Scaffold:
mkdir -p /tmp/dd-apps-scaffold-test && cd /tmp/dd-apps-scaffold-test
npm create @datadog/apps@latest -- test-app --template vite-react -y

# 2. Build this branch's plugin:
cd ~/dd/build-plugins/packages/published/vite-plugin
NO_TYPES=1 yarn build

# 3. Swap this package's `exports` to point at the built dist/src instead of raw src/index.ts —
#    required before linking, otherwise the consuming project gets un-transpiled TS:
cd ~/dd/build-plugins
yarn cli prepare-link

# 4. Register the built package as linkable:
cd ~/dd/build-plugins/packages/published/vite-plugin
npm link

# 5. Link it into the scaffolded app, in place of the npm-published @datadog/vite-plugin:
cd /tmp/dd-apps-scaffold-test/test-app
npm link @datadog/vite-plugin
# Verify it actually points at the local build, not node_modules:
node -e "console.log(require.resolve('@datadog/vite-plugin'))"
# → /Users/.../build-plugins/packages/published/vite-plugin/dist/src/index.js

Step 6 — edit test-app/vite.config.ts. Not shell commands, so it's its own step: two changes, everything else (identifier, name, logLevel: 'debug' default, etc.) left exactly as scaffolded.

Change 6a — point at staging instead of prod:

             auth: {
-                site: 'datadoghq.com',
+                site: 'datad0g.com',
                 apiKey: process.env.DD_API_KEY,
                 appKey: process.env.DD_APP_KEY,
             },

Change 6b — route traffic to this branch's test drive instead of shared staging. Add this block right after the imports at the top of the file, before the hasDatadogApiKeys line. This is TD-only plumbing, not something a real app needs — a real app talking to real staging/prod skips this entirely:

// TD-only routing, not something a real app needs: sends every api.datad0g.com
// request through this branch's app-builder-code test drive instead of shared
// staging. A real app talking to real staging/prod would not do this.
const realFetch = globalThis.fetch;
globalThis.fetch = (url, init) => {
    const urlStr = url.toString();
    if (urlStr.includes('api.datad0g.com')) {
        const headers = new Headers(init?.headers);
        headers.set('test-drive-apps2787-real-cli-qa', '1');   // ← your test drive's name here
        console.log(`>>> [test-drive routing] ${init?.method ?? 'GET'} ${urlStr}`);
        return realFetch(url, { ...init, headers });
    }
    return realFetch(url, init);
};

Steps 7-8 — publish, then clean up. Step 7 is the doc's own command, verbatim, no modification:

# 7. Publish:
dd-auth --domain dd.datad0g.com -- npm run upload

# Upload  → https://dd.datad0g.com/app-builder/apps/edit/7c782a98-d310-413b-82c0-85756c584bd8?viewMode=preview  ✅ VERIFIED
# Release → https://dd.datad0g.com/app-builder/apps/7c782a98-d310-413b-82c0-85756c584bd8                        ✅ VERIFIED

# Re-run after a9294fb2 (typed response) + ddoghq/dd-source#12477's fd8a801 (scheme-less URL
# fix). Redeployed the test drive first (rapid td update; turbo hit a rollout timeout, fell
# back to the documented full-CI update) so this exercised the actual current backend tip:
# Upload  → https://dd.datad0g.com/app-builder/apps/edit/6f2f81ad-1ed7-4e0a-a68f-1e01387f6b2a?viewMode=preview  ✅ VERIFIED
# Release → https://dd.datad0g.com/app-builder/apps/6f2f81ad-1ed7-4e0a-a68f-1e01387f6b2a                        ✅ VERIFIED

# 8. Cleanup:
cd ~/dd/build-plugins/packages/published/vite-plugin && npm unlink
cd ~/dd/build-plugins && yarn cli prepare-link --revert   # undoes step 3's exports swap
rm -rf /tmp/dd-apps-scaffold-test

vite.config.ts ships with logLevel: 'debug' by default, which is also how the success line's exact wording got confirmed (see the logLevel finding in pass 2).

2. Real plugin/build wiring, mocked network — done before pass 1's scaffold test existed, using a minimal hand-built fixture project instead of a real scaffold, with the network mocked (nock) instead of a live test drive, specifically to isolate the missing-URL warning path in one run. Exact commands:

# Fixture: index.js/index.html/greet.backend.js copied from
# packages/tests/src/e2e/appsPlugin/project (the existing e2e test's own fixture), into
# ~/dd/build-plugins/.scratch-real-cli-test/. A single script,
# .scratch-real-cli-test/run.mjs, takes a scenario arg, sets up nock against
# https://api.datadoghq.com's upload/release routes, then runs a real `vite build` using
# the built @datadog/vite-plugin (auth: dummy apiKey/appKey — nock never checks them).
cd ~/dd/build-plugins

node .scratch-real-cli-test/run.mjs resolved
# → nothing printed at all (see the logLevel finding below)

node .scratch-real-cli-test/run.mjs omitted
# [warn|vite|apps] Could not resolve the App Builder URL for this upload — find
#   "e2e-real-cli-test-app" (app ID: builder-real-cli-test) in your App Builder apps list
#   to view it.                                                                            ✅ VERIFIED
# [warn|vite|apps] Could not resolve the App Builder URL for this release — find
#   "e2e-real-cli-test-app" (app ID: builder-real-cli-test) in your App Builder apps list
#   to view it.                                                                            ✅ VERIFIED
# [warn|vite|apps] Warnings while uploading assets:
#     - Could not resolve the App Builder URL for this upload — ...
#     - Could not resolve the App Builder URL for this release — ...                      ✅ VERIFIED

Finding, not a regression: the success line (Your application is available at: ...) uses log.info, and the plugin's own internal default logLevel is 'warn' (packages/factory/src/validate.ts) — with that bare default it never prints. In practice this rarely bites anyone: npm init @datadog/apps's generated vite.config.ts already sets logLevel: 'debug' explicitly (confirmed in pass 1), so every newly-scaffolded app shows this line unless someone removes that option. This predates APPS-2777 (confirmed via git log -p on that line); it isn't something this PR introduced. See Out of Scope.

3. Real backend integration via curl — the first verification done, before either build-driven pass above; full commands are in ddoghq/dd-source#12477's QA section. Kept here as the original data point:

Upload  → https://dd.datad0g.com/app-builder/apps/edit/{id}?viewMode=preview        ✅ VERIFIED
Release → https://dd.datad0g.com/app-builder/apps/{id} (no /edit/, no query string)  ✅ VERIFIED

Mutation-tested the fix by locally reintroducing the old bug (query-string leak, hardcoded domain) and confirming the relevant tests fail, then reverting and confirming they pass — see commit history for detail.

Blast Radius

  • Affects only the @dd/apps-plugin package's post-upload/post-release console log message — no change to what gets uploaded, no change to request payloads, no change to error/warning handling.
  • No feature flag — this is a log-message fix in an npm package; it takes effect for all users on their next upgrade.
  • Depends on ddoghq/dd-source#12477 being deployed for the printed URL to be correct for custom-subdomain orgs when called via a direct API host (this plugin's traffic pattern). Until then, app_builder_url reflects api.{site} rather than the org's real web domain — a regression from this plugin's pre-fix behavior for standard (non-custom-domain) orgs specifically. Should not merge/release ahead of #12477.
  • Risk: low — purely a display change, covered by 24 unit tests; full E2E suite (326 tests across all plugins) passed unaffected on this PR's CI run.

Out of Scope / Follow-ups

Item Status Next step
The E2E test's mocked upload/release responses (packages/tests/src/e2e/appsPlugin/appsPlugin.spec.ts) don't include app_builder_url, so the E2E suite doesn't exercise the new log content — only unit tests do independent Low priority. Could update the nock mocks for closer parity with production, but the E2E suite has no mechanism to capture CLI log output today, so there's nothing for it to assert against without further investment
The success message (Your application is available at: ...) is log.info, silent under the plugin's own internal default logLevel: 'warn' — pre-existing since APPS-2777, not introduced here, but discovered while QA'ing this PR independent (pre-existing), working as intended Two independent confirmations this is intentional, not a gap: the High Code Apps "Getting started" guide's CI/CD section recommends logLevel: 'info' for deployed apps, and npm init @datadog/apps's generated vite.config.ts already ships with logLevel: 'debug' by default (verified in Manual QA pass 1). The plugin's bare internal default only matters for hand-written configs that opt out of both. No code change proposed

Documentation

@tyffical tyffical force-pushed the tiffany.trinh/apps-2777-app-builder-url branch 3 times, most recently from 14b7e06 to 8af9ab3 Compare July 6, 2026 19:28
@datadog-prod-us1-4

datadog-prod-us1-4 Bot commented Jul 6, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: a9294fb | Docs | Datadog PR Page | Give us feedback!

@tyffical tyffical force-pushed the tiffany.trinh/apps-2777-app-builder-url branch 8 times, most recently from e9046ec to de20efe Compare July 6, 2026 20:23
…ing it

The backend now returns app_builder_url in upload and release responses,
which reflects the correct host for orgs with custom domains (e.g.
dd.datad0g.com). The previous hardcoded https://app.${site}/... produced
wrong URLs for those orgs.

The upload response's URL includes ?viewMode=preview (edit view); the
release response's URL does not (published view) — this matches the
backend behavior in ddoghq/dd-source#1038.

Follows the "omit when absent" test discipline from #446: adds a test
confirming no available-at message is logged when app_builder_url is
absent from the upload response, matching the existing guard.

Related: ddoghq/dd-source#1038
@tyffical tyffical force-pushed the tiffany.trinh/apps-2777-app-builder-url branch from de20efe to 5b37ce7 Compare July 6, 2026 20:59
tyffical added 3 commits July 6, 2026 22:44
Two gaps found during a production-readiness review of the backend fix
(ddoghq/dd-source#12477):

1. The release path had no guard on app_builder_url at all — a missing
   value would interpolate into the log message as a blank line.
2. Both paths (upload already guarded, release didn't) went completely
   silent when the URL was missing, with no signal to the user or CI logs
   that anything was degraded.

The backend's org lookup can legitimately fail (e.g. a transient ouisvc
issue) — by design it doesn't fail the upload/release itself, so the
frontend must handle "succeeded, but no URL" as a real, expected outcome
rather than an edge case. Now both paths push a warning (surfaced via the
existing warnings aggregation in handle-upload.ts, which logs but never
fails the build) instead of going silent or printing malformed output.
The warning previously said "check the App Builder UI directly" with no
concrete next step. Uses context.name -- not context.identifier, which is
an opaque SHA-256 hash in the default case (see identifier.ts's
buildIdentifier) and not something anyone would recognize or search for --
so the message tells the caller exactly what to search for in the App
Builder apps list.

This is deliberately still a warning, not a failure -- see PR discussion:
failing the upload/release when ouisvc is down (even after retries) would
couple app-builder-code's core CI/CD path to an unrelated service's
availability, breaking every customer's automated pipeline over a
convenience link they may never click. A missing URL costs one extra
search in the App Builder UI; a failed upload costs a broken deploy.
app_builder_id is present whenever this warning fires, except for a narrow
DB invariant violation in the backend's upload no-op path (documented in
app-builder-code's service.go) that shouldn't occur in normal operation.
Surfacing it alongside the display name gives an unambiguous reference:
disambiguates if multiple apps share a name, and is directly usable by
anyone who already knows their org's domain.

Extracted the message construction into buildMissingAppUrlWarning to
avoid duplicating the ID-suffix logic across the upload and release call
sites. Added a test for the edge case where app_builder_id is also absent,
confirming the suffix is omitted rather than printing something broken.
@tyffical tyffical force-pushed the tiffany.trinh/apps-2777-app-builder-url branch from f9f7ab5 to 9ea97b4 Compare July 7, 2026 05:50
@tyffical tyffical requested a review from Copilot July 7, 2026 06:47
@DataDog DataDog deleted a comment from chatgpt-codex-connector Bot Jul 7, 2026
@tyffical tyffical requested a review from sdkennedy2 July 7, 2026 06:47
@tyffical tyffical marked this pull request as ready for review July 7, 2026 06:47
@tyffical tyffical requested review from a team as code owners July 7, 2026 06:47

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread packages/plugins/apps/src/upload.ts Outdated
if (response.version_id && shouldPublish) {
const releaseUrl = getReleaseUrl(context.site, context.identifier);
await doAuthenticatedRequest({
const releaseResponse: any = await doAuthenticatedRequest({

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fair — fixed in a9294fb: both response and releaseResponse now use doAuthenticatedRequest()'s existing generic (matching vite/dev-server.ts's pattern) instead of any. Typecheck, lint, and all 26 unit tests still pass.

response/releaseResponse were typed any, so accessing app_builder_url/
app_builder_id/version_id had no compile-time protection against typos
or backend field renames. Uses doAuthenticatedRequest<T>()'s existing
generic, matching vite/dev-server.ts's pattern elsewhere in this package.

Caught by an automated PR review (copilot-pull-request-reviewer) on #449.

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@gh-worker-dd-mergequeue-cf854d gh-worker-dd-mergequeue-cf854d Bot merged commit 8f9a903 into master Jul 9, 2026
6 checks passed
@gh-worker-dd-mergequeue-cf854d gh-worker-dd-mergequeue-cf854d Bot deleted the tiffany.trinh/apps-2777-app-builder-url branch July 9, 2026 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants