From 2bcd612231f178c562eb2ad1e37f355b4a51947e Mon Sep 17 00:00:00 2001 From: Oskar Otwinowski Date: Mon, 17 Aug 2026 17:39:04 +0200 Subject: [PATCH] feat(webapp): show the external deployment id on deployments and runs Deployments page: an always-visible External ID column after Deployed by, and an External ID row in the deployment inspector under Worker type, both showing an en dash when a deploy carried no id. The Vercel Linked column now renders before Git, still only when a Vercel integration is connected. Also corrects the blank-row colSpan, which was already off by one before this column existed. Run inspector: an External deployment ID row between Version and SDK version, read from the run annotations, so an operator can see which id a run was pinned to - including a run that expired before its deployment ever arrived, where the locked version is empty but the id is the whole story. Buffered runs read the id from the same annotations rather than reporting none. Long ids are head-truncated with the full value behind the copy button: a commit SHA is meaningful in its prefix, and the inspector panel can be narrowed to 250px, where an unbroken 40-character SHA would otherwise scroll the properties list sideways and push the copy button off-panel (TRI-12923, TRI-13000). --- .../external-deployment-id-dashboard.md | 6 +++ .../v3/DeploymentListPresenter.server.ts | 3 ++ .../v3/DeploymentPresenter.server.ts | 2 + .../app/presenters/v3/SpanPresenter.server.ts | 2 + .../route.tsx | 21 +++++++++ .../route.tsx | 45 ++++++++++++++++--- .../route.tsx | 27 +++++++++++ .../v3/mollifier/syntheticSpanRun.server.ts | 2 + internal-packages/run-engine/src/index.ts | 1 + 9 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 .server-changes/external-deployment-id-dashboard.md diff --git a/.server-changes/external-deployment-id-dashboard.md b/.server-changes/external-deployment-id-dashboard.md new file mode 100644 index 0000000000..b337b1fc12 --- /dev/null +++ b/.server-changes/external-deployment-id-dashboard.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: feature +--- + +Deployments and runs now show the external ID they were deployed or pinned with, so you can trace a run back to the release of your app that triggered it. diff --git a/apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts b/apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts index f73366a5da..9968324052 100644 --- a/apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts @@ -153,6 +153,7 @@ export class DeploymentListPresenter { userDisplayName: string | null; userAvatarUrl: string | null; type: WorkerInstanceGroupType; + externalId: string | null; git: Prisma.JsonValue | null; integrationDeploymentId: string | null; }[] @@ -173,6 +174,7 @@ export class DeploymentListPresenter { wd."builtAt", wd."deployedAt", wd."type", + wd."externalId", wd."git" ${vercelSelect} FROM @@ -258,6 +260,7 @@ LIMIT ${pageSize} OFFSET ${pageSize * (page - 1)};`; avatarUrl: deployment.userAvatarUrl, } : undefined, + externalId: deployment.externalId, git: processGitMetadata(deployment.git), vercelDeploymentUrl, }; diff --git a/apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts b/apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts index a990957588..3e5df60525 100644 --- a/apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts @@ -101,6 +101,7 @@ export class DeploymentPresenter { externalBuildData: true, projectId: true, type: true, + externalId: true, environment: { select: { id: true, @@ -272,6 +273,7 @@ export class DeploymentPresenter { errorData: DeploymentPresenter.prepareErrorData(deployment.errorData), isBuilt: !!deployment.builtAt, type: deployment.type, + externalId: deployment.externalId, git: gitMetadata, triggeredVia: deployment.triggeredVia, vercelDeploymentUrl, diff --git a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts index 11b923566c..8482bb39dc 100644 --- a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts @@ -1,3 +1,4 @@ +import { readExternalDeploymentIdAnnotation } from "@internal/run-engine"; import { type MachinePreset, prettyPrintPacket, @@ -399,6 +400,7 @@ export class SpanPresenter extends BasePresenter { ttl: run.ttl, taskIdentifier: run.taskIdentifier, version: lockedWorker?.lockedToVersion?.version, + externalDeploymentId: readExternalDeploymentIdAnnotation(run.annotations), sdkVersion: lockedWorker?.lockedToVersion?.sdkVersion, runtime: lockedWorker?.lockedToVersion?.runtime, runtimeVersion: lockedWorker?.lockedToVersion?.runtimeVersion, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments.$deploymentParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments.$deploymentParam/route.tsx index 320a3e66d0..15a9c3915e 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments.$deploymentParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments.$deploymentParam/route.tsx @@ -180,6 +180,21 @@ function getTriggeredViaDisplay(triggeredVia: string | null | undefined): { } } +const EXTERNAL_ID_DISPLAY_LENGTH = 24; + +function ExternalIdValue({ externalId }: { externalId: string | null }) { + if (!externalId) { + return <>–; + } + + const display = + externalId.length > EXTERNAL_ID_DISPLAY_LENGTH + ? `${externalId.slice(0, EXTERNAL_ID_DISPLAY_LENGTH)}…` + : externalId; + + return ; +} + export default function Page() { const { deployment, eventStream } = useTypedLoaderData(); const organization = useOrganization(); @@ -445,6 +460,12 @@ export default function Page() { Worker type {capitalizeWord(deployment.type)} + + External ID + + + + Started at diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments/route.tsx index 66fdc0d964..06f4ded69d 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments/route.tsx @@ -42,6 +42,7 @@ import { collapsibleHandleClassName, } from "~/components/primitives/Resizable"; import { + CopyableTableCell, Table, TableBlankRow, TableBody, @@ -256,8 +257,9 @@ export default function Page() { Tasks Deployed at Deployed by - Git + External ID {hasVercelIntegration && Linked} + Git Go to page @@ -332,11 +334,11 @@ export default function Page() { "–" )} - -
- -
-
+ {hasVercelIntegration && ( {deployment.vercelDeploymentUrl ? ( @@ -353,6 +355,11 @@ export default function Page() { )} )} + +
+ +
+
+ No deploys match your filters @@ -602,6 +609,30 @@ function DeploymentActionsCell({ ); } +function DeploymentExternalIdCell({ + externalId, + path, + isSelected, +}: { + externalId: string | null; + path: string; + isSelected: boolean; +}) { + if (!externalId) { + return ( + + – + + ); + } + + return ( + + {externalId.length > 12 ? `${externalId.slice(0, 12)}…` : externalId} + + ); +} + type RollbackDeploymentDialogProps = { projectId: string; deploymentShortCode: string; diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx index 807e527793..bcfabb8449 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx @@ -872,6 +872,12 @@ function RunBody({ )}
+ + External deployment ID + + + + SDK version @@ -1274,6 +1280,27 @@ function MiniStat({ ); } +const EXTERNAL_DEPLOYMENT_ID_DISPLAY_LENGTH = 24; + +function ExternalDeploymentIdValue({ + externalDeploymentId, +}: { + externalDeploymentId: string | undefined; +}) { + if (!externalDeploymentId) { + return <>–; + } + + const display = + externalDeploymentId.length > EXTERNAL_DEPLOYMENT_ID_DISPLAY_LENGTH + ? `${externalDeploymentId.slice(0, EXTERNAL_DEPLOYMENT_ID_DISPLAY_LENGTH)}…` + : externalDeploymentId; + + return ( + + ); +} + // A compact "mini queue" for a waiting run: the queue page's stat blocks + charts, scaled down. function WaitingInQueueBlock({ queueName, diff --git a/apps/webapp/app/v3/mollifier/syntheticSpanRun.server.ts b/apps/webapp/app/v3/mollifier/syntheticSpanRun.server.ts index f36d5a5d28..11aa7c85ba 100644 --- a/apps/webapp/app/v3/mollifier/syntheticSpanRun.server.ts +++ b/apps/webapp/app/v3/mollifier/syntheticSpanRun.server.ts @@ -1,3 +1,4 @@ +import { readExternalDeploymentIdAnnotation } from "@internal/run-engine"; import { prettyPrintPacket, RunAnnotations } from "@trigger.dev/core/v3"; import { getMaxDuration } from "@trigger.dev/core/v3/isomorphic"; import { @@ -124,6 +125,7 @@ export async function buildSyntheticSpanRun(args: { ttl: run.ttl ?? null, taskIdentifier: run.taskIdentifier ?? "", version: undefined, + externalDeploymentId: readExternalDeploymentIdAnnotation(run.annotations), sdkVersion: undefined, runtime: undefined, runtimeVersion: undefined, diff --git a/internal-packages/run-engine/src/index.ts b/internal-packages/run-engine/src/index.ts index b7898e6433..2c54e4c20c 100644 --- a/internal-packages/run-engine/src/index.ts +++ b/internal-packages/run-engine/src/index.ts @@ -13,6 +13,7 @@ export type { PendingVersionRunIdLookupResult, } from "./engine/services/pendingVersionLookup.js"; export { NoopPendingVersionRunIdLookup } from "./engine/services/pendingVersionLookup.js"; +export { readExternalDeploymentIdAnnotation } from "./engine/systems/pendingVersionSystem.js"; export { PassthroughControlPlaneResolver } from "./engine/controlPlaneResolver.js"; export type { ControlPlaneResolver,