Skip to content

Commit

Permalink
Load from file revamp (#685)
Browse files Browse the repository at this point in the history
* revamp loading values as YAML

* revamp loading values as YAML

* loading values as YAML messages

* loading values as YAML messages

* remove imports
  • Loading branch information
petar-cvit authored Nov 21, 2024
1 parent 52da1d2 commit 5056c0e
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 92 deletions.
121 changes: 30 additions & 91 deletions cyclops-ui/src/components/pages/NewModule/NewModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
Alert,
Button,
Col,
Collapse,
Divider,
Form,
Input,
Expand All @@ -15,11 +14,11 @@ import {
notification,
} from "antd";
import axios from "axios";
import { findMaps, flattenObjectKeys, mapsToArray } from "../../../utils/form";
import { deepMerge, findMaps, flattenObjectKeys, mapsToArray } from "../../../utils/form";
import "./custom.css";
import defaultTemplate from "../../../static/img/default-template-icon.png";

import YAML from "yaml";
import YAML, { YAMLError } from "yaml";

import AceEditor from "react-ace";

Expand Down Expand Up @@ -79,12 +78,7 @@ const NewModule = () => {
const [loadingTemplateInitialValues, setLoadingTemplateInitialValues] =
useState(false);

var initLoadedFrom: string[];
initLoadedFrom = [];
const [newFile, setNewFile] = useState("");
const [loadedFrom, setLoadedFrom] = useState(initLoadedFrom);
const [loadedValues, setLoadedValues] = useState("");
const [loadingValuesFile, setLoadingValuesFile] = useState(false);
const [loadingValuesModal, setLoadingValuesModal] = useState(false);

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

const onLoadFromFile = () => {
setLoadingValuesFile(true);
setLoadedValues("");

if (newFile.trim() === "") {
setError({
message: "Invalid values file",
description: "Values file can't be empty",
});
setLoadingValuesFile(false);
return;
}

setLoadingValuesModal(true);

loadValues(newFile);
setLoadingValuesFile(false);
};

function renderFormFields() {
if (!loadingTemplate && !loadingTemplateInitialValues) {
return (
Expand All @@ -333,56 +308,34 @@ const NewModule = () => {
};

const handleImportValues = () => {
form.setFieldsValue(
mapsToArray(config.root.properties, YAML.parse(loadedValues)),
);
setLoadedValues("");
setLoadingValuesModal(false);
};
let yamlValues = null;
try {
yamlValues = YAML.parse(loadedValues)
} catch(err: any) {
if (err instanceof YAMLError) {
setError({
message: err.name,
description: err.message,
});
return;
}

const renderLoadedFromFiles = () => {
if (loadedFrom.length === 0) {
setError({
message: "Failed injecting YAML to values",
description: "check if YAML is correctly indented",
});
return;
}

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

loadedFrom.forEach((value: string) => {
files.push(<p>{value}</p>);
});

return (
<Collapse
ghost
items={[
{
key: "1",
label: "Imported values from",
children: files,
},
]}
/>
form.setFieldsValue(
mapsToArray(config.root.properties, values),
);
};

const loadValues = (fileName: string) => {
axios
.get(fileName)
.then((res) => {
setLoadedValues(res.data);
setError({
message: "",
description: "",
});
let tmp = loadedFrom;
tmp.push(newFile);
setLoadedFrom(tmp);
})
.catch(function (error) {
// setLoadingTemplate(false);
// setSuccessLoad(false);
setError(mapResponseError(error));
});
setLoadedValues("");
setLoadingValuesModal(false);
setError({message: "", description: ""});
};

const onFinishFailed = (errors: any) => {
Expand Down Expand Up @@ -609,7 +562,7 @@ const NewModule = () => {
!config.root.properties
}
>
Load values from file
Import values as YAML
</Button>{" "}
<Button
type="primary"
Expand Down Expand Up @@ -638,7 +591,7 @@ const NewModule = () => {
</Col>
</Row>
<Modal
title="Values to import"
title="Import values as YAML"
visible={loadingValuesModal}
onCancel={handleCancel}
onOk={handleImportValues}
Expand All @@ -659,24 +612,10 @@ const NewModule = () => {
style={{ marginBottom: "20px" }}
/>
)}
{renderLoadedFromFiles()}
<Input
placeholder={"File reference"}
style={{ width: "90%", marginBottom: "10px" }}
onChange={(value: any) => {
setNewFile(value.target.value);
}}
/>
{" "}
<Button
type="primary"
htmlType="button"
style={{ width: "9%" }}
onClick={onLoadFromFile}
loading={loadingValuesFile}
>
Load
</Button>
<div style={{paddingRight: "16px", paddingBottom: "16px", color: "#777"}}>
You can paste your values in YAML format here, and after submitting them, you can see them in the form and edit them further.
If you set a value in YAML that does not exist in the UI, it will not be applied to your Module.
</div>
<AceEditor
mode={"yaml"}
theme="github"
Expand Down
24 changes: 24 additions & 0 deletions cyclops-ui/src/utils/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,30 @@ export function flattenObjectKeys(
}, []);
}

export function deepMerge(target: any, source: any): any {
if (!source && !target) {
return {};
}

if (!source) {
return target;
}

if (!target) {
return source;
}

for (const key in source) {
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
target[key] = deepMerge(target[key] || {}, source[key]);
} else {
target[key] = source[key];
}
}

return target;
};

export function findMaps(fields: any[], values: any, initialValues: any): any {
let out: any = initialValues ? initialValues : {};

Expand Down
157 changes: 156 additions & 1 deletion cyclops-ui/src/utils/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fileExtension } from "./form";
import { deepMerge, fileExtension } from "./form";

describe("fileExtension", () => {
const testCases = [
Expand Down Expand Up @@ -38,3 +38,158 @@ describe("fileExtension", () => {
}
});
});

describe("deepMerge", () => {
const testCases = [
{
description: "both source and target empty",
target: {},
source: {},
out: {},
},
{
description: "null target",
target: null,
source: {},
out: {},
},
{
description: "null source",
target: {},
source: null,
out: {},
},
{
description: "both source and target null",
target: null,
source: null,
out: {},
},
{
description: "target undefined",
target: undefined,
source: {},
out: {},
},
{
description: "source undefined",
target: {},
source: undefined,
out: {},
},
{
description: "both source and target undefined",
target: undefined,
source: undefined,
out: {},
},
{
description: "target has fields",
target: {name: "my-app"},
source: {},
out: {name: "my-app"},
},
{
description: "field overlap",
target: {name: "my-app"},
source: {name: "another-app"},
out: {name: "another-app"},
},
{
description: "no field overlap",
target: {name: "my-app"},
source: {someField: "value"},
out: {name: "my-app", someField: "value"},
},
{
description: "nested fields, no overlap",
target: {general: {image: "nginx", version: 3}},
source: {someField: "value"},
out: {general: {image: "nginx", version: 3}, someField: "value"},
},
{
description: "both have nested fields, no overlap",
target: {general: {image: "nginx", version: 3}},
source: { someField: "value", networking: {expose: true, host: "example.com", serviceType: ""}},
out: {general: {image: "nginx", version: 3}, someField: "value", networking: {expose: true, host: "example.com", serviceType: ""}},
},
{
description: "both have nested fields, no overlap, null value",
target: {general: {image: "nginx", version: 3}},
source: { someField: "value", networking: {expose: true, host: "example.com", serviceType: null}},
out: {general: {image: "nginx", version: 3}, someField: "value", networking: {expose: true, host: "example.com", serviceType: null}},
},
{
description: "both have nested fields, no overlap, undefined value",
target: {general: {image: "nginx", version: 3}},
source: { someField: "value", networking: {expose: true, host: "example.com", serviceType: undefined}},
out: {general: {image: "nginx", version: 3}, someField: "value", networking: {expose: true, host: "example.com", serviceType: undefined}},
},
{
description: "both have nested fields, overlap",
target: {general: {image: "nginx", version: 3}},
source: { someField: "value", general: {image: "redis", version: 5}, networking: {expose: true, host: "example.com", serviceType: undefined}},
out: {general: {image: "redis", version: 5}, someField: "value", networking: {expose: true, host: "example.com", serviceType: undefined}},
},
{
description: "both have same nested fields",
target: { someField: "value", general: {image: "redis", version: 5}, networking: {expose: true, host: "example.com", serviceType: undefined}},
source: { someField: "value", general: {image: "redis", version: 5}, networking: {expose: true, host: "example.com", serviceType: undefined}},
out: { someField: "value", general: {image: "redis", version: 5}, networking: {expose: true, host: "example.com", serviceType: undefined}},
},
{
description: "target has arrays",
target: {myList: [1, 2, 3]},
source: {},
out: {myList: [1, 2, 3]},
},
{
description: "source has arrays",
target: {},
source: {myList: [1, 2, 3]},
out: {myList: [1, 2, 3]},
},
{
description: "both have arrays",
target: {myList: [4, 5, 6]},
source: {myList: [1, 2, 3]},
out: {myList: [1, 2, 3]},
},
{
description: "target has empty array",
target: {myList: []},
source: {},
out: {myList: []},
},
{
description: "source has empty array",
target: {},
source: {myList: []},
out: {myList: []},
},
{
description: "both have empyt arrays",
target: {myList: []},
source: {myList: []},
out: {myList: []},
},
{
description: "both have nested fields, target has arrays",
target: {general: {image: "nginx", version: 3}, myList: ["here", "I", "am"]},
source: { someField: "value", networking: {expose: true, host: "example.com", serviceType: undefined}},
out: {general: {image: "nginx", version: 3}, someField: "value", networking: {expose: true, host: "example.com", serviceType: undefined}, myList: ["here", "I", "am"]},
},
{
description: "both have nested fields, source has arrays",
target: {general: {image: "nginx", version: 3}},
source: { someField: "value", networking: {expose: true, host: "example.com", serviceType: undefined}, myList: ["am", "I", "here"]},
out: {general: {image: "nginx", version: 3}, someField: "value", networking: {expose: true, host: "example.com", serviceType: undefined}, myList: ["am", "I", "here"]},
},
];

testCases.forEach((testCase) => {
it(testCase.description, () => {
expect(deepMerge(testCase.target, testCase.source)).toStrictEqual(testCase.out);
});
})
});

0 comments on commit 5056c0e

Please sign in to comment.