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`}
Download all
{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}
) : (
)}
{file.dir}
{file.name}
{hasEarlier && (
{file.versions.length}{" "}
{plural(file.versions.length, "version", "versions")}
)}
{formatBytes(latest.size)}
{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 (
Download
);
}
function errorMessage(error: unknown): string | undefined {
return error instanceof Error ? error.message : undefined;
}