Implement governed hybrid campaign delivery

This commit is contained in:
2026-08-02 13:58:37 +02:00
parent b38597f2be
commit 7733265cc8
40 changed files with 2531 additions and 122 deletions
@@ -12,6 +12,7 @@ import {
appendSent,
buildVersion,
cancelCampaign,
downloadCampaignPrintArtifact,
getCampaignDeliveryOptions,
getCampaignJobs,
getCampaignJobsDelta,
@@ -151,6 +152,8 @@ export default function ReviewSendPage({
);
const validation = asRecord(version?.validation_summary);
const build = asRecord(version?.build_summary);
const printOutput = asRecord(build.print_output);
const printArtifact = asRecord(printOutput.artifact);
const summary = liveSummary ?? data.summary;
const cards = summary?.cards;
const attachmentSummary = asRecord(summary?.attachments);
@@ -1449,6 +1452,34 @@ export default function ReviewSendPage({
<WorkflowFact label="i18n:govoplan-campaign.attachment_issues.69748336" value={missingAttachments + ambiguousAttachments} />
</div>
<p className="muted">i18n:govoplan-campaign.building_freezes_the_current_recipients_rendered.273a8170</p>
{getText(printOutput, "render_id") && (
<div className="review-flow-data-section">
<div className="page-heading split">
<div>
<h3>Printable output</h3>
<p className="muted small-note">
Template revision {String(printOutput.template_revision ?? "—")} · {String(printOutput.item_count ?? 0)} recipient item(s) · {String(printOutput.page_count ?? 0)} page(s) · {String(printOutput.output_size_bytes ?? 0)} B
</p>
</div>
{getText(printArtifact, "download_path") && (
<Button
onClick={() => void downloadCampaignPrintArtifact(
settings,
getText(printArtifact, "download_path"),
getText(printArtifact, "filename", "campaign-print-output.html")
).catch((reason: unknown) => setError(reason instanceof Error ? reason.message : String(reason)))}
>
Download output
</Button>
)}
</div>
<dl className="detail-list">
<div><dt>Template hash</dt><dd><code>{getText(printOutput, "template_hash") || "—"}</code></dd></div>
<div><dt>Input hash</dt><dd><code>{getText(printOutput, "input_hash") || "—"}</code></dd></div>
<div><dt>Output hash</dt><dd><code>{getText(printOutput, "output_sha256") || "—"}</code></dd></div>
</dl>
</div>
)}
<div className="button-row compact-actions review-flow-stage-actions">
<Button variant="primary" onClick={() => void runBuild()} disabled={!version || Boolean(busy) || readOnlyVersion || !readyForDelivery || deliveryQueued || deliveryStarted}>
{busy === "build" ? "i18n:govoplan-campaign.building.7cc766ce" : hasBuild ? "i18n:govoplan-campaign.build_again.bd018b93" : "i18n:govoplan-campaign.build_exact_messages.bc53f55e"}