fix(campaign): use stable aggregate status filters
This commit is contained in:
@@ -14,7 +14,8 @@ import {
|
|||||||
formatDateTime,
|
formatDateTime,
|
||||||
i18nMessage,
|
i18nMessage,
|
||||||
type ApiSettings,
|
type ApiSettings,
|
||||||
type DataGridColumn
|
type DataGridColumn,
|
||||||
|
type DataGridListOption
|
||||||
} from "@govoplan/core-webui";
|
} from "@govoplan/core-webui";
|
||||||
import {
|
import {
|
||||||
getAggregateCampaignReport,
|
getAggregateCampaignReport,
|
||||||
@@ -24,6 +25,21 @@ import {
|
|||||||
type AggregateReportCount
|
type AggregateReportCount
|
||||||
} from "../../api/campaigns";
|
} from "../../api/campaigns";
|
||||||
|
|
||||||
|
const CAMPAIGN_STATUS_OPTIONS: DataGridListOption[] = [
|
||||||
|
{ value: "draft", label: "i18n:govoplan-campaign.draft.23d33e22" },
|
||||||
|
{ value: "validated", label: "i18n:govoplan-campaign.valid.a4aefa35" },
|
||||||
|
{ value: "needs_review", label: "i18n:govoplan-campaign.needs_review.33a506cf" },
|
||||||
|
{ value: "ready_to_queue", label: "i18n:govoplan-campaign.ready.20c7c552" },
|
||||||
|
{ value: "queued", label: "i18n:govoplan-campaign.queued.6a599877" },
|
||||||
|
{ value: "sending", label: "i18n:govoplan-campaign.sending.cf765512" },
|
||||||
|
{ value: "sent", label: "i18n:govoplan-campaign.sent.35f49dcf" },
|
||||||
|
{ value: "partially_completed", label: "i18n:govoplan-campaign.partially_completed.760bc5e6" },
|
||||||
|
{ value: "outcome_unknown", label: "i18n:govoplan-campaign.outcome_unknown.6e929fca" },
|
||||||
|
{ value: "failed", label: "i18n:govoplan-campaign.failed.09fef5d8" },
|
||||||
|
{ value: "cancelled", label: "i18n:govoplan-campaign.cancelled.a1bf92ef" },
|
||||||
|
{ value: "archived", label: "i18n:govoplan-campaign.archived.eddc813f" }
|
||||||
|
];
|
||||||
|
|
||||||
export default function AggregateReportsPage({ settings }: {settings: ApiSettings;}) {
|
export default function AggregateReportsPage({ settings }: {settings: ApiSettings;}) {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const selectedFromUrl = searchParams.get("campaign") ?? "";
|
const selectedFromUrl = searchParams.get("campaign") ?? "";
|
||||||
@@ -110,6 +126,8 @@ export default function AggregateReportsPage({ settings }: {settings: ApiSetting
|
|||||||
width: 170,
|
width: 170,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
columnType: "from-list",
|
||||||
|
list: { options: CAMPAIGN_STATUS_OPTIONS, display: "pill" },
|
||||||
render: (campaign) => <StatusBadge status={campaign.status} />,
|
render: (campaign) => <StatusBadge status={campaign.status} />,
|
||||||
value: (campaign) => campaign.status
|
value: (campaign) => campaign.status
|
||||||
},
|
},
|
||||||
@@ -190,6 +208,8 @@ export default function AggregateReportsPage({ settings }: {settings: ApiSetting
|
|||||||
</dl>
|
</dl>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Aggregate outcome cards are deliberately not filter shortcuts: each subgroup
|
||||||
|
would require a separately suppressed aggregate response from the server. */}
|
||||||
<div className="dashboard-grid">
|
<div className="dashboard-grid">
|
||||||
<MetricCard label="i18n:govoplan-campaign.smtp_accepted.e3aa7603" value={countValue(outcomes.smtp_accepted)} tone="good" />
|
<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.failed.09fef5d8" value={countValue(outcomes.failed)} tone="danger" />
|
||||||
|
|||||||
@@ -23,6 +23,28 @@ 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("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("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("disabled: campaign.id === selectedFromUrl"), "the selected row action stays visible and disabled");
|
||||||
|
assert(page.includes('columnType: "from-list"'), "campaign status uses the stable shared list-filter model");
|
||||||
|
const expectedCampaignStatuses = [
|
||||||
|
"draft",
|
||||||
|
"validated",
|
||||||
|
"needs_review",
|
||||||
|
"ready_to_queue",
|
||||||
|
"queued",
|
||||||
|
"sending",
|
||||||
|
"sent",
|
||||||
|
"partially_completed",
|
||||||
|
"outcome_unknown",
|
||||||
|
"failed",
|
||||||
|
"cancelled",
|
||||||
|
"archived"
|
||||||
|
];
|
||||||
|
for (const status of expectedCampaignStatuses) {
|
||||||
|
assert(page.includes(`{ value: "${status}", label:`), `the aggregate list declares the ${status} status explicitly`);
|
||||||
|
}
|
||||||
|
const outcomeCardsStart = page.indexOf("Aggregate outcome cards are deliberately not filter shortcuts");
|
||||||
|
const outcomeCardsEnd = page.indexOf('<Card title="i18n:govoplan-campaign.population_and_exclusions', outcomeCardsStart);
|
||||||
|
assert(outcomeCardsStart >= 0 && outcomeCardsEnd > outcomeCardsStart, "the aggregate privacy boundary is documented beside the outcome cards");
|
||||||
|
assert(!page.slice(outcomeCardsStart, outcomeCardsEnd).includes("onClick"), "aggregate outcome cards cannot create unsuppressed filtered subgroups");
|
||||||
assert(!page.includes("{report.population.denominator_definition}"), "the known denominator contract uses a localized UI explanation");
|
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("{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("all_persisted_recipient_delivery_jobs_for_the_se.208e90cc"), "the denominator explanation is bilingual");
|
||||||
|
|||||||
Reference in New Issue
Block a user