Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/models/componentSpec/validation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type ValidationIssueCode =
| "MISSING_REQUIRED_INPUT"
| "EMPTY_TASK_NAME"
| "MISSING_COMPONENT_REF"
| "COMPONENT_HYDRATION_FAILED"
| "BAD_INPUT_REFERENCE"
| "BAD_TASK_REFERENCE"
| "BAD_OUTPUT_REFERENCE"
Expand Down
10 changes: 10 additions & 0 deletions src/models/componentSpec/validation/validateSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,16 @@ function validateSingleTask(
severity: "error",
issueCode: "MISSING_COMPONENT_REF",
});
} else if (!task.subgraphSpec && !task.resolvedComponentSpec) {
// The ref points at a loadable component (url/digest/text) but hydration
// never populated its spec, so the component could not be resolved.
issues.push({
type: "task",
message: "Failed to load component",
entityId: task.$id,
severity: "error",
issueCode: "COMPONENT_HYDRATION_FAILED",
});
}

issues.push(...validateTaskArguments(task, spec));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ const RESOLUTION_MAP: Record<ValidationIssueCode, ResolutionResolver> = {
DUPLICATE_OUTPUT_NAME: (p) => renderDuplicateNameResolution("output", p),
MISSING_COMPONENT_REF: (p) =>
renderDeleteResolution("Delete Task", "task", p),
COMPONENT_HYDRATION_FAILED: () =>
renderInfoResolution(
"The component could not be loaded from its source. Check the component URL or your connection, then reload the pipeline.",
),
BAD_INPUT_REFERENCE: renderBadRefResolution,
BAD_TASK_REFERENCE: renderBadRefResolution,
BAD_OUTPUT_REFERENCE: renderBadRefResolution,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import { ComponentSpec } from "@/models/componentSpec/entities/componentSpec";
import { Task } from "@/models/componentSpec/entities/task";
import { IncrementingIdGenerator } from "@/models/componentSpec/factories/idGenerator";
import { validateSpec } from "@/models/componentSpec/validation/validateSpec";
import { hydrateLoadedSpecRefs } from "@/routes/v2/pages/Editor/utils/hydrateSpecRefs";
import { hydrateComponentReference } from "@/services/componentService";
import type {
Expand Down Expand Up @@ -66,6 +67,31 @@ describe("hydrateLoadedSpecRefs", () => {
expect(task.resolvedComponentSpec?.name).toBe("Foo");
});

it("leaves the ref untouched and surfaces a validation issue on failure", async () => {
const task = new Task({
$id: idGen.next("task"),
name: "Process",
componentRef: { url: "https://example.com/comp.yaml", digest: "abc" },
});
const spec = specWithTask(task);

mockHydrate.mockResolvedValue(null);

await hydrateLoadedSpecRefs(spec);

expect(task.componentRef.url).toBe("https://example.com/comp.yaml");
expect(task.resolvedComponentSpec).toBeUndefined();

const issues = validateSpec(spec);
expect(
issues.some(
(issue) =>
issue.entityId === task.$id &&
issue.issueCode === "COMPONENT_HYDRATION_FAILED",
),
).toBe(true);
});

it("recurses into subgraph tasks without re-hydrating the subgraph ref", async () => {
const innerTask = new Task({
$id: idGen.next("task"),
Expand Down
Loading