Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 64 additions & 10 deletions frontend/src/lib/components/workstation/FolderStudioView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,28 @@
}
}

async function retryEncode() {
const action = 'retry-encode';
const actionState = workflowActionState(action);
if (actionState.disabled) return;
workflowPending = action;
profileMessage = '';
profileError = '';
try {
const response = await postJson<{ ok: boolean; message?: string }>(
`${resolve('/')}api/encode-queue/retry-prefix`,
{ prefix }
);
profileMessage = response.message || 'Queued retry for this folder.';
await invalidateAll();
} catch (error) {
profileError =
error instanceof Error ? error.message : 'Folder processing could not be retried.';
} finally {
workflowPending = null;
}
}

async function stopSample() {
const action = 'stop-sample';
const actionState = workflowActionState(action);
Expand Down Expand Up @@ -360,16 +382,6 @@
{/each}
</div>
<div class="decision__actions">
{#if reviewPackReady && workflow.primaryAction !== 'download-review-pack' && workflow.secondaryAction !== 'download-review-pack'}
<form
class="action-form"
method="get"
action={`${resolve('/')}api/folders/${encodedPrefix}/review-compare/download`}
data-mf-action="download-review-pack"
>
<button class="control" type="submit">Download side-by-side video</button>
</form>
{/if}
{#if workflow.primaryAction === 'focus-bench'}
<button
class="control control--primary"
Expand Down Expand Up @@ -414,6 +426,17 @@
data-mf-wire="live"
>{workflowPending === workflow.primaryAction ? 'Approving' : workflow.primary}</button
>
{:else if workflow.primaryAction === 'retry-encode'}
<button
class="control control--primary"
type="button"
disabled={workflowActionState(workflow.primaryAction).disabled}
title={workflowActionState(workflow.primaryAction).title}
onclick={retryEncode}
data-mf-action={workflow.primaryAction}
data-mf-wire="live"
>{workflowPending === workflow.primaryAction ? 'Retrying' : workflow.primary}</button
>
{:else}
<button
class="control control--primary"
Expand Down Expand Up @@ -483,6 +506,19 @@
? 'Approving'
: workflow.secondary}</button
>
{:else if workflow.secondaryAction === 'retry-encode'}
<button
class="control"
type="button"
disabled={workflowActionState(workflow.secondaryAction).disabled}
title={workflowActionState(workflow.secondaryAction).title}
onclick={retryEncode}
data-mf-action={workflow.secondaryAction}
data-mf-wire="live"
>{workflowPending === workflow.secondaryAction
? 'Retrying'
: workflow.secondary}</button
>
{:else}
<button
class="control"
Expand All @@ -493,6 +529,16 @@
data-mf-wire="pending">{workflow.secondary}</button
>
{/if}
{#if reviewPackReady && workflow.primaryAction !== 'download-review-pack' && workflow.secondaryAction !== 'download-review-pack'}
<form
class="action-form"
method="get"
action={`${resolve('/')}api/folders/${encodedPrefix}/review-compare/download`}
data-mf-action="download-review-pack"
>
<button class="control" type="submit">Download side-by-side video</button>
</form>
{/if}
{#if profileError}
<p class="decision__status decision__status--fail">{profileError}</p>
{:else if profileMessage}
Expand Down Expand Up @@ -810,12 +856,17 @@
padding: var(--mf-space-6);
}

.studio__main > :global(*) {
order: 5;
}

.folder-header {
align-items: start;
border-bottom: var(--mf-border);
display: grid;
gap: var(--mf-space-6);
grid-template-columns: minmax(0, 1fr);
order: 2;
padding-bottom: var(--mf-space-5);
}

Expand Down Expand Up @@ -896,6 +947,7 @@
border: var(--mf-border);
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
order: 3;
}

.workflow-strip__step {
Expand Down Expand Up @@ -972,6 +1024,7 @@
display: grid;
gap: var(--mf-space-6);
grid-template-columns: minmax(24rem, 1.25fr) minmax(14rem, 0.75fr);
order: 1;
padding: var(--mf-space-6);
}

Expand Down Expand Up @@ -1048,6 +1101,7 @@
border-left: 3px solid var(--mf-ready-fg);
display: grid;
gap: var(--mf-space-5);
order: 4;
padding: var(--mf-space-5);
}

Expand Down
36 changes: 36 additions & 0 deletions frontend/src/lib/components/workstation/folder-studio-view.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,42 @@ describe('Folder Studio review request mapping', () => {
});
});

it('makes retry processing the primary action for stopped folder jobs', () => {
const workflow = resolveWorkflow(
folderPayload(),
folderStatusPayload(),
null,
null,
null,
null,
{
status: 'stopped',
attempt_summary: 'Processing was stopped while pending.'
} as never
);

expect(workflow).toMatchObject({
label: 'Processing stopped',
title: 'Retry the stopped folder job',
copy: 'Processing was stopped while pending.',
primary: 'Retry processing',
primaryAction: 'retry-encode',
secondary: 'Open Ops',
secondaryAction: 'open-ops'
});
expect(
resolveWorkflowActionState('retry-encode', {
reviewPackReady: false,
pendingProposal: null,
calibrationJob: null
})
).toEqual({ disabled: false, title: '' });
expect(buildWorkflowSteps(workflow).find((step) => step.label === 'Process')).toMatchObject({
current: true,
detail: 'Retry the stopped folder job'
});
});

it('names measured budget enforcement as the next sample action', () => {
const pendingProposal = {
proposal_id: 'draft-3',
Expand Down
20 changes: 12 additions & 8 deletions frontend/src/lib/components/workstation/folder-studio-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ export function resolveWorkflowActionState(
}
if (action === 'revise-proposal') return { disabled: false, title: '' };
if (action === 'open-ops') return { disabled: false, title: '' };
if (action === 'retry-encode') return { disabled: false, title: '' };
if (action === 'stop-sample') {
if (activeCalibrationStatus(calibrationJob?.status)) return { disabled: false, title: '' };
return { disabled: true, title: 'No sample job is running.' };
Expand Down Expand Up @@ -1010,18 +1011,21 @@ export function resolveWorkflow(
): WorkflowState {
const encodeStatus = String(encodeJob?.status ?? '').toLowerCase();
if (['failed', 'needs_attention', 'stopped'].includes(encodeStatus)) {
const label = encodeStatus === 'failed' ? 'Processing failed' : 'Processing stopped';
const title =
encodeStatus === 'failed' ? 'Retry the failed folder job' : 'Retry the stopped folder job';
return {
tone: 'wait',
label: 'Retry available',
title: 'Processing needs recovery',
label,
title,
copy:
encodeJob?.error ??
encodeJob?.attempt_summary ??
'The approved folder needs review before it runs again. Retry from Ops when the folder is still safe to process.',
primary: 'Open Ops',
primaryAction: 'open-ops',
secondary: 'Retry',
secondaryAction: 'retry-encode'
'The folder already has approved settings. Retry processing when it is safe to run the approved work again.',
primary: 'Retry processing',
primaryAction: 'retry-encode',
secondary: 'Open Ops',
secondaryAction: 'open-ops'
};
}
if (['running', 'queued', 'retry_backoff'].includes(encodeStatus)) {
Expand Down Expand Up @@ -1238,7 +1242,7 @@ export function buildWorkflowSteps(workflow: WorkflowState): WorkflowStep[] {
['queue-encode', 'approve-size-tradeoff'].includes(activeAction) || activeLabel === 'approved';
const encodeCurrent =
['open-ops', 'retry-encode'].includes(activeAction) ||
['processing', 'retry available'].includes(activeLabel);
['processing', 'processing failed', 'processing stopped'].includes(activeLabel);
return [
{
label: 'Sample',
Expand Down
2 changes: 1 addition & 1 deletion scripts/seed-web-smoke-fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ def seed(config_path: Path, *, profile: str = "default") -> dict[str, Any]:
{
"label": "Folder Studio retryable processing fixture",
"route": "/folders/tv/Failed%20Encode/Season%201",
"marker": "Processing needs recovery",
"marker": "Retry processing",
},
],
"completedPrefix": COMPLETED_PREFIX,
Expand Down