feat: add authoring lifecycle transformation diagrams
This commit is contained in:
@@ -0,0 +1,55 @@
|
|||||||
|
import { cleanup, render, screen, within } from "@testing-library/react";
|
||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
|
import { projectPreparedLifecycleStep } from "./authoring-projection.js";
|
||||||
|
import { AuthoringLifecycleDiagram } from "./AuthoringLifecycleDiagram.js";
|
||||||
|
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
const renderLifecycle = (step: "discover" | "artifact" | "deployment") => {
|
||||||
|
const projection = projectPreparedLifecycleStep(step);
|
||||||
|
if (
|
||||||
|
projection.evidence.kind !== "inventory"
|
||||||
|
&& projection.evidence.kind !== "artifact"
|
||||||
|
&& projection.evidence.kind !== "deployment"
|
||||||
|
) {
|
||||||
|
throw new Error(`unexpected evidence for ${step}`);
|
||||||
|
}
|
||||||
|
return render(<AuthoringLifecycleDiagram evidence={projection.evidence} />);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("AuthoringLifecycleDiagram", () => {
|
||||||
|
it("shows configured sources feeding the discovered capability", () => {
|
||||||
|
renderLifecycle("discover");
|
||||||
|
const diagram = screen.getByRole("img", { name: /source capability map/i });
|
||||||
|
|
||||||
|
expect(within(diagram).getByText("local.lda_docs")).toBeInTheDocument();
|
||||||
|
expect(within(diagram).getByText("local.lda_report")).toBeInTheDocument();
|
||||||
|
expect(within(diagram).getByText("local.issue_board")).toBeInTheDocument();
|
||||||
|
expect(within(diagram).getByText("local.lda_report.analyze_documents")).toBeInTheDocument();
|
||||||
|
expect(diagram).toHaveAttribute("data-lifecycle-diagram", "inventory");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps the workflow silhouette and requirements attached to the immutable version", () => {
|
||||||
|
renderLifecycle("artifact");
|
||||||
|
const diagram = screen.getByRole("img", { name: /versioned artifact map/i });
|
||||||
|
|
||||||
|
expect(within(diagram).getByText("read_documents")).toBeInTheDocument();
|
||||||
|
expect(within(diagram).getByText("analyze")).toBeInTheDocument();
|
||||||
|
expect(within(diagram).getByText("lda_report_case_study")).toBeInTheDocument();
|
||||||
|
expect(within(diagram).getByText("Version 1")).toBeInTheDocument();
|
||||||
|
expect(within(diagram).getByText("local.lda_docs")).toBeInTheDocument();
|
||||||
|
expect(diagram).toHaveAttribute("data-lifecycle-diagram", "artifact");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("maps every requirement to a concrete source and one deployment", () => {
|
||||||
|
renderLifecycle("deployment");
|
||||||
|
const diagram = screen.getByRole("img", { name: /deployment binding map/i });
|
||||||
|
|
||||||
|
expect(within(diagram).getAllByText("local.lda_docs")).toHaveLength(2);
|
||||||
|
expect(within(diagram).getAllByText("local.lda_report")).toHaveLength(2);
|
||||||
|
expect(within(diagram).getAllByText("local.issue_board")).toHaveLength(2);
|
||||||
|
expect(within(diagram).getByText("lda_report_case_study.default")).toBeInTheDocument();
|
||||||
|
expect(within(diagram).getByText("Runnable")).toBeInTheDocument();
|
||||||
|
expect(diagram).toHaveAttribute("data-lifecycle-diagram", "deployment");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import {
|
||||||
|
ArrowRight,
|
||||||
|
Braces,
|
||||||
|
Cable,
|
||||||
|
CircleCheck,
|
||||||
|
Database,
|
||||||
|
FileLock2,
|
||||||
|
Workflow,
|
||||||
|
} from "lucide-react";
|
||||||
|
import type { ReviewedAuthoringEvidence } from "./reviewed-authoring-evidence.js";
|
||||||
|
import { reviewedAuthoringEvidenceFor } from "./reviewed-authoring-evidence.js";
|
||||||
|
|
||||||
|
type LifecycleEvidence = Extract<
|
||||||
|
ReviewedAuthoringEvidence,
|
||||||
|
{ readonly kind: "inventory" | "artifact" | "deployment" }
|
||||||
|
>;
|
||||||
|
|
||||||
|
type AuthoringLifecycleDiagramProps = {
|
||||||
|
readonly evidence: LifecycleEvidence;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SourceCapabilityMap = ({
|
||||||
|
evidence,
|
||||||
|
}: {
|
||||||
|
readonly evidence: Extract<LifecycleEvidence, { readonly kind: "inventory" }>;
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
className="authoring-lifecycle-diagram authoring-lifecycle-diagram--inventory"
|
||||||
|
role="img"
|
||||||
|
aria-label="Source capability map"
|
||||||
|
data-lifecycle-diagram="inventory"
|
||||||
|
>
|
||||||
|
<div className="authoring-lifecycle-diagram__source-stack">
|
||||||
|
{evidence.sources.map((source) => (
|
||||||
|
<span key={source} className="authoring-lifecycle-diagram__source">
|
||||||
|
<Database aria-hidden="true" />
|
||||||
|
<code>{source}</code>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="authoring-lifecycle-diagram__flow" aria-hidden="true">
|
||||||
|
<span>Discover</span>
|
||||||
|
<ArrowRight />
|
||||||
|
</div>
|
||||||
|
<section className="authoring-lifecycle-diagram__capability">
|
||||||
|
<Braces aria-hidden="true" />
|
||||||
|
<span>Typed capability</span>
|
||||||
|
<strong>{evidence.capability.name}</strong>
|
||||||
|
<dl>
|
||||||
|
<div><dt>Input</dt><dd>{evidence.capability.inputs.join(", ")}</dd></div>
|
||||||
|
<div><dt>Output</dt><dd>{evidence.capability.outputs.join(", ")}</dd></div>
|
||||||
|
<div><dt>Outcome</dt><dd>{evidence.capability.outcomes.join(", ")}</dd></div>
|
||||||
|
</dl>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const VersionedArtifactMap = ({
|
||||||
|
evidence,
|
||||||
|
}: {
|
||||||
|
readonly evidence: Extract<LifecycleEvidence, { readonly kind: "artifact" }>;
|
||||||
|
}) => {
|
||||||
|
const draftEvidence = reviewedAuthoringEvidenceFor("draft");
|
||||||
|
if (draftEvidence.kind !== "draft") {
|
||||||
|
throw new Error("reviewed artifact diagram requires reviewed draft evidence");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="authoring-lifecycle-diagram authoring-lifecycle-diagram--artifact"
|
||||||
|
role="img"
|
||||||
|
aria-label="Versioned artifact map"
|
||||||
|
data-lifecycle-diagram="artifact"
|
||||||
|
>
|
||||||
|
<section className="authoring-lifecycle-diagram__draft-shape">
|
||||||
|
<Workflow aria-hidden="true" />
|
||||||
|
<span>Validated draft</span>
|
||||||
|
<div>
|
||||||
|
{draftEvidence.steps.map((step) => <code key={step}>{step}</code>)}
|
||||||
|
<code>END</code>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div className="authoring-lifecycle-diagram__flow" aria-hidden="true">
|
||||||
|
<span>Freeze</span>
|
||||||
|
<ArrowRight />
|
||||||
|
</div>
|
||||||
|
<section className="authoring-lifecycle-diagram__artifact-object">
|
||||||
|
<FileLock2 aria-hidden="true" />
|
||||||
|
<span>Immutable artifact</span>
|
||||||
|
<strong>{evidence.artifactId}</strong>
|
||||||
|
<b>Version {evidence.version}</b>
|
||||||
|
<div className="authoring-lifecycle-diagram__requirements">
|
||||||
|
{evidence.requiredSources.map((source) => <code key={source}>{source}</code>)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeploymentBindingMap = ({
|
||||||
|
evidence,
|
||||||
|
}: {
|
||||||
|
readonly evidence: Extract<LifecycleEvidence, { readonly kind: "deployment" }>;
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
className="authoring-lifecycle-diagram authoring-lifecycle-diagram--deployment"
|
||||||
|
role="img"
|
||||||
|
aria-label="Deployment binding map"
|
||||||
|
data-lifecycle-diagram="deployment"
|
||||||
|
>
|
||||||
|
<div className="authoring-lifecycle-diagram__bindings">
|
||||||
|
<div className="authoring-lifecycle-diagram__binding-headings" aria-hidden="true">
|
||||||
|
<span>Requirement</span>
|
||||||
|
<span>Configured source</span>
|
||||||
|
</div>
|
||||||
|
{evidence.bindings.map((binding) => (
|
||||||
|
<div className="authoring-lifecycle-diagram__binding" key={binding.requirement}>
|
||||||
|
<code>{binding.requirement}</code>
|
||||||
|
<Cable aria-hidden="true" />
|
||||||
|
<code>{binding.source}</code>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="authoring-lifecycle-diagram__flow" aria-hidden="true">
|
||||||
|
<span>Bind</span>
|
||||||
|
<ArrowRight />
|
||||||
|
</div>
|
||||||
|
<section className="authoring-lifecycle-diagram__deployment-object">
|
||||||
|
<CircleCheck aria-hidden="true" />
|
||||||
|
<span>Deployment</span>
|
||||||
|
<strong>{evidence.deploymentId}</strong>
|
||||||
|
<b>{evidence.status === "runnable" ? "Runnable" : evidence.status}</b>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Renders fixed lifecycle mappings where graph interaction would add no value. */
|
||||||
|
export const AuthoringLifecycleDiagram = ({ evidence }: AuthoringLifecycleDiagramProps) => {
|
||||||
|
switch (evidence.kind) {
|
||||||
|
case "inventory":
|
||||||
|
return <SourceCapabilityMap evidence={evidence} />;
|
||||||
|
case "artifact":
|
||||||
|
return <VersionedArtifactMap evidence={evidence} />;
|
||||||
|
case "deployment":
|
||||||
|
return <DeploymentBindingMap evidence={evidence} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user