feat(render-pipeline): sync task title to preview version, fill slate ShotName
finalizePreview now retitles the export's task so its version suffix matches the preview that was just uploaded (UNG_102_019_010_cmp_TT_v004 -> _v005), which is the rename the manual batch upload already does on rename-and-upload. The new nextTaskTitle helper mirrors nextExrOutputBase so the task title, EXR base and preview filename all derive the suffix the same way. It uses the export's versionString rather than versionNumber, since the DB number is bumped to stay monotonic per task while the filename follows the shot's shotVersion. The rename only fires when a Version was actually created, runs in the same transaction, and is recorded in the ExportEvent note; the finalize response now returns the task id, title and whether it changed. Task status is deliberately untouched (pipeline renders must not resurface in the review flow, S10.0). The headless preview build was addressing the slate's identity fields by index only, so ShotName was never filled on the farm. setSlateIdentity writes both the named property and the index -- the name-with-index-fallback pattern the panel already uses for VersionName -- and warns with the template's actual property names when neither resolves, instead of failing silently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -230,6 +230,26 @@
|
||||
return names;
|
||||
}
|
||||
|
||||
// Slate identity fields (version name, shot name). Current templates expose
|
||||
// these under a stable name, older ones only positionally, and the two do
|
||||
// not always line up — so write both. Setting the same property twice is
|
||||
// harmless; the panel uses the same name-with-index-fallback pattern.
|
||||
function setSlateIdentity(layer, propName, index, value, label) {
|
||||
var byName;
|
||||
var byIndex;
|
||||
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return; // leave the template's own default
|
||||
}
|
||||
byName = setEssentialProperty(layer, propName, value);
|
||||
byIndex = setEssentialPropertyByIndex(layer, index, value);
|
||||
if (!byName && !byIndex) {
|
||||
warn(label + ": neither Essential Property \"" + propName + "\" nor index " +
|
||||
index + " could be set. Available: " +
|
||||
listEssentialPropertyNames(layer).join(", "));
|
||||
}
|
||||
}
|
||||
|
||||
// Per-submission fields (VFX Scope / Submission Note). The property names
|
||||
// come from config; if one does not exist, report the template's actual
|
||||
// property names so it can be corrected without another build.
|
||||
@@ -439,12 +459,15 @@
|
||||
|
||||
var slateLayer = previewComp.layer("NETFLIX_SLATE");
|
||||
if (slateLayer) {
|
||||
// Index-addressed properties match the panel exactly (3 = version
|
||||
// name, 10 = shot code); the rest are addressed by name.
|
||||
setEssentialPropertyByIndex(slateLayer, 3, ctx.slate.versionName);
|
||||
// Identity fields match the panel's indices (3 = version name,
|
||||
// 10 = shot name) and are also written by name; the rest are
|
||||
// addressed by name only.
|
||||
// VersionName carries the full "{shotCode}_cmp_TT_{version}";
|
||||
// ShotName is the bare shot code, with no version suffix.
|
||||
setSlateIdentity(slateLayer, "VersionName", 3, ctx.slate.versionName, "Version Name");
|
||||
setSlateIdentity(slateLayer, "ShotName", 10, ctx.slate.shotCode, "Shot Name");
|
||||
setEssentialProperty(slateLayer, "Date", ctx.slate.date);
|
||||
setEssentialProperty(slateLayer, "Desc", ctx.slate.description);
|
||||
setEssentialPropertyByIndex(slateLayer, 10, ctx.slate.shotCode);
|
||||
setEssentialProperty(slateLayer, "Episode", ctx.slate.episode);
|
||||
setEssentialProperty(slateLayer, "Scene", ctx.slate.scene);
|
||||
setEssentialProperty(slateLayer, "Frames", result.frameCount);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { nextTaskTitle } from "./preview";
|
||||
|
||||
describe("nextTaskTitle", () => {
|
||||
it("swaps an existing version suffix", () => {
|
||||
expect(nextTaskTitle("UNG_102_019_010_cmp_TT_v004", "v005")).toBe(
|
||||
"UNG_102_019_010_cmp_TT_v005"
|
||||
);
|
||||
});
|
||||
|
||||
it("is idempotent when the title already matches", () => {
|
||||
expect(nextTaskTitle("UNG_102_019_010_cmp_TT_v005", "v005")).toBe(
|
||||
"UNG_102_019_010_cmp_TT_v005"
|
||||
);
|
||||
});
|
||||
|
||||
it("appends when the title carries no version yet", () => {
|
||||
expect(nextTaskTitle("UNG_102_019_010_cmp_TT", "v005")).toBe(
|
||||
"UNG_102_019_010_cmp_TT_v005"
|
||||
);
|
||||
});
|
||||
|
||||
it("only touches a trailing suffix, not a version inside the name", () => {
|
||||
expect(nextTaskTitle("UNG_v001_019_010_cmp_TT_v004", "v005")).toBe(
|
||||
"UNG_v001_019_010_cmp_TT_v005"
|
||||
);
|
||||
});
|
||||
|
||||
it("handles double-digit and uppercase suffixes", () => {
|
||||
expect(nextTaskTitle("SHOT_cmp_TT_V12", "v013")).toBe("SHOT_cmp_TT_v013");
|
||||
});
|
||||
});
|
||||
@@ -129,6 +129,16 @@ export async function createPreviewJob(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* `UNG_102_019_010_cmp_TT_v004` + v005 → `UNG_102_019_010_cmp_TT_v005`.
|
||||
* Mirrors `nextExrOutputBase` (exports.ts) so the task title, the EXR base and
|
||||
* the preview filename all carry the same version suffix.
|
||||
*/
|
||||
export function nextTaskTitle(current: string, versionString: string): string {
|
||||
if (/_v\d+$/i.test(current)) return current.replace(/_v\d+$/i, `_${versionString}`);
|
||||
return `${current}_${versionString}`;
|
||||
}
|
||||
|
||||
export interface FinalizeInput {
|
||||
artifacts?: {
|
||||
deliveryMovPath?: string;
|
||||
@@ -167,6 +177,7 @@ export async function finalizePreview(jobId: string, machineId: string, body: Fi
|
||||
return {
|
||||
export: { id: exportRow.id, status: exportRow.status },
|
||||
version: { id: exportRow.versionId },
|
||||
task: null,
|
||||
alreadyApplied: true,
|
||||
};
|
||||
}
|
||||
@@ -227,6 +238,29 @@ export async function finalizePreview(jobId: string, machineId: string, body: Fi
|
||||
versionId = version.id;
|
||||
}
|
||||
|
||||
// The task title carries the version it is showing (house convention:
|
||||
// "UNG_102_019_010_cmp_TT_v004"). The preview just uploaded *is* the task's
|
||||
// new latest version, so the title has to follow the file — the same rename
|
||||
// the manual batch upload does on "rename-and-upload".
|
||||
let taskTitle: string | null = null;
|
||||
let taskRenamed = false;
|
||||
if (versionId && exportRow.taskId) {
|
||||
const task = await tx.task.findUnique({
|
||||
where: { id: exportRow.taskId },
|
||||
select: { title: true },
|
||||
});
|
||||
if (task) {
|
||||
taskTitle = nextTaskTitle(task.title, exportRow.versionString);
|
||||
taskRenamed = taskTitle !== task.title;
|
||||
if (taskRenamed) {
|
||||
await tx.task.update({
|
||||
where: { id: exportRow.taskId },
|
||||
data: { title: taskTitle },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updated = await applyTransition(
|
||||
tx,
|
||||
exportRow,
|
||||
@@ -234,7 +268,10 @@ export async function finalizePreview(jobId: string, machineId: string, body: Fi
|
||||
{
|
||||
type: "WORKER",
|
||||
id: machineId,
|
||||
note: `Preview complete in ${body.renderStats?.previewSeconds ?? "?"}s${versionId ? " — review version created" : " — no preview media reported"}`,
|
||||
note:
|
||||
`Preview complete in ${body.renderStats?.previewSeconds ?? "?"}s` +
|
||||
(versionId ? " — review version created" : " — no preview media reported") +
|
||||
(taskRenamed ? ` — task renamed to ${taskTitle}` : ""),
|
||||
},
|
||||
{
|
||||
...(versionId ? { versionId } : {}),
|
||||
@@ -248,6 +285,7 @@ export async function finalizePreview(jobId: string, machineId: string, body: Fi
|
||||
return {
|
||||
export: { id: updated.id, status: updated.status },
|
||||
version: versionId ? { id: versionId } : null,
|
||||
task: exportRow.taskId ? { id: exportRow.taskId, title: taskTitle, renamed: taskRenamed } : null,
|
||||
alreadyApplied: false,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user