From 01104945d4748424cab550c1750340de7dbd5edc 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 + .../engine/systems/pendingVersionSystem.ts | 2 +- internal-packages/run-engine/src/index.ts | 1 + 10 files changed, 103 insertions(+), 8 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 00000000000..b337b1fc124 --- /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 f73366a5dac..99683240529 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 a9909575884..3e5df605257 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 11b923566cb..8482bb39dc0 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 320a3e66d0c..15a9c3915e6 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 66fdc0d9646..06f4ded69df 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 807e527793c..bcfabb84496 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 f36d5a5d28e..11aa7c85ba6 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/engine/systems/pendingVersionSystem.ts b/internal-packages/run-engine/src/engine/systems/pendingVersionSystem.ts index d1108743907..fddd7565458 100644 --- a/internal-packages/run-engine/src/engine/systems/pendingVersionSystem.ts +++ b/internal-packages/run-engine/src/engine/systems/pendingVersionSystem.ts @@ -47,7 +47,7 @@ type ExternalDeploymentWorker = { cliVersion?: string; }; -function readExternalDeploymentIdAnnotation(annotations: unknown): string | undefined { +export function readExternalDeploymentIdAnnotation(annotations: unknown): string | undefined { if (typeof annotations !== "object" || annotations === null) { return undefined; } diff --git a/internal-packages/run-engine/src/index.ts b/internal-packages/run-engine/src/index.ts index b7898e64331..2c54e4c20c0 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,