web/app/canopy/%5Bowner%5D/%5Brepo%5D/runs/%5BrunSlug%5D/page.tsxblame
View source
9d879c01import type { Metadata } from "next";
9d879c02import { redirect } from "next/navigation";
bc1b2ba3import { getCanopyRunsForRepo } from "@/lib/grove-api";
9d879c04import {
9d879c05 formatTriggerType,
9d879c06 getInvocationRunSlug,
9d879c07 getSeedRunIdFromInvocationSlug,
9d879c08 getInvocationStatus,
9d879c09 groupByInvocation,
9d879c010} from "@/lib/canopy-invocations";
fe3b50911import { InvocationDetail } from "./invocation-detail";
5bcd5db12import { CanopyLiveRefresh } from "@/app/components/canopy-live-refresh";
9d879c013
9d879c014interface Props {
9d879c015 params: Promise<{ owner: string; repo: string; runSlug: string }>;
9d879c016}
9d879c017
9d879c018interface Run {
9d879c019 id: number;
9d879c020 pipeline_name: string;
9d879c021 status: string;
9d879c022 trigger_type?: string | null;
9d879c023 commit_id: string | null;
9d879c024 commit_message: string | null;
9d879c025 trigger_ref?: string | null;
9d879c026 started_at?: string | null;
9d879c027 duration_ms?: number | null;
9d879c028 created_at: string;
9d879c029 repo_name: string;
9d879c030 owner_name: string;
9d879c031}
9d879c032
9d879c033export async function generateMetadata({ params }: Props): Promise<Metadata> {
86450dc34 const { repo } = await params;
86450dc35 return { title: `Run · ${repo}` };
9d879c036}
9d879c037
9d879c038async function getRuns(owner: string, repo: string): Promise<Run[]> {
bc1b2ba39 const data = await getCanopyRunsForRepo<Run>(owner, repo, { limit: 200 });
bc1b2ba40 return data?.runs ?? [];
9d879c041}
9d879c042
9d879c043export default async function CanopyInvocationPage({ params }: Props) {
9d879c044 const { owner, repo, runSlug } = await params;
9d879c045 const normalizedRunSlug = runSlug.toLowerCase();
9d879c046 const seedId = getSeedRunIdFromInvocationSlug(normalizedRunSlug);
9d879c047 const runs = await getRuns(owner, repo);
9d879c048 const invocations = groupByInvocation(runs);
9d879c049 const invocation =
9d879c050 (seedId !== null
9d879c051 ? invocations.find((group) => group.runs.some((run) => run.id === seedId))
9d879c052 : null) ??
9d879c053 invocations.find((group) => {
9d879c054 if (group.commitId) {
9d879c055 return group.commitId.toLowerCase().startsWith(normalizedRunSlug);
9d879c056 }
8a2c7d457 return getInvocationRunSlug(group, invocations) === normalizedRunSlug;
9d879c058 });
9d879c059
9d879c060 if (!invocation) {
9d879c061 return (
fe3b50962 <div className="px-4 sm:px-6 py-6">
9d879c063 <p className="text-sm" style={{ color: "var(--text-faint)" }}>
9d879c064 Build invocation not found.
9d879c065 </p>
9d879c066 </div>
9d879c067 );
9d879c068 }
9d879c069
9d879c070 const title =
9d879c071 invocation.commitMessage ||
9d879c072 (invocation.commitId
9d879c073 ? invocation.commitId.substring(0, 8)
9d879c074 : `${formatTriggerType(invocation.triggerType)} build`);
9d879c075 const status = getInvocationStatus(invocation.runs);
9d879c076 const newestRun = invocation.runs[invocation.runs.length - 1];
8a2c7d477 const canonicalSlug = getInvocationRunSlug(invocation, invocations);
9d879c078 if (normalizedRunSlug !== canonicalSlug) {
9d879c079 redirect(`/${owner}/${repo}/runs/${canonicalSlug}`);
9d879c080 }
9d879c081
9d879c082 return (
5bcd5db83 <>
5bcd5db84 <CanopyLiveRefresh scope="repo" owner={owner} repo={repo} />
5bcd5db85 <InvocationDetail
5bcd5db86 owner={owner}
5bcd5db87 repo={repo}
5bcd5db88 runs={invocation.runs}
5bcd5db89 title={title}
5bcd5db90 status={status}
5bcd5db91 triggerType={invocation.triggerType ?? null}
5bcd5db92 commitId={invocation.commitId}
5bcd5db93 newestCreatedAt={newestRun.created_at}
5bcd5db94 />
5bcd5db95 </>
9d879c096 );
9d879c097}