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,