import { useMemo } from "react"; import { useParams } from "react-router"; import { Disclosure, DisclosureButton, DisclosurePanel } from "@headlessui/react"; import { ArrowDownTrayIcon, ChevronRightIcon, PaperClipIcon } from "@heroicons/react/24/outline"; import type { RunArtifactEntry } from "@qltysh/fabro-api-client"; import { EmptyState, ErrorState, LoadingState } from "../components/state"; import { StageSidebar } from "../components/stage-sidebar"; import { runArtifactsDownloadUrl, stageArtifactDownloadUrl, } from "../lib/api-client"; import { formatBytes } from "../lib/format"; import { plural } from "../lib/plural"; import { useRunArtifacts, useRunStages } from "../lib/queries"; import { mapRunStagesToSidebarStages } from "../lib/stage-sidebar"; import type { ArtifactFile, ArtifactVersion } from "./run-artifacts/group"; import { groupArtifactsByFile } from "./run-artifacts/group"; export const handle = { wide: true }; export default function RunArtifacts() { const { id } = useParams(); const stagesQuery = useRunStages(id); const artifactsQuery = useRunArtifacts(id); const stages = useMemo( () => mapRunStagesToSidebarStages(stagesQuery.data), [stagesQuery.data], ); return (
); } function RunArtifactsBody({ runId, artifactsQuery, stagesQuery, stages, }: { runId: string; artifactsQuery: ReturnType; stagesQuery: ReturnType; stages: ReturnType; }) { const error = artifactsQuery.error ?? stagesQuery.error; if (error) { return ( { if (artifactsQuery.error) void artifactsQuery.mutate(); if (stagesQuery.error) void stagesQuery.mutate(); }} /> ); } if (artifactsQuery.data === undefined || stagesQuery.data === undefined) { return ; } return ( ); } function ArtifactFiles({ runId, entries, stages, }: { runId: string; entries: readonly RunArtifactEntry[]; stages: ReturnType; }) { const files = useMemo(() => groupArtifactsByFile(entries, stages), [entries, stages]); if (files.length === 0) { return ( ); } return ; } function ArtifactList({ runId, files }: { runId: string; files: readonly ArtifactFile[] }) { const { captures, latestBytes, storedBytes } = useMemo(() => { let captures = 0; let latestBytes = 0; let storedBytes = 0; for (const file of files) { captures += file.versions.length; latestBytes += file.versions[0].size; for (const version of file.versions) storedBytes += version.size; } return { captures, latestBytes, storedBytes }; }, [files]); // Only mention versions once some file actually has more than one. const versioned = captures > files.length; return (

{files.length} {plural(files.length, "file", "files")} {versioned && ( {" "} · {captures} {plural(captures, "version", "versions")} )}

{versioned ? `${formatBytes(latestBytes)} latest · ${formatBytes(storedBytes)} stored` : `${formatBytes(latestBytes)} total`}
{files.map((file) => ( ))}
); } function ArtifactFileRow({ runId, file }: { runId: string; file: ArtifactFile }) { const hasEarlier = file.versions.length > 1; const latest = file.versions[0]; return ( {({ open }) => ( <>
{hasEarlier ? ( {open ? "Hide" : "Show"} earlier versions of {file.name} ) : (
{hasEarlier && ( )} )}
); } function EarlierVersions({ runId, file }: { runId: string; file: ArtifactFile }) { return ( <> {file.versions.map((version, index) => index === 0 ? null : (
  • {formatBytes(version.size)}
  • ), )} ); } function VersionLabel({ version }: { version: ArtifactVersion }) { const attempt = attemptLabel(version); return ( <> {version.stageLabel} {attempt && {attempt}} ); } function attemptLabel(version: ArtifactVersion): string | null { return version.retry > 1 ? `attempt ${version.retry}` : null; } function SizeDelta({ delta }: { delta: number | null }) { if (delta === null) { return first; } const tone = delta < 0 ? "text-amber" : "text-mint"; const sign = delta < 0 ? "−" : "+"; return ( {sign} {formatBytes(Math.abs(delta))} ); } function DownloadLink({ runId, file, version, }: { runId: string; file: ArtifactFile; version: ArtifactVersion; }) { const href = stageArtifactDownloadUrl( runId, version.stageId, file.path, version.retry, ); const attempt = attemptLabel(version); const source = attempt ? `${version.stageLabel}, ${attempt}` : version.stageLabel; return ( ); } function errorMessage(error: unknown): string | undefined { return error instanceof Error ? error.message : undefined; }