Skip to content

Add links to and from deployments #1921

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,52 @@ LIMIT ${pageSize} OFFSET ${pageSize * (page - 1)};`;
}),
};
}

public async findPageForVersion({
userId,
projectSlug,
organizationSlug,
environmentSlug,
version,
}: {
userId: User["id"];
projectSlug: Project["slug"];
organizationSlug: Organization["slug"];
environmentSlug: string;
version: string;
}) {
const project = await this.#prismaClient.project.findFirstOrThrow({
select: {
id: true,
},
where: {
slug: projectSlug,
organization: {
slug: organizationSlug,
members: {
some: {
userId,
},
},
},
},
});

const environment = await findEnvironmentBySlug(project.id, environmentSlug, userId);
if (!environment) {
throw new Error(`Environment not found`);
}

// Find how many deployments have been made since this version
const deploymentsSinceVersion = await this.#prismaClient.$queryRaw<{ count: BigInt }[]>`
SELECT COUNT(*) as count
FROM ${sqlDatabaseSchema}."WorkerDeployment"
WHERE "projectId" = ${project.id}
AND "environmentId" = ${environment.id}
AND string_to_array(version, '.')::int[] > string_to_array(${version}, '.')::int[]
`;

const count = Number(deploymentsSinceVersion[0].count);
return Math.floor(count / pageSize) + 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { useUser } from "~/hooks/useUser";
import { DeploymentPresenter } from "~/presenters/v3/DeploymentPresenter.server";
import { requireUserId } from "~/services/session.server";
import { cn } from "~/utils/cn";
import { v3DeploymentParams, v3DeploymentsPath } from "~/utils/pathBuilder";
import { v3DeploymentParams, v3DeploymentsPath, v3RunsPath } from "~/utils/pathBuilder";
import { capitalizeWord } from "~/utils/string";

export const loader = async ({ request, params }: LoaderFunctionArgs) => {
Expand Down Expand Up @@ -231,16 +231,19 @@ export default function Page() {
</TableHeader>
<TableBody>
{deployment.tasks.map((t) => {
const path = v3RunsPath(organization, project, environment, {
tasks: [t.slug],
});
return (
<TableRow key={t.slug}>
<TableCell>
<TableCell to={path}>
<div className="inline-flex flex-col gap-0.5">
<Paragraph variant="extra-small" className="text-text-dimmed">
{t.slug}
</Paragraph>
</div>
</TableCell>
<TableCell>{t.filePath}</TableCell>
<TableCell to={path}>{t.filePath}</TableCell>
</TableRow>
);
})}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { ArrowPathIcon, ArrowUturnLeftIcon, BookOpenIcon } from "@heroicons/react/20/solid";
import { type MetaFunction, Outlet, useLocation, useParams } from "@remix-run/react";
import { type MetaFunction, Outlet, useLocation, useParams, useNavigate } from "@remix-run/react";
import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
import { PromoteIcon } from "~/assets/icons/PromoteIcon";
import { DeploymentsNone, DeploymentsNoneDev } from "~/components/BlankStatePanels";
import { UserAvatar } from "~/components/UserProfilePhoto";
import { EnvironmentCombo } from "~/components/environments/EnvironmentLabel";
import { MainCenteredContainer, PageBody, PageContainer } from "~/components/layout/AppLayout";
import { Badge } from "~/components/primitives/Badge";
import { Button, LinkButton } from "~/components/primitives/Buttons";
Expand Down Expand Up @@ -53,6 +52,7 @@ import { EnvironmentParamSchema, docsPath, v3DeploymentPath } from "~/utils/path
import { createSearchParams } from "~/utils/searchParams";
import { deploymentIndexingIsRetryable } from "~/v3/deploymentStatus";
import { compareDeploymentVersions } from "~/v3/utils/deploymentVersions";
import { useEffect } from "react";

export const meta: MetaFunction = () => {
return [
Expand All @@ -64,17 +64,37 @@ export const meta: MetaFunction = () => {

const SearchParams = z.object({
page: z.coerce.number().optional(),
version: z.string().optional(),
});

export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { organizationSlug, projectParam, envParam } = EnvironmentParamSchema.parse(params);

const searchParams = createSearchParams(request.url, SearchParams);
const page = searchParams.success ? searchParams.params.get("page") ?? 1 : 1;

let page = searchParams.success ? Number(searchParams.params.get("page") ?? 1) : 1;
const version = searchParams.success ? searchParams.params.get("version")?.toString() : undefined;

const presenter = new DeploymentListPresenter();

// If we have a version, find its page
if (version) {
try {
page = await presenter.findPageForVersion({
userId,
organizationSlug,
projectSlug: projectParam,
environmentSlug: envParam,
version,
});
} catch (error) {
console.error("Error finding page for version", error);
// Carry on, we'll just show the selected page
}
}

try {
const presenter = new DeploymentListPresenter();
const result = await presenter.call({
userId,
organizationSlug,
Expand All @@ -83,7 +103,12 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
page,
});

return typedjson(result);
// If we have a version, find the deployment
const selectedDeployment = version
? result.deployments.find((d) => d.version === version)
: undefined;

return typedjson({ ...result, selectedDeployment });
} catch (error) {
console.error(error);
throw new Response(undefined, {
Expand All @@ -97,10 +122,23 @@ export default function Page() {
const organization = useOrganization();
const project = useProject();
const environment = useEnvironment();
const { deployments, currentPage, totalPages } = useTypedLoaderData<typeof loader>();
const { deployments, currentPage, totalPages, selectedDeployment } =
useTypedLoaderData<typeof loader>();
const hasDeployments = totalPages > 0;

const { deploymentParam } = useParams();
const location = useLocation();
const navigate = useNavigate();

// If we have a selected deployment from the version param, show it
useEffect(() => {
if (selectedDeployment && !deploymentParam) {
const searchParams = new URLSearchParams(location.search);
searchParams.delete("version");
searchParams.set("page", currentPage.toString());
navigate(`${location.pathname}/${selectedDeployment.shortCode}?${searchParams.toString()}`);
}
}, [selectedDeployment, deploymentParam, location.search]);

const currentDeployment = deployments.find((d) => d.isCurrent);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
return new Response("Not found", { status: 404 });
}

// Redirect to the project's runs page
// Redirect to the project's deployments page
return redirect(
`/orgs/${project.organization.slug}/projects/${project.slug}/deployments/${validatedParams.deploymentParam}`
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import { formatCurrencyAccurate } from "~/utils/numberFormatter";
import {
docsPath,
v3BatchPath,
v3DeploymentVersionPath,
v3RunDownloadLogsPath,
v3RunPath,
v3RunSpanPath,
Expand Down Expand Up @@ -527,7 +528,26 @@ function RunBody({
<Property.Label>Version</Property.Label>
<Property.Value>
{run.version ? (
run.version
environment.type === "DEVELOPMENT" ? (
run.version
) : (
<SimpleTooltip
button={
<TextLink
to={v3DeploymentVersionPath(
organization,
project,
environment,
run.version
)}
className="group flex flex-wrap items-center gap-x-1 gap-y-0"
>
{run.version}
</TextLink>
}
content={"Jump to deployment"}
/>
)
) : (
<span className="flex items-center gap-1">
<span>Never started</span>
Expand Down
9 changes: 9 additions & 0 deletions apps/webapp/app/utils/pathBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,15 @@ export function v3DeploymentPath(
return `${v3DeploymentsPath(organization, project, environment)}/${deployment.shortCode}${query}`;
}

export function v3DeploymentVersionPath(
organization: OrgForPath,
project: ProjectForPath,
environment: EnvironmentForPath,
version: string
) {
return `${v3DeploymentsPath(organization, project, environment)}?version=${version}`;
}

export function v3BillingPath(organization: OrgForPath, message?: string) {
return `${organizationPath(organization)}/settings/billing${
message ? `?message=${encodeURIComponent(message)}` : ""
Expand Down
Loading