Skip to content

Commit a2ad79e

Browse files
authored
Merge pull request #53 from Exabyte-io/epic/SOF-6009
Epic/SOF-6009
2 parents 1961a1b + 9e4dc54 commit a2ad79e

17 files changed

+493
-213
lines changed

package-lock.json

+22-21
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"@babel/preset-react": "7.16.7",
5757
"@babel/register": "^7.16.0",
5858
"@babel/runtime-corejs3": "7.16.8",
59-
"@exabyte-io/esse.js": "2023.8.30-0",
59+
"@exabyte-io/esse.js": "2023.8.31-0",
6060
"@types/chai": "^4.3.5",
6161
"@types/crypto-js": "^4.1.1",
6262
"@types/js-yaml": "^4.0.5",
@@ -77,6 +77,7 @@
7777
"mixwith": "^0.1.1",
7878
"nunjucks": "^3.2.4",
7979
"react-jsonschema-form": "^1.8.1",
80+
"semver": "^7.5.3",
8081
"ts-node": "^10.9.1",
8182
"typescript": "^4.5.5",
8283
"underscore": "^1.13.3",

src/utils/filter.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ interface PathObject {
66
}
77

88
// Filter conditions
9-
interface FilterObject {
9+
export interface FilterObject {
1010
path?: string;
1111
regex?: RegExp;
1212
}
@@ -57,6 +57,7 @@ export function filterEntityList({
5757
filterObjects = [],
5858
multiPathSeparator = "",
5959
}: FilterEntityListProps) {
60+
if (!filterObjects || !filterObjects.length) return [];
6061
const filterObjects_ = filterObjects.map((o) => (o.regex ? { regex: new RegExp(o.regex) } : o));
6162

6263
let filtered;

src/utils/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { getSchemaWithDependencies } from "./schemas";
3131
import { getSearchQuerySelector } from "./selector";
3232
import {
3333
convertArabicToRoman,
34+
findPreviousVersion,
3435
randomAlphanumeric,
3536
removeCommentsFromSourceCode,
3637
removeEmptyLinesFromString,
@@ -89,4 +90,5 @@ export {
8990
getFilesInDirectory,
9091
getDirectories,
9192
createObjectPathFromFilePath,
93+
findPreviousVersion,
9294
};

src/utils/object.ts

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import omit from "lodash/omit";
1010

1111
import { AnyObject } from "../entity/in_memory";
1212
import { NameResultSchema } from "../types";
13+
import { safeMakeArray } from "./array";
1314
import { deepClone } from "./clone";
1415

1516
/**
@@ -248,6 +249,7 @@ function isTreeObject<T>(value: unknown): value is Tree<T> {
248249
* mergeTerminalNodes(tree); // ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l']
249250
*/
250251
export function mergeTerminalNodes<T = string>(tree: Tree<T>, unique = false): T[] {
252+
if (!isTreeObject<T>(tree)) return safeMakeArray(tree);
251253
const terminalValues = Object.values(tree).reduce((accumulator: T[], value) => {
252254
if (isTreeObject<T>(value)) {
253255
return accumulator.concat(mergeTerminalNodes(value));

src/utils/schemas.ts

+63-44
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { JSONSchema } from "@exabyte-io/esse.js/schema";
22
import forEach from "lodash/forEach";
3-
import getValue from "lodash/get";
43
import hasProperty from "lodash/has";
54
import isEmpty from "lodash/isEmpty";
65

@@ -10,19 +9,26 @@ export * from "@exabyte-io/esse.js/lib/js/esse/schemaUtils";
109

1110
export const schemas: { [key: string]: string } = {};
1211

12+
interface SubstitutionMap {
13+
[key: string]: string;
14+
}
15+
16+
interface Parameter {
17+
key: string;
18+
values: string[] | boolean[];
19+
namesMap?: SubstitutionMap;
20+
}
21+
1322
interface Node {
14-
dataSelector: {
23+
data: {
1524
key: string;
1625
value: string;
1726
name: string;
1827
};
28+
staticOptions: Parameter[];
1929
children?: Node[];
2030
}
2131

22-
function isNodeWithChildren(node: Node): node is Required<Node> {
23-
return Boolean(node.children?.length);
24-
}
25-
2632
/**
2733
* Returns previously registered schema for InMemoryEntity
2834
* @returns
@@ -52,53 +58,67 @@ export function typeofSchema(schema: JSONSchema) {
5258
}
5359
}
5460

55-
function getEnumValues(nodes: Node[]) {
56-
if (!nodes.length) return {};
61+
function extractEnumOptions(nodes?: Node[]) {
62+
if (!nodes || !nodes.length) return {};
5763
return {
58-
enum: nodes.map((node) => getValue(node, node.dataSelector.value)),
64+
enum: nodes.map((node) => node.data.value),
65+
enumNames: nodes.map((node) => node.data.name),
5966
};
6067
}
6168

62-
function getEnumNames(nodes: Node[]) {
63-
if (!nodes.length) return {};
64-
return {
65-
enumNames: nodes.map((node) => getValue(node, node.dataSelector.name)),
66-
};
69+
function substituteName(value: unknown, mapping?: SubstitutionMap) {
70+
if (typeof value !== "string") {
71+
return JSON.stringify(value);
72+
}
73+
return mapping ? mapping[value] : value;
74+
}
75+
76+
function createStaticFields(node: Node) {
77+
if (!node.staticOptions) return {};
78+
const fields: { [key: string]: { enum: string[] | boolean[]; enumNames: string[] } } = {};
79+
node.staticOptions
80+
.filter((o) => o.key && o.values)
81+
.forEach((o) => {
82+
fields[o.key] = {
83+
enum: o.values,
84+
enumNames: o.values.map((v) => substituteName(v, o.namesMap)),
85+
};
86+
});
87+
return fields;
6788
}
6889

6990
/**
7091
* @summary Recursively generate `dependencies` for RJSF schema based on tree.
7192
* @param {Object[]} nodes - Array of nodes (e.g. `[tree]` or `node.children`)
7293
* @returns {{}|{dependencies: {}}}
7394
*/
74-
export function buildDependencies(nodes: Node[]): JSONSchema {
75-
if (nodes.length === 0 || nodes.every((n) => !n.children?.length)) return {};
76-
77-
const nodesWithChildren = nodes.filter(isNodeWithChildren);
78-
const parentKey = nodesWithChildren[0].dataSelector.key;
79-
const childKey = nodesWithChildren[0].children[0].dataSelector.key;
80-
81-
return {
82-
dependencies: {
83-
[parentKey]: {
84-
oneOf: nodesWithChildren.map((node) => {
85-
return {
86-
properties: {
87-
[parentKey]: {
88-
...getEnumValues([node]),
89-
...getEnumNames([node]),
90-
},
91-
[childKey]: {
92-
...getEnumValues(node.children),
93-
...getEnumNames(node.children),
94-
},
95-
},
96-
...buildDependencies(node.children),
97-
};
98-
}),
95+
export function buildDependencies(nodes?: Node[]): JSONSchema {
96+
const isEveryTerminal = nodes && nodes.every((node) => !node.children?.length);
97+
const isWithStaticOptions = nodes && nodes.some((node) => node?.staticOptions);
98+
if (!nodes || !nodes.length || !nodes[0].data) return {};
99+
const parentKey = nodes[0].data.key;
100+
101+
const cases = nodes.map((node) => {
102+
const childKey = node.children?.length && node.children[0].data.key;
103+
return {
104+
properties: {
105+
[parentKey]: extractEnumOptions([node]),
106+
...(childKey ? { [childKey]: extractEnumOptions(node.children) } : {}),
107+
...createStaticFields(node),
99108
},
100-
},
101-
};
109+
...buildDependencies(node.children),
110+
};
111+
});
112+
113+
return cases.length && (!isEveryTerminal || isWithStaticOptions)
114+
? {
115+
dependencies: {
116+
[parentKey]: {
117+
oneOf: cases,
118+
},
119+
},
120+
}
121+
: {};
102122
}
103123

104124
interface Props {
@@ -131,9 +151,8 @@ export function getSchemaWithDependencies({
131151
// RJSF does not automatically render dropdown widget if `enum` is not present
132152
if (modifyProperties && nodes.length) {
133153
const mod = {
134-
[nodes[0].dataSelector.key]: {
135-
...getEnumNames(nodes),
136-
...getEnumValues(nodes),
154+
[nodes[0].data.key]: {
155+
...extractEnumOptions(nodes),
137156
},
138157
};
139158
forEach(mod, (extraFields, key) => {

src/utils/str.js

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import lodash from "lodash";
22
import nunjucks from "nunjucks";
3+
import semverCoerce from "semver/functions/coerce";
4+
import semverLt from "semver/functions/lt";
5+
import semverRcompare from "semver/functions/rcompare";
36
import _ from "underscore";
47

58
export function removeNewLinesAndExtraSpaces(str) {
@@ -124,3 +127,18 @@ export function renderTextWithSubstitutes(template, data, substitutionMap = {})
124127
substituteNested(renderData);
125128
return nunjucks.renderString(template, renderData);
126129
}
130+
131+
/**
132+
* Find the next smallest version from a list of semantic version strings.
133+
* @param {string[]} versions - Array of semantic version strings.
134+
* @param {string} inputVersion - Version to compare to.
135+
* @returns {string | undefined}
136+
*/
137+
export function findPreviousVersion(versions, inputVersion) {
138+
const version = semverCoerce(inputVersion);
139+
const versions_ = versions
140+
.map((v) => ({ raw: v, coerced: semverCoerce(v) }))
141+
.sort((a, b) => semverRcompare(a.coerced, b.coerced));
142+
const prev = versions_.find((o) => semverLt(o.coerced, version));
143+
return prev?.raw;
144+
}

src/utils/yaml.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,30 @@ function readFromYaml(ref) {
9090
* - `key`: name or path of parameter, e.g. `job.workflow`
9191
* - `values`: list of values (use either `ref` or `values`)
9292
* - `ref`: reference to values in another YAML file.
93+
* - `exclude`: regular expression for excluding items.
9394
* - `isOptional`: whether parameter is optional (adds `null` to values array)
95+
* - `merge`: arrays or values from other sources to merge
9496
* See the tests for example usage.
9597
*/
9698
export const parameterType = new yaml.Type("!parameter", {
9799
kind: "mapping",
98100
construct(data) {
99-
const { key, values = [], ref, exclude, isOptional = false, ...otherProps } = data;
101+
const {
102+
key,
103+
values = [],
104+
ref,
105+
exclude,
106+
isOptional = false,
107+
merge = [],
108+
...otherProps
109+
} = data;
100110

101111
try {
102112
let values_ = ref && !values.length ? readFromYaml(ref) : values;
103113
values_ = safeMakeArray(values_);
114+
if (Array.isArray(merge)) {
115+
values_ = values_.concat(merge.flat());
116+
}
104117
if (exclude) {
105118
const regex = new RegExp(exclude);
106119
values_ = values_.filter((v) => !regex.test(v));

0 commit comments

Comments
 (0)