Skip to content

Commit d4c1b4d

Browse files
committed
feat(webapp): show Vercel deployment links in UI
Add Vercel integration lookup and expose a deployment URL so linked Vercel deployments are discoverable from the app. - Import VercelProjectIntegrationDataSchema and query for organizationProjectIntegration to detect Vercel project data. - Parse integrationData and, when a vercelTeamSlug is present, look up the latest integrationDeployment for the deployment and construct a Vercel URL (vercel.com/<team>/<project>/<id>). - Attach vercelDeploymentUrl to the DeploymentPresenter output. - Render the Vercel link in deployment detail and deployments list by using the VercelLink component where vercelDeploymentUrl exists. - Remove an unused tooltip import and swap VercelLogo usage to the VercelLink component in the list view. This makes it easier for users to jump directly to the corresponding Vercel deployment from the webapp.
1 parent 38981f5 commit d4c1b4d

File tree

4 files changed

+89
-16
lines changed

4 files changed

+89
-16
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { VercelLogo } from "./VercelLogo";
2+
import { LinkButton } from "~/components/primitives/Buttons";
3+
import { SimpleTooltip } from "~/components/primitives/Tooltip";
4+
5+
export function VercelLink({ vercelDeploymentUrl }: { vercelDeploymentUrl: string }) {
6+
return (
7+
<SimpleTooltip
8+
button={
9+
<LinkButton
10+
variant="minimal/small"
11+
LeadingIcon={<VercelLogo className="size-3.5" />}
12+
iconSpacing="gap-x-1"
13+
to={vercelDeploymentUrl}
14+
className="pl-1"
15+
>
16+
Vercel
17+
</LinkButton>
18+
}
19+
content="View on Vercel"
20+
/>
21+
);
22+
}

apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
1212
import { type User } from "~/models/user.server";
1313
import { getUsername } from "~/utils/username";
1414
import { processGitMetadata } from "./BranchesPresenter.server";
15+
import { VercelProjectIntegrationDataSchema } from "~/v3/vercel/vercelProjectIntegrationSchema";
1516
import { S2 } from "@s2-dev/streamstore";
1617
import { env } from "~/env.server";
1718
import { createRedisClient } from "~/redis.server";
@@ -161,6 +162,51 @@ export class DeploymentPresenter {
161162
});
162163

163164
const gitMetadata = processGitMetadata(deployment.git);
165+
166+
// Look up Vercel integration data to construct a deployment URL
167+
let vercelDeploymentUrl: string | undefined;
168+
const vercelProjectIntegration =
169+
await this.#prismaClient.organizationProjectIntegration.findFirst({
170+
where: {
171+
projectId: project.id,
172+
deletedAt: null,
173+
organizationIntegration: {
174+
service: "VERCEL",
175+
deletedAt: null,
176+
},
177+
},
178+
select: {
179+
integrationData: true,
180+
},
181+
});
182+
183+
if (vercelProjectIntegration) {
184+
const parsed = VercelProjectIntegrationDataSchema.safeParse(
185+
vercelProjectIntegration.integrationData
186+
);
187+
188+
if (parsed.success && parsed.data.vercelTeamSlug) {
189+
const integrationDeployment =
190+
await this.#prismaClient.integrationDeployment.findFirst({
191+
where: {
192+
deploymentId: deployment.id,
193+
integrationName: "vercel",
194+
},
195+
select: {
196+
integrationDeploymentId: true,
197+
},
198+
orderBy: {
199+
createdAt: "desc",
200+
},
201+
});
202+
203+
if (integrationDeployment) {
204+
const vercelId = integrationDeployment.integrationDeploymentId.replace(/^dpl_/, "");
205+
vercelDeploymentUrl = `https://vercel.com/${parsed.data.vercelTeamSlug}/${parsed.data.vercelProjectName}/${vercelId}`;
206+
}
207+
}
208+
}
209+
164210
const externalBuildData = deployment.externalBuildData
165211
? ExternalBuildData.safeParse(deployment.externalBuildData)
166212
: undefined;
@@ -227,6 +273,7 @@ export class DeploymentPresenter {
227273
type: deployment.type,
228274
git: gitMetadata,
229275
triggeredVia: deployment.triggeredVia,
276+
vercelDeploymentUrl,
230277
},
231278
};
232279
}

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments.$deploymentParam/route.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from "lucide-react";
1616
import { ExitIcon } from "~/assets/icons/ExitIcon";
1717
import { GitMetadata } from "~/components/GitMetadata";
18+
import { VercelLink } from "~/components/integrations/VercelLink";
1819
import { RuntimeIcon } from "~/components/RuntimeIcon";
1920
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
2021
import { EnvironmentCombo } from "~/components/environments/EnvironmentLabel";
@@ -516,6 +517,16 @@ export default function Page() {
516517
})()}
517518
</Property.Value>
518519
</Property.Item>
520+
{deployment.vercelDeploymentUrl && (
521+
<Property.Item>
522+
<Property.Label>Linked</Property.Label>
523+
<Property.Value>
524+
<div className="-ml-1 mt-0.5 flex flex-col">
525+
<VercelLink vercelDeploymentUrl={deployment.vercelDeploymentUrl} />
526+
</div>
527+
</Property.Value>
528+
</Property.Item>
529+
)}
519530
</Property.Table>
520531
</div>
521532

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments/route.tsx

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { useEffect } from "react";
1919
import { typedjson, useTypedLoaderData } from "remix-typedjson";
2020
import { z } from "zod";
2121
import { PromoteIcon } from "~/assets/icons/PromoteIcon";
22-
import { VercelLogo } from "~/components/integrations/VercelLogo";
22+
import { VercelLink } from "~/components/integrations/VercelLink";
2323
import { DeploymentsNone, DeploymentsNoneDev } from "~/components/BlankStatePanels";
2424
import { OctoKitty } from "~/components/GitHubLoginButton";
2525
import { GitMetadata } from "~/components/GitMetadata";
@@ -56,7 +56,6 @@ import {
5656
TableHeaderCell,
5757
TableRow,
5858
} from "~/components/primitives/Table";
59-
import { SimpleTooltip } from "~/components/primitives/Tooltip";
6059
import {
6160
DeploymentStatus,
6261
deploymentStatusDescription,
@@ -314,20 +313,14 @@ export default function Page() {
314313
{hasVercelIntegration && (
315314
<TableCell isSelected={isSelected}>
316315
{deployment.vercelDeploymentUrl ? (
317-
<SimpleTooltip
318-
button={
319-
<a
320-
href={deployment.vercelDeploymentUrl}
321-
target="_blank"
322-
rel="noreferrer noopener"
323-
className="flex items-center text-text-dimmed transition-colors hover:text-text-bright"
324-
onClick={(e) => e.stopPropagation()}
325-
>
326-
<VercelLogo className="size-3.5" />
327-
</a>
328-
}
329-
content="View on Vercel"
330-
/>
316+
<div
317+
className="-ml-1 flex items-center"
318+
onClick={(e) => e.stopPropagation()}
319+
>
320+
<VercelLink
321+
vercelDeploymentUrl={deployment.vercelDeploymentUrl}
322+
/>
323+
</div>
331324
) : (
332325
"–"
333326
)}

0 commit comments

Comments
 (0)