diff --git a/dashboard/src/shared/schema.test.ts b/dashboard/src/shared/schema.test.ts index 522fd5d68d7..83ebcd84ebc 100644 --- a/dashboard/src/shared/schema.test.ts +++ b/dashboard/src/shared/schema.test.ts @@ -348,12 +348,36 @@ describe("setValue", () => { result: "foo: bar\nnew: value\n", }, { - description: "[Not Supported] Adding a new nested value returns an error", + description: "should add a new nested value", + values: "foo: bar", + path: "this.new", + newValue: 1, + result: "foo: bar\nthis:\n new: 1\n", + error: false, + }, + { + description: "should add a new deeply nested value", values: "foo: bar", path: "this.new.value", - newValue: "1", - result: undefined, - error: true, + newValue: 1, + result: "foo: bar\nthis:\n new:\n value: 1\n", + error: false, + }, + { + description: "Adding a value for a path partially defined (null)", + values: "foo: bar\nthis:\n", + path: "this.new.value", + newValue: 1, + result: "foo: bar\nthis:\n new:\n value: 1\n", + error: false, + }, + { + description: "Adding a value for a path partially defined (object)", + values: "foo: bar\nthis: {}\n", + path: "this.new.value", + newValue: 1, + result: "foo: bar\nthis: { new: { value: 1 } }\n", + error: false, }, ].forEach(t => { it(t.description, () => { diff --git a/dashboard/src/shared/schema.ts b/dashboard/src/shared/schema.ts index a9107a34819..46e19c0a183 100644 --- a/dashboard/src/shared/schema.ts +++ b/dashboard/src/shared/schema.ts @@ -3,6 +3,7 @@ // that are used in this package import * as AJV from "ajv"; import * as jsonSchema from "json-schema"; +import { set } from "lodash"; import * as YAML from "yaml"; import { IBasicFormEnablerParam, IBasicFormParam } from "./types"; @@ -82,10 +83,42 @@ function orderParams(params: { return params; } +function getDefinedPath(allElementsButTheLast: string[], doc: YAML.ast.Document) { + let currentPath: string[] = []; + let foundUndefined = false; + allElementsButTheLast.forEach(p => { + // Iterate over the path until finding an element that is not defined + if (!foundUndefined) { + const pathToEvaluate = currentPath.concat(p); + const elem = (doc as any).getIn(pathToEvaluate); + if (elem === undefined || elem === null) { + foundUndefined = true; + } else { + currentPath = pathToEvaluate; + } + } + }); + return currentPath; +} + // setValue modifies the current values (text) based on a path export function setValue(values: string, path: string, newValue: any) { const doc = YAML.parseDocument(values); - const splittedPath = path.split("."); + let splittedPath = path.split("."); + // If the path is not defined (the parent nodes are undefined) + // We need to change the path and the value to set to avoid accessing + // the undefined node. For example, if a.b is undefined: + // path: a.b.c, value: 1 ==> path: a.b, value: {c: 1} + // TODO(andresmgot): In the future, this may be implemented in the YAML library itself + // https://github.com/eemeli/yaml/issues/131 + const allElementsButTheLast = splittedPath.slice(0, splittedPath.length - 1); + const parentNode = (doc as any).getIn(allElementsButTheLast); + if (parentNode === undefined) { + const definedPath = getDefinedPath(allElementsButTheLast, doc); + const remainingPath = splittedPath.slice(definedPath.length + 1); + newValue = set({}, remainingPath.join("."), newValue); + splittedPath = splittedPath.slice(0, definedPath.length + 1); + } (doc as any).setIn(splittedPath, newValue); return doc.toString(); }