refactor: make lifecycle diagrams primary
This commit is contained in:
@@ -27,6 +27,22 @@ describe("AuthoringPhaseVisual", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["discover", "inventory"],
|
||||||
|
["draft", "workflow-draft"],
|
||||||
|
["diagnose", "workflow-diagnostic"],
|
||||||
|
["repair", "workflow-repair"],
|
||||||
|
["artifact", "artifact"],
|
||||||
|
["deployment", "deployment"],
|
||||||
|
] as const)("makes the %s diagram primary", (step, diagramKind) => {
|
||||||
|
renderStep(step);
|
||||||
|
|
||||||
|
expect(screen.getByTestId("authoring-primary-diagram")).toHaveAttribute(
|
||||||
|
"data-diagram-kind",
|
||||||
|
diagramKind,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("renders total inventory separately from configured local sources", () => {
|
it("renders total inventory separately from configured local sources", () => {
|
||||||
renderStep("discover");
|
renderStep("discover");
|
||||||
const result = screen.getByRole("region", { name: /source inventory result/i });
|
const result = screen.getByRole("region", { name: /source inventory result/i });
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
import type { LucideIcon } from "lucide-react";
|
import type { LucideIcon } from "lucide-react";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import type { PreparedLifecycleStepProjection } from "./authoring-projection.js";
|
import type { PreparedLifecycleStepProjection } from "./authoring-projection.js";
|
||||||
|
import { AuthoringLifecycleDiagram } from "./AuthoringLifecycleDiagram.js";
|
||||||
|
import { AuthoringWorkflowDiagram } from "./AuthoringWorkflowDiagram.js";
|
||||||
import { reviewedAuthoringEvidenceFor } from "./reviewed-authoring-evidence.js";
|
import { reviewedAuthoringEvidenceFor } from "./reviewed-authoring-evidence.js";
|
||||||
|
|
||||||
type Evidence = PreparedLifecycleStepProjection["evidence"];
|
type Evidence = PreparedLifecycleStepProjection["evidence"];
|
||||||
@@ -50,13 +52,11 @@ const EvidenceRows = ({
|
|||||||
</dl>
|
</dl>
|
||||||
);
|
);
|
||||||
|
|
||||||
const StringList = ({ label, items }: { readonly label: string; readonly items: readonly string[] }) => (
|
const RouteList = ({ routes }: { readonly routes: readonly string[] }) => (
|
||||||
<section className="authoring-result__list-section">
|
<section className="authoring-result__list-section">
|
||||||
<h3>{label}</h3>
|
<h3>Routes</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{items.map((item) => (
|
{routes.map((route) => <li key={route}><code>{route}</code></li>)}
|
||||||
<li key={item}><code>{item}</code></li>
|
|
||||||
))}
|
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@@ -83,95 +83,108 @@ const ResultRoot = ({
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ResultComposition = ({
|
||||||
|
diagramKind,
|
||||||
|
diagram,
|
||||||
|
receiptLabel,
|
||||||
|
receipt,
|
||||||
|
}: {
|
||||||
|
readonly diagramKind: string;
|
||||||
|
readonly diagram: ReactNode;
|
||||||
|
readonly receiptLabel: string;
|
||||||
|
readonly receipt: ReactNode;
|
||||||
|
}) => (
|
||||||
|
<div className="authoring-result__composition">
|
||||||
|
<div
|
||||||
|
className="authoring-result__diagram"
|
||||||
|
data-testid="authoring-primary-diagram"
|
||||||
|
data-diagram-kind={diagramKind}
|
||||||
|
>
|
||||||
|
{diagram}
|
||||||
|
</div>
|
||||||
|
<aside className="authoring-result__receipt" aria-label={receiptLabel}>
|
||||||
|
{receipt}
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const InventoryResult = ({ evidence }: { readonly evidence: EvidenceOf<"inventory"> }) => (
|
const InventoryResult = ({ evidence }: { readonly evidence: EvidenceOf<"inventory"> }) => (
|
||||||
<ResultRoot kind={evidence.kind} label="source inventory result">
|
<ResultRoot kind={evidence.kind} label="source inventory result">
|
||||||
<ResultHeader
|
<ResultHeader icon={Database} label="SOURCE INVENTORY" status={`${evidence.sourceCount} TOTAL SOURCES`} />
|
||||||
icon={Database}
|
<ResultComposition
|
||||||
label="SOURCE INVENTORY"
|
diagramKind="inventory"
|
||||||
status={`${evidence.sourceCount} TOTAL SOURCES`}
|
diagram={<AuthoringLifecycleDiagram evidence={evidence} />}
|
||||||
|
receiptLabel="source inventory technical receipt"
|
||||||
|
receipt={(
|
||||||
|
<>
|
||||||
|
<h3>Configured local sources ({evidence.sources.length})</h3>
|
||||||
|
<EvidenceRows
|
||||||
|
rows={[
|
||||||
|
{ label: "Inventory", value: <strong>{evidence.sourceCount} total inventory sources</strong> },
|
||||||
|
{ label: "Configured", value: <strong>{evidence.sources.length} configured local source IDs</strong> },
|
||||||
|
{ label: "Inputs", value: <code>{evidence.capability.inputs.join(", ")}</code> },
|
||||||
|
{ label: "Outputs", value: <code>{evidence.capability.outputs.join(", ")}</code> },
|
||||||
|
{ label: "Outcomes", value: <code>{evidence.capability.outcomes.join(", ")}</code> },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="authoring-result__body">
|
|
||||||
<EvidenceRows
|
|
||||||
rows={[
|
|
||||||
{
|
|
||||||
label: "Inventory",
|
|
||||||
value: <strong>{evidence.sourceCount} total inventory sources</strong>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Configured local sources",
|
|
||||||
value: <strong>{evidence.sources.length} configured local source IDs</strong>,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<StringList label={`Configured local sources (${evidence.sources.length})`} items={evidence.sources} />
|
|
||||||
<section className="authoring-result__contract">
|
|
||||||
<h3>Capability contract</h3>
|
|
||||||
<EvidenceRows
|
|
||||||
rows={[
|
|
||||||
{ label: "Capability", value: <code>{evidence.capability.name}</code> },
|
|
||||||
{ label: "Inputs", value: <code>{evidence.capability.inputs.join(", ")}</code> },
|
|
||||||
{ label: "Outputs", value: <code>{evidence.capability.outputs.join(", ")}</code> },
|
|
||||||
{ label: "Outcomes", value: <code>{evidence.capability.outcomes.join(", ")}</code> },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</ResultRoot>
|
</ResultRoot>
|
||||||
);
|
);
|
||||||
|
|
||||||
const DraftResult = ({ evidence }: { readonly evidence: EvidenceOf<"draft"> }) => (
|
const DraftResult = ({ evidence }: { readonly evidence: EvidenceOf<"draft"> }) => (
|
||||||
<ResultRoot kind={evidence.kind} label="draft structure result">
|
<ResultRoot kind={evidence.kind} label="draft structure result">
|
||||||
<ResultHeader icon={Workflow} label="VALID DRAFT" status="Valid" revision={evidence.revision} />
|
<ResultHeader icon={Workflow} label="VALID DRAFT" status="Valid" revision={evidence.revision} />
|
||||||
<div className="authoring-result__body">
|
<ResultComposition
|
||||||
<EvidenceRows
|
diagramKind="workflow-draft"
|
||||||
rows={[
|
diagram={<AuthoringWorkflowDiagram mode="draft" evidence={evidence} />}
|
||||||
{ label: "Workspace", value: <code>{evidence.workspaceId}</code> },
|
receiptLabel="draft structure technical receipt"
|
||||||
{ label: "Steps", value: evidence.stepCount },
|
receipt={(
|
||||||
{ label: "Routes", value: evidence.routeCount },
|
<>
|
||||||
]}
|
<EvidenceRows
|
||||||
/>
|
rows={[
|
||||||
<StringList label="Steps" items={evidence.steps} />
|
{ label: "Workspace", value: <code>{evidence.workspaceId}</code> },
|
||||||
<StringList label="Routes" items={evidence.routes} />
|
{ label: "Steps", value: evidence.stepCount },
|
||||||
</div>
|
{ label: "Routes", value: evidence.routeCount },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<RouteList routes={evidence.routes} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</ResultRoot>
|
</ResultRoot>
|
||||||
);
|
);
|
||||||
|
|
||||||
const DiagnosticResult = ({ evidence }: { readonly evidence: EvidenceOf<"diagnostic"> }) => (
|
const DiagnosticResult = ({ evidence }: { readonly evidence: EvidenceOf<"diagnostic"> }) => (
|
||||||
<ResultRoot kind={evidence.kind} label="draft validation diagnostic">
|
<ResultRoot kind={evidence.kind} label="draft validation diagnostic">
|
||||||
<ResultHeader
|
<ResultHeader icon={AlertTriangle} label="INVALID DRAFT" status="Invalid" revision={evidence.revision} />
|
||||||
icon={AlertTriangle}
|
<ResultComposition
|
||||||
label="INVALID DRAFT"
|
diagramKind="workflow-diagnostic"
|
||||||
status={evidence.status === "invalid" ? "Invalid" : evidence.status}
|
diagram={<AuthoringWorkflowDiagram mode="diagnostic" evidence={evidence} />}
|
||||||
revision={evidence.revision}
|
receiptLabel="draft validation technical receipt"
|
||||||
|
receipt={(
|
||||||
|
<>
|
||||||
|
<EvidenceRows
|
||||||
|
rows={[
|
||||||
|
{ label: "Diagnostic", value: <code>{evidence.diagnostic.code}</code> },
|
||||||
|
{ label: "Path", value: <code>{evidence.diagnostic.path}</code> },
|
||||||
|
{ label: "Message", value: evidence.diagnostic.message },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<p className="authoring-result__explanation">{evidence.diagnostic.explanation}</p>
|
||||||
|
<section className="authoring-result__fault-injection" role="note" aria-label={evidence.faultInjection.label}>
|
||||||
|
<span>{evidence.faultInjection.label}</span>
|
||||||
|
<code>{evidence.faultInjection.command}</code>
|
||||||
|
<p>Valid revision {evidence.faultInjection.fromRevision} became invalid revision {evidence.faultInjection.toRevision}.</p>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="authoring-result__body" data-result-primary="true">
|
|
||||||
<EvidenceRows
|
|
||||||
rows={[
|
|
||||||
{ label: "Diagnostic", value: <code>{evidence.diagnostic.code}</code> },
|
|
||||||
{ label: "Path", value: <code>{evidence.diagnostic.path}</code> },
|
|
||||||
{ label: "Message", value: evidence.diagnostic.message },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<p className="authoring-result__explanation">{evidence.diagnostic.explanation}</p>
|
|
||||||
</div>
|
|
||||||
<aside
|
|
||||||
className="authoring-result__fault-injection"
|
|
||||||
role="note"
|
|
||||||
aria-label={evidence.faultInjection.label}
|
|
||||||
>
|
|
||||||
<span>{evidence.faultInjection.label}</span>
|
|
||||||
<code>{evidence.faultInjection.command}</code>
|
|
||||||
<p>
|
|
||||||
Revision {evidence.faultInjection.fromRevision} was valid; this prepared edit intentionally
|
|
||||||
produced invalid revision {evidence.faultInjection.toRevision} for diagnosis.
|
|
||||||
</p>
|
|
||||||
</aside>
|
|
||||||
</ResultRoot>
|
</ResultRoot>
|
||||||
);
|
);
|
||||||
|
|
||||||
const RepairResult = ({ evidence }: { readonly evidence: EvidenceOf<"repair"> }) => {
|
const RepairResult = ({ evidence }: { readonly evidence: EvidenceOf<"repair"> }) => {
|
||||||
// The repair record keeps the successful result compact; prior invalid context
|
|
||||||
// comes from the same reviewed catalog and is never presented as a new result.
|
|
||||||
const priorEvidence = reviewedAuthoringEvidenceFor("diagnose");
|
const priorEvidence = reviewedAuthoringEvidenceFor("diagnose");
|
||||||
if (priorEvidence.kind !== "diagnostic") {
|
if (priorEvidence.kind !== "diagnostic") {
|
||||||
throw new Error("reviewed repair context has an unexpected shape");
|
throw new Error("reviewed repair context has an unexpected shape");
|
||||||
@@ -179,26 +192,30 @@ const RepairResult = ({ evidence }: { readonly evidence: EvidenceOf<"repair"> })
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ResultRoot kind={evidence.kind} label="route repair result">
|
<ResultRoot kind={evidence.kind} label="route repair result">
|
||||||
<ResultHeader
|
<ResultHeader icon={Route} label="ROUTE REPAIR" status="Valid" revision={evidence.toRevision} />
|
||||||
icon={Route}
|
<ResultComposition
|
||||||
label="ROUTE REPAIR"
|
diagramKind="workflow-repair"
|
||||||
status={evidence.status === "valid" ? "Valid" : evidence.status}
|
diagram={<AuthoringWorkflowDiagram mode="repair" evidence={evidence} />}
|
||||||
revision={evidence.toRevision}
|
receiptLabel="route repair technical receipt"
|
||||||
|
receipt={(
|
||||||
|
<>
|
||||||
|
<aside className="authoring-result__prior" role="note" aria-label="prior validation diagnostic">
|
||||||
|
<span>Prior validation</span>
|
||||||
|
<code>{priorEvidence.diagnostic.code} · {priorEvidence.diagnostic.path}</code>
|
||||||
|
<p>{priorEvidence.diagnostic.message}</p>
|
||||||
|
</aside>
|
||||||
|
<div data-result-primary="true">
|
||||||
|
<EvidenceRows
|
||||||
|
rows={[
|
||||||
|
{ label: "Command", value: <code>{evidence.command}</code> },
|
||||||
|
{ label: "Result", value: <strong>Valid</strong> },
|
||||||
|
{ label: "Diagnostics", value: <strong>{evidence.diagnosticCount} diagnostics</strong> },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<aside className="authoring-result__prior" role="note" aria-label="prior validation diagnostic">
|
|
||||||
<span>Prior validation</span>
|
|
||||||
<code>{priorEvidence.diagnostic.code} · {priorEvidence.diagnostic.path}</code>
|
|
||||||
<p>{priorEvidence.diagnostic.message}</p>
|
|
||||||
</aside>
|
|
||||||
<div className="authoring-result__body" data-result-primary="true">
|
|
||||||
<EvidenceRows
|
|
||||||
rows={[
|
|
||||||
{ label: "Command", value: <code>{evidence.command}</code> },
|
|
||||||
{ label: "Result", value: <strong>Valid</strong> },
|
|
||||||
{ label: "Diagnostics", value: <strong>0 diagnostics</strong> },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ResultRoot>
|
</ResultRoot>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -206,53 +223,42 @@ const RepairResult = ({ evidence }: { readonly evidence: EvidenceOf<"repair"> })
|
|||||||
const ArtifactResult = ({ evidence }: { readonly evidence: EvidenceOf<"artifact"> }) => (
|
const ArtifactResult = ({ evidence }: { readonly evidence: EvidenceOf<"artifact"> }) => (
|
||||||
<ResultRoot kind={evidence.kind} label="immutable artifact result">
|
<ResultRoot kind={evidence.kind} label="immutable artifact result">
|
||||||
<ResultHeader icon={LockKeyhole} label="IMMUTABLE ARTIFACT" status="Immutable" />
|
<ResultHeader icon={LockKeyhole} label="IMMUTABLE ARTIFACT" status="Immutable" />
|
||||||
<div className="authoring-result__body">
|
<ResultComposition
|
||||||
<EvidenceRows
|
diagramKind="artifact"
|
||||||
rows={[
|
diagram={<AuthoringLifecycleDiagram evidence={evidence} />}
|
||||||
{ label: "Artifact", value: <code>{evidence.artifactId}</code> },
|
receiptLabel="immutable artifact technical receipt"
|
||||||
{ label: "Version", value: <strong>Version {evidence.version}</strong> },
|
receipt={(
|
||||||
{ label: "Required sources", value: `${evidence.requiredSources.length} configured local sources` },
|
<EvidenceRows
|
||||||
]}
|
rows={[
|
||||||
/>
|
{ label: "Required sources", value: `${evidence.requiredSources.length} configured local sources` },
|
||||||
<StringList label="Required local sources" items={evidence.requiredSources} />
|
]}
|
||||||
</div>
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</ResultRoot>
|
</ResultRoot>
|
||||||
);
|
);
|
||||||
|
|
||||||
const DeploymentResult = ({ evidence }: { readonly evidence: EvidenceOf<"deployment"> }) => (
|
const DeploymentResult = ({ evidence }: { readonly evidence: EvidenceOf<"deployment"> }) => (
|
||||||
<ResultRoot kind={evidence.kind} label="runnable deployment result">
|
<ResultRoot kind={evidence.kind} label="runnable deployment result">
|
||||||
<ResultHeader
|
<ResultHeader icon={Link2} label="RUNNABLE DEPLOYMENT" status={`${evidence.bindings.length} BINDINGS`} />
|
||||||
icon={Link2}
|
<ResultComposition
|
||||||
label="RUNNABLE DEPLOYMENT"
|
diagramKind="deployment"
|
||||||
status={evidence.status === "runnable" ? "Runnable" : evidence.status}
|
diagram={<AuthoringLifecycleDiagram evidence={evidence} />}
|
||||||
|
receiptLabel="runnable deployment technical receipt"
|
||||||
|
receipt={(
|
||||||
|
<EvidenceRows
|
||||||
|
rows={[
|
||||||
|
{ label: "Bindings", value: <strong>{evidence.bindings.length} valid bindings</strong> },
|
||||||
|
{ label: "Next", value: <span className="authoring-result__status"><CheckCircle2 aria-hidden="true" />Ready for a persisted run</span> },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="authoring-result__body">
|
|
||||||
<EvidenceRows
|
|
||||||
rows={[{ label: "Deployment", value: <code>{evidence.deploymentId}</code> }]}
|
|
||||||
/>
|
|
||||||
<section className="authoring-result__list-section">
|
|
||||||
<h3>Bindings</h3>
|
|
||||||
<ul>
|
|
||||||
{evidence.bindings.map((binding) => (
|
|
||||||
<li key={binding.requirement}>
|
|
||||||
<code>{binding.requirement}</code>
|
|
||||||
<span aria-hidden="true">-></span>
|
|
||||||
<code>{binding.source}</code>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<p className="authoring-result__status"><CheckCircle2 aria-hidden="true" /> Ready for a persisted run</p>
|
|
||||||
</div>
|
|
||||||
</ResultRoot>
|
</ResultRoot>
|
||||||
);
|
);
|
||||||
|
|
||||||
/** Renders one audience-facing product result from the reviewed evidence union. */
|
/** Renders one audience-facing product result from the reviewed evidence union. */
|
||||||
export const AuthoringPhaseVisual = ({
|
export const AuthoringPhaseVisual = ({ projection }: { readonly projection: PreparedLifecycleStepProjection }) => {
|
||||||
projection,
|
|
||||||
}: {
|
|
||||||
readonly projection: PreparedLifecycleStepProjection;
|
|
||||||
}) => {
|
|
||||||
switch (projection.evidence.kind) {
|
switch (projection.evidence.kind) {
|
||||||
case "inventory":
|
case "inventory":
|
||||||
return <InventoryResult evidence={projection.evidence} />;
|
return <InventoryResult evidence={projection.evidence} />;
|
||||||
|
|||||||
Reference in New Issue
Block a user