Skip to content

Commit 182f57a

Browse files
committed
refactoring: introduce componentSpecFromYaml
1 parent 304f7b9 commit 182f57a

File tree

7 files changed

+115
-119
lines changed

7 files changed

+115
-119
lines changed

src/services/componentService.test.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,15 @@ describe("componentService", () => {
7878

7979
describe("parseComponentData", () => {
8080
it("should parse valid YAML data", () => {
81-
const yamlData = "name: test-component\nversion: 1.2";
82-
const result = parseComponentData(yamlData);
83-
84-
expect(result).toEqual({
81+
const spec = {
8582
name: "test-component",
8683
version: 1.2,
87-
});
84+
implementation: { container: { image: "test" } },
85+
};
86+
const yamlData = yaml.dump(spec);
87+
const result = parseComponentData(yamlData);
88+
89+
expect(result).toEqual(spec);
8890
});
8991

9092
it("should return null for invalid YAML", () => {

src/services/componentService.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import yaml from "js-yaml";
2-
31
import { getAppSettings } from "@/appSettings";
42
import {
53
type ComponentFolder,
@@ -36,6 +34,7 @@ import {
3634
saveComponent,
3735
type UserComponent,
3836
} from "@/utils/localforage";
37+
import { componentSpecFromYaml } from "@/utils/yaml";
3938

4039
export interface ExistingAndNewComponent {
4140
existingComponent: UserComponent | undefined;
@@ -154,7 +153,7 @@ export const fetchAndStoreComponentByUrl = async (
154153
const text = await fetchComponentTextFromUrl(url);
155154
if (text) {
156155
try {
157-
return yaml.load(text) as ComponentSpec;
156+
return componentSpecFromYaml(text);
158157
} catch (error) {
159158
console.error(`Error parsing component at ${url}:`, error);
160159
}
@@ -182,7 +181,7 @@ export const fetchAndStoreComponentByUrl = async (
182181
});
183182

184183
try {
185-
return yaml.load(text) as ComponentSpec;
184+
return componentSpecFromYaml(text);
186185
} catch (error) {
187186
console.error(`Error parsing component at ${url}:`, error);
188187
return null;
@@ -263,7 +262,7 @@ const parseTextToSpec = async (
263262
): Promise<ComponentSpec | null> => {
264263
if (text) {
265264
try {
266-
return yaml.load(text) as ComponentSpec;
265+
return componentSpecFromYaml(text);
267266
} catch (error) {
268267
console.error("Error parsing component text:", error);
269268
}
@@ -316,7 +315,7 @@ export const fetchComponentTextFromUrl = async (
316315
*/
317316
export const parseComponentData = (data: string): ComponentSpec | null => {
318317
try {
319-
return yaml.load(data) as ComponentSpec;
318+
return componentSpecFromYaml(data);
320319
} catch (error) {
321320
console.error("Error parsing component data:", error);
322321
return null;
@@ -445,7 +444,7 @@ async function hydrateFromPartialContentfulComponentReference(
445444
: component.text;
446445

447446
const spec = isTextOnlyComponentReference(component)
448-
? (yaml.load(component.text) as ComponentSpec)
447+
? componentSpecFromYaml(component.text)
449448
: component.spec;
450449

451450
if (!text || !spec) {

src/services/importPipeline.test.ts

Lines changed: 57 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,40 @@
1+
import yaml from "js-yaml";
12
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
23

3-
import * as componentSpecModule from "@/utils/componentSpec";
44
import * as componentStore from "@/utils/componentStore";
55
import { USER_PIPELINES_LIST_NAME } from "@/utils/constants";
66

77
import { importPipelineFromYaml } from "./pipelineService";
88

9-
vi.mock("@/utils/componentStore", () => ({
10-
componentSpecToYaml: vi.fn(),
11-
getComponentFileFromList: vi.fn(),
12-
writeComponentToFileListFromText: vi.fn().mockResolvedValue({}),
13-
}));
14-
15-
vi.mock("@/utils/componentSpec", () => ({
16-
isGraphImplementation: vi.fn(),
17-
}));
18-
199
describe("importPipelineFromYaml", () => {
20-
const validYamlContent = `
21-
name: Test Pipeline
22-
metadata:
23-
annotations:
24-
sdk: https://cloud-pipelines.net/pipeline-editor/
25-
implementation:
26-
graph:
27-
tasks: {}
28-
outputValues: {}
29-
`;
10+
const validYamlObject = {
11+
name: "Test Pipeline",
12+
metadata: {
13+
annotations: {
14+
sdk: "https://cloud-pipelines.net/pipeline-editor/",
15+
},
16+
},
17+
implementation: {
18+
graph: {
19+
tasks: [],
20+
outputValues: [],
21+
},
22+
},
23+
};
24+
25+
const validYamlContent = yaml.dump(validYamlObject);
3026

3127
beforeEach(() => {
3228
vi.clearAllMocks();
33-
// Default to returning true for valid tests
34-
vi.mocked(componentSpecModule.isGraphImplementation).mockReturnValue(true);
35-
// Default for componentSpecToYaml
36-
vi.mocked(componentStore.componentSpecToYaml).mockReturnValue(
37-
"mocked-yaml",
29+
vi.spyOn(console, "error").mockImplementation(() => {});
30+
31+
vi.spyOn(componentStore, "getComponentFileFromList").mockResolvedValue(
32+
null,
3833
);
34+
vi.spyOn(
35+
componentStore,
36+
"writeComponentToFileListFromText",
37+
).mockResolvedValue({} as any);
3938
});
4039

4140
afterEach(() => {
@@ -48,17 +47,8 @@ implementation:
4847

4948
const result = await importPipelineFromYaml(validYamlContent);
5049

51-
// Expect the function to call componentSpecToYaml
52-
expect(componentStore.componentSpecToYaml).toHaveBeenCalled();
53-
5450
// Expect writeComponentToFileListFromText to be called with correct parameters
55-
expect(
56-
componentStore.writeComponentToFileListFromText,
57-
).toHaveBeenCalledWith(
58-
USER_PIPELINES_LIST_NAME,
59-
"Test Pipeline",
60-
"mocked-yaml",
61-
);
51+
expect(componentStore.writeComponentToFileListFromText).toHaveBeenCalled();
6252

6353
// Expect successful result
6454
expect(result).toEqual({
@@ -79,9 +69,6 @@ implementation:
7969
},
8070
);
8171

82-
// Mock the console.error to prevent test output pollution
83-
vi.spyOn(console, "error").mockImplementation(() => {});
84-
8572
const result = await importPipelineFromYaml(validYamlContent, false);
8673

8774
// Since we're now renaming rather than erroring, expect a successful result
@@ -95,7 +82,7 @@ implementation:
9582
).toHaveBeenCalledWith(
9683
USER_PIPELINES_LIST_NAME,
9784
"Test Pipeline (1)",
98-
"mocked-yaml",
85+
expect.stringContaining("name: Test Pipeline (1)"),
9986
);
10087
});
10188

@@ -114,9 +101,6 @@ implementation:
114101
},
115102
);
116103

117-
// Mock the console.error
118-
vi.spyOn(console, "error").mockImplementation(() => {});
119-
120104
const result = await importPipelineFromYaml(validYamlContent, false);
121105

122106
// Expect a successful result with the name incremented to (3)
@@ -129,14 +113,14 @@ implementation:
129113
).toHaveBeenCalledWith(
130114
USER_PIPELINES_LIST_NAME,
131115
"Test Pipeline (3)",
132-
"mocked-yaml",
116+
yaml.dump({
117+
...validYamlObject,
118+
name: "Test Pipeline (3)",
119+
}),
133120
);
134121
});
135122

136123
it("should handle invalid YAML content", async () => {
137-
// Mock the console.error to prevent test output pollution
138-
vi.spyOn(console, "error").mockImplementation(() => {});
139-
140124
const result = await importPipelineFromYaml("invalid: yaml: content: -");
141125

142126
// Expect unsuccessful result
@@ -146,19 +130,16 @@ implementation:
146130
});
147131

148132
it("should handle non-graph pipelines", async () => {
149-
// Mock isGraphImplementation to return false
150-
vi.mocked(componentSpecModule.isGraphImplementation).mockReturnValue(false);
151-
152-
// Mock the console.error
153-
vi.spyOn(console, "error").mockImplementation(() => {});
154-
155-
const containerPipeline = `
156-
name: Container Pipeline
157-
implementation:
158-
container:
159-
image: test-image
160-
command: ["echo", "hello"]
161-
`;
133+
const containerPipelineObj = {
134+
name: "Container Pipeline",
135+
implementation: {
136+
container: {
137+
image: "test-image",
138+
command: ["echo", "hello"],
139+
},
140+
},
141+
};
142+
const containerPipeline = yaml.dump(containerPipelineObj);
162143

163144
const result = await importPipelineFromYaml(containerPipeline);
164145

@@ -173,18 +154,21 @@ implementation:
173154
});
174155

175156
it("should use default name for unnamed pipelines", async () => {
176-
// Mock isGraphImplementation to return true
177-
vi.mocked(componentSpecModule.isGraphImplementation).mockReturnValue(true);
178-
179-
const unnamedYaml = `
180-
metadata:
181-
annotations:
182-
sdk: https://cloud-pipelines.net/pipeline-editor/
183-
implementation:
184-
graph:
185-
tasks: {}
186-
outputValues: {}
187-
`;
157+
const unnamedPipelineSpec = {
158+
metadata: {
159+
annotations: {
160+
sdk: "https://cloud-pipelines.net/pipeline-editor/",
161+
},
162+
},
163+
implementation: {
164+
graph: {
165+
tasks: {},
166+
outputValues: {},
167+
},
168+
},
169+
};
170+
171+
const unnamedYaml = yaml.dump(unnamedPipelineSpec);
188172

189173
vi.mocked(componentStore.getComponentFileFromList).mockResolvedValue(null);
190174

@@ -196,7 +180,7 @@ implementation:
196180
).toHaveBeenCalledWith(
197181
USER_PIPELINES_LIST_NAME,
198182
"Imported Pipeline",
199-
"mocked-yaml",
183+
unnamedYaml,
200184
);
201185

202186
expect(result.name).toBe("Imported Pipeline");

src/services/pipelineService.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
writeComponentToFileListFromText,
1818
} from "@/utils/componentStore";
1919
import { USER_PIPELINES_LIST_NAME } from "@/utils/constants";
20+
import { componentSpecFromYaml } from "@/utils/yaml";
2021

2122
export const deletePipeline = async (name: string, onDelete?: () => void) => {
2223
try {
@@ -178,19 +179,7 @@ export async function importPipelineFromYaml(
178179
): Promise<ImportResult> {
179180
try {
180181
// Parse the YAML content to get the component spec
181-
const componentSpec = yaml.load(yamlContent) as ComponentSpec;
182-
183-
if (!componentSpec || typeof componentSpec !== "object") {
184-
const errorMessage =
185-
"Invalid YAML content. Could not parse as a component spec.";
186-
console.error(errorMessage);
187-
return {
188-
name: "",
189-
overwritten: false,
190-
successful: false,
191-
errorMessage,
192-
};
193-
}
182+
const componentSpec = componentSpecFromYaml(yamlContent);
194183

195184
// Validate the component spec has the required structure
196185
if (
@@ -239,6 +228,8 @@ export async function importPipelineFromYaml(
239228
standardizedYaml,
240229
);
241230

231+
console.log("wrote pipeline to file list", pipelineName, standardizedYaml);
232+
242233
return {
243234
name: pipelineName,
244235
overwritten: Boolean(existingPipeline && overwrite),

src/utils/componentStore.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import { fetchComponentTextFromUrl } from "@/services/componentService";
66
import type { DownloadDataType } from "./cache";
77
import { downloadDataWithCache } from "./cache";
88
import type { ComponentReference, ComponentSpec } from "./componentSpec";
9-
import { isValidComponentSpec } from "./componentSpec";
109
import { USER_COMPONENTS_LIST_NAME } from "./constants";
1110
import { getIdOrTitleFromPath } from "./URL";
11+
import { componentSpecFromYaml } from "./yaml";
1212

1313
// IndexedDB: DB and table names
1414
const DB_NAME = "components";
@@ -74,16 +74,7 @@ export const loadComponentAsRefFromText = async (
7474
? new TextEncoder().encode(componentText)
7575
: componentText;
7676

77-
const loadedObj = yaml.load(componentString);
78-
if (typeof loadedObj !== "object" || loadedObj === null) {
79-
throw Error(`componentText is not a YAML-encoded object: ${loadedObj}`);
80-
}
81-
if (!isValidComponentSpec(loadedObj)) {
82-
throw Error(
83-
`componentText does not encode a valid pipeline component: ${loadedObj}`,
84-
);
85-
}
86-
const componentSpec: ComponentSpec = loadedObj;
77+
const componentSpec = componentSpecFromYaml(componentString);
8778

8879
const digest = await generateDigest(componentBytes as ArrayBuffer);
8980
const componentRef: ComponentReferenceWithSpec = {

src/utils/submitPipeline.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import yaml from "js-yaml";
2-
31
import type { BodyCreateApiPipelineRunsPost } from "@/api/types.gen";
42
import { getArgumentsFromInputs } from "@/components/shared/ReactFlow/FlowCanvas/utils/getArgumentsFromInputs";
53
import {
@@ -9,6 +7,7 @@ import {
97
import type { PipelineRun } from "@/types/pipelineRun";
108

119
import type { ComponentReference, ComponentSpec } from "./componentSpec";
10+
import { componentSpecFromYaml } from "./yaml";
1211

1312
export async function submitPipelineRun(
1413
componentSpec: ComponentSpec,
@@ -159,13 +158,7 @@ const parseComponentYaml = (text: string): ComponentSpec => {
159158
throw new Error("Received empty component specification");
160159
}
161160

162-
const loadedSpec = yaml.load(text) as ComponentSpec;
163-
164-
if (!loadedSpec || typeof loadedSpec !== "object") {
165-
throw new Error("Invalid component specification format");
166-
}
167-
168-
return loadedSpec;
161+
return componentSpecFromYaml(text);
169162
};
170163

171164
// Fetch component with timeout to avoid hanging on unresponsive URLs

0 commit comments

Comments
 (0)