Files
govoplan-campaign/webui/src/features/operator/operatorQueueModel.ts

144 lines
4.7 KiB
TypeScript

export type OperatorQueueAction =
| "details"
| "report"
| "pause"
| "resume"
| "retry"
| "queue-unsent"
| "reconcile"
| "cancel";
export type OperatorQueuePermissions = {
canRead: boolean;
canOpenReport: boolean;
canControl: boolean;
canRetry: boolean;
canQueue: boolean;
canReconcile: boolean;
};
export type OperatorQueueFacts = {
retryable: number;
outcomeUnknown: number;
queueableUnattempted: number;
pausable: number;
paused: number;
cancellable: number;
};
export const OPERATOR_QUEUE_REASON = {
permissionDenied: "i18n:govoplan-campaign.you_do_not_have_permission_for_this_action.dfcfbad6",
noQueued: "i18n:govoplan-campaign.no_queued_jobs_can_be_paused.acaa0b4e",
noPaused: "i18n:govoplan-campaign.no_paused_jobs_can_be_resumed.869b6275",
noFailed: "i18n:govoplan-campaign.no_failed_jobs_are_eligible_for_retry.e65a69f9",
noUnsent: "i18n:govoplan-campaign.no_unsent_jobs_are_available_to_queue.585bfe87",
noUnknown: "i18n:govoplan-campaign.no_uncertain_outcomes_require_reconciliation.933bc53c",
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 });
}
export function operatorDeliveryStateCounts(
queue: {queued?: number;},
// send.queued intentionally does not contribute: paused queue rows retain that send state.
send: {queued?: number;claimed?: number;sending?: number;smtpAccepted?: number;sent?: number;}
) {
const queued = queue.queued ?? 0;
const claimed = send.claimed ?? 0;
const sending = send.sending ?? 0;
const completed = (send.smtpAccepted ?? 0) + (send.sent ?? 0);
return { queued, claimed, sending, completed, queuedOrActive: queued + claimed + sending };
}
export function shouldRefreshOperatorVersionSummary(input: {
fullDiscovery: boolean;
versionChanged: boolean;
selected: boolean;
cached: boolean;
queuedOrActive: number;
needsAttention: number;
}): boolean {
return input.fullDiscovery
|| input.versionChanged
|| !input.cached
|| input.selected
|| input.queuedOrActive > 0
|| input.needsAttention > 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.
*/
export function operatorQueueActionBlocks(
facts: OperatorQueueFacts,
permissions: OperatorQueuePermissions
): 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.pausable > 0
? null
: OPERATOR_QUEUE_REASON.noQueued,
resume: !permissions.canControl
? OPERATOR_QUEUE_REASON.permissionDenied
: facts.paused > 0
? null
: OPERATOR_QUEUE_REASON.noPaused,
retry: !permissions.canRetry
? OPERATOR_QUEUE_REASON.permissionDenied
: facts.retryable > 0
? null
: OPERATOR_QUEUE_REASON.noFailed,
"queue-unsent": !permissions.canQueue
? OPERATOR_QUEUE_REASON.permissionDenied
: facts.queueableUnattempted > 0
? null
: OPERATOR_QUEUE_REASON.noUnsent,
reconcile: !permissions.canReconcile
? OPERATOR_QUEUE_REASON.permissionDenied
: facts.outcomeUnknown > 0
? null
: OPERATOR_QUEUE_REASON.noUnknown,
cancel: !permissions.canControl
? OPERATOR_QUEUE_REASON.permissionDenied
: facts.cancellable > 0
? null
: OPERATOR_QUEUE_REASON.noCancellable
};
}