Skip to content

Commit 2d273fd

Browse files
committed
track-container-exits
1 parent b55f66d commit 2d273fd

File tree

10 files changed

+163
-7
lines changed

10 files changed

+163
-7
lines changed

web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/deployments/[deploymentId]/(deployment-progress)/deployment-progress.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,20 @@ export function DeploymentProgress({ stepsData }: { stepsData?: StepsData }) {
3838

3939
const { getDomainsForDeployment, projectId } = useProjectData();
4040

41+
// Fetch instance errors for the failed deployment banner.
42+
// Only enabled when the deployment has failed so we don't waste queries on healthy deploys.
43+
const { data: deploymentTree } = trpc.deploy.network.get.useQuery(
44+
{ deploymentId: deployment.id },
45+
{ enabled: isFailed },
46+
);
47+
const instanceErrors = isFailed
48+
? (deploymentTree?.children ?? [])
49+
.flatMap((sentinel) => ("children" in sentinel && sentinel.children) ? sentinel.children : [])
50+
.filter((inst) => inst.metadata.type === "instance" && "message" in inst.metadata && inst.metadata.message)
51+
.map((inst) => (inst.metadata as { message: string }).message)
52+
.filter((msg, i, arr) => arr.indexOf(msg) === i) // dedupe
53+
: [];
54+
4155
const [now, setNow] = useState(Date.now);
4256
useEffect(() => {
4357
if (isFailed) {
@@ -210,6 +224,7 @@ export function DeploymentProgress({ stepsData }: { stepsData?: StepsData }) {
210224
redeployOpen={redeployOpen}
211225
onRedeployClose={() => setRedeployOpen(false)}
212226
deployment={deployment}
227+
instanceErrors={instanceErrors}
213228
/>
214229
)}
215230
{network?.completed && (

web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/deployments/[deploymentId]/(deployment-progress)/failed-deployment-banner.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,21 @@ export function FailedDeploymentBanner({
3232
redeployOpen,
3333
onRedeployClose,
3434
deployment,
35+
instanceErrors,
3536
}: {
3637
steps: StepEntry[];
3738
settingsUrl: string;
3839
onRedeploy: () => void;
3940
redeployOpen: boolean;
4041
onRedeployClose: () => void;
4142
deployment: Deployment;
43+
instanceErrors?: string[];
4244
}) {
43-
const errorMessage = steps.find((s) => s?.error)?.error ?? "Deployment failed";
45+
const stepError = steps.find((s) => s?.error)?.error;
46+
const errorMessage = stepError
47+
?? (instanceErrors && instanceErrors.length > 0
48+
? instanceErrors.join("; ")
49+
: "Deployment failed");
4450
const showSettingsLink = isSettingsRelatedError(errorMessage);
4551

4652
return (

web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/deployments/[deploymentId]/deployment-utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ export function deriveStatusFromSteps(
4040
return "finalizing";
4141
}
4242
if (finalizing?.completed) {
43+
// Pipeline completed, but the deployment may have been marked as failed
44+
// post-deploy (e.g. all instances crashed). Respect the DB status.
45+
if (fallback === "failed") {
46+
return "failed";
47+
}
4348
return "ready";
4449
}
4550
if (network && !network.endedAt) {

web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/deployments/[deploymentId]/network/unkey-flow/components/nodes/instance-node.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ type InstanceNodeProps = {
1212
};
1313

1414
export function InstanceNode({ node, flagCode, deploymentId }: InstanceNodeProps) {
15-
const { cpu, memory, health } = node.metadata;
15+
const { cpu, memory, health, message } = node.metadata;
1616

1717
const { data: rps } = trpc.deploy.network.getInstanceRps.useQuery(
1818
{
@@ -34,7 +34,7 @@ export function InstanceNode({ node, flagCode, deploymentId }: InstanceNodeProps
3434
</div>
3535
}
3636
title={node.label}
37-
subtitle="Instance Replica"
37+
subtitle={health === "unhealthy" && message ? message : "Instance Replica"}
3838
health={health}
3939
/>
4040
<CardFooter type="instance" flagCode={flagCode} rps={rps} cpu={cpu} memory={memory} />

web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/deployments/[deploymentId]/network/unkey-flow/components/nodes/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ type InstanceNode = BaseNode & {
3737
type: "instance";
3838
description: string;
3939
replicas: number;
40+
message?: string;
41+
failureReason?: string;
4042
} & BaseMetrics;
4143
};
4244

web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/deployments/[deploymentId]/network/unkey-flow/components/overlay/node-details-panel/region-node/sentinel-instances.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ export function SentinelInstances({ instances }: SentinelInstancesProps) {
8888
showGlow={health !== "normal"}
8989
/>
9090
</div>
91+
{health === "unhealthy" && instance.metadata.type === "instance" && instance.metadata.message && (
92+
<div className="text-[11px] text-error-11 mt-1">
93+
{instance.metadata.message}
94+
</div>
95+
)}
9196
</div>
9297
</div>
9398
);

web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/deployments/[deploymentId]/page.tsx

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
"use client";
22
import { trpc } from "@/lib/trpc/client";
3-
import { useEffect, useMemo } from "react";
3+
import { useEffect, useMemo, useState } from "react";
4+
import { useParams } from "next/navigation";
45
import { DeploymentDomainsCard } from "../../../components/deployment-domains-card";
56
import { ProjectContentWrapper } from "../../../components/project-content-wrapper";
67
import { useProjectData } from "../../data-provider";
78
import { DeploymentApproval } from "./(deployment-progress)/deployment-approval";
89
import { DeploymentInfo } from "./(deployment-progress)/deployment-info";
10+
import { FailedDeploymentBanner } from "./(deployment-progress)/failed-deployment-banner";
911
import { DeploymentProgress } from "./(deployment-progress)/deployment-progress";
1012
import { SkippedDeploymentView } from "./(deployment-progress)/skipped-deployment-view";
1113
import { DeploymentNetworkSection } from "./(overview)/components/sections/deployment-network-section";
@@ -35,6 +37,29 @@ export default function DeploymentOverview() {
3537
[stepsQuery.data, deployment.status, skipped],
3638
);
3739

40+
// A post-deploy failure is when the pipeline completed successfully but the
41+
// deployment was later marked as failed (e.g. all instances crashed).
42+
const pipelineCompleted = stepsQuery.data?.finalizing?.completed === true;
43+
const isPostDeployFailure = derivedStatus === "failed" && pipelineCompleted;
44+
45+
const [redeployOpen, setRedeployOpen] = useState(false);
46+
const params = useParams();
47+
const workspaceSlug = params.workspaceSlug as string;
48+
const { projectId } = useProjectData();
49+
50+
// Fetch instance errors for post-deploy failure banner
51+
const { data: deploymentTree } = trpc.deploy.network.get.useQuery(
52+
{ deploymentId: deployment.id },
53+
{ enabled: isPostDeployFailure },
54+
);
55+
const instanceErrors = isPostDeployFailure
56+
? (deploymentTree?.children ?? [])
57+
.flatMap((sentinel) => ("children" in sentinel && sentinel.children) ? sentinel.children : [])
58+
.filter((inst) => inst.metadata.type === "instance" && "message" in inst.metadata && inst.metadata.message)
59+
.map((inst) => (inst.metadata as { message: string }).message)
60+
.filter((msg, i, arr) => arr.indexOf(msg) === i)
61+
: [];
62+
3863
useEffect(() => {
3964
if (ready) {
4065
stepsQuery.refetch();
@@ -60,8 +85,19 @@ export default function DeploymentOverview() {
6085
<div key="skipped" className="animate-fade-slide-in">
6186
<SkippedDeploymentView />
6287
</div>
63-
) : ready ? (
88+
) : ready || isPostDeployFailure ? (
6489
<div key="ready" className="flex flex-col gap-5 animate-fade-slide-in">
90+
{isPostDeployFailure && (
91+
<FailedDeploymentBanner
92+
steps={[]}
93+
settingsUrl={`/${workspaceSlug}/projects/${projectId}/settings`}
94+
onRedeploy={() => setRedeployOpen(true)}
95+
redeployOpen={redeployOpen}
96+
onRedeployClose={() => setRedeployOpen(false)}
97+
deployment={deployment}
98+
instanceErrors={instanceErrors}
99+
/>
100+
)}
65101
<DeploymentDomainsCard />
66102
<DeploymentNetworkSection />
67103
</div>

0 commit comments

Comments
 (0)