fix(webui): localize Campaign delivery reporting

This commit is contained in:
2026-07-22 09:04:42 +02:00
parent b0282ebff2
commit 79b576b4bc
11 changed files with 380 additions and 94 deletions

View File

@@ -41,7 +41,7 @@ const SEND_STATUS_OPTIONS: DataGridListOption[] = [
"failed_temporary",
"failed_permanent",
"cancelled"].
map((value) => ({ value, label: humanize(value) }));
map((value) => ({ value, label: deliveryStatusLabel(value) ?? humanize(value) }));
const IMAP_STATUS_OPTIONS: DataGridListOption[] = [
"not_requested",
@@ -51,7 +51,7 @@ const IMAP_STATUS_OPTIONS: DataGridListOption[] = [
"outcome_unknown",
"failed",
"skipped"].
map((value) => ({ value, label: humanize(value) }));
map((value) => ({ value, label: deliveryStatusLabel(value) ?? humanize(value) }));
const VALIDATION_STATUS_OPTIONS: DataGridListOption[] = [
"ready",
@@ -318,8 +318,8 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
{ id: "subject", header: "i18n:govoplan-campaign.subject.8d183dbd", width: "minmax(260px, 1fr)", resizable: true, sortable: true, filterable: true, value: (row) => String(row.subject ?? "—") },
{ id: "validation", header: "i18n:govoplan-campaign.validation.dd74d182", width: 145, sortable: true, filterable: true, columnType: "from-list", list: { options: VALIDATION_STATUS_OPTIONS, display: "pill" }, render: (row) => <StatusBadge status={String(row.validation_status ?? "unknown")} />, value: (row) => String(row.validation_status ?? "unknown") },
{ id: "queue", header: "i18n:govoplan-campaign.queue.d325fcd9", width: 130, sortable: true, filterable: true, columnType: "from-list", list: { options: QUEUE_STATUS_OPTIONS, display: "pill" }, render: (row) => <StatusBadge status={String(row.queue_status ?? "unknown")} />, value: (row) => String(row.queue_status ?? "unknown") },
{ id: "send", header: "i18n:govoplan-campaign.smtp.efff9cca", width: 160, sortable: true, filterable: true, columnType: "from-list", list: { options: SEND_STATUS_OPTIONS, display: "pill" }, render: (row) => <StatusBadge status={String(row.send_status ?? "unknown")} />, value: (row) => String(row.send_status ?? "unknown") },
{ id: "imap", header: "i18n:govoplan-campaign.imap.271f9ef2", width: 130, sortable: true, filterable: true, columnType: "from-list", list: { options: IMAP_STATUS_OPTIONS, display: "pill" }, render: (row) => <StatusBadge status={String(row.imap_status ?? "unknown")} />, value: (row) => String(row.imap_status ?? "unknown") },
{ id: "send", header: "i18n:govoplan-campaign.smtp.efff9cca", width: 160, sortable: true, filterable: true, columnType: "from-list", list: { options: SEND_STATUS_OPTIONS, display: "pill" }, render: (row) => <StatusBadge status={String(row.send_status ?? "unknown")} label={deliveryStatusLabel(String(row.send_status ?? "unknown"))} />, value: (row) => String(row.send_status ?? "unknown") },
{ id: "imap", header: "i18n:govoplan-campaign.imap.271f9ef2", width: 130, sortable: true, filterable: true, columnType: "from-list", list: { options: IMAP_STATUS_OPTIONS, display: "pill" }, render: (row) => <StatusBadge status={String(row.imap_status ?? "unknown")} label={deliveryStatusLabel(String(row.imap_status ?? "unknown"))} />, value: (row) => String(row.imap_status ?? "unknown") },
{ id: "attempts", header: "i18n:govoplan-campaign.attempts.5a29585e", width: 105, align: "right", sortable: true, filterType: "integer", value: (row) => Number(row.attempt_count ?? 0) },
{
id: "evidence",
@@ -388,7 +388,7 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
<div><dt>i18n:govoplan-campaign.failed.09fef5d8</dt><dd>{cards?.failed ?? 0}</dd></div>
<div><dt>i18n:govoplan-campaign.outcome_unknown.6e929fca</dt><dd>{cards?.outcome_unknown ?? 0}</dd></div>
<div><dt>i18n:govoplan-campaign.not_attempted.e1be3c69</dt><dd>{cards?.not_attempted ?? 0}</dd></div>
<div><dt>SMTP skipped (excluded)</dt><dd>{cards?.skipped ?? jobs.counts.send?.skipped ?? 0}</dd></div>
<div><dt>i18n:govoplan-campaign.smtp_skipped_excluded_.df6eca19</dt><dd>{cards?.skipped ?? jobs.counts.send?.skipped ?? 0}</dd></div>
<div><dt>i18n:govoplan-campaign.cancelled.a1bf92ef</dt><dd>{cards?.cancelled ?? 0}</dd></div>
</dl>
</Card>
@@ -396,7 +396,7 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
<dl className="detail-list">
<div><dt>i18n:govoplan-campaign.imap_appended.56017ea3</dt><dd>{cards?.imap_appended ?? 0}</dd></div>
<div><dt>i18n:govoplan-campaign.imap_failed.50dbca55</dt><dd>{cards?.imap_failed ?? 0}</dd></div>
<div><dt>IMAP skipped</dt><dd>{cards?.imap_skipped ?? jobs.counts.imap?.skipped ?? 0}</dd></div>
<div><dt>i18n:govoplan-campaign.imap_skipped.5a97b542</dt><dd>{cards?.imap_skipped ?? jobs.counts.imap?.skipped ?? 0}</dd></div>
<div><dt>i18n:govoplan-campaign.append_policy.f195cb05</dt><dd>{imapPolicy.enabled === true ? i18nMessage("i18n:govoplan-campaign.enabled_value.e395e48f", { value0: String(imapPolicy.folder ?? "i18n:govoplan-campaign.auto.0d612c12") }) : "i18n:govoplan-campaign.disabled.f4f4473d"}</dd></div>
<div><dt>i18n:govoplan-campaign.rate_limit.d08e55f5</dt><dd>{rateLimit.messages_per_minute ? i18nMessage("i18n:govoplan-campaign.value_minute.aeb1a9ea", { value0: String(rateLimit.messages_per_minute) }) : "—"}</dd></div>
<div><dt>i18n:govoplan-campaign.minimum_remaining_duration.639b792c</dt><dd>{String(delivery.estimated_remaining_send_human ?? "—")}</dd></div>
@@ -417,7 +417,7 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
<Card title="i18n:govoplan-campaign.recipient_delivery_jobs.52492608">
<p className="muted small-note">
Excluded rows are intentionally omitted from delivery. Their SMTP and IMAP states are shown as Skipped because no transport effect is attempted; use the separate status filters to isolate them.
i18n:govoplan-campaign.excluded_rows_are_intentionally_omitted_from_del.421a1f00
</p>
<div className="page-heading split">
<div className="button-row compact-actions">
@@ -487,8 +487,8 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
<div><dt>i18n:govoplan-campaign.recipient.90343260</dt><dd>{String(detail.job.recipient_email ?? "—")}</dd></div>
<div><dt>i18n:govoplan-campaign.subject.8d183dbd</dt><dd>{String(detail.job.subject ?? "—")}</dd></div>
<div><dt>i18n:govoplan-campaign.message_id.465056ba</dt><dd>{String(detail.job.message_id_header ?? "—")}</dd></div>
<div><dt>i18n:govoplan-campaign.smtp_state.ff372566</dt><dd><StatusBadge status={String(detail.job.send_status ?? "unknown")} /></dd></div>
<div><dt>i18n:govoplan-campaign.imap_state.03b83be0</dt><dd><StatusBadge status={String(detail.job.imap_status ?? "unknown")} /></dd></div>
<div><dt>i18n:govoplan-campaign.smtp_state.ff372566</dt><dd><StatusBadge status={String(detail.job.send_status ?? "unknown")} label={deliveryStatusLabel(String(detail.job.send_status ?? "unknown"))} /></dd></div>
<div><dt>i18n:govoplan-campaign.imap_state.03b83be0</dt><dd><StatusBadge status={String(detail.job.imap_status ?? "unknown")} label={deliveryStatusLabel(String(detail.job.imap_status ?? "unknown"))} /></dd></div>
<div><dt>i18n:govoplan-campaign.attachments.6771ade6</dt><dd>{String(detail.job.matched_file_count ?? detail.job.attachment_count ?? 0)}</dd></div>
</dl>
<AttemptHistoryTable kind="smtp" rows={detail.attempts.smtp ?? []} />
@@ -556,6 +556,10 @@ function initialReportGridFilters(): Record<string, string | string[]> {
return result;
}
function deliveryStatusLabel(status: string): string | undefined {
return status === "skipped" ? "i18n:govoplan-campaign.skipped.5a000ad7" : undefined;
}
function initialReportQuery(): string {
if (typeof window === "undefined") return "";
return new URLSearchParams(window.location.search).get("q")?.trim() ?? "";

View File

@@ -70,6 +70,7 @@ import {
isVersionReadyForDelivery,
stringifyPreview } from
"./utils/campaignView";
import { deliveryModeLabel } from "./utils/deliveryMode";
import { getBool, getText } from "./utils/draftEditor";
import { attachmentPreviewLinkableFiles, attachmentPreviewMatchedFiles } from "./utils/attachmentPreview";
import { emptyCampaignJobsResponse, mergeCampaignJobsDelta } from "./utils/jobDeltas";
@@ -863,7 +864,7 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings
async function runQueueForWorkers() {
if (!version || busy || !canQueueForWorkers || !workerQueueAvailable || readOnlyVersion || !readyForDelivery || !hasBuild || !mockGateSatisfied || deliveryQueued || deliveryStarted || synchronousEligibleCount <= 0) return;
setBusy("queue");
setMessage("Committing the reviewed execution to the background-worker queue…");
setMessage("i18n:govoplan-campaign.committing_the_reviewed_execution_to_the_backgro.6ae349e2");
setQueueResult(null);
setError("");
try {
@@ -875,7 +876,10 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings
});
const result = asRecord(response);
setQueueResult(result);
setMessage(`Queued ${String(result.queued_count ?? 0)} message(s); ${String(result.enqueued_count ?? 0)} worker task(s) published.`);
setMessage(i18nMessage("i18n:govoplan-campaign.queued_value0_message_s_value1_worker_task_s_pub.18d67ec8", {
value0: String(result.queued_count ?? 0),
value1: String(result.enqueued_count ?? 0)
}));
setQueueConfirmOpen(false);
await reload();
await refreshDeliveryOptions(true);
@@ -890,7 +894,7 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings
async function runDeliveryControl(action: "pause" | "resume" | "cancel") {
if (busy || !canControlDelivery) return;
setBusy("control");
setMessage(`${action === "pause" ? "Pausing" : action === "resume" ? "Resuming" : "Cancelling"} eligible delivery jobs…`);
setMessage(deliveryControlProgressMessage(action));
setError("");
try {
const response = action === "pause" ?
@@ -900,9 +904,18 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings
await cancelCampaign(settings, campaignId);
const result = asRecord(response.result ?? response);
setMessage(
action === "pause" ? `Paused ${String(result.paused_count ?? 0)} queued message(s).` :
action === "resume" ? `Resumed ${String(result.resumed_count ?? 0)} message(s); ${String(result.enqueued_count ?? 0)} worker task(s) published.` :
`Cancelled ${String(result.cancelled_count ?? 0)} unsent message(s); ${String(result.protected_count ?? 0)} protected outcome(s) and ${String(result.skipped_count ?? 0)} excluded/skipped message(s) were retained.`
action === "pause" ? i18nMessage("i18n:govoplan-campaign.paused_value0_queued_message_s_.c7d568d2", {
value0: String(result.paused_count ?? 0)
}) :
action === "resume" ? i18nMessage("i18n:govoplan-campaign.resumed_value0_message_s_value1_worker_task_s_pu.0f59afb4", {
value0: String(result.resumed_count ?? 0),
value1: String(result.enqueued_count ?? 0)
}) :
i18nMessage("i18n:govoplan-campaign.cancelled_value0_unsent_message_s_value1_protect.d24c5e24", {
value0: String(result.cancelled_count ?? 0),
value1: String(result.protected_count ?? 0),
value2: String(result.skipped_count ?? 0)
})
);
if (action === "cancel") setCancelDeliveryConfirmOpen(false);
await reload();
@@ -918,12 +931,15 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings
async function runRetryFailed() {
if (busy || !canRetryDelivery || retryableCount <= 0 || !workerQueueAvailable) return;
setBusy("retry");
setMessage("Queueing retryable failed messages for background workers…");
setMessage("i18n:govoplan-campaign.queueing_retryable_failed_messages_for_backgroun.4bc80cd9");
setError("");
try {
const response = await retryCampaignJobs(settings, campaignId, { enqueue_celery: true });
const result = asRecord(response.result ?? response);
setMessage(`Queued ${String(result.selected_count ?? 0)} retryable message(s); ${String(result.enqueued_count ?? 0)} worker task(s) published.`);
setMessage(i18nMessage("i18n:govoplan-campaign.queued_value0_retryable_message_s_value1_worker_.f4795bdb", {
value0: String(result.selected_count ?? 0),
value1: String(result.enqueued_count ?? 0)
}));
await reload();
await refreshDeliveryOptions(true);
} catch (err) {
@@ -1192,14 +1208,24 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings
state: messagesPerMinute > 0 ? "ready" : "warning"
},
{
label: "Delivery mode",
label: "i18n:govoplan-campaign.delivery_mode.109ed9d1",
detail: deliveryOptionsLoading ?
"Loading the effective delivery policy" :
"i18n:govoplan-campaign.loading_the_effective_delivery_policy_.d6893011" :
synchronousSendAllowed ?
`Send now is available for ${synchronousEligibleCount} eligible message(s), within the effective limit of ${synchronousSendLimit}. ${workerQueueAvailable ? "The worker queue is also available." : "Background workers are not configured."}` :
i18nMessage("i18n:govoplan-campaign.send_now_is_available_for_value0_eligible_messag.6ec0ed6c", {
value0: synchronousEligibleCount,
value1: synchronousSendLimit,
value2: workerQueueAvailable
? "i18n:govoplan-campaign.the_worker_queue_is_also_available_.7b77144e"
: "i18n:govoplan-campaign.background_workers_are_not_configured_.efa72396"
}) :
workerQueueAvailable ?
`Send now is unavailable (${synchronousSendReason(synchronousSendOption)}). Queue for workers remains available.` :
`No real delivery mode is currently available (${synchronousSendReason(synchronousSendOption)}).`,
i18nMessage("i18n:govoplan-campaign.send_now_is_unavailable_value0_queue_for_workers.59bfc873", {
value0: synchronousSendReason(synchronousSendOption)
}) :
i18nMessage("i18n:govoplan-campaign.no_real_delivery_mode_is_currently_available_val.618cce1f", {
value0: synchronousSendReason(synchronousSendOption)
}),
state: synchronousSendAllowed || workerQueueAvailable ? "ready" : deliveryOptionsLoading ? "info" : "blocked"
},
{
@@ -1443,9 +1469,9 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings
<div><span>i18n:govoplan-campaign.rate_limit.d08e55f5</span><strong>{messagesPerMinute > 0 ? i18nMessage("i18n:govoplan-campaign.value_min.c9d89eae", { value0: messagesPerMinute }) : "i18n:govoplan-campaign.not_set.93039e60"}</strong></div>
<div><span>i18n:govoplan-campaign.minimum_duration.91a71a6d</span><strong>{estimatedMinutes ? i18nMessage("i18n:govoplan-campaign.about_value_min.7c2e77fc", { value0: estimatedMinutes }) : "—"}</strong></div>
<div><span>i18n:govoplan-campaign.imap_append.8c0d9e96</span><strong>{Boolean(imapAppend.enabled) ? "i18n:govoplan-campaign.enabled.df174a3f" : "i18n:govoplan-campaign.disabled.f4f4473d"}</strong></div>
<div><span>Synchronous limit</span><strong>{deliveryOptionsLoading ? "…" : synchronousSendLimit}</strong></div>
<div><span>Limit source</span><strong>{humanize(String(synchronousSendPolicy.source ?? "not available"))}</strong></div>
<div><span>Worker queue</span><strong>{workerQueueAvailable ? "Available" : "Not configured"}</strong></div>
<div><span>i18n:govoplan-campaign.synchronous_limit.f88c0bcd</span><strong>{deliveryOptionsLoading ? "…" : synchronousSendLimit}</strong></div>
<div><span>i18n:govoplan-campaign.limit_source.bd933adb</span><strong>{deliveryPolicySourceLabel(String(synchronousSendPolicy.source ?? ""))}</strong></div>
<div><span>i18n:govoplan-campaign.worker_queue.c911e32c</span><strong>{workerQueueAvailable ? "i18n:govoplan-campaign.available.7c62a142" : "i18n:govoplan-campaign.not_configured.811931bb"}</strong></div>
<div><span>i18n:govoplan-campaign.version.2da600bf</span><strong>{version ? `v${version.version_number}` : "—"}</strong></div>
</div>
<DeliverabilityPreflight items={deliverabilityPreflightItems} />
@@ -1472,7 +1498,7 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings
variant="primary"
onClick={() => setQueueConfirmOpen(true)}
disabled={!version || Boolean(busy) || !canQueueForWorkers || !workerQueueAvailable || readOnlyVersion || !readyForDelivery || !hasBuild || !mockGateSatisfied || deliveryQueued || deliveryStarted || synchronousEligibleCount <= 0}>
{busy === "queue" ? "Queueing for workers…" : "Queue for workers"}
{busy === "queue" ? "i18n:govoplan-campaign.queueing_for_workers_.d24584fe" : "i18n:govoplan-campaign.queue_for_workers.dae9a3e4"}
</Button>
<Button
onClick={() => selectedDryRun ? void runSendNow() : setSendConfirmOpen(true)}
@@ -1488,13 +1514,24 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings
</Button>
</div>
<p className="muted small-note">
Queue for workers commits durable jobs and returns immediately; progress remains visible here after you leave. Send now keeps this request open and is limited to {synchronousSendLimit || "the configured maximum"} eligible recipient jobs.
{i18nMessage("i18n:govoplan-campaign.queue_for_workers_commits_durable_jobs_and_retur.d0dbe81a", {
value0: synchronousSendLimit || "i18n:govoplan-campaign.the_configured_maximum.eb10006b"
})}
</p>
{!synchronousSendAllowed && canSendSynchronously &&
<p className="review-flow-inline-note is-warning">Send now is unavailable: {synchronousSendReason(synchronousSendOption)}.</p>
<p className="review-flow-inline-note is-warning">
{i18nMessage("i18n:govoplan-campaign.send_now_is_unavailable_value0_.d93c0b29", {
value0: synchronousSendReason(synchronousSendOption)
})}
</p>
}
{queueResult &&
<p className="review-flow-inline-note is-stale">Worker queue committed {String(queueResult.queued_count ?? 0)} message(s) and published {String(queueResult.enqueued_count ?? 0)} task(s). Refreshing progress does not requeue them.</p>
<p className="review-flow-inline-note is-stale">
{i18nMessage("i18n:govoplan-campaign.worker_queue_committed_value0_message_s_and_publ.24400d3c", {
value0: String(queueResult.queued_count ?? 0),
value1: String(queueResult.enqueued_count ?? 0)
})}
</p>
}
{sendResult &&
<div className="review-flow-data-section">
@@ -1518,8 +1555,8 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings
<WorkflowFact label="i18n:govoplan-campaign.smtp_accepted.e3aa7603" value={sentCount} />
<WorkflowFact label="i18n:govoplan-campaign.smtp_failed.0ce5516d" value={failedCount} />
<WorkflowFact label="i18n:govoplan-campaign.queued_active.a2784a4a" value={queuedOrActiveCount} />
<WorkflowFact label="Paused" value={pausedQueueCount} />
<WorkflowFact label="Delivery mode" value={persistedDeliveryMode ? humanize(persistedDeliveryMode) : "—"} />
<WorkflowFact label="i18n:govoplan-campaign.paused.c7dfb6f1" value={pausedQueueCount} />
<WorkflowFact label="i18n:govoplan-campaign.delivery_mode.109ed9d1" value={deliveryModeLabel(persistedDeliveryMode)} />
<WorkflowFact label="i18n:govoplan-campaign.outcome_unknown.6e929fca" value={outcomeUnknownCount} />
<WorkflowFact label="i18n:govoplan-campaign.imap_appended.56017ea3" value={imapAppended} />
<WorkflowFact label="i18n:govoplan-campaign.imap_pending.ed50375e" value={imapPendingForDisplay} />
@@ -1532,29 +1569,36 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings
</div>
{persistedDeliveryMode &&
<p className="muted small-note">
This version last entered {humanize(persistedDeliveryMode)} mode{persistedDeliveryModeSelectedAt ? ` at ${formatDateTime(persistedDeliveryModeSelectedAt)}` : ""}. This record remains available after leaving and returning.
{persistedDeliveryModeSelectedAt
? i18nMessage("i18n:govoplan-campaign.this_version_last_entered_value0_mode_at_value1_.c087d2f3", {
value0: deliveryModeLabel(persistedDeliveryMode),
value1: formatDateTime(persistedDeliveryModeSelectedAt)
})
: i18nMessage("i18n:govoplan-campaign.this_version_last_entered_value0_mode_this_recor.ea2f201f", {
value0: deliveryModeLabel(persistedDeliveryMode)
})}
</p>
}
<div className="button-row compact-actions review-flow-stage-actions">
<Button
onClick={() => void runDeliveryControl("pause")}
disabled={Boolean(busy) || !canControlDelivery || queuedSendCount <= 0}>
Pause queued work
i18n:govoplan-campaign.pause_queued_work.35ab4a5b
</Button>
<Button
onClick={() => void runDeliveryControl("resume")}
disabled={Boolean(busy) || !canControlDelivery || pausedQueueCount <= 0 || !workerQueueAvailable}>
Resume with workers
i18n:govoplan-campaign.resume_with_workers.510a2a9a
</Button>
<Button
onClick={() => setCancelDeliveryConfirmOpen(true)}
disabled={Boolean(busy) || !canControlDelivery || queuedSendCount + pausedQueueCount <= 0}>
Cancel unsent work
i18n:govoplan-campaign.cancel_unsent_work.66df9f0d
</Button>
<Button
onClick={() => void runRetryFailed()}
disabled={Boolean(busy) || !canRetryDelivery || retryableCount <= 0 || !workerQueueAvailable}>
Retry failed with workers
i18n:govoplan-campaign.retry_failed_with_workers.a4b7dcc5
</Button>
</div>
{Boolean(imapAppend.enabled) && imapPendingForDisplay > 0 &&
@@ -1664,9 +1708,12 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings
<ConfirmDialog
open={queueConfirmOpen}
title="Queue this version for background workers?"
message={`This commits ${synchronousEligibleCount} eligible message(s) from version ${String(version?.version_number ?? "—")} to durable worker jobs. You may leave this page and return to the same progress and recovery state.`}
confirmLabel="Queue for workers"
title="i18n:govoplan-campaign.queue_this_version_for_background_workers_.943d032c"
message={i18nMessage("i18n:govoplan-campaign.this_commits_value0_eligible_message_s_from_vers.f53e7222", {
value0: synchronousEligibleCount,
value1: String(version?.version_number ?? "—")
})}
confirmLabel="i18n:govoplan-campaign.queue_for_workers.dae9a3e4"
cancelLabel="i18n:govoplan-campaign.cancel.77dfd213"
busy={busy === "queue"}
onCancel={() => setQueueConfirmOpen(false)}
@@ -1675,7 +1722,11 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings
<ConfirmDialog
open={sendConfirmOpen}
title="i18n:govoplan-campaign.send_this_version_now.10a0ca56"
message={`${i18nMessage("i18n:govoplan-campaign.this_sends_the_frozen_execution_snapshot_for_ver.1f7c53cb", { value0: String(version?.version_number ?? "—"), value1: jobsTotal, value2: builtCount, value3: buildBlocked, value4: String(attachmentSummary.total_matched_files ?? 0), value5: String(rateLimit.messages_per_minute ?? "i18n:govoplan-campaign.not_set.ef374c57"), value6: imapAppend.enabled === true ? "enabled" : "disabled", value7: version?.execution_snapshot_hash ? i18nMessage("i18n:govoplan-campaign.value.382bcd25", { value0: version.execution_snapshot_hash.slice(0, 12) }) : "missing" })} This is a synchronous request for ${synchronousEligibleCount} eligible message(s), within the effective limit of ${synchronousSendLimit}. All messages are preflighted before SMTP is contacted.`}
message={i18nMessage("i18n:govoplan-campaign.value0_this_is_a_synchronous_request_for_value1_.0267bc6b", {
value0: i18nMessage("i18n:govoplan-campaign.this_sends_the_frozen_execution_snapshot_for_ver.1f7c53cb", { value0: String(version?.version_number ?? "—"), value1: jobsTotal, value2: builtCount, value3: buildBlocked, value4: String(attachmentSummary.total_matched_files ?? 0), value5: String(rateLimit.messages_per_minute ?? "i18n:govoplan-campaign.not_set.ef374c57"), value6: imapAppend.enabled === true ? "enabled" : "disabled", value7: version?.execution_snapshot_hash ? i18nMessage("i18n:govoplan-campaign.value.382bcd25", { value0: version.execution_snapshot_hash.slice(0, 12) }) : "missing" }),
value1: synchronousEligibleCount,
value2: synchronousSendLimit
})}
confirmLabel={directQueuedSendAllowed ? "i18n:govoplan-campaign.send_queued_now.bbace803" : "i18n:govoplan-campaign.send_now.dae33010"}
tone="danger"
busy={busy === "send"}
@@ -1684,10 +1735,10 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings
<ConfirmDialog
open={cancelDeliveryConfirmOpen}
title="Cancel all unsent delivery work?"
message={`This cancels queued or paused messages that have not crossed the SMTP boundary. Accepted, active, or outcome-unknown messages remain protected for audit and reconciliation.`}
confirmLabel="Cancel unsent work"
cancelLabel="Keep delivery work"
title="i18n:govoplan-campaign.cancel_all_unsent_delivery_work_.a2f56cda"
message="i18n:govoplan-campaign.this_cancels_queued_or_paused_messages_that_have.add583bc"
confirmLabel="i18n:govoplan-campaign.cancel_unsent_work.66df9f0d"
cancelLabel="i18n:govoplan-campaign.keep_delivery_work.10dbcb13"
tone="danger"
busy={busy === "control"}
onCancel={() => setCancelDeliveryConfirmOpen(false)}
@@ -2559,14 +2610,28 @@ function synchronousSendReason(option: Record<string, unknown>): string {
const configuredMessage = String(option.message ?? "").trim();
if (configuredMessage) return configuredMessage;
switch (String(option.reason ?? "")) {
case "recipient_limit_exceeded":return "the exact built run exceeds the effective recipient-job limit";
case "no_eligible_recipient_jobs":return "the built run has no eligible message";
case "version_not_ready":return "validate, lock, build, and review the current version first";
case "policy_configuration_invalid":return "the delivery policy configuration is invalid";
default:return "delivery options are still being evaluated";
case "recipient_limit_exceeded":return "i18n:govoplan-campaign.the_exact_built_run_exceeds_the_effective_limit_.d7812d6a";
case "no_eligible_recipient_jobs":return "i18n:govoplan-campaign.the_built_run_has_no_eligible_message.48e5410c";
case "version_not_ready":return "i18n:govoplan-campaign.validate_lock_build_and_review_the_current_versi.d0567dcc";
case "policy_configuration_invalid":return "i18n:govoplan-campaign.the_delivery_policy_configuration_is_invalid.a804a8f8";
default:return "i18n:govoplan-campaign.delivery_options_are_still_being_evaluated.8395096e";
}
}
function deliveryControlProgressMessage(action: "pause" | "resume" | "cancel"): string {
if (action === "pause") return "i18n:govoplan-campaign.pausing_eligible_delivery_jobs_.eb6b9d58";
if (action === "resume") return "i18n:govoplan-campaign.resuming_eligible_delivery_jobs_.dd12c5a2";
return "i18n:govoplan-campaign.cancelling_eligible_delivery_jobs_.4090fa47";
}
function deliveryPolicySourceLabel(source: string): string {
if (source === "tenant") return "i18n:govoplan-campaign.tenant.3ca93c78";
if (source === "deployment") return "i18n:govoplan-campaign.deployment.327a55f8";
if (source === "deployment_default") return "i18n:govoplan-campaign.deployment_default.aa39f7b4";
if (source === "deployment_ceiling") return "i18n:govoplan-campaign.deployment_ceiling.abbec75b";
return "i18n:govoplan-campaign.not_available.d1a17af1";
}
function numberFrom(record: Record<string, unknown>, keys: string[]): number {
for (const key of keys) {
const value = record[key];

View File

@@ -0,0 +1,6 @@
export function deliveryModeLabel(mode: string | null | undefined): string {
if (mode === "synchronous") return "i18n:govoplan-campaign.synchronous.77c61919";
if (mode === "worker_queue") return "i18n:govoplan-campaign.worker_queue.c911e32c";
if (mode === "database_queue") return "i18n:govoplan-campaign.database_queue.8bf98437";
return mode ? "i18n:govoplan-campaign.unknown.bc7819b3" : "—";
}

View File

@@ -29,7 +29,8 @@ import {
useGuardedNavigate,
type AuthInfo
} from "@govoplan/core-webui";
import { asRecord, formatDateTime, humanize } from "../campaigns/utils/campaignView";
import { asRecord, formatDateTime } from "../campaigns/utils/campaignView";
import { deliveryModeLabel } from "../campaigns/utils/deliveryMode";
import {
operatorQueueActionBlocks,
type OperatorQueueAction,
@@ -237,7 +238,7 @@ export default function OperatorQueuePage({ settings, auth }: {settings: ApiSett
const columns = useMemo<DataGridColumn<OperatorRow>[]>(() => [
{ id: "campaign", header: "i18n:govoplan-campaign.campaign.69390e16", width: "minmax(260px, 1.2fr)", sticky: "start", sortable: true, filterable: true, value: (row) => row.campaign.name },
{ id: "status", header: "i18n:govoplan-campaign.status.bae7d5be", width: 145, sortable: true, filterable: true, render: (row) => <StatusBadge status={row.campaign.status} />, value: (row) => row.campaign.status },
{ id: "mode", header: "i18n:govoplan-campaign.mode.a7b93d21", width: 155, sortable: true, filterable: true, value: (row) => row.mode ? humanize(row.mode) : "—", sortValue: (row) => row.mode ?? "" },
{ id: "mode", header: "i18n:govoplan-campaign.mode.a7b93d21", width: 155, sortable: true, filterable: true, value: (row) => deliveryModeLabel(row.mode), sortValue: (row) => row.mode ?? "" },
{ id: "attention", header: "i18n:govoplan-campaign.attention.74e0b9c8", width: 120, align: "right", sortable: true, filterType: "integer", value: (row) => row.needsAttention },
{ id: "failed", header: "i18n:govoplan-campaign.failed.09fef5d8", width: 100, align: "right", sortable: true, filterType: "integer", value: (row) => row.failed },
{ id: "unknown", header: "i18n:govoplan-campaign.unknown.bc7819b3", width: 110, align: "right", sortable: true, filterType: "integer", value: (row) => row.outcomeUnknown },

View File

@@ -25,7 +25,7 @@ export type OperatorQueueFacts = {
};
export const OPERATOR_QUEUE_REASON = {
permission: "i18n:govoplan-campaign.you_do_not_have_permission_for_this_action.dfcfbad6",
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",
@@ -43,34 +43,34 @@ export function operatorQueueActionBlocks(
permissions: OperatorQueuePermissions
): Record<OperatorQueueAction, string | null> {
return {
details: permissions.canRead ? null : OPERATOR_QUEUE_REASON.permission,
details: permissions.canRead ? null : OPERATOR_QUEUE_REASON.permissionDenied,
pause: !permissions.canControl
? OPERATOR_QUEUE_REASON.permission
? OPERATOR_QUEUE_REASON.permissionDenied
: facts.queued > 0
? null
: OPERATOR_QUEUE_REASON.noQueued,
resume: !permissions.canControl
? OPERATOR_QUEUE_REASON.permission
? OPERATOR_QUEUE_REASON.permissionDenied
: facts.paused > 0
? null
: OPERATOR_QUEUE_REASON.noPaused,
retry: !permissions.canRetry
? OPERATOR_QUEUE_REASON.permission
? OPERATOR_QUEUE_REASON.permissionDenied
: facts.retryable > 0
? null
: OPERATOR_QUEUE_REASON.noFailed,
"queue-unsent": !permissions.canQueue
? OPERATOR_QUEUE_REASON.permission
? OPERATOR_QUEUE_REASON.permissionDenied
: facts.queueableUnattempted > 0
? null
: OPERATOR_QUEUE_REASON.noUnsent,
reconcile: !permissions.canReconcile
? OPERATOR_QUEUE_REASON.permission
? OPERATOR_QUEUE_REASON.permissionDenied
: facts.outcomeUnknown > 0
? null
: OPERATOR_QUEUE_REASON.noUnknown,
cancel: !permissions.canControl
? OPERATOR_QUEUE_REASON.permission
? OPERATOR_QUEUE_REASON.permissionDenied
: facts.cancellable > 0
? null
: OPERATOR_QUEUE_REASON.noCancellable

View File

@@ -12,6 +12,7 @@ import {
StatusBadge,
TableActionGroup,
formatDateTime,
i18nMessage,
type ApiSettings,
type DataGridColumn
} from "@govoplan/core-webui";
@@ -131,11 +132,11 @@ export default function AggregateReportsPage({ settings }: {settings: ApiSetting
render: (campaign) => (
<TableActionGroup actions={[{
id: "view-aggregate-report",
label: "View aggregate report",
label: "i18n:govoplan-campaign.view_aggregate_report.94b42124",
icon: <Eye aria-hidden="true" />,
variant: campaign.id === selectedFromUrl ? "primary" : "secondary",
disabled: campaign.id === selectedFromUrl,
disabledReason: campaign.id === selectedFromUrl ? "This report is already selected." : undefined,
disabledReason: campaign.id === selectedFromUrl ? "i18n:govoplan-campaign.this_report_is_already_selected_.983c5be4" : undefined,
onClick: () => selectCampaign(campaign.id)
}]} />
)
@@ -150,75 +151,75 @@ export default function AggregateReportsPage({ settings }: {settings: ApiSetting
<div className="page-heading split workspace-heading">
<div>
<PageTitle loading={listLoading || reportLoading}>i18n:govoplan-campaign.reports.88bc3fe3</PageTitle>
<p className="muted">Privacy-protected Campaign outcomes without recipient or delivery diagnostics.</p>
<p className="muted">i18n:govoplan-campaign.privacy_protected_campaign_outcomes_without_reci.5b8a100d</p>
</div>
<div className="button-row compact-actions">
<Button
onClick={() => void Promise.all([loadCampaigns(), selectedFromUrl ? loadReport(selectedFromUrl) : Promise.resolve()])}
disabled={listLoading || reportLoading}
>
<RefreshCw size={16} aria-hidden="true" /> Refresh
<RefreshCw size={16} aria-hidden="true" /> i18n:govoplan-campaign.refresh.56e3badc
</Button>
</div>
</div>
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
<Card title="Campaign reports available to you">
<LoadingFrame loading={listLoading} label="Loading Campaign reports">
<Card title="i18n:govoplan-campaign.campaign_reports_available_to_you.f14fa403">
<LoadingFrame loading={listLoading} label="i18n:govoplan-campaign.loading_campaign_reports_.61ec1ee8">
<DataGrid
id="campaign-aggregate-report-list"
rows={campaigns}
columns={columns}
getRowKey={(campaign) => campaign.id}
initialSort={{ columnId: "updated", direction: "desc" }}
emptyText="No aggregate Campaign reports are available to you."
emptyText="i18n:govoplan-campaign.no_aggregate_campaign_reports_are_available_to_y.dafb2781"
/>
</LoadingFrame>
</Card>
<LoadingFrame loading={reportLoading} label="Loading aggregate Campaign report">
<LoadingFrame loading={reportLoading} label="i18n:govoplan-campaign.loading_aggregate_campaign_report_.72f2f091">
{report && outcomes && (
<>
<Card title={report.campaign.name}>
<dl className="detail-list">
<div><dt>Status</dt><dd><StatusBadge status={report.campaign.status} /></dd></div>
<div><dt>Completion</dt><dd><StatusBadge status={report.completion_state} /></dd></div>
<div><dt>Version</dt><dd>{report.version_number ?? "—"}</dd></div>
<div><dt>Generated</dt><dd>{formatDateTime(report.generated_at)}</dd></div>
<div><dt>i18n:govoplan-campaign.status.bae7d5be</dt><dd><StatusBadge status={report.campaign.status} /></dd></div>
<div><dt>i18n:govoplan-campaign.completion.2ff25568</dt><dd><StatusBadge status={report.completion_state} label={aggregateCompletionLabel(report.completion_state)} /></dd></div>
<div><dt>i18n:govoplan-campaign.version.2da600bf</dt><dd>{report.version_number ?? "—"}</dd></div>
<div><dt>i18n:govoplan-campaign.generated.8eefdd52</dt><dd>{formatDateTime(report.generated_at)}</dd></div>
</dl>
</Card>
<div className="dashboard-grid">
<MetricCard label="SMTP accepted" value={countValue(outcomes.smtp_accepted)} tone="good" />
<MetricCard label="Failed" value={countValue(outcomes.failed)} tone="danger" />
<MetricCard label="Outcome unknown" value={countValue(outcomes.outcome_unknown)} tone="warning" />
<MetricCard label="Queued or active" value={countValue(outcomes.queued_or_active)} tone="info" />
<MetricCard label="Not attempted" value={countValue(outcomes.not_attempted)} />
<MetricCard label="Cancelled" value={countValue(outcomes.cancelled)} />
<MetricCard label="Excluded" value={countValue(outcomes.excluded)} />
<MetricCard label="i18n:govoplan-campaign.smtp_accepted.e3aa7603" value={countValue(outcomes.smtp_accepted)} tone="good" />
<MetricCard label="i18n:govoplan-campaign.failed.09fef5d8" value={countValue(outcomes.failed)} tone="danger" />
<MetricCard label="i18n:govoplan-campaign.outcome_unknown.6e929fca" value={countValue(outcomes.outcome_unknown)} tone="warning" />
<MetricCard label="i18n:govoplan-campaign.queued_or_active.afdcd7da" value={countValue(outcomes.queued_or_active)} tone="info" />
<MetricCard label="i18n:govoplan-campaign.not_attempted.e1be3c69" value={countValue(outcomes.not_attempted)} />
<MetricCard label="i18n:govoplan-campaign.cancelled.a1bf92ef" value={countValue(outcomes.cancelled)} />
<MetricCard label="i18n:govoplan-campaign.excluded.9804952b" value={countValue(outcomes.excluded)} />
</div>
<Card title="Population and exclusions">
<Card title="i18n:govoplan-campaign.population_and_exclusions.cdff62ad">
<dl className="detail-list">
<div><dt>Report denominator</dt><dd>{countValue(report.population.denominator)}</dd></div>
<div><dt>Inactive source entries</dt><dd>{countValue(report.population.inactive_source_entries)}</dd></div>
<div><dt>Excluded or blocked jobs</dt><dd>{countValue(report.population.excluded_or_blocked_jobs)}</dd></div>
<div><dt>i18n:govoplan-campaign.report_denominator.d089074a</dt><dd>{countValue(report.population.denominator)}</dd></div>
<div><dt>i18n:govoplan-campaign.inactive_source_entries.88fa0cf8</dt><dd>{countValue(report.population.inactive_source_entries)}</dd></div>
<div><dt>i18n:govoplan-campaign.excluded_or_blocked_jobs.e2190d47</dt><dd>{countValue(report.population.excluded_or_blocked_jobs)}</dd></div>
</dl>
<p className="muted">{report.population.denominator_definition}</p>
<p className="muted">i18n:govoplan-campaign.all_persisted_recipient_delivery_jobs_for_the_se.208e90cc</p>
</Card>
<Card title="Delivery activity range">
<Card title="i18n:govoplan-campaign.delivery_activity_range.0d1b62ec">
<dl className="detail-list">
<div><dt>First activity</dt><dd>{report.time_range.suppressed ? "Suppressed" : formatDateTime(report.time_range.first_activity_at ?? undefined)}</dd></div>
<div><dt>Last activity</dt><dd>{report.time_range.suppressed ? "Suppressed" : formatDateTime(report.time_range.last_activity_at ?? undefined)}</dd></div>
<div><dt>i18n:govoplan-campaign.first_activity.3fe36ef4</dt><dd>{report.time_range.suppressed ? "i18n:govoplan-campaign.suppressed.fce88db5" : formatDateTime(report.time_range.first_activity_at ?? undefined)}</dd></div>
<div><dt>i18n:govoplan-campaign.last_activity.1c73b806</dt><dd>{report.time_range.suppressed ? "i18n:govoplan-campaign.suppressed.fce88db5" : formatDateTime(report.time_range.last_activity_at ?? undefined)}</dd></div>
</dl>
</Card>
<Card title="Privacy boundary">
<p>{report.privacy.rule}</p>
<Card title="i18n:govoplan-campaign.privacy_boundary.d37e12bc">
<p>i18n:govoplan-campaign.positive_counts_below_the_threshold_are_hidden_a.1dc97093</p>
<p className="muted">
Small-cell threshold: {threshold}. Suppressed values cannot be opened, filtered, exported, or inspected from this view.
{i18nMessage("i18n:govoplan-campaign.small_cell_threshold_value0_suppressed_values_ca.d73f87a6", { value0: threshold })}
</p>
</Card>
</>
@@ -229,5 +230,18 @@ export default function AggregateReportsPage({ settings }: {settings: ApiSetting
}
function countValue(count: AggregateReportCount): string | number {
return count.suppressed ? "Suppressed" : count.value ?? "—";
return count.suppressed ? "i18n:govoplan-campaign.suppressed.fce88db5" : count.value ?? "—";
}
function aggregateCompletionLabel(state: AggregateCampaignReport["completion_state"]): string {
const labels: Record<AggregateCampaignReport["completion_state"], string> = {
not_started: "i18n:govoplan-campaign.not_started.db2c45d5",
in_progress: "i18n:govoplan-campaign.in_progress.b6bd42e4",
completed: "i18n:govoplan-campaign.completed.1798b3ba",
partially_completed: "i18n:govoplan-campaign.partially_completed.760bc5e6",
incomplete: "i18n:govoplan-campaign.incomplete.387fd1bb",
outcome_unknown: "i18n:govoplan-campaign.outcome_unknown.6e929fca",
suppressed: "i18n:govoplan-campaign.suppressed.fce88db5"
};
return labels[state];
}

View File

@@ -21,6 +21,82 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-campaign.reconcile_outcomes.bd81d8a7": "Reconcile outcomes",
"i18n:govoplan-campaign.resume_queue.1f22f964": "Resume queue",
"i18n:govoplan-campaign.you_do_not_have_permission_for_this_action.dfcfbad6": "You do not have permission for this action.",
"i18n:govoplan-campaign.campaign_reports_available_to_you.f14fa403": "Campaign reports available to you",
"i18n:govoplan-campaign.cancelled_value0_unsent_message_s_value1_protect.d24c5e24": "Cancelled {value0} unsent message(s); {value1} protected outcome(s) and {value2} excluded/skipped message(s) were retained.",
"i18n:govoplan-campaign.completion.2ff25568": "Completion",
"i18n:govoplan-campaign.database_queue.8bf98437": "Database queue",
"i18n:govoplan-campaign.delivery_activity_range.0d1b62ec": "Delivery activity range",
"i18n:govoplan-campaign.delivery_mode.109ed9d1": "Delivery mode",
"i18n:govoplan-campaign.excluded_or_blocked_jobs.e2190d47": "Excluded or blocked jobs",
"i18n:govoplan-campaign.excluded_rows_are_intentionally_omitted_from_del.421a1f00": "Excluded rows are intentionally omitted from delivery. Their SMTP and IMAP states are shown as Skipped because no transport effect is attempted; use the separate status filters to isolate them.",
"i18n:govoplan-campaign.first_activity.3fe36ef4": "First activity",
"i18n:govoplan-campaign.in_progress.b6bd42e4": "In progress",
"i18n:govoplan-campaign.inactive_source_entries.88fa0cf8": "Inactive source entries",
"i18n:govoplan-campaign.incomplete.387fd1bb": "Incomplete",
"i18n:govoplan-campaign.last_activity.1c73b806": "Last activity",
"i18n:govoplan-campaign.loading_aggregate_campaign_report_.72f2f091": "Loading aggregate Campaign report…",
"i18n:govoplan-campaign.loading_campaign_reports_.61ec1ee8": "Loading Campaign reports…",
"i18n:govoplan-campaign.no_aggregate_campaign_reports_are_available_to_y.dafb2781": "No aggregate Campaign reports are available to you.",
"i18n:govoplan-campaign.not_started.db2c45d5": "Not started",
"i18n:govoplan-campaign.partially_completed.760bc5e6": "Partially completed",
"i18n:govoplan-campaign.population_and_exclusions.cdff62ad": "Population and exclusions",
"i18n:govoplan-campaign.privacy_boundary.d37e12bc": "Privacy boundary",
"i18n:govoplan-campaign.privacy_protected_campaign_outcomes_without_reci.5b8a100d": "Privacy-protected Campaign outcomes without recipient or delivery diagnostics.",
"i18n:govoplan-campaign.queued_or_active.afdcd7da": "Queued or active",
"i18n:govoplan-campaign.report_denominator.d089074a": "Report denominator",
"i18n:govoplan-campaign.small_cell_threshold_value0_suppressed_values_ca.d73f87a6": "Small-cell threshold: {value0}. Suppressed values cannot be opened, filtered, exported, or inspected from this view.",
"i18n:govoplan-campaign.smtp_skipped_excluded_.df6eca19": "SMTP skipped (excluded)",
"i18n:govoplan-campaign.suppressed.fce88db5": "Suppressed",
"i18n:govoplan-campaign.synchronous.77c61919": "Synchronous",
"i18n:govoplan-campaign.this_report_is_already_selected_.983c5be4": "This report is already selected.",
"i18n:govoplan-campaign.this_version_last_entered_value0_mode_at_value1_.c087d2f3": "This version last entered {value0} mode at {value1}. This record remains available after leaving and returning.",
"i18n:govoplan-campaign.this_version_last_entered_value0_mode_this_recor.ea2f201f": "This version last entered {value0} mode. This record remains available after leaving and returning.",
"i18n:govoplan-campaign.view_aggregate_report.94b42124": "View aggregate report",
"i18n:govoplan-campaign.worker_queue.c911e32c": "Worker queue",
"i18n:govoplan-campaign.all_persisted_recipient_delivery_jobs_for_the_se.208e90cc": "All persisted recipient delivery jobs for the selected campaign version, including excluded or blocked jobs. Inactive source entries without a job record are excluded and reported separately.",
"i18n:govoplan-campaign.background_workers_are_not_configured_.efa72396": "Background workers are not configured.",
"i18n:govoplan-campaign.cancel_all_unsent_delivery_work_.a2f56cda": "Cancel all unsent delivery work?",
"i18n:govoplan-campaign.cancel_unsent_work.66df9f0d": "Cancel unsent work",
"i18n:govoplan-campaign.cancelling_eligible_delivery_jobs_.4090fa47": "Cancelling eligible delivery jobs…",
"i18n:govoplan-campaign.committing_the_reviewed_execution_to_the_backgro.6ae349e2": "Committing the reviewed execution to the background-worker queue…",
"i18n:govoplan-campaign.delivery_options_are_still_being_evaluated.8395096e": "delivery options are still being evaluated",
"i18n:govoplan-campaign.deployment.327a55f8": "Deployment",
"i18n:govoplan-campaign.deployment_ceiling.abbec75b": "Deployment ceiling",
"i18n:govoplan-campaign.deployment_default.aa39f7b4": "Deployment default",
"i18n:govoplan-campaign.keep_delivery_work.10dbcb13": "Keep delivery work",
"i18n:govoplan-campaign.limit_source.bd933adb": "Limit source",
"i18n:govoplan-campaign.loading_the_effective_delivery_policy_.d6893011": "Loading the effective delivery policy…",
"i18n:govoplan-campaign.no_real_delivery_mode_is_currently_available_val.618cce1f": "No real delivery mode is currently available ({value0}).",
"i18n:govoplan-campaign.not_available.d1a17af1": "Not available",
"i18n:govoplan-campaign.pause_queued_work.35ab4a5b": "Pause queued work",
"i18n:govoplan-campaign.paused_value0_queued_message_s_.c7d568d2": "Paused {value0} queued message(s).",
"i18n:govoplan-campaign.pausing_eligible_delivery_jobs_.eb6b9d58": "Pausing eligible delivery jobs…",
"i18n:govoplan-campaign.positive_counts_below_the_threshold_are_hidden_a.1dc97093": "Positive counts below the threshold are hidden. At least one additional count or the denominator is hidden when needed to prevent subtraction.",
"i18n:govoplan-campaign.queued_value0_message_s_value1_worker_task_s_pub.18d67ec8": "Queued {value0} message(s); {value1} worker task(s) published.",
"i18n:govoplan-campaign.queued_value0_retryable_message_s_value1_worker_.f4795bdb": "Queued {value0} retryable message(s); {value1} worker task(s) published.",
"i18n:govoplan-campaign.queue_for_workers.dae9a3e4": "Queue for workers",
"i18n:govoplan-campaign.queue_for_workers_commits_durable_jobs_and_retur.d0dbe81a": "Queue for workers commits durable jobs and returns immediately; progress remains visible here after you leave. Send now keeps this request open and is limited to {value0} eligible recipient jobs.",
"i18n:govoplan-campaign.queue_this_version_for_background_workers_.943d032c": "Queue this version for background workers?",
"i18n:govoplan-campaign.queueing_for_workers_.d24584fe": "Queueing for workers…",
"i18n:govoplan-campaign.queueing_retryable_failed_messages_for_backgroun.4bc80cd9": "Queueing retryable failed messages for background workers…",
"i18n:govoplan-campaign.resume_with_workers.510a2a9a": "Resume with workers",
"i18n:govoplan-campaign.resumed_value0_message_s_value1_worker_task_s_pu.0f59afb4": "Resumed {value0} message(s); {value1} worker task(s) published.",
"i18n:govoplan-campaign.resuming_eligible_delivery_jobs_.dd12c5a2": "Resuming eligible delivery jobs…",
"i18n:govoplan-campaign.retry_failed_with_workers.a4b7dcc5": "Retry failed with workers",
"i18n:govoplan-campaign.send_now_is_available_for_value0_eligible_messag.6ec0ed6c": "Send now is available for {value0} eligible message(s), within the effective limit of {value1}. {value2}",
"i18n:govoplan-campaign.send_now_is_unavailable_value0_.d93c0b29": "Send now is unavailable: {value0}.",
"i18n:govoplan-campaign.send_now_is_unavailable_value0_queue_for_workers.59bfc873": "Send now is unavailable ({value0}). Queue for workers remains available.",
"i18n:govoplan-campaign.synchronous_limit.f88c0bcd": "Synchronous limit",
"i18n:govoplan-campaign.the_built_run_has_no_eligible_message.48e5410c": "the built run has no eligible message",
"i18n:govoplan-campaign.the_configured_maximum.eb10006b": "the configured maximum",
"i18n:govoplan-campaign.the_delivery_policy_configuration_is_invalid.a804a8f8": "the delivery policy configuration is invalid",
"i18n:govoplan-campaign.the_exact_built_run_exceeds_the_effective_limit_.d7812d6a": "the exact built run exceeds the effective limit for recipient jobs",
"i18n:govoplan-campaign.the_worker_queue_is_also_available_.7b77144e": "The worker queue is also available.",
"i18n:govoplan-campaign.this_cancels_queued_or_paused_messages_that_have.add583bc": "This cancels queued or paused messages that have not crossed the SMTP boundary. Accepted, active, or outcome-unknown messages remain protected for audit and reconciliation.",
"i18n:govoplan-campaign.this_commits_value0_eligible_message_s_from_vers.f53e7222": "This commits {value0} eligible message(s) from version {value1} to durable worker jobs. You may leave this page and return to the same progress and recovery state.",
"i18n:govoplan-campaign.validate_lock_build_and_review_the_current_versi.d0567dcc": "validate, lock, build, and review the current version first",
"i18n:govoplan-campaign.value0_this_is_a_synchronous_request_for_value1_.0267bc6b": "{value0} This is a synchronous request for {value1} eligible message(s), within the effective limit of {value2}. All messages are preflighted before SMTP is contacted.",
"i18n:govoplan-campaign.worker_queue_committed_value0_message_s_and_publ.24400d3c": "Worker queue committed {value0} message(s) and published {value1} task(s). Refreshing progress does not requeue them.",
"i18n:govoplan-campaign.campaign_scoped_mail_profile.9cbf3505": "Campaign-scoped Mail profile",
"i18n:govoplan-campaign.campaign_stores_only_this_stable_profile_referen.de554809": "Campaign stores only this stable profile reference. The Mail module owns, encrypts, authorizes, tests, and resolves all SMTP/IMAP settings and credentials.",
"i18n:govoplan-campaign.configured.668c5fff": "Configured",
@@ -1210,6 +1286,82 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-campaign.reconcile_outcomes.bd81d8a7": "Ergebnisse abstimmen",
"i18n:govoplan-campaign.resume_queue.1f22f964": "Warteschlange fortsetzen",
"i18n:govoplan-campaign.you_do_not_have_permission_for_this_action.dfcfbad6": "Sie haben keine Berechtigung für diese Aktion.",
"i18n:govoplan-campaign.campaign_reports_available_to_you.f14fa403": "Für Sie verfügbare Kampagnenberichte",
"i18n:govoplan-campaign.cancelled_value0_unsent_message_s_value1_protect.d24c5e24": "{value0} ungesendete Nachricht(en) wurden abgebrochen; {value1} geschützte Ergebnisse und {value2} ausgeschlossene/übersprungene Nachricht(en) wurden beibehalten.",
"i18n:govoplan-campaign.completion.2ff25568": "Abschluss",
"i18n:govoplan-campaign.database_queue.8bf98437": "Datenbank-Warteschlange",
"i18n:govoplan-campaign.delivery_activity_range.0d1b62ec": "Zeitraum der Versandaktivität",
"i18n:govoplan-campaign.delivery_mode.109ed9d1": "Versandmodus",
"i18n:govoplan-campaign.excluded_or_blocked_jobs.e2190d47": "Ausgeschlossene oder blockierte Aufträge",
"i18n:govoplan-campaign.excluded_rows_are_intentionally_omitted_from_del.421a1f00": "Ausgeschlossene Zeilen werden absichtlich nicht versendet. Ihr SMTP- und IMAP-Status wird als „Übersprungen“ angezeigt, da kein Transport versucht wird; über die separaten Statusfilter können Sie diese Zeilen gezielt anzeigen.",
"i18n:govoplan-campaign.first_activity.3fe36ef4": "Erste Aktivität",
"i18n:govoplan-campaign.in_progress.b6bd42e4": "In Bearbeitung",
"i18n:govoplan-campaign.inactive_source_entries.88fa0cf8": "Inaktive Quelleinträge",
"i18n:govoplan-campaign.incomplete.387fd1bb": "Unvollständig",
"i18n:govoplan-campaign.last_activity.1c73b806": "Letzte Aktivität",
"i18n:govoplan-campaign.loading_aggregate_campaign_report_.72f2f091": "Aggregierter Kampagnenbericht wird geladen…",
"i18n:govoplan-campaign.loading_campaign_reports_.61ec1ee8": "Kampagnenberichte werden geladen…",
"i18n:govoplan-campaign.no_aggregate_campaign_reports_are_available_to_y.dafb2781": "Für Sie sind keine aggregierten Kampagnenberichte verfügbar.",
"i18n:govoplan-campaign.not_started.db2c45d5": "Nicht begonnen",
"i18n:govoplan-campaign.partially_completed.760bc5e6": "Teilweise abgeschlossen",
"i18n:govoplan-campaign.population_and_exclusions.cdff62ad": "Grundgesamtheit und Ausschlüsse",
"i18n:govoplan-campaign.privacy_boundary.d37e12bc": "Datenschutzgrenze",
"i18n:govoplan-campaign.privacy_protected_campaign_outcomes_without_reci.5b8a100d": "Datenschutzgeschützte Kampagnenergebnisse ohne Empfänger- oder Versanddiagnosen.",
"i18n:govoplan-campaign.queued_or_active.afdcd7da": "In Warteschlange oder aktiv",
"i18n:govoplan-campaign.report_denominator.d089074a": "Bezugsgröße des Berichts",
"i18n:govoplan-campaign.small_cell_threshold_value0_suppressed_values_ca.d73f87a6": "Schwellenwert für kleine Fallzahlen: {value0}. Unterdrückte Werte können in dieser Ansicht nicht geöffnet, gefiltert, exportiert oder geprüft werden.",
"i18n:govoplan-campaign.smtp_skipped_excluded_.df6eca19": "SMTP übersprungen (ausgeschlossen)",
"i18n:govoplan-campaign.suppressed.fce88db5": "Unterdrückt",
"i18n:govoplan-campaign.synchronous.77c61919": "Synchron",
"i18n:govoplan-campaign.this_report_is_already_selected_.983c5be4": "Dieser Bericht ist bereits ausgewählt.",
"i18n:govoplan-campaign.this_version_last_entered_value0_mode_at_value1_.c087d2f3": "Diese Version wechselte zuletzt am {value1} in den Modus „{value0}“. Der Eintrag bleibt auch nach dem Verlassen und erneuten Öffnen verfügbar.",
"i18n:govoplan-campaign.this_version_last_entered_value0_mode_this_recor.ea2f201f": "Diese Version wechselte zuletzt in den Modus „{value0}“. Der Eintrag bleibt auch nach dem Verlassen und erneuten Öffnen verfügbar.",
"i18n:govoplan-campaign.view_aggregate_report.94b42124": "Aggregierten Bericht anzeigen",
"i18n:govoplan-campaign.worker_queue.c911e32c": "Worker-Warteschlange",
"i18n:govoplan-campaign.all_persisted_recipient_delivery_jobs_for_the_se.208e90cc": "Alle dauerhaft gespeicherten Empfänger-Versandaufträge der ausgewählten Kampagnenversion werden berücksichtigt, einschließlich ausgeschlossener oder blockierter Aufträge. Inaktive Quelleinträge ohne Auftragsdatensatz sind ausgeschlossen und werden separat ausgewiesen.",
"i18n:govoplan-campaign.background_workers_are_not_configured_.efa72396": "Hintergrund-Worker sind nicht konfiguriert.",
"i18n:govoplan-campaign.cancel_all_unsent_delivery_work_.a2f56cda": "Alle ungesendeten Versandaufträge abbrechen?",
"i18n:govoplan-campaign.cancel_unsent_work.66df9f0d": "Ungesendete Aufträge abbrechen",
"i18n:govoplan-campaign.cancelling_eligible_delivery_jobs_.4090fa47": "Geeignete Versandaufträge werden abgebrochen…",
"i18n:govoplan-campaign.committing_the_reviewed_execution_to_the_backgro.6ae349e2": "Die geprüfte Ausführung wird an die Hintergrund-Worker-Warteschlange übergeben…",
"i18n:govoplan-campaign.delivery_options_are_still_being_evaluated.8395096e": "die Versandoptionen werden noch ausgewertet",
"i18n:govoplan-campaign.deployment.327a55f8": "Bereitstellung",
"i18n:govoplan-campaign.deployment_ceiling.abbec75b": "Bereitstellungsobergrenze",
"i18n:govoplan-campaign.deployment_default.aa39f7b4": "Bereitstellungsstandard",
"i18n:govoplan-campaign.keep_delivery_work.10dbcb13": "Versandaufträge beibehalten",
"i18n:govoplan-campaign.limit_source.bd933adb": "Quelle des Grenzwerts",
"i18n:govoplan-campaign.loading_the_effective_delivery_policy_.d6893011": "Wirksame Versandrichtlinie wird geladen…",
"i18n:govoplan-campaign.no_real_delivery_mode_is_currently_available_val.618cce1f": "Derzeit ist kein echter Versandmodus verfügbar ({value0}).",
"i18n:govoplan-campaign.not_available.d1a17af1": "Nicht verfügbar",
"i18n:govoplan-campaign.pause_queued_work.35ab4a5b": "Wartende Aufträge pausieren",
"i18n:govoplan-campaign.paused_value0_queued_message_s_.c7d568d2": "{value0} wartende Nachricht(en) pausiert.",
"i18n:govoplan-campaign.pausing_eligible_delivery_jobs_.eb6b9d58": "Geeignete Versandaufträge werden pausiert…",
"i18n:govoplan-campaign.positive_counts_below_the_threshold_are_hidden_a.1dc97093": "Positive Anzahlen unterhalb des Schwellenwerts werden ausgeblendet. Falls erforderlich, wird mindestens eine weitere Anzahl oder die Bezugsgröße ausgeblendet, damit keine Rückrechnung möglich ist.",
"i18n:govoplan-campaign.queued_value0_message_s_value1_worker_task_s_pub.18d67ec8": "{value0} Nachricht(en) wurden eingereiht; {value1} Worker-Aufgabe(n) wurden veröffentlicht.",
"i18n:govoplan-campaign.queued_value0_retryable_message_s_value1_worker_.f4795bdb": "{value0} wiederholbare Nachricht(en) wurden eingereiht; {value1} Worker-Aufgabe(n) wurden veröffentlicht.",
"i18n:govoplan-campaign.queue_for_workers.dae9a3e4": "Für Worker einreihen",
"i18n:govoplan-campaign.queue_for_workers_commits_durable_jobs_and_retur.d0dbe81a": "Durch das Einreihen für Worker werden dauerhafte Aufträge erstellt und die Aktion kehrt sofort zurück; der Fortschritt bleibt nach dem Verlassen sichtbar. „Jetzt senden“ hält diese Anfrage offen und ist auf {value0} geeignete Empfängeraufträge begrenzt.",
"i18n:govoplan-campaign.queue_this_version_for_background_workers_.943d032c": "Diese Version für Hintergrund-Worker einreihen?",
"i18n:govoplan-campaign.queueing_for_workers_.d24584fe": "Wird für Worker eingereiht…",
"i18n:govoplan-campaign.queueing_retryable_failed_messages_for_backgroun.4bc80cd9": "Wiederholbare fehlgeschlagene Nachrichten werden für Hintergrund-Worker eingereiht…",
"i18n:govoplan-campaign.resume_with_workers.510a2a9a": "Mit Workern fortsetzen",
"i18n:govoplan-campaign.resumed_value0_message_s_value1_worker_task_s_pu.0f59afb4": "{value0} Nachricht(en) wurden fortgesetzt; {value1} Worker-Aufgabe(n) wurden veröffentlicht.",
"i18n:govoplan-campaign.resuming_eligible_delivery_jobs_.dd12c5a2": "Geeignete Versandaufträge werden fortgesetzt…",
"i18n:govoplan-campaign.retry_failed_with_workers.a4b7dcc5": "Fehlgeschlagene Aufträge mit Workern wiederholen",
"i18n:govoplan-campaign.send_now_is_available_for_value0_eligible_messag.6ec0ed6c": "„Jetzt senden“ ist für {value0} geeignete Nachricht(en) innerhalb des wirksamen Grenzwerts von {value1} verfügbar. {value2}",
"i18n:govoplan-campaign.send_now_is_unavailable_value0_.d93c0b29": "„Jetzt senden“ ist nicht verfügbar: {value0}.",
"i18n:govoplan-campaign.send_now_is_unavailable_value0_queue_for_workers.59bfc873": "„Jetzt senden“ ist nicht verfügbar ({value0}). Die Worker-Warteschlange bleibt verfügbar.",
"i18n:govoplan-campaign.synchronous_limit.f88c0bcd": "Grenzwert für synchronen Versand",
"i18n:govoplan-campaign.the_built_run_has_no_eligible_message.48e5410c": "der erstellte Lauf enthält keine geeignete Nachricht",
"i18n:govoplan-campaign.the_configured_maximum.eb10006b": "das konfigurierte Maximum",
"i18n:govoplan-campaign.the_delivery_policy_configuration_is_invalid.a804a8f8": "die Konfiguration der Versandrichtlinie ist ungültig",
"i18n:govoplan-campaign.the_exact_built_run_exceeds_the_effective_limit_.d7812d6a": "der exakt erstellte Lauf überschreitet den wirksamen Grenzwert für Empfängeraufträge",
"i18n:govoplan-campaign.the_worker_queue_is_also_available_.7b77144e": "Die Worker-Warteschlange ist ebenfalls verfügbar.",
"i18n:govoplan-campaign.this_cancels_queued_or_paused_messages_that_have.add583bc": "Dadurch werden wartende oder pausierte Nachrichten abgebrochen, die die SMTP-Grenze noch nicht überschritten haben. Angenommene, aktive oder ungeklärte Nachrichten bleiben für Audit und Abstimmung geschützt.",
"i18n:govoplan-campaign.this_commits_value0_eligible_message_s_from_vers.f53e7222": "Dadurch werden {value0} geeignete Nachricht(en) aus Version {value1} als dauerhafte Worker-Aufträge eingereiht. Sie können diese Seite verlassen und später zum selben Fortschritts- und Wiederherstellungszustand zurückkehren.",
"i18n:govoplan-campaign.validate_lock_build_and_review_the_current_versi.d0567dcc": "validieren, sperren, erstellen und prüfen Sie zuerst die aktuelle Version",
"i18n:govoplan-campaign.value0_this_is_a_synchronous_request_for_value1_.0267bc6b": "{value0} Dies ist eine synchrone Anfrage für {value1} geeignete Nachricht(en) innerhalb des wirksamen Grenzwerts von {value2}. Alle Nachrichten werden vor dem SMTP-Kontakt vollständig vorgeprüft.",
"i18n:govoplan-campaign.worker_queue_committed_value0_message_s_and_publ.24400d3c": "Die Worker-Warteschlange hat {value0} Nachricht(en) übernommen und {value1} Aufgabe(n) veröffentlicht. Eine Fortschrittsaktualisierung reiht sie nicht erneut ein.",
"i18n:govoplan-campaign.campaign_scoped_mail_profile.9cbf3505": "Kampagnenspezifisches Mail-Profil",
"i18n:govoplan-campaign.campaign_stores_only_this_stable_profile_referen.de554809": "Campaign speichert nur diese stabile Profilreferenz. Das Mail-Modul verwaltet, verschlüsselt, autorisiert, testet und löst alle SMTP-/IMAP-Einstellungen und Zugangsdaten auf.",
"i18n:govoplan-campaign.configured.668c5fff": "Konfiguriert",

View File

@@ -1,8 +1,14 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
const api = readFileSync("src/api/campaigns.ts", "utf8");
const page = readFileSync("src/features/reports/AggregateReportsPage.tsx", "utf8");
const reportPage = readFileSync("src/features/campaigns/CampaignReportPage.tsx", "utf8");
const reviewPage = readFileSync("src/features/campaigns/ReviewSendPage.tsx", "utf8");
const operatorPage = readFileSync("src/features/operator/OperatorQueuePage.tsx", "utf8");
const deliveryMode = readFileSync("src/features/campaigns/utils/deliveryMode.ts", "utf8");
const translationCatalog = readFileSync("src/i18n/generatedTranslations.ts", "utf8");
assert(api.includes('"/api/v1/campaigns/aggregate-reports"'), "the report list uses the safe aggregate collection");
assert(api.includes("/api/v1/campaigns/aggregate-reports/${encodeURIComponent(campaignId)}"), "the selected report uses the safe aggregate projection");
@@ -17,5 +23,32 @@ assert(!page.includes("localStorage"), "the aggregate page does not persist repo
assert(!page.includes("sessionStorage"), "the aggregate page does not persist report data in session storage");
assert(page.includes("TableActionGroup"), "campaign selection uses the central icon-only table action group");
assert(page.includes("disabled: campaign.id === selectedFromUrl"), "the selected row action stays visible and disabled");
assert(!page.includes("{report.population.denominator_definition}"), "the known denominator contract uses a localized UI explanation");
assert(!page.includes("{report.privacy.rule}"), "the known privacy contract uses a localized UI explanation");
assert(page.includes("all_persisted_recipient_delivery_jobs_for_the_se.208e90cc"), "the denominator explanation is bilingual");
assert(page.includes("positive_counts_below_the_threshold_are_hidden_a.1dc97093"), "the suppression rule is bilingual");
const touchedSources = [page, reportPage, reviewPage, operatorPage, deliveryMode].join("\n");
const touchedKeys = new Set(touchedSources.match(/i18n:govoplan-campaign\.[a-z0-9_]+\.[a-f0-9]{8}/g) ?? []);
for (const key of touchedKeys) {
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const catalogEntries = [...translationCatalog.matchAll(new RegExp(`"${escapedKey}":\\s*("(?:[^"\\\\]|\\\\.)*")`, "g"))];
assert.equal(catalogEntries.length, 2, `${key} is present exactly once in both language catalogs`);
const english = JSON.parse(catalogEntries[0][1]);
const german = JSON.parse(catalogEntries[1][1]);
assert.notEqual(english.trim(), "", `${key} has a non-empty English translation`);
assert.notEqual(german.trim(), "", `${key} has a non-empty German translation`);
}
const sliceSections = [...translationCatalog.matchAll(/ "i18n:govoplan-campaign\.campaign_reports_available_to_you\.f14fa403":[\s\S]*? "i18n:govoplan-campaign\.worker_queue_committed_value0_message_s_and_publ\.24400d3c":.*\n/g)];
assert.equal(sliceSections.length, 2, "the current slice has one contiguous English and German catalog section");
const newEnglishEntries = [...sliceSections[0][0].matchAll(/"(i18n:govoplan-campaign\.[a-z0-9_]+\.[a-f0-9]{8})":\s*("(?:[^"\\]|\\.)*")/g)];
for (const [, key, encodedEnglish] of newEnglishEntries) {
const english = JSON.parse(encodedEnglish);
const digest = createHash("sha1").update(english).digest("hex").slice(0, 8);
const slug = english.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+/, "").slice(0, 48);
assert.equal(key, `i18n:govoplan-campaign.${slug}.${digest}`, `${key} uses the normalized source slug and exact SHA-1 suffix`);
assert(touchedKeys.has(key), `${key} is referenced by the current Campaign UI slices`);
}
console.log("aggregate report UI structure checks passed");

View File

@@ -5,12 +5,22 @@ import { dirname, resolve } from "node:path";
const here = dirname(fileURLToPath(import.meta.url));
const source = readFileSync(resolve(here, "../src/features/campaigns/ReviewSendPage.tsx"), "utf8");
const modeSource = readFileSync(resolve(here, "../src/features/campaigns/utils/deliveryMode.ts"), "utf8");
assert.match(source, /getCampaignDeliveryOptions/);
assert.match(source, /Queue for workers/);
assert.match(source, /Send now keeps this request open/);
assert.match(source, /queue_for_workers\.dae9a3e4/);
assert.match(source, /queue_for_workers_commits_durable_jobs_and_retur\.d0dbe81a/);
assert.match(source, /loading_the_effective_delivery_policy_\.d6893011/);
assert.match(source, /deliveryControlProgressMessage/);
assert.match(source, /synchronousSendAllowed/);
assert.match(source, /commandInProgress[\s\S]*refreshQueueStatus/);
assert.match(source, /<ConfirmDialog[\s\S]*open=\{queueConfirmOpen\}/);
assert.match(source, /<ConfirmDialog[\s\S]*open=\{cancelDeliveryConfirmOpen\}/);
assert.match(source, /deliveryModeLabel\(persistedDeliveryMode\)/);
assert.match(source, /this_version_last_entered_value0_mode_at_value1_\.c087d2f3/);
assert.match(source, /this_version_last_entered_value0_mode_this_recor\.ea2f201f/);
assert.doesNotMatch(source, /This version last entered/);
assert.match(modeSource, /synchronous\.77c61919/);
assert.match(modeSource, /worker_queue\.c911e32c/);
assert.match(modeSource, /database_queue\.8bf98437/);
assert.doesNotMatch(source, /window\.alert\s*\(/);

View File

@@ -66,6 +66,6 @@ test("permission explanations take precedence over queue state", () => {
const blocks = operatorQueueActionBlocks(everything, noPermissions);
assert.deepEqual(
Object.values(blocks),
Array.from({ length: 7 }, () => OPERATOR_QUEUE_REASON.permission)
Array.from({ length: 7 }, () => OPERATOR_QUEUE_REASON.permissionDenied)
);
});

View File

@@ -41,7 +41,8 @@ assert(reportSource.includes("onQueryChange={handleJobGridQuery}"), "header sort
assert(reportSource.includes("initialReportGridFilters()"), "status deep links initialize the DataGrid filters");
assert(reportSource.includes("initialReportQuery()"), "q deep links initialize the report search");
assert(reportSource.includes('"skipped",\n"queued"'), "SMTP skipped is a first-class report filter option");
assert(reportSource.includes("Excluded rows are intentionally omitted from delivery"), "the report explains excluded transport semantics");
assert(reportSource.includes("i18n:govoplan-campaign.excluded_rows_are_intentionally_omitted_from_del.421a1f00"), "the report explains excluded transport semantics through the bilingual catalog");
assert(reportSource.includes('return status === "skipped" ? "i18n:govoplan-campaign.skipped.5a000ad7"'), "the new skipped filter and status badges use the localized label");
assert(reportSource.includes("cards?.skipped ?? jobs.counts.send?.skipped"), "SMTP skipped has a separate report count");
assert(reportSource.includes("cards?.imap_skipped ?? jobs.counts.imap?.skipped"), "IMAP skipped has a separate report count");
assert(!reportSource.includes("setPage((value) => Math.max(1, value - 1))"), "the one-off report pager is removed in favor of the central DataGrid pager");