Skip to content

Commit a4ae23c

Browse files
authored
fix(orchestrator): validation order with ui:order (#1308)
Signed-off-by: Marek Libra <marek.libra@gmail.com>
1 parent c79ffa7 commit a4ae23c

5 files changed

Lines changed: 85 additions & 23 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-react': patch
3+
---
4+
5+
Fixing validation of multi-step wizard when ui:order is used by supplying correct step fields.

workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorFormWrapper.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
useOrchestratorFormApiOrDefault,
3333
} from '@red-hat-developer-hub/backstage-plugin-orchestrator-form-api';
3434

35+
import { getActiveStepKey } from '../utils/getSortedStepEntries';
3536
import { useStepperContext } from '../utils/StepperContext';
3637
import useValidator from '../utils/useValidator';
3738
import { AuthRequester } from './AuthRequester';
@@ -75,7 +76,8 @@ const FormComponent = (decoratorProps: FormDecoratorProps) => {
7576
if (!isMultiStep) {
7677
return undefined;
7778
}
78-
return Object.keys(schema.properties || {})[activeStep];
79+
80+
return getActiveStepKey(schema, activeStep);
7981
};
8082

8183
const onSubmit = async (_formData: JsonObject) => {

workspaces/orchestrator/plugins/orchestrator-form-react/src/components/StepperObjectField.tsx

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
import { useMemo } from 'react';
1617

1718
import { JsonObject } from '@backstage/types';
1819

1920
import ObjectField from '@rjsf/core/lib/components/fields/ObjectField';
2021
import { ErrorSchema, FieldProps, IdSchema } from '@rjsf/utils';
21-
import type { JSONSchema7, JSONSchema7Definition } from 'json-schema';
22-
import get from 'lodash/get';
22+
import type { JSONSchema7 } from 'json-schema';
2323

24+
import { getSortedStepEntries } from '../utils/getSortedStepEntries';
2425
import OrchestratorFormStepper, {
2526
OrchestratorFormStep,
2627
OrchestratorFormToolbar,
@@ -36,28 +37,16 @@ const StepperObjectField = ({
3637
errorSchema,
3738
...props
3839
}: FieldProps<JsonObject, JSONSchema7>) => {
39-
if (schema.properties === undefined) {
40+
const sortedStepEntries = useMemo(
41+
() => getSortedStepEntries(schema),
42+
[schema],
43+
);
44+
if (sortedStepEntries === undefined) {
4045
throw new Error(
4146
"Stepper object field is not supported for schema that doesn't contain properties",
4247
);
4348
}
4449

45-
const uiOrder = get(schema, 'ui:order') as string[] | undefined;
46-
let sortedStepEntries = Object.entries(schema.properties);
47-
if (uiOrder && uiOrder.length > 0) {
48-
sortedStepEntries = uiOrder
49-
.map(key =>
50-
schema.properties?.[key] ? [key, schema.properties[key]] : undefined,
51-
)
52-
.filter(Boolean) as [string, JSONSchema7Definition][];
53-
54-
Object.entries(schema.properties).forEach(([key, subSchema]) => {
55-
if (!uiOrder.includes(key)) {
56-
sortedStepEntries.push([key, subSchema]);
57-
}
58-
});
59-
}
60-
6150
const steps = sortedStepEntries.reduce<OrchestratorFormStep[]>(
6251
(prev, [key, subSchema]) => {
6352
if (typeof subSchema === 'boolean') {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { JSONSchema7, JSONSchema7Definition } from 'json-schema';
17+
import get from 'lodash/get';
18+
19+
/**
20+
* Get step entries from the schema sorted by the ui:order property.
21+
* If the ui:order property is not present, the step entries are sorted by the order of the properties in the schema.
22+
*
23+
* @param schema - The schema to get the sorted step entries from.
24+
* @returns An array of [key, subSchema] pairs, a subSchema conforms a single wizard step.
25+
*/
26+
export const getSortedStepEntries = (
27+
schema: JSONSchema7,
28+
): [string, JSONSchema7Definition][] | undefined => {
29+
if (!schema.properties) {
30+
return undefined;
31+
}
32+
33+
let sortedStepEntries = Object.entries(schema.properties);
34+
35+
const uiOrder = get(schema, 'ui:order') as string[] | undefined;
36+
if (uiOrder && uiOrder.length > 0) {
37+
sortedStepEntries = uiOrder
38+
.map(key =>
39+
schema.properties?.[key] ? [key, schema.properties[key]] : undefined,
40+
)
41+
.filter(Boolean) as [string, JSONSchema7Definition][];
42+
43+
Object.entries(schema.properties).forEach(([key, subSchema]) => {
44+
if (!uiOrder.includes(key)) {
45+
sortedStepEntries.push([key, subSchema]);
46+
}
47+
});
48+
}
49+
50+
return sortedStepEntries;
51+
};
52+
53+
export const getActiveStepKey = (
54+
schema: JSONSchema7,
55+
activeStep: number,
56+
): string => {
57+
const sortedStepEntries = getSortedStepEntries(schema) ?? [];
58+
const activeKey = sortedStepEntries[activeStep]?.[0];
59+
if (!activeKey) {
60+
throw new Error(
61+
`Active step key not found for activeStep: ${activeStep} in schema: ${JSON.stringify(schema)}`,
62+
);
63+
}
64+
return activeKey;
65+
};

workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/useValidator.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 The Backstage Authors
2+
* Copyright Red Hat, Inc.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
1617
import { JsonObject } from '@backstage/types';
1718

1819
import {
@@ -29,6 +30,7 @@ import validatorAjv from '@rjsf/validator-ajv8';
2930
import _validator from '@rjsf/validator-ajv8';
3031
import type { JSONSchema7 } from 'json-schema';
3132

33+
import { getActiveStepKey } from './getSortedStepEntries';
3234
import { useStepperContext } from './StepperContext';
3335

3436
// add the activeStep to the validator to force rjsf form to rerender when activeStep changes. This doesn't happen because it assumes function are equal.
@@ -51,7 +53,6 @@ const useValidator = (isMultiStepSchema: boolean) => {
5153
customValidate: CustomValidator<JsonObject, JSONSchema7, any>,
5254
): ValidationData<JsonObject> => {
5355
let validationData = validatorAjv.validateFormData(formData, _schema);
54-
5556
if (customValidate) {
5657
const errorHandler = customValidate(
5758
formData,
@@ -68,7 +69,7 @@ const useValidator = (isMultiStepSchema: boolean) => {
6869
return validationData;
6970
}
7071

71-
const activeKey = Object.keys(_schema.properties || {})[activeStep];
72+
const activeKey = getActiveStepKey(_schema, activeStep);
7273
return {
7374
errors: validationData.errors.filter(err =>
7475
err.property?.startsWith(`.${activeKey}.`),

0 commit comments

Comments
 (0)