Skip to content

Commit 5056c0e

Browse files
authored
Load from file revamp (#685)
* revamp loading values as YAML * revamp loading values as YAML * loading values as YAML messages * loading values as YAML messages * remove imports
1 parent 52da1d2 commit 5056c0e

File tree

3 files changed

+210
-92
lines changed

3 files changed

+210
-92
lines changed

cyclops-ui/src/components/pages/NewModule/NewModule.tsx

Lines changed: 30 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
Alert,
44
Button,
55
Col,
6-
Collapse,
76
Divider,
87
Form,
98
Input,
@@ -15,11 +14,11 @@ import {
1514
notification,
1615
} from "antd";
1716
import axios from "axios";
18-
import { findMaps, flattenObjectKeys, mapsToArray } from "../../../utils/form";
17+
import { deepMerge, findMaps, flattenObjectKeys, mapsToArray } from "../../../utils/form";
1918
import "./custom.css";
2019
import defaultTemplate from "../../../static/img/default-template-icon.png";
2120

22-
import YAML from "yaml";
21+
import YAML, { YAMLError } from "yaml";
2322

2423
import AceEditor from "react-ace";
2524

@@ -79,12 +78,7 @@ const NewModule = () => {
7978
const [loadingTemplateInitialValues, setLoadingTemplateInitialValues] =
8079
useState(false);
8180

82-
var initLoadedFrom: string[];
83-
initLoadedFrom = [];
84-
const [newFile, setNewFile] = useState("");
85-
const [loadedFrom, setLoadedFrom] = useState(initLoadedFrom);
8681
const [loadedValues, setLoadedValues] = useState("");
87-
const [loadingValuesFile, setLoadingValuesFile] = useState(false);
8882
const [loadingValuesModal, setLoadingValuesModal] = useState(false);
8983

9084
const [templateStore, setTemplateStore] = useState<templateStoreOption[]>([]);
@@ -290,25 +284,6 @@ const NewModule = () => {
290284
loadTemplate(ts.ref.repo, ts.ref.path, ts.ref.version, ts.ref.sourceType);
291285
};
292286

293-
const onLoadFromFile = () => {
294-
setLoadingValuesFile(true);
295-
setLoadedValues("");
296-
297-
if (newFile.trim() === "") {
298-
setError({
299-
message: "Invalid values file",
300-
description: "Values file can't be empty",
301-
});
302-
setLoadingValuesFile(false);
303-
return;
304-
}
305-
306-
setLoadingValuesModal(true);
307-
308-
loadValues(newFile);
309-
setLoadingValuesFile(false);
310-
};
311-
312287
function renderFormFields() {
313288
if (!loadingTemplate && !loadingTemplateInitialValues) {
314289
return (
@@ -333,56 +308,34 @@ const NewModule = () => {
333308
};
334309

335310
const handleImportValues = () => {
336-
form.setFieldsValue(
337-
mapsToArray(config.root.properties, YAML.parse(loadedValues)),
338-
);
339-
setLoadedValues("");
340-
setLoadingValuesModal(false);
341-
};
311+
let yamlValues = null;
312+
try {
313+
yamlValues = YAML.parse(loadedValues)
314+
} catch(err: any) {
315+
if (err instanceof YAMLError) {
316+
setError({
317+
message: err.name,
318+
description: err.message,
319+
});
320+
return;
321+
}
342322

343-
const renderLoadedFromFiles = () => {
344-
if (loadedFrom.length === 0) {
323+
setError({
324+
message: "Failed injecting YAML to values",
325+
description: "check if YAML is correctly indented",
326+
});
345327
return;
346328
}
347329

348-
const files: {} | any = [];
330+
const currentValues = findMaps(config.root.properties, form.getFieldsValue(), null);
331+
const values = deepMerge(currentValues, yamlValues)
349332

350-
loadedFrom.forEach((value: string) => {
351-
files.push(<p>{value}</p>);
352-
});
353-
354-
return (
355-
<Collapse
356-
ghost
357-
items={[
358-
{
359-
key: "1",
360-
label: "Imported values from",
361-
children: files,
362-
},
363-
]}
364-
/>
333+
form.setFieldsValue(
334+
mapsToArray(config.root.properties, values),
365335
);
366-
};
367-
368-
const loadValues = (fileName: string) => {
369-
axios
370-
.get(fileName)
371-
.then((res) => {
372-
setLoadedValues(res.data);
373-
setError({
374-
message: "",
375-
description: "",
376-
});
377-
let tmp = loadedFrom;
378-
tmp.push(newFile);
379-
setLoadedFrom(tmp);
380-
})
381-
.catch(function (error) {
382-
// setLoadingTemplate(false);
383-
// setSuccessLoad(false);
384-
setError(mapResponseError(error));
385-
});
336+
setLoadedValues("");
337+
setLoadingValuesModal(false);
338+
setError({message: "", description: ""});
386339
};
387340

388341
const onFinishFailed = (errors: any) => {
@@ -609,7 +562,7 @@ const NewModule = () => {
609562
!config.root.properties
610563
}
611564
>
612-
Load values from file
565+
Import values as YAML
613566
</Button>{" "}
614567
<Button
615568
type="primary"
@@ -638,7 +591,7 @@ const NewModule = () => {
638591
</Col>
639592
</Row>
640593
<Modal
641-
title="Values to import"
594+
title="Import values as YAML"
642595
visible={loadingValuesModal}
643596
onCancel={handleCancel}
644597
onOk={handleImportValues}
@@ -659,24 +612,10 @@ const NewModule = () => {
659612
style={{ marginBottom: "20px" }}
660613
/>
661614
)}
662-
{renderLoadedFromFiles()}
663-
<Input
664-
placeholder={"File reference"}
665-
style={{ width: "90%", marginBottom: "10px" }}
666-
onChange={(value: any) => {
667-
setNewFile(value.target.value);
668-
}}
669-
/>
670-
{" "}
671-
<Button
672-
type="primary"
673-
htmlType="button"
674-
style={{ width: "9%" }}
675-
onClick={onLoadFromFile}
676-
loading={loadingValuesFile}
677-
>
678-
Load
679-
</Button>
615+
<div style={{paddingRight: "16px", paddingBottom: "16px", color: "#777"}}>
616+
You can paste your values in YAML format here, and after submitting them, you can see them in the form and edit them further.
617+
If you set a value in YAML that does not exist in the UI, it will not be applied to your Module.
618+
</div>
680619
<AceEditor
681620
mode={"yaml"}
682621
theme="github"

cyclops-ui/src/utils/form.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,30 @@ export function flattenObjectKeys(
3535
}, []);
3636
}
3737

38+
export function deepMerge(target: any, source: any): any {
39+
if (!source && !target) {
40+
return {};
41+
}
42+
43+
if (!source) {
44+
return target;
45+
}
46+
47+
if (!target) {
48+
return source;
49+
}
50+
51+
for (const key in source) {
52+
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
53+
target[key] = deepMerge(target[key] || {}, source[key]);
54+
} else {
55+
target[key] = source[key];
56+
}
57+
}
58+
59+
return target;
60+
};
61+
3862
export function findMaps(fields: any[], values: any, initialValues: any): any {
3963
let out: any = initialValues ? initialValues : {};
4064

cyclops-ui/src/utils/index.test.ts

Lines changed: 156 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { fileExtension } from "./form";
1+
import { deepMerge, fileExtension } from "./form";
22

33
describe("fileExtension", () => {
44
const testCases = [
@@ -38,3 +38,158 @@ describe("fileExtension", () => {
3838
}
3939
});
4040
});
41+
42+
describe("deepMerge", () => {
43+
const testCases = [
44+
{
45+
description: "both source and target empty",
46+
target: {},
47+
source: {},
48+
out: {},
49+
},
50+
{
51+
description: "null target",
52+
target: null,
53+
source: {},
54+
out: {},
55+
},
56+
{
57+
description: "null source",
58+
target: {},
59+
source: null,
60+
out: {},
61+
},
62+
{
63+
description: "both source and target null",
64+
target: null,
65+
source: null,
66+
out: {},
67+
},
68+
{
69+
description: "target undefined",
70+
target: undefined,
71+
source: {},
72+
out: {},
73+
},
74+
{
75+
description: "source undefined",
76+
target: {},
77+
source: undefined,
78+
out: {},
79+
},
80+
{
81+
description: "both source and target undefined",
82+
target: undefined,
83+
source: undefined,
84+
out: {},
85+
},
86+
{
87+
description: "target has fields",
88+
target: {name: "my-app"},
89+
source: {},
90+
out: {name: "my-app"},
91+
},
92+
{
93+
description: "field overlap",
94+
target: {name: "my-app"},
95+
source: {name: "another-app"},
96+
out: {name: "another-app"},
97+
},
98+
{
99+
description: "no field overlap",
100+
target: {name: "my-app"},
101+
source: {someField: "value"},
102+
out: {name: "my-app", someField: "value"},
103+
},
104+
{
105+
description: "nested fields, no overlap",
106+
target: {general: {image: "nginx", version: 3}},
107+
source: {someField: "value"},
108+
out: {general: {image: "nginx", version: 3}, someField: "value"},
109+
},
110+
{
111+
description: "both have nested fields, no overlap",
112+
target: {general: {image: "nginx", version: 3}},
113+
source: { someField: "value", networking: {expose: true, host: "example.com", serviceType: ""}},
114+
out: {general: {image: "nginx", version: 3}, someField: "value", networking: {expose: true, host: "example.com", serviceType: ""}},
115+
},
116+
{
117+
description: "both have nested fields, no overlap, null value",
118+
target: {general: {image: "nginx", version: 3}},
119+
source: { someField: "value", networking: {expose: true, host: "example.com", serviceType: null}},
120+
out: {general: {image: "nginx", version: 3}, someField: "value", networking: {expose: true, host: "example.com", serviceType: null}},
121+
},
122+
{
123+
description: "both have nested fields, no overlap, undefined value",
124+
target: {general: {image: "nginx", version: 3}},
125+
source: { someField: "value", networking: {expose: true, host: "example.com", serviceType: undefined}},
126+
out: {general: {image: "nginx", version: 3}, someField: "value", networking: {expose: true, host: "example.com", serviceType: undefined}},
127+
},
128+
{
129+
description: "both have nested fields, overlap",
130+
target: {general: {image: "nginx", version: 3}},
131+
source: { someField: "value", general: {image: "redis", version: 5}, networking: {expose: true, host: "example.com", serviceType: undefined}},
132+
out: {general: {image: "redis", version: 5}, someField: "value", networking: {expose: true, host: "example.com", serviceType: undefined}},
133+
},
134+
{
135+
description: "both have same nested fields",
136+
target: { someField: "value", general: {image: "redis", version: 5}, networking: {expose: true, host: "example.com", serviceType: undefined}},
137+
source: { someField: "value", general: {image: "redis", version: 5}, networking: {expose: true, host: "example.com", serviceType: undefined}},
138+
out: { someField: "value", general: {image: "redis", version: 5}, networking: {expose: true, host: "example.com", serviceType: undefined}},
139+
},
140+
{
141+
description: "target has arrays",
142+
target: {myList: [1, 2, 3]},
143+
source: {},
144+
out: {myList: [1, 2, 3]},
145+
},
146+
{
147+
description: "source has arrays",
148+
target: {},
149+
source: {myList: [1, 2, 3]},
150+
out: {myList: [1, 2, 3]},
151+
},
152+
{
153+
description: "both have arrays",
154+
target: {myList: [4, 5, 6]},
155+
source: {myList: [1, 2, 3]},
156+
out: {myList: [1, 2, 3]},
157+
},
158+
{
159+
description: "target has empty array",
160+
target: {myList: []},
161+
source: {},
162+
out: {myList: []},
163+
},
164+
{
165+
description: "source has empty array",
166+
target: {},
167+
source: {myList: []},
168+
out: {myList: []},
169+
},
170+
{
171+
description: "both have empyt arrays",
172+
target: {myList: []},
173+
source: {myList: []},
174+
out: {myList: []},
175+
},
176+
{
177+
description: "both have nested fields, target has arrays",
178+
target: {general: {image: "nginx", version: 3}, myList: ["here", "I", "am"]},
179+
source: { someField: "value", networking: {expose: true, host: "example.com", serviceType: undefined}},
180+
out: {general: {image: "nginx", version: 3}, someField: "value", networking: {expose: true, host: "example.com", serviceType: undefined}, myList: ["here", "I", "am"]},
181+
},
182+
{
183+
description: "both have nested fields, source has arrays",
184+
target: {general: {image: "nginx", version: 3}},
185+
source: { someField: "value", networking: {expose: true, host: "example.com", serviceType: undefined}, myList: ["am", "I", "here"]},
186+
out: {general: {image: "nginx", version: 3}, someField: "value", networking: {expose: true, host: "example.com", serviceType: undefined}, myList: ["am", "I", "here"]},
187+
},
188+
];
189+
190+
testCases.forEach((testCase) => {
191+
it(testCase.description, () => {
192+
expect(deepMerge(testCase.target, testCase.source)).toStrictEqual(testCase.out);
193+
});
194+
})
195+
});

0 commit comments

Comments
 (0)