144 lines
4.7 KiB
TypeScript
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
|
|
};
|
|
}
|