Clarify campaign review interventions
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
"test:recipient-search": "node tests/recipient-search-ui-structure.test.mjs",
|
||||
"test:report-grid": "rm -rf .report-grid-test-build && mkdir -p .report-grid-test-build && printf '{\"type\":\"commonjs\"}\\n' > .report-grid-test-build/package.json && tsc -p tsconfig.report-grid-tests.json && node .report-grid-test-build/tests/report-grid-query.test.js",
|
||||
"test:review-preview-ui": "rm -rf .review-preview-test-build && mkdir -p .review-preview-test-build && printf '{\"type\":\"commonjs\"}\\n' > .review-preview-test-build/package.json && tsc -p tsconfig.review-preview-tests.json && node .review-preview-test-build/tests/review-preview-ui.test.js && node tests/delivery-mode-ui-structure.test.mjs",
|
||||
"test:review-workflow": "node --experimental-strip-types --test tests/review-workflow-guidance.test.ts && node tests/review-workflow-guidance-ui-structure.test.mjs",
|
||||
"test:operator-queue": "node --experimental-strip-types --test tests/operator-queue-model.test.ts && node tests/operator-queue-ui-structure.test.mjs",
|
||||
"test:aggregate-report": "tsc -p tsconfig.aggregate-report-tests.json && node tests/aggregate-report-ui-structure.test.mjs",
|
||||
"test:wizards": "node tests/wizard-directory-ui-structure.test.mjs",
|
||||
|
||||
@@ -72,6 +72,12 @@ import DeliverabilityPreflight, {
|
||||
} from "./review/DeliverabilityPreflight";
|
||||
import DeliveryJobDetailOverlay from "./review/DeliveryJobDetailOverlay";
|
||||
import BuiltMessagePreview from "./review/BuiltMessagePreview";
|
||||
import {
|
||||
BuiltMessageReviewProgress,
|
||||
BuiltMessageWorkflowGuidance,
|
||||
ValidationWorkflowGuidance
|
||||
} from "./review/ReviewWorkflowGuidance";
|
||||
import { calculateBuildReviewProgress } from "./review/reviewProgress";
|
||||
import {
|
||||
WorkflowFact,
|
||||
WorkflowNavigation,
|
||||
@@ -466,7 +472,6 @@ export default function ReviewSendPage({
|
||||
Number(reviewMetadata.reviewed_required_count ?? 0) + newlyReviewedRequiredKeys.size
|
||||
);
|
||||
const reviewRequiredCount = explicitReviewCount + bulkAcceptableCount;
|
||||
const reviewedRequiredCount = reviewedExplicitCount;
|
||||
const automaticInspectionComplete = reviewJobs.total_unfiltered > 0 &&
|
||||
blockingReviewCount === 0 &&
|
||||
reviewRequiredCount === 0;
|
||||
@@ -505,6 +510,13 @@ export default function ReviewSendPage({
|
||||
|
||||
const downstreamDeliveryActivity = deliveryQueued || deliveryStarted;
|
||||
const inspectionSatisfied = automaticInspectionComplete || messageReviewComplete || downstreamDeliveryActivity;
|
||||
const buildReviewProgress = calculateBuildReviewProgress({
|
||||
blocking: Math.max(blockingReviewCount, buildBlocked),
|
||||
individualRequired: explicitReviewCount,
|
||||
individualReviewed: reviewedExplicitCount,
|
||||
groupRequired: bulkAcceptableCount,
|
||||
reviewComplete: messageReviewComplete || downstreamDeliveryActivity
|
||||
});
|
||||
|
||||
const buildReviewState: FlowState = !readyForDelivery ?
|
||||
"locked" :
|
||||
@@ -512,7 +524,7 @@ export default function ReviewSendPage({
|
||||
"running" :
|
||||
hasBuild && buildBlocked > 0 ?
|
||||
"danger" :
|
||||
hasBuild && (buildNeedsReview > 0 || buildWarnings > 0 || !inspectionSatisfied) ?
|
||||
hasBuild && !inspectionSatisfied && (buildNeedsReview > 0 || buildWarnings > 0 || buildReviewProgress.remaining > 0) ?
|
||||
"warning" :
|
||||
hasBuild && inspectionSatisfied ?
|
||||
"complete" :
|
||||
@@ -1392,13 +1404,14 @@ export default function ReviewSendPage({
|
||||
<WorkflowFact label="i18n:govoplan-campaign.warnings.1430f976" value={validationPresent ? validationWarnings : "—"} />
|
||||
<WorkflowFact label="i18n:govoplan-campaign.jobs_needing_attention.95613b02" value={cards?.needs_attention ?? "—"} />
|
||||
</div>
|
||||
{validationStale && <p className="review-flow-inline-note is-stale">i18n:govoplan-campaign.the_stored_validation_result_is_no_longer_an_act.45d2dd14</p>}
|
||||
<ValidationWorkflowGuidance
|
||||
errors={validationErrors}
|
||||
warnings={validationWarnings}
|
||||
stale={validationStale}
|
||||
/>
|
||||
{validationPresent && validationErrors === 0 &&
|
||||
<p className="review-flow-inline-note is-complete"><Check size={17} aria-hidden="true" /> i18n:govoplan-campaign.no_blocking_validation_exceptions_remain.73186d0d</p>
|
||||
}
|
||||
{validationErrors > 0 &&
|
||||
<p className="review-flow-inline-note is-danger">i18n:govoplan-campaign.resolve_the_blocking_entries_then_validate_again.e282ae0f</p>
|
||||
}
|
||||
<AttachmentLinkingPreview
|
||||
preview={attachmentPreview}
|
||||
loading={attachmentPreviewLoading}
|
||||
@@ -1447,10 +1460,13 @@ export default function ReviewSendPage({
|
||||
<WorkflowFact label="i18n:govoplan-campaign.built.a6ad3f82" value={hasBuild ? builtCount : "—"} />
|
||||
<WorkflowFact label="i18n:govoplan-campaign.blocked.99613c74" value={hasBuild ? buildBlocked : "—"} />
|
||||
<WorkflowFact label="i18n:govoplan-campaign.need_review.201a4493" value={hasBuild ? buildNeedsReview : "—"} />
|
||||
<WorkflowFact label="i18n:govoplan-campaign.reviewed.31ef8593" value={hasBuild ? reviewedRequiredCount : "—"} />
|
||||
<WorkflowFact label="i18n:govoplan-campaign.reviewed.31ef8593" value={hasBuild ? buildReviewProgress.reviewed : "—"} />
|
||||
<WorkflowFact label="i18n:govoplan-campaign.remaining.cc632b5e" value={hasBuild ? buildReviewProgress.remaining : "—"} />
|
||||
<WorkflowFact label="i18n:govoplan-campaign.review_candidates.438b8b57" value={reviewJobs.total || "—"} />
|
||||
<WorkflowFact label="i18n:govoplan-campaign.attachment_issues.69748336" value={missingAttachments + ambiguousAttachments} />
|
||||
</div>
|
||||
{hasBuild && <BuiltMessageReviewProgress progress={buildReviewProgress} />}
|
||||
{hasBuild && <BuiltMessageWorkflowGuidance progress={buildReviewProgress} buildWarnings={buildWarnings} />}
|
||||
<p className="muted">i18n:govoplan-campaign.building_freezes_the_current_recipients_rendered.273a8170</p>
|
||||
{getText(printOutput, "render_id") && (
|
||||
<div className="review-flow-data-section">
|
||||
@@ -1501,9 +1517,6 @@ export default function ReviewSendPage({
|
||||
{automaticInspectionComplete &&
|
||||
<p className="review-flow-inline-note is-complete"><Check size={17} aria-hidden="true" /> i18n:govoplan-campaign.all_built_messages_are_ready_no_manual_review_ac.c5549791</p>
|
||||
}
|
||||
{blockingReviewCount > 0 &&
|
||||
<p className="review-flow-inline-note is-danger">{blockingReviewCount} i18n:govoplan-campaign.blocked_or_failed_message_s_must_be_resolved_bef.5c6b5140</p>
|
||||
}
|
||||
{hasBuild &&
|
||||
<div className="review-flow-data-section">
|
||||
<div className="page-heading split">
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import {
|
||||
ActionBlockerHint,
|
||||
GuidedReviewList,
|
||||
i18nMessage,
|
||||
type ActionBlockerReason
|
||||
} from "@govoplan/core-webui";
|
||||
|
||||
import type { BuildReviewProgress } from "./reviewProgress";
|
||||
|
||||
type InterventionHintProps = {
|
||||
tone: "warning" | "danger";
|
||||
summary: string;
|
||||
requiredAction: string;
|
||||
destination: string;
|
||||
};
|
||||
|
||||
function InterventionHint({ tone, summary, requiredAction, destination }: InterventionHintProps) {
|
||||
const requiredActionLabel = i18nMessage("i18n:govoplan-campaign.required_action.f2429497");
|
||||
const actorLabel = i18nMessage("i18n:govoplan-campaign.who_can_resolve_this.e60939b2");
|
||||
const destinationLabel = i18nMessage("i18n:govoplan-campaign.where_to_go.bb1c6969");
|
||||
const actor = i18nMessage("i18n:govoplan-campaign.campaign_editor_or_reviewer.7869b106");
|
||||
const reason: ActionBlockerReason = {
|
||||
summary,
|
||||
requiredAction,
|
||||
actor,
|
||||
target: destination
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionBlockerHint
|
||||
tone={tone}
|
||||
reason={reason}
|
||||
labels={{
|
||||
requiredAction: requiredActionLabel,
|
||||
actor: actorLabel,
|
||||
target: destinationLabel
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function ValidationWorkflowGuidance({
|
||||
errors,
|
||||
warnings,
|
||||
stale
|
||||
}: {
|
||||
errors: number;
|
||||
warnings: number;
|
||||
stale: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="review-workflow-guidance">
|
||||
{errors > 0 && (
|
||||
<InterventionHint
|
||||
tone="danger"
|
||||
summary={i18nMessage("i18n:govoplan-campaign.value0_blocking_validation_issue_s_prevent_building.b1b18d9a", { value0: errors })}
|
||||
requiredAction={i18nMessage("i18n:govoplan-campaign.correct_the_affected_campaign_data_then_validate_again.ae956e99")}
|
||||
destination={i18nMessage("i18n:govoplan-campaign.campaign_identity_sender_recipients_template_or_files.69bc29f0")}
|
||||
/>
|
||||
)}
|
||||
{stale && (
|
||||
<InterventionHint
|
||||
tone="warning"
|
||||
summary={i18nMessage("i18n:govoplan-campaign.the_validation_evidence_is_stale.408e8930")}
|
||||
requiredAction={i18nMessage("i18n:govoplan-campaign.run_validation_again_before_relying_on_this_result.97116fc9")}
|
||||
destination={i18nMessage("i18n:govoplan-campaign.review_send_validate.c1b403c7")}
|
||||
/>
|
||||
)}
|
||||
{warnings > 0 && (
|
||||
<InterventionHint
|
||||
tone="warning"
|
||||
summary={i18nMessage("i18n:govoplan-campaign.value0_validation_warning_s_need_review_but_do_not_block.ff03efd1", { value0: warnings })}
|
||||
requiredAction={i18nMessage("i18n:govoplan-campaign.inspect_the_warning_details_correct_the_data_or_accept_t.a558e0d4")}
|
||||
destination={i18nMessage("i18n:govoplan-campaign.review_send_validate.c1b403c7")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BuiltMessageReviewProgress({ progress }: { progress: BuildReviewProgress }) {
|
||||
const blockerDetail = progress.blocking > 0
|
||||
? i18nMessage("i18n:govoplan-campaign.must_be_corrected_before_delivery.b8e6a54b")
|
||||
: i18nMessage("i18n:govoplan-campaign.no_critical_built_message_blockers_remain.97b3c64f");
|
||||
const individualDetail = progress.individualRemaining > 0
|
||||
? i18nMessage("i18n:govoplan-campaign.requires_an_individual_decision.44cd81c9")
|
||||
: i18nMessage("i18n:govoplan-campaign.all_required_review_decisions_are_complete.503919dc");
|
||||
const groupDetail = progress.groupRemaining > 0
|
||||
? i18nMessage("i18n:govoplan-campaign.may_be_accepted_together_after_critical_review_is_comple.8753522f")
|
||||
: progress.groupRequired > 0
|
||||
? i18nMessage("i18n:govoplan-campaign.already_acknowledged_in_the_completed_review.76a263f6")
|
||||
: i18nMessage("i18n:govoplan-campaign.all_required_review_decisions_are_complete.503919dc");
|
||||
|
||||
return (
|
||||
<GuidedReviewList
|
||||
className="review-workflow-progress"
|
||||
items={[
|
||||
{
|
||||
label: i18nMessage("i18n:govoplan-campaign.critical_blockers.8a37e088"),
|
||||
value: progress.blocking,
|
||||
detail: blockerDetail,
|
||||
tone: progress.blocking > 0 ? "danger" : "success"
|
||||
},
|
||||
{
|
||||
label: i18nMessage("i18n:govoplan-campaign.individual_review.402783bf"),
|
||||
value: `${progress.individualReviewed} / ${progress.individualRequired}`,
|
||||
detail: individualDetail,
|
||||
tone: progress.individualRemaining > 0 ? "warning" : "success"
|
||||
},
|
||||
{
|
||||
label: i18nMessage("i18n:govoplan-campaign.group_review.a809a9d9"),
|
||||
value: `${progress.groupReviewed} / ${progress.groupRequired}`,
|
||||
detail: groupDetail,
|
||||
tone: progress.groupRemaining > 0 ? "warning" : "success"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function BuiltMessageWorkflowGuidance({
|
||||
progress,
|
||||
buildWarnings
|
||||
}: {
|
||||
progress: BuildReviewProgress;
|
||||
buildWarnings: number;
|
||||
}) {
|
||||
const showUnacknowledgedWarning = progress.remaining === 0 && buildWarnings > 0;
|
||||
return (
|
||||
<div className="review-workflow-guidance">
|
||||
{progress.blocking > 0 && (
|
||||
<InterventionHint
|
||||
tone="danger"
|
||||
summary={i18nMessage("i18n:govoplan-campaign.value0_built_message_s_are_blocked_or_failed.8061ea60", { value0: progress.blocking })}
|
||||
requiredAction={i18nMessage("i18n:govoplan-campaign.correct_the_affected_recipient_template_mail_or_attachme.5a65e373")}
|
||||
destination={i18nMessage("i18n:govoplan-campaign.sender_recipients_template_mail_settings_or_files.bdd91b62")}
|
||||
/>
|
||||
)}
|
||||
{progress.remaining > 0 && (
|
||||
<InterventionHint
|
||||
tone="warning"
|
||||
summary={i18nMessage("i18n:govoplan-campaign.value0_review_decision_s_remain_value1_individual_value2.4a6a503c", {
|
||||
value0: progress.remaining,
|
||||
value1: progress.individualRemaining,
|
||||
value2: progress.groupRemaining
|
||||
})}
|
||||
requiredAction={i18nMessage("i18n:govoplan-campaign.open_every_critical_message_and_record_a_decision_then_e.9e08c029")}
|
||||
destination={i18nMessage("i18n:govoplan-campaign.review_send_built_messages.6b030946")}
|
||||
/>
|
||||
)}
|
||||
{showUnacknowledgedWarning && (
|
||||
<InterventionHint
|
||||
tone="warning"
|
||||
summary={i18nMessage("i18n:govoplan-campaign.value0_built_message_warning_s_need_acknowledgement.7d8e30a2", { value0: buildWarnings })}
|
||||
requiredAction={i18nMessage("i18n:govoplan-campaign.inspect_the_warning_details_correct_the_data_or_accept_t.a558e0d4")}
|
||||
destination={i18nMessage("i18n:govoplan-campaign.review_send_built_messages.6b030946")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
export type BuildReviewProgressInput = {
|
||||
blocking: number;
|
||||
individualRequired: number;
|
||||
individualReviewed: number;
|
||||
groupRequired: number;
|
||||
reviewComplete: boolean;
|
||||
};
|
||||
|
||||
export type BuildReviewProgress = {
|
||||
blocking: number;
|
||||
individualRequired: number;
|
||||
individualReviewed: number;
|
||||
individualRemaining: number;
|
||||
groupRequired: number;
|
||||
groupReviewed: number;
|
||||
groupRemaining: number;
|
||||
required: number;
|
||||
reviewed: number;
|
||||
remaining: number;
|
||||
};
|
||||
|
||||
function count(value: number): number {
|
||||
return Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
|
||||
}
|
||||
|
||||
export function calculateBuildReviewProgress(input: BuildReviewProgressInput): BuildReviewProgress {
|
||||
const blocking = count(input.blocking);
|
||||
const individualRequired = count(input.individualRequired);
|
||||
const groupRequired = count(input.groupRequired);
|
||||
const individualReviewed = input.reviewComplete
|
||||
? individualRequired
|
||||
: Math.min(individualRequired, count(input.individualReviewed));
|
||||
const groupReviewed = input.reviewComplete ? groupRequired : 0;
|
||||
const individualRemaining = individualRequired - individualReviewed;
|
||||
const groupRemaining = groupRequired - groupReviewed;
|
||||
|
||||
return {
|
||||
blocking,
|
||||
individualRequired,
|
||||
individualReviewed,
|
||||
individualRemaining,
|
||||
groupRequired,
|
||||
groupReviewed,
|
||||
groupRemaining,
|
||||
required: individualRequired + groupRequired,
|
||||
reviewed: individualReviewed + groupReviewed,
|
||||
remaining: individualRemaining + groupRemaining
|
||||
};
|
||||
}
|
||||
@@ -4,6 +4,35 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"en": {
|
||||
"i18n:govoplan-campaign.guided_workflows": "Guided workflows",
|
||||
"i18n:govoplan-campaign.no_guided_workflows": "No guided workflows are available.",
|
||||
"i18n:govoplan-campaign.required_action.f2429497": "Required action",
|
||||
"i18n:govoplan-campaign.who_can_resolve_this.e60939b2": "Who can resolve this",
|
||||
"i18n:govoplan-campaign.where_to_go.bb1c6969": "Where to go",
|
||||
"i18n:govoplan-campaign.critical_blockers.8a37e088": "Critical blockers",
|
||||
"i18n:govoplan-campaign.remaining.cc632b5e": "Remaining",
|
||||
"i18n:govoplan-campaign.individual_review.402783bf": "Individual review",
|
||||
"i18n:govoplan-campaign.group_review.a809a9d9": "Group review",
|
||||
"i18n:govoplan-campaign.value0_blocking_validation_issue_s_prevent_building.b1b18d9a": "{value0} blocking validation issue(s) prevent building.",
|
||||
"i18n:govoplan-campaign.correct_the_affected_campaign_data_then_validate_again.ae956e99": "Correct the affected campaign data, then validate again.",
|
||||
"i18n:govoplan-campaign.campaign_identity_sender_recipients_template_or_files.69bc29f0": "Campaign identity, Sender & Recipients, Template, or Files",
|
||||
"i18n:govoplan-campaign.campaign_editor_or_reviewer.7869b106": "Campaign editor or reviewer",
|
||||
"i18n:govoplan-campaign.value0_validation_warning_s_need_review_but_do_not_block.ff03efd1": "{value0} validation warning(s) need review but do not block building.",
|
||||
"i18n:govoplan-campaign.inspect_the_warning_details_correct_the_data_or_accept_t.a558e0d4": "Inspect the warning details; correct the data or accept the documented condition.",
|
||||
"i18n:govoplan-campaign.review_send_validate.c1b403c7": "Review & Send - Validate",
|
||||
"i18n:govoplan-campaign.the_validation_evidence_is_stale.408e8930": "The validation evidence is stale.",
|
||||
"i18n:govoplan-campaign.run_validation_again_before_relying_on_this_result.97116fc9": "Run validation again before relying on this result.",
|
||||
"i18n:govoplan-campaign.value0_built_message_s_are_blocked_or_failed.8061ea60": "{value0} built message(s) are blocked or failed.",
|
||||
"i18n:govoplan-campaign.correct_the_affected_recipient_template_mail_or_attachme.5a65e373": "Correct the affected recipient, template, mail, or attachment data and rebuild.",
|
||||
"i18n:govoplan-campaign.sender_recipients_template_mail_settings_or_files.bdd91b62": "Sender & Recipients, Template, Mail settings, or Files",
|
||||
"i18n:govoplan-campaign.value0_review_decision_s_remain_value1_individual_value2.4a6a503c": "{value0} review decision(s) remain ({value1} individual, {value2} group).",
|
||||
"i18n:govoplan-campaign.open_every_critical_message_and_record_a_decision_then_e.9e08c029": "Open every critical message and record a decision; then explicitly accept any non-critical group.",
|
||||
"i18n:govoplan-campaign.review_send_built_messages.6b030946": "Review & Send - Built messages",
|
||||
"i18n:govoplan-campaign.value0_built_message_warning_s_need_acknowledgement.7d8e30a2": "{value0} built message warning(s) need acknowledgement.",
|
||||
"i18n:govoplan-campaign.must_be_corrected_before_delivery.b8e6a54b": "Must be corrected before delivery.",
|
||||
"i18n:govoplan-campaign.requires_an_individual_decision.44cd81c9": "Requires an individual decision.",
|
||||
"i18n:govoplan-campaign.may_be_accepted_together_after_critical_review_is_comple.8753522f": "May be accepted together after critical review is complete.",
|
||||
"i18n:govoplan-campaign.already_acknowledged_in_the_completed_review.76a263f6": "Already acknowledged in the completed review.",
|
||||
"i18n:govoplan-campaign.all_required_review_decisions_are_complete.503919dc": "All required review decisions are complete.",
|
||||
"i18n:govoplan-campaign.no_critical_built_message_blockers_remain.97b3c64f": "No critical built-message blockers remain.",
|
||||
"i18n:govoplan-campaign.1_campaign.ccd70074": "1 campaign",
|
||||
"i18n:govoplan-campaign.2_campaigns.35b84804": "2 campaigns",
|
||||
"i18n:govoplan-campaign.appended.979f5a82": "Appended",
|
||||
@@ -1304,6 +1333,35 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"de": {
|
||||
"i18n:govoplan-campaign.guided_workflows": "Geführte Abläufe",
|
||||
"i18n:govoplan-campaign.no_guided_workflows": "Es sind keine geführten Abläufe verfügbar.",
|
||||
"i18n:govoplan-campaign.required_action.f2429497": "Erforderliche Aktion",
|
||||
"i18n:govoplan-campaign.who_can_resolve_this.e60939b2": "Wer kann dies beheben",
|
||||
"i18n:govoplan-campaign.where_to_go.bb1c6969": "Ziel",
|
||||
"i18n:govoplan-campaign.critical_blockers.8a37e088": "Kritische Blockaden",
|
||||
"i18n:govoplan-campaign.remaining.cc632b5e": "Verbleibend",
|
||||
"i18n:govoplan-campaign.individual_review.402783bf": "Einzelprüfung",
|
||||
"i18n:govoplan-campaign.group_review.a809a9d9": "Gruppenprüfung",
|
||||
"i18n:govoplan-campaign.value0_blocking_validation_issue_s_prevent_building.b1b18d9a": "{value0} blockierende Validierungsprobleme verhindern die Erstellung.",
|
||||
"i18n:govoplan-campaign.correct_the_affected_campaign_data_then_validate_again.ae956e99": "Korrigieren Sie die betroffenen Kampagnendaten und validieren Sie erneut.",
|
||||
"i18n:govoplan-campaign.campaign_identity_sender_recipients_template_or_files.69bc29f0": "Kampagnenidentität, Absender & Empfänger, Vorlage oder Dateien",
|
||||
"i18n:govoplan-campaign.campaign_editor_or_reviewer.7869b106": "Kampagnenbearbeitung oder -prüfung",
|
||||
"i18n:govoplan-campaign.value0_validation_warning_s_need_review_but_do_not_block.ff03efd1": "{value0} Validierungswarnungen müssen geprüft werden, verhindern die Erstellung aber nicht.",
|
||||
"i18n:govoplan-campaign.inspect_the_warning_details_correct_the_data_or_accept_t.a558e0d4": "Prüfen Sie die Warnungsdetails; korrigieren Sie die Daten oder akzeptieren Sie die dokumentierte Bedingung.",
|
||||
"i18n:govoplan-campaign.review_send_validate.c1b403c7": "Prüfen & Senden - Validieren",
|
||||
"i18n:govoplan-campaign.the_validation_evidence_is_stale.408e8930": "Der Validierungsnachweis ist veraltet.",
|
||||
"i18n:govoplan-campaign.run_validation_again_before_relying_on_this_result.97116fc9": "Validieren Sie erneut, bevor Sie sich auf dieses Ergebnis verlassen.",
|
||||
"i18n:govoplan-campaign.value0_built_message_s_are_blocked_or_failed.8061ea60": "{value0} erstellte Nachrichten sind blockiert oder fehlgeschlagen.",
|
||||
"i18n:govoplan-campaign.correct_the_affected_recipient_template_mail_or_attachme.5a65e373": "Korrigieren Sie die betroffenen Empfänger-, Vorlagen-, Mail- oder Anhangsdaten und erstellen Sie erneut.",
|
||||
"i18n:govoplan-campaign.sender_recipients_template_mail_settings_or_files.bdd91b62": "Absender & Empfänger, Vorlage, Mail-Einstellungen oder Dateien",
|
||||
"i18n:govoplan-campaign.value0_review_decision_s_remain_value1_individual_value2.4a6a503c": "{value0} Prüfentscheidungen verbleiben ({value1} einzeln, {value2} als Gruppe).",
|
||||
"i18n:govoplan-campaign.open_every_critical_message_and_record_a_decision_then_e.9e08c029": "Öffnen Sie jede kritische Nachricht und dokumentieren Sie eine Entscheidung; akzeptieren Sie anschließend ausdrücklich die nichtkritische Gruppe.",
|
||||
"i18n:govoplan-campaign.review_send_built_messages.6b030946": "Prüfen & Senden - Erstellte Nachrichten",
|
||||
"i18n:govoplan-campaign.value0_built_message_warning_s_need_acknowledgement.7d8e30a2": "{value0} Warnungen zu erstellten Nachrichten müssen bestätigt werden.",
|
||||
"i18n:govoplan-campaign.must_be_corrected_before_delivery.b8e6a54b": "Muss vor dem Versand korrigiert werden.",
|
||||
"i18n:govoplan-campaign.requires_an_individual_decision.44cd81c9": "Erfordert eine Einzelentscheidung.",
|
||||
"i18n:govoplan-campaign.may_be_accepted_together_after_critical_review_is_comple.8753522f": "Kann nach Abschluss der kritischen Prüfung gemeinsam akzeptiert werden.",
|
||||
"i18n:govoplan-campaign.already_acknowledged_in_the_completed_review.76a263f6": "In der abgeschlossenen Prüfung bereits bestätigt.",
|
||||
"i18n:govoplan-campaign.all_required_review_decisions_are_complete.503919dc": "Alle erforderlichen Prüfentscheidungen sind abgeschlossen.",
|
||||
"i18n:govoplan-campaign.no_critical_built_message_blockers_remain.97b3c64f": "Es verbleiben keine kritischen Blockaden für erstellte Nachrichten.",
|
||||
"i18n:govoplan-campaign.1_campaign.ccd70074": "1 campaign",
|
||||
"i18n:govoplan-campaign.2_campaigns.35b84804": "2 campaigns",
|
||||
"i18n:govoplan-campaign.appended.979f5a82": "Abgelegt",
|
||||
|
||||
@@ -1627,6 +1627,16 @@
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.review-workflow-guidance {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.review-workflow-progress {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.attachment-linking-preview {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const guidance = readFileSync(resolve(here, "../src/features/campaigns/review/ReviewWorkflowGuidance.tsx"), "utf8");
|
||||
const page = readFileSync(resolve(here, "../src/features/campaigns/ReviewSendPage.tsx"), "utf8");
|
||||
|
||||
assert.match(guidance, /<ActionBlockerHint/);
|
||||
assert.match(guidance, /<GuidedReviewList/);
|
||||
assert.match(guidance, /tone="danger"/);
|
||||
assert.match(guidance, /tone="warning"/);
|
||||
assert.match(guidance, /requiredAction: requiredActionLabel/);
|
||||
assert.match(guidance, /target: destinationLabel/);
|
||||
assert.match(page, /calculateBuildReviewProgress/);
|
||||
assert.match(page, /<WorkflowFact label="i18n:govoplan-campaign\.remaining\.cc632b5e"/);
|
||||
assert.doesNotMatch(page, /blocked_or_failed_message_s_must_be_resolved_bef/);
|
||||
assert.doesNotMatch(page, /resolve_the_blocking_entries_then_validate_again/);
|
||||
@@ -0,0 +1,54 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { calculateBuildReviewProgress } from "../src/features/campaigns/review/reviewProgress.ts";
|
||||
|
||||
test("separates blocking, individual, and group review work", () => {
|
||||
assert.deepEqual(calculateBuildReviewProgress({
|
||||
blocking: 2,
|
||||
individualRequired: 3,
|
||||
individualReviewed: 1,
|
||||
groupRequired: 4,
|
||||
reviewComplete: false
|
||||
}), {
|
||||
blocking: 2,
|
||||
individualRequired: 3,
|
||||
individualReviewed: 1,
|
||||
individualRemaining: 2,
|
||||
groupRequired: 4,
|
||||
groupReviewed: 0,
|
||||
groupRemaining: 4,
|
||||
required: 7,
|
||||
reviewed: 1,
|
||||
remaining: 6
|
||||
});
|
||||
});
|
||||
|
||||
test("a recorded review completion acknowledges every review decision", () => {
|
||||
const progress = calculateBuildReviewProgress({
|
||||
blocking: 0,
|
||||
individualRequired: 3,
|
||||
individualReviewed: 3,
|
||||
groupRequired: 4,
|
||||
reviewComplete: true
|
||||
});
|
||||
assert.equal(progress.reviewed, 7);
|
||||
assert.equal(progress.remaining, 0);
|
||||
assert.equal(progress.individualRemaining, 0);
|
||||
assert.equal(progress.groupRemaining, 0);
|
||||
});
|
||||
|
||||
test("normalizes malformed counters without overstating progress", () => {
|
||||
const progress = calculateBuildReviewProgress({
|
||||
blocking: -2,
|
||||
individualRequired: 2.9,
|
||||
individualReviewed: 99,
|
||||
groupRequired: Number.NaN,
|
||||
reviewComplete: false
|
||||
});
|
||||
assert.equal(progress.blocking, 0);
|
||||
assert.equal(progress.individualRequired, 2);
|
||||
assert.equal(progress.individualReviewed, 2);
|
||||
assert.equal(progress.required, 2);
|
||||
assert.equal(progress.remaining, 0);
|
||||
});
|
||||
Reference in New Issue
Block a user