fix(webui): localize Campaign delivery reporting
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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*\(/);
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user