88112112d4
Deploy / deploy (push) Successful in 3m27s
Shows what last reached the client with evidence (shared EXR package or manual delivery), anything newer still on its way, and the last review MOV, so it's clear on Shot Status what has actually been sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
125 lines
4.6 KiB
TypeScript
125 lines
4.6 KiB
TypeScript
/**
|
|
* What has gone out for one shot, and what is on its way — the answer to
|
|
* "have we sent this?" on Shot Status. Kept free of database imports; the page
|
|
* gathers the rows and this decides what they mean.
|
|
*/
|
|
|
|
export type ShotDeliveryPackage = {
|
|
kind: string; // EXR_DELIVERY | MOV_REVIEW
|
|
status: string; // DeliveryStatus
|
|
packageNumber: number;
|
|
versionString: string;
|
|
createdAt: Date;
|
|
builtAt: Date | null;
|
|
uploadedAt: Date | null;
|
|
deliveredAt: Date | null;
|
|
sharedWith: string | null;
|
|
};
|
|
|
|
export type PendingTone = "progress" | "waiting" | "failed";
|
|
|
|
export type ShotDeliverySummary = {
|
|
/** The last time the shot reached the client with evidence. */
|
|
sent: {
|
|
via: "PACKAGE" | "MANUAL";
|
|
version: string | null; // unknown for a manual delivery
|
|
at: Date;
|
|
to: string | null;
|
|
label: string; // "Package #15" | "Outside pipeline"
|
|
note: string | null;
|
|
} | null;
|
|
/** The last review MOV package the shot was built into. */
|
|
review: { version: string; at: Date; label: string } | null;
|
|
/** Something newer than `sent` that has not gone out yet. */
|
|
pending: { version: string | null; label: string; tone: PendingTone } | null;
|
|
};
|
|
|
|
const EXPORT_STAGE: Record<string, { label: string; tone: PendingTone }> = {
|
|
QUEUED: { label: "queued to render", tone: "progress" },
|
|
RENDERING: { label: "rendering", tone: "progress" },
|
|
RENDER_FAILED: { label: "render failed", tone: "failed" },
|
|
VALIDATING: { label: "validating", tone: "progress" },
|
|
VALIDATION_FAILED: { label: "failed validation", tone: "failed" },
|
|
GENERATING_PREVIEW: { label: "building preview", tone: "progress" },
|
|
PREVIEW_FAILED: { label: "preview failed", tone: "failed" },
|
|
READY_FOR_QC: { label: "in QC", tone: "waiting" },
|
|
QC_FAILED: { label: "failed QC", tone: "failed" },
|
|
READY_FOR_DELIVERY: { label: "ready to package", tone: "waiting" },
|
|
};
|
|
|
|
export function summariseShotDelivery(input: {
|
|
packages: ShotDeliveryPackage[];
|
|
manual: { deliveredAt: Date | null; sharedWith: string | null; note: string | null };
|
|
/** The shot's newest export, if any. */
|
|
latestExport: { versionString: string; status: string } | null;
|
|
}): ShotDeliverySummary {
|
|
const { packages, manual, latestExport } = input;
|
|
const live = packages.filter((p) => p.status !== "CANCELLED");
|
|
const exr = live.filter((p) => p.kind === "EXR_DELIVERY");
|
|
|
|
let sent: ShotDeliverySummary["sent"] = null;
|
|
for (const p of exr) {
|
|
if (p.status !== "DELIVERED" || !p.deliveredAt) continue;
|
|
if (!sent || p.deliveredAt > sent.at) {
|
|
sent = {
|
|
via: "PACKAGE",
|
|
version: p.versionString,
|
|
at: p.deliveredAt,
|
|
to: p.sharedWith,
|
|
label: `Package #${p.packageNumber}`,
|
|
note: null,
|
|
};
|
|
}
|
|
}
|
|
if (manual.deliveredAt && (!sent || manual.deliveredAt > sent.at)) {
|
|
sent = {
|
|
via: "MANUAL",
|
|
version: null,
|
|
at: manual.deliveredAt,
|
|
to: manual.sharedWith,
|
|
label: "Outside pipeline",
|
|
note: manual.note,
|
|
};
|
|
}
|
|
|
|
let review: ShotDeliverySummary["review"] = null;
|
|
for (const p of live) {
|
|
if (p.kind !== "MOV_REVIEW" || p.status !== "READY") continue;
|
|
const at = p.builtAt ?? p.createdAt;
|
|
if (!review || at > review.at) {
|
|
review = { version: p.versionString, at, label: `Review #${p.packageNumber}` };
|
|
}
|
|
}
|
|
|
|
return { sent, review, pending: findPending(exr, sent, latestExport) };
|
|
}
|
|
|
|
function findPending(
|
|
exr: ShotDeliveryPackage[],
|
|
sent: ShotDeliverySummary["sent"],
|
|
latestExport: { versionString: string; status: string } | null
|
|
): ShotDeliverySummary["pending"] {
|
|
// The newest EXR package started after the last send and not delivered.
|
|
const open = exr
|
|
.filter((p) => p.status !== "DELIVERED" && (!sent || p.createdAt > sent.at))
|
|
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())[0];
|
|
if (open) {
|
|
const pkg = `#${open.packageNumber}`;
|
|
if (open.status === "READY") {
|
|
return open.uploadedAt
|
|
? { version: open.versionString, label: `uploaded in ${pkg}, link not shared`, tone: "waiting" }
|
|
: { version: open.versionString, label: `built in ${pkg}, not uploaded`, tone: "waiting" };
|
|
}
|
|
if (open.status === "FAILED") {
|
|
return { version: open.versionString, label: `package ${pkg} failed`, tone: "failed" };
|
|
}
|
|
return { version: open.versionString, label: `package ${pkg} building`, tone: "progress" };
|
|
}
|
|
|
|
// Otherwise the newest export, if it is a version that has not gone out.
|
|
if (!latestExport) return null;
|
|
if (sent?.version === latestExport.versionString) return null;
|
|
const stage = EXPORT_STAGE[latestExport.status];
|
|
return stage ? { version: latestExport.versionString, ...stage } : null;
|
|
}
|