feat(campaign): add durable operator queue controls

This commit is contained in:
2026-07-22 08:39:44 +02:00
parent 62a68792a4
commit 21f3014ac5
10 changed files with 420 additions and 29 deletions

View File

@@ -0,0 +1,71 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
OPERATOR_QUEUE_REASON,
operatorQueueActionBlocks,
type OperatorQueueFacts,
type OperatorQueuePermissions
} from "../src/features/operator/operatorQueueModel.ts";
const everything: OperatorQueueFacts = {
retryable: 2,
outcomeUnknown: 1,
queueableUnattempted: 3,
queued: 4,
paused: 5,
cancellable: 14
};
const allPermissions: OperatorQueuePermissions = {
canRead: true,
canControl: true,
canRetry: true,
canQueue: true,
canReconcile: true
};
test("enables every fixed queue action when state and permission allow it", () => {
assert.deepEqual(operatorQueueActionBlocks(everything, allPermissions), {
details: null,
pause: null,
resume: null,
retry: null,
"queue-unsent": null,
reconcile: null,
cancel: null
});
});
test("retains every unavailable action with a state-specific explanation", () => {
assert.deepEqual(
operatorQueueActionBlocks(
{ retryable: 0, outcomeUnknown: 0, queueableUnattempted: 0, queued: 0, paused: 0, cancellable: 0 },
allPermissions
),
{
details: null,
pause: OPERATOR_QUEUE_REASON.noQueued,
resume: OPERATOR_QUEUE_REASON.noPaused,
retry: OPERATOR_QUEUE_REASON.noFailed,
"queue-unsent": OPERATOR_QUEUE_REASON.noUnsent,
reconcile: OPERATOR_QUEUE_REASON.noUnknown,
cancel: OPERATOR_QUEUE_REASON.noCancellable
}
);
});
test("permission explanations take precedence over queue state", () => {
const noPermissions: OperatorQueuePermissions = {
canRead: false,
canControl: false,
canRetry: false,
canQueue: false,
canReconcile: false
};
const blocks = operatorQueueActionBlocks(everything, noPermissions);
assert.deepEqual(
Object.values(blocks),
Array.from({ length: 7 }, () => OPERATOR_QUEUE_REASON.permission)
);
});

View File

@@ -0,0 +1,18 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const here = dirname(fileURLToPath(import.meta.url));
const source = readFileSync(resolve(here, "../src/features/operator/OperatorQueuePage.tsx"), "utf8");
assert.match(source, /<DataGrid/);
assert.match(source, /<TableActionGroup actions=/);
for (const action of ["details", "pause", "resume", "retry", "queue-unsent", "reconcile", "cancel"]) {
assert.match(source, new RegExp(`id: "${action}"`));
}
assert.match(source, /useDeltaWatermarks/);
assert.match(source, /window\.setInterval[\s\S]*10_000/);
assert.match(source, /<ConfirmDialog[\s\S]*cancelTarget/);
assert.doesNotMatch(source, /claim_token|eml_local_path|storage_key|smtp_transport_revision/);
assert.doesNotMatch(source, /window\.alert\s*\(/);