Release govoplan-campaign v0.1.28: stabilize saving, review and delivery recovery
Module Package Release / publish-packages (push) Successful in 12s

This commit is contained in:
2026-09-08 01:32:26 +02:00
parent 1b32427813
commit c51fc180fb
111 changed files with 6905 additions and 980 deletions
@@ -1,41 +1,43 @@
import { Check, Search } from "lucide-react";
import { Search } from "lucide-react";
import {
StatusBadge,
TableActionGroup,
i18nMessage,
type DataGridColumn,
type DataGridListOption
} from "@govoplan/core-webui";
import type { MockMailboxMessage } from "../../../api/mail";
import type { CampaignMessagePreviewAttachment } from "../components/MessagePreviewOverlay";
import { asArray, asRecord, stringifyPreview } from "../utils/campaignView";
import { builtMessageKey } from "./builtMessageQuery";
import { SEND_STATUS_OPTIONS, IMAP_STATUS_OPTIONS, deliveryStatusLabel } from "../utils/deliveryStatusOptions";
import { builtMessageState } from "./builtMessageState";
import { countResolvedAttachments, formatAddressList } from "./reviewFormatters";
const MESSAGE_VALIDATION_OPTIONS: DataGridListOption[] = [
{ value: "ready", label: "i18n:govoplan-campaign.ready.20c7c552" },
{ value: "warning", label: "i18n:govoplan-campaign.warning.e9c45563" },
{ value: "needs_review", label: "i18n:govoplan-campaign.needs_review.33a506cf" },
{ value: "blocked", label: "i18n:govoplan-campaign.blocked.99613c74" },
{ value: "excluded", label: "i18n:govoplan-campaign.excluded.9804952b" }];
const MESSAGE_STATE_OPTIONS: DataGridListOption[] = [
{ value: "ready", label: "i18n:govoplan-campaign.message_state_ready" },
{ value: "needs_review", label: "i18n:govoplan-campaign.message_state_needs_review" },
{ value: "blocked", label: "i18n:govoplan-campaign.message_state_blocked_label" },
{ value: "excluded", label: "i18n:govoplan-campaign.message_state_excluded_label" }];
export function builtMessageColumns(
openMessage: (row: Record<string, unknown>) => void,
reviewedKeys: Set<string>)
reviewedKeys: Set<string>,
inspectionComplete = false)
: DataGridColumn<Record<string, unknown>>[] {
return [
{ id: "number", header: "#", width: 70, sortable: true, filterType: "integer", sticky: "start", value: (row, index) => Number(row.entry_index ?? index + 1) },
{ id: "recipient", header: "i18n:govoplan-campaign.recipient.90343260", width: 250, resizable: true, sortable: true, filterable: true, value: (row) => formatAddressList(asRecord(row.resolved_recipients).to) || String(row.recipient_email ?? "—") },
{ id: "subject", header: "i18n:govoplan-campaign.subject.8d183dbd", width: "minmax(260px, 1fr)", maxWidth: 640, resizable: true, sortable: true, filterable: true, value: (row) => String(row.subject ?? "—") },
{
id: "validation",
header: "i18n:govoplan-campaign.validation.dd74d182",
id: "messageState",
header: "i18n:govoplan-campaign.message_state_heading",
width: 145,
sortable: true,
filterable: true,
columnType: "from-list",
list: { options: MESSAGE_VALIDATION_OPTIONS, display: "pill" },
value: (row) => String(row.validation_status ?? "unknown")
list: { options: MESSAGE_STATE_OPTIONS, display: "pill" },
value: (row, index) => builtMessageState(row, index, reviewedKeys, inspectionComplete).state
},
{
id: "postboxTargets",
@@ -55,7 +57,8 @@ reviewedKeys: Set<string>)
}
},
{ id: "attachments", header: "i18n:govoplan-campaign.attachments.6771ade6", width: 125, sortable: true, filterable: true, filterType: "integer", align: "right", value: (row) => Number(row.attachment_count ?? countResolvedAttachments(row.attachments)) },
{ id: "reviewed", header: "i18n:govoplan-campaign.reviewed.31ef8593", width: 110, sortable: true, filterable: true, columnType: "from-list", list: { options: [{ value: "yes", label: "i18n:govoplan-campaign.reviewed.31ef8593" }, { value: "no", label: "i18n:govoplan-campaign.not_reviewed.0a0e3cff" }] }, render: (row, index) => row.reviewed === true || reviewedKeys.has(String(row.review_key ?? builtMessageKey(row, index))) ? <Check size={17} aria-label="i18n:govoplan-campaign.reviewed.31ef8593" /> : <span className="muted"></span>, value: (row, index) => row.reviewed === true || reviewedKeys.has(String(row.review_key ?? builtMessageKey(row, index))) ? "yes" : "no" },
{ id: "stateExplanation", header: "i18n:govoplan-campaign.message_state_explanation_heading", width: "minmax(220px, 1fr)", maxWidth: 480, resizable: true,
value: (row, index) => builtMessageState(row, index, reviewedKeys, inspectionComplete).explanationLabel },
{ id: "actions", header: "i18n:govoplan-campaign.actions.c3cd636a", width: 72, sticky: "end", render: (row) => <TableActionGroup actions={[{ id: "review", label: "i18n:govoplan-campaign.review.e29a79fe", icon: <Search aria-hidden="true" />, onClick: () => openMessage(row) }]} /> }];
}
@@ -107,8 +110,8 @@ export function imapDiagnosticColumns(openDetail: (jobId: string) => Promise<voi
return [
{ id: "recipient", header: "i18n:govoplan-campaign.recipient.90343260", width: 240, sticky: "start", resizable: true, sortable: true, filterable: true, value: (row) => formatAddressList(asRecord(row.resolved_recipients).to) || String(row.recipient_email ?? "-") },
{ id: "subject", header: "i18n:govoplan-campaign.subject.8d183dbd", width: "minmax(260px, 1fr)", maxWidth: 640, resizable: true, sortable: true, filterable: true, value: (row) => String(row.subject ?? "-") },
{ id: "send", header: "i18n:govoplan-campaign.smtp.efff9cca", width: 150, sortable: true, filterable: true, render: (row) => <StatusBadge status={String(row.send_status ?? "info")} />, value: (row) => String(row.send_status ?? "-") },
{ id: "imap", header: "i18n:govoplan-campaign.imap.271f9ef2", width: 150, sortable: true, filterable: true, render: (row) => <StatusBadge status={String(row.imap_status ?? "info")} />, value: (row) => String(row.imap_status ?? "-") },
{ id: "send", header: "i18n:govoplan-campaign.smtp.efff9cca", width: 150, sortable: true, filterable: true, columnType: "from-list", list: { options: SEND_STATUS_OPTIONS, display: "pill" }, render: (row) => <StatusBadge status={String(row.send_status ?? "info")} label={deliveryStatusLabel(String(row.send_status ?? "info"))} />, value: (row) => String(row.send_status ?? "-") },
{ id: "imap", header: "i18n:govoplan-campaign.imap.271f9ef2", width: 150, sortable: true, filterable: true, columnType: "from-list", list: { options: IMAP_STATUS_OPTIONS, display: "pill" }, render: (row) => <StatusBadge status={String(row.imap_status ?? "info")} label={deliveryStatusLabel(String(row.imap_status ?? "info"))} />, value: (row) => String(row.imap_status ?? "-") },
{ id: "error", header: "i18n:govoplan-campaign.last_error.5e4df866", width: "minmax(260px, 1fr)", maxWidth: 720, resizable: true, filterable: true, value: (row) => String(row.last_error ?? "-") },
{ id: "actions", header: "i18n:govoplan-campaign.actions.c3cd636a", width: 72, sticky: "end", render: (row) => <TableActionGroup actions={[{ id: "details", label: "i18n:govoplan-campaign.details.dc3decbb", icon: <Search aria-hidden="true" />, onClick: () => void openDetail(String(row.id ?? "")) }]} /> }];
@@ -153,7 +156,10 @@ export 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 "i18n:govoplan-campaign.the_exact_built_run_exceeds_the_effective_limit_.d7812d6a";
case "recipient_limit_exceeded":return i18nMessage("i18n:govoplan-campaign.synchronous_limit_explanation", {
value0: Number(option.eligible_recipient_job_count ?? 0),
value1: Number(asRecord(option.policy).max_recipient_jobs ?? 0)
});
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";
@@ -168,6 +174,7 @@ export function deliveryControlProgressMessage(action: "pause" | "resume" | "can
}
export function deliveryPolicySourceLabel(source: string): string {
if (source === "system" || source === "system_ceiling") return "i18n:govoplan-campaign.system_delivery_policy_source";
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";