From 79b576b4bcf92c28d6fa46a0c73971185d8a78c5 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Wed, 22 Jul 2026 09:04:42 +0200 Subject: [PATCH] fix(webui): localize Campaign delivery reporting --- .../features/campaigns/CampaignReportPage.tsx | 22 +-- .../src/features/campaigns/ReviewSendPage.tsx | 145 ++++++++++++----- .../features/campaigns/utils/deliveryMode.ts | 6 + .../features/operator/OperatorQueuePage.tsx | 5 +- .../features/operator/operatorQueueModel.ts | 16 +- .../features/reports/AggregateReportsPage.tsx | 76 +++++---- webui/src/i18n/generatedTranslations.ts | 152 ++++++++++++++++++ .../aggregate-report-ui-structure.test.mjs | 33 ++++ .../tests/delivery-mode-ui-structure.test.mjs | 14 +- webui/tests/operator-queue-model.test.ts | 2 +- webui/tests/report-grid-query.test.ts | 3 +- 11 files changed, 380 insertions(+), 94 deletions(-) create mode 100644 webui/src/features/campaigns/utils/deliveryMode.ts diff --git a/webui/src/features/campaigns/CampaignReportPage.tsx b/webui/src/features/campaigns/CampaignReportPage.tsx index aa55e0a..d63eed6 100644 --- a/webui/src/features/campaigns/CampaignReportPage.tsx +++ b/webui/src/features/campaigns/CampaignReportPage.tsx @@ -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) => , 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) => , 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) => , 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) => , 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) => , 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) => , 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:
i18n:govoplan-campaign.failed.09fef5d8
{cards?.failed ?? 0}
i18n:govoplan-campaign.outcome_unknown.6e929fca
{cards?.outcome_unknown ?? 0}
i18n:govoplan-campaign.not_attempted.e1be3c69
{cards?.not_attempted ?? 0}
-
SMTP skipped (excluded)
{cards?.skipped ?? jobs.counts.send?.skipped ?? 0}
+
i18n:govoplan-campaign.smtp_skipped_excluded_.df6eca19
{cards?.skipped ?? jobs.counts.send?.skipped ?? 0}
i18n:govoplan-campaign.cancelled.a1bf92ef
{cards?.cancelled ?? 0}
@@ -396,7 +396,7 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
i18n:govoplan-campaign.imap_appended.56017ea3
{cards?.imap_appended ?? 0}
i18n:govoplan-campaign.imap_failed.50dbca55
{cards?.imap_failed ?? 0}
-
IMAP skipped
{cards?.imap_skipped ?? jobs.counts.imap?.skipped ?? 0}
+
i18n:govoplan-campaign.imap_skipped.5a97b542
{cards?.imap_skipped ?? jobs.counts.imap?.skipped ?? 0}
i18n:govoplan-campaign.append_policy.f195cb05
{imapPolicy.enabled === true ? i18nMessage("i18n:govoplan-campaign.enabled_value.e395e48f", { value0: String(imapPolicy.folder ?? "i18n:govoplan-campaign.auto.0d612c12") }) : "i18n:govoplan-campaign.disabled.f4f4473d"}
i18n:govoplan-campaign.rate_limit.d08e55f5
{rateLimit.messages_per_minute ? i18nMessage("i18n:govoplan-campaign.value_minute.aeb1a9ea", { value0: String(rateLimit.messages_per_minute) }) : "—"}
i18n:govoplan-campaign.minimum_remaining_duration.639b792c
{String(delivery.estimated_remaining_send_human ?? "—")}
@@ -417,7 +417,7 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:

- 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

