feat(campaign): complete durable operator queue

This commit is contained in:
2026-07-22 09:31:33 +02:00
parent f095a3e2c7
commit 99d44eeb8d
5 changed files with 833 additions and 82 deletions

View File

@@ -1,5 +1,6 @@
export type OperatorQueueAction =
| "details"
| "report"
| "pause"
| "resume"
| "retry"
@@ -9,6 +10,7 @@ export type OperatorQueueAction =
export type OperatorQueuePermissions = {
canRead: boolean;
canOpenReport: boolean;
canControl: boolean;
canRetry: boolean;
canQueue: boolean;
@@ -19,7 +21,7 @@ export type OperatorQueueFacts = {
retryable: number;
outcomeUnknown: number;
queueableUnattempted: number;
queued: number;
pausable: number;
paused: number;
cancellable: number;
};
@@ -34,6 +36,40 @@ export const OPERATOR_QUEUE_REASON = {
noCancellable: "i18n:govoplan-campaign.no_cancellable_queued_work_remains.80e31d49"
} as const;
export type CampaignVersionWorkItem<T> = {
campaignId: string;
versionId: string;
versionNumber: number;
isCurrent: boolean;
summary: T | null;
};
/** Preserve every audit version so work does not disappear when a new draft becomes current. */
export function campaignVersionWorkItems<T>(
campaignId: string,
currentVersionId: string | null | undefined,
versions: Array<{id: string;version_number: number;}>,
summaries: Record<string, T | null>
): CampaignVersionWorkItem<T>[] {
return versions.map((version) => ({
campaignId,
versionId: version.id,
versionNumber: version.version_number,
isCurrent: version.id === currentVersionId,
summary: summaries[version.id] ?? null
}));
}
export type CampaignLifecycleTotals = {pausable: number;paused: number;cancellable: number;};
export function campaignLifecycleTotals(items: CampaignLifecycleTotals[]): CampaignLifecycleTotals {
return items.reduce((totals, item) => ({
pausable: totals.pausable + item.pausable,
paused: totals.paused + item.paused,
cancellable: totals.cancellable + item.cancellable
}), { pausable: 0, paused: 0, cancellable: 0 });
}
/**
* Return the reason each fixed queue action is unavailable. A null result means
* the action is permitted and meaningful for the current queue projection.
@@ -44,9 +80,10 @@ export function operatorQueueActionBlocks(
): Record<OperatorQueueAction, string | null> {
return {
details: permissions.canRead ? null : OPERATOR_QUEUE_REASON.permissionDenied,
report: permissions.canOpenReport ? null : OPERATOR_QUEUE_REASON.permissionDenied,
pause: !permissions.canControl
? OPERATOR_QUEUE_REASON.permissionDenied
: facts.queued > 0
: facts.pausable > 0
? null
: OPERATOR_QUEUE_REASON.noQueued,
resume: !permissions.canControl