Skip to content

Commit

Permalink
Merge pull request #868 from InseeFrLab/v10
Browse files Browse the repository at this point in the history
v10
  • Loading branch information
garronej authored Nov 6, 2024
2 parents 74c2cd4 + d9d864f commit 97c632f
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { RootForm } from "../formTypes";
describe(symToStr({ computeRootForm }), () => {
it("simple case", () => {
const got = computeRootForm({
"chartName": "foo",
"helmValuesSchema": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -143,4 +144,141 @@ describe(symToStr({ computeRootForm }), () => {

expect(got).toStrictEqual(expected);
});

it("works with dependency same name as chart", () => {
const got = computeRootForm({
"chartName": "foo",
"helmValuesSchema": {
"type": "object",
"properties": {
"services": {
"type": "object",
"properties": {
"a": { "type": "string" }
}
},
"foo": {
"type": "object",
"properties": {
"b": { "type": "string" }
}
},
"global": {
"type": "object",
"properties": {
"foo": {
"type": "object",
"properties": {
"username": { "type": "string" },
"password": { "type": "string" }
}
},
"c": { "type": "number" }
}
}
}
},
"helmDependencies": [
{
"chartName": "foo",
"condition": undefined
}
],
"xOnyxiaContext": {},
"helmValues": {
"services": {
"a": "value of services.a"
},
"foo": {
"b": "value of foo.b"
},
"global": {
"foo": {
"username": "value of global.foo.username",
"password": "value of global.foo.password"
},
"c": 2
}
}
});

const expected: RootForm = {
"main": [
{
"type": "group",
"helmValuesPath": ["services"],
"description": undefined,
"nodes": [
{
"type": "field",
"title": "a",
"isReadonly": false,
"fieldType": "text field",
"helmValuesPath": ["services", "a"],
"description": undefined,
"doRenderAsTextArea": false,
"isSensitive": false,
"pattern": undefined,
"value": "value of services.a"
}
],
"canAdd": false,
"canRemove": false
},
{
"type": "field",
"title": "b",
"isReadonly": false,
"fieldType": "text field",
"helmValuesPath": ["foo", "b"],
"description": undefined,
"doRenderAsTextArea": false,
"isSensitive": false,
"pattern": undefined,
"value": "value of foo.b"
}
],
"disabledDependencies": [],
"global": [
{
"type": "field",
"title": "c",
"isReadonly": false,
"fieldType": "number field",
"helmValuesPath": ["global", "c"],
"description": undefined,
"value": 2,
"isInteger": false,
"minimum": undefined
},
{
"type": "field",
"title": "username",
"isReadonly": false,
"fieldType": "text field",
"helmValuesPath": ["global", "foo", "username"],
"description": undefined,
"doRenderAsTextArea": false,
"isSensitive": false,
"pattern": undefined,
"value": "value of global.foo.username"
},
{
"type": "field",
"title": "password",
"isReadonly": false,
"fieldType": "text field",
"helmValuesPath": ["global", "foo", "password"],
"description": undefined,
"doRenderAsTextArea": false,
"isSensitive": false,
"pattern": undefined,
"value": "value of global.foo.password"
}
],
"dependencies": {}
};

expect(got).toStrictEqual(expected);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type XOnyxiaContextLike = XOnyxiaContextLike_computeRootFormFieldGroup;
assert<XOnyxiaContext extends XOnyxiaContextLike ? true : false>();

export function computeRootForm(params: {
chartName: string;
helmValuesSchema: JSONSchema;
helmValues: Record<string, Stringifyable>;
xOnyxiaContext: XOnyxiaContextLike;
Expand All @@ -24,7 +25,8 @@ export function computeRootForm(params: {
condition: (string | number)[] | undefined;
}[];
}): RootForm {
const { helmValuesSchema, helmValues, xOnyxiaContext, helmDependencies } = params;
const { chartName, helmValuesSchema, helmValues, xOnyxiaContext, helmDependencies } =
params;

const rootForm: RootForm = {
"main": (() => {
Expand Down Expand Up @@ -98,5 +100,50 @@ export function computeRootForm(params: {
rootForm.dependencies[chartName] = { main, global };
});

extract_dependency_matching_chartName: {
const dependency = rootForm.dependencies[chartName];

if (dependency === undefined) {
break extract_dependency_matching_chartName;
}

(["main", "global"] as const).forEach(mainOrGlobal =>
[...dependency[mainOrGlobal]].forEach(node => {
{
const conflictingNode = rootForm[mainOrGlobal].find(
(() => {
switch (node.type) {
case "group":
return node_i =>
node_i.type === "group" &&
node_i.helmValuesPath.slice(-1)[0] ===
node.helmValuesPath.slice(-1)[0];
case "field":
return node_i =>
node_i.type === "field" &&
node_i.title === node.title;
}
})()
);

if (conflictingNode !== undefined) {
return;
}
}

dependency[mainOrGlobal].splice(
dependency[mainOrGlobal].indexOf(node),
1
);

rootForm[mainOrGlobal].push(node);
})
);

if (dependency.global.length === 0 && dependency.main.length === 0) {
delete rootForm.dependencies[chartName];
}
}

return rootForm;
}
12 changes: 11 additions & 1 deletion web/src/core/usecases/launcher/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const helmValues = createSelector(readyState, state => {

const rootForm = createSelector(
isReady,
chartName,
createSelector(readyState, state => {
if (state === null) {
return null;
Expand All @@ -72,17 +73,26 @@ const rootForm = createSelector(

return state.xOnyxiaContext;
}),
(isReady, helmValuesSchema, helmValues, helmDependencies, xOnyxiaContext) => {
(
isReady,
chartName,
helmValuesSchema,
helmValues,
helmDependencies,
xOnyxiaContext
) => {
if (!isReady) {
return null;
}

assert(chartName !== null);
assert(helmValuesSchema !== null);
assert(helmValues !== null);
assert(helmDependencies !== null);
assert(xOnyxiaContext !== null);

return computeRootForm({
chartName,
helmValuesSchema,
helmValues,
xOnyxiaContext,
Expand Down
96 changes: 67 additions & 29 deletions web/src/ui/pages/launcher/Launcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -257,39 +257,77 @@ export default function Launcher(props: Props) {
"labelWhenMismatchingLanguage": true
});

const { erroredFormFields, onFieldErrorChange } = (function useClosure() {
const [erroredFormFields, setErroredFormFields] = useState<(string | number)[][]>(
[]
);

const onFieldErrorChange = useConstCallback(
({
helmValuesPath,
hasError
}: Param0<FormCallbacks["onFieldErrorChange"]>) => {
const erroredFormFields_new = [...erroredFormFields];

if (hasError) {
erroredFormFields_new.push(helmValuesPath);
arrRemoveDuplicates(erroredFormFields_new, (a, b) => same(a, b));
} else {
const index = erroredFormFields_new.findIndex(erroredFormField =>
same(erroredFormField, helmValuesPath)
);

if (index === -1) {
return;
const { erroredFormFields, onFieldErrorChange, removeAllErroredFormFields } =
(function useClosure() {
const [erroredFormFields, setErroredFormFields] = useState<
(string | number)[][]
>([]);

const onFieldErrorChange = useConstCallback(
({
helmValuesPath,
hasError
}: Param0<FormCallbacks["onFieldErrorChange"]>) => {
const erroredFormFields_new = [...erroredFormFields];

if (hasError) {
erroredFormFields_new.push(helmValuesPath);
arrRemoveDuplicates(erroredFormFields_new, (a, b) => same(a, b));
} else {
const index = erroredFormFields_new.findIndex(erroredFormField =>
same(erroredFormField, helmValuesPath)
);

if (index === -1) {
return;
}

erroredFormFields_new.splice(index, 1);
}

erroredFormFields_new.splice(index, 1);
setErroredFormFields(erroredFormFields_new);
}
);

const removeAllErroredFormFields = useConstCallback(
(params: { startingWithHelmValuesPath: (string | number)[] }) => {
const { startingWithHelmValuesPath } = params;

const erroredFormFields_new = erroredFormFields.filter(
erroredFormField => {
if (
erroredFormField.length <
startingWithHelmValuesPath.length
) {
return true;
}

for (let i = 0; i < startingWithHelmValuesPath.length; i++) {
if (
erroredFormField[i] !== startingWithHelmValuesPath[i]
) {
return true;
}
}

return false;
}
);

setErroredFormFields(erroredFormFields_new);
}
);
setErroredFormFields(erroredFormFields_new);
}
);

return { erroredFormFields, onFieldErrorChange };
})();
return { erroredFormFields, onFieldErrorChange, removeAllErroredFormFields };
})();

const onRemove = useConstCallback<FormCallbacks["onRemove"]>(
({ helmValuesPath, index }) => {
removeAllErroredFormFields({ "startingWithHelmValuesPath": helmValuesPath });

launcher.removeArrayItem({ helmValuesPath, index });
}
);

if (!isReady) {
return null;
Expand Down Expand Up @@ -390,7 +428,7 @@ export default function Launcher(props: Props) {
callbacks={{
"onAdd": launcher.addArrayItem,
"onChange": launcher.changeFormFieldValue,
"onRemove": launcher.removeArrayItem,
onRemove,
onFieldErrorChange
}}
/>
Expand Down
Loading

0 comments on commit 97c632f

Please sign in to comment.