@@ -487,8 +487,8 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
i18n:govoplan-campaign.recipient.90343260
{String(detail.job.recipient_email ?? "—")}
i18n:govoplan-campaign.subject.8d183dbd
{String(detail.job.subject ?? "—")}
i18n:govoplan-campaign.message_id.465056ba
{String(detail.job.message_id_header ?? "—")}
-
i18n:govoplan-campaign.smtp_state.ff372566
-
i18n:govoplan-campaign.imap_state.03b83be0
+
i18n:govoplan-campaign.smtp_state.ff372566
+
i18n:govoplan-campaign.imap_state.03b83be0
i18n:govoplan-campaign.attachments.6771ade6
{String(detail.job.matched_file_count ?? detail.job.attachment_count ?? 0)}
@@ -556,6 +556,10 @@ function initialReportGridFilters(): Record { 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() ?? ""; diff --git a/webui/src/features/campaigns/ReviewSendPage.tsx b/webui/src/features/campaigns/ReviewSendPage.tsx index b3aa8ab..2f57d1f 100644 --- a/webui/src/features/campaigns/ReviewSendPage.tsx +++ b/webui/src/features/campaigns/ReviewSendPage.tsx @@ -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
i18n:govoplan-campaign.rate_limit.d08e55f5{messagesPerMinute > 0 ? i18nMessage("i18n:govoplan-campaign.value_min.c9d89eae", { value0: messagesPerMinute }) : "i18n:govoplan-campaign.not_set.93039e60"}
i18n:govoplan-campaign.minimum_duration.91a71a6d{estimatedMinutes ? i18nMessage("i18n:govoplan-campaign.about_value_min.7c2e77fc", { value0: estimatedMinutes }) : "—"}
i18n:govoplan-campaign.imap_append.8c0d9e96{Boolean(imapAppend.enabled) ? "i18n:govoplan-campaign.enabled.df174a3f" : "i18n:govoplan-campaign.disabled.f4f4473d"}
-
Synchronous limit{deliveryOptionsLoading ? "…" : synchronousSendLimit}
-
Limit source{humanize(String(synchronousSendPolicy.source ?? "not available"))}
-
Worker queue{workerQueueAvailable ? "Available" : "Not configured"}
+
i18n:govoplan-campaign.synchronous_limit.f88c0bcd{deliveryOptionsLoading ? "…" : synchronousSendLimit}
+
i18n:govoplan-campaign.limit_source.bd933adb{deliveryPolicySourceLabel(String(synchronousSendPolicy.source ?? ""))}
+
i18n:govoplan-campaign.worker_queue.c911e32c{workerQueueAvailable ? "i18n:govoplan-campaign.available.7c62a142" : "i18n:govoplan-campaign.not_configured.811931bb"}
i18n:govoplan-campaign.version.2da600bf{version ? `v${version.version_number}` : "—"}
@@ -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"}

- 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" + })}

{!synchronousSendAllowed && canSendSynchronously && -

Send now is unavailable: {synchronousSendReason(synchronousSendOption)}.

+

+ {i18nMessage("i18n:govoplan-campaign.send_now_is_unavailable_value0_.d93c0b29", { + value0: synchronousSendReason(synchronousSendOption) + })} +

} {queueResult && -

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.

+

+ {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) + })} +

} {sendResult &&
@@ -1518,8 +1555,8 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings - - + + @@ -1532,29 +1569,36 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings
{persistedDeliveryMode &&

- 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) + })}

}
{Boolean(imapAppend.enabled) && imapPendingForDisplay > 0 && @@ -1664,9 +1708,12 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings setQueueConfirmOpen(false)} @@ -1675,7 +1722,11 @@ export default function ReviewSendPage({ settings, auth, campaignId }: {settings setCancelDeliveryConfirmOpen(false)} @@ -2559,14 +2610,28 @@ function synchronousSendReason(option: Record): 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, keys: string[]): number { for (const key of keys) { const value = record[key]; diff --git a/webui/src/features/campaigns/utils/deliveryMode.ts b/webui/src/features/campaigns/utils/deliveryMode.ts new file mode 100644 index 0000000..34f26c4 --- /dev/null +++ b/webui/src/features/campaigns/utils/deliveryMode.ts @@ -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" : "—"; +} diff --git a/webui/src/features/operator/OperatorQueuePage.tsx b/webui/src/features/operator/OperatorQueuePage.tsx index 83a558b..1b703ed 100644 --- a/webui/src/features/operator/OperatorQueuePage.tsx +++ b/webui/src/features/operator/OperatorQueuePage.tsx @@ -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[]>(() => [ { 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) => , 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 }, diff --git a/webui/src/features/operator/operatorQueueModel.ts b/webui/src/features/operator/operatorQueueModel.ts index d8422e1..7f2016e 100644 --- a/webui/src/features/operator/operatorQueueModel.ts +++ b/webui/src/features/operator/operatorQueueModel.ts @@ -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 { 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 diff --git a/webui/src/features/reports/AggregateReportsPage.tsx b/webui/src/features/reports/AggregateReportsPage.tsx index fff3cc0..2736996 100644 --- a/webui/src/features/reports/AggregateReportsPage.tsx +++ b/webui/src/features/reports/AggregateReportsPage.tsx @@ -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) => (