chore: reworking how we do the review step

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2022-08-19 11:44:56 +02:00
parent 64658427a0
commit 784673638a
4 changed files with 158 additions and 32 deletions
@@ -0,0 +1,61 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { ReviewState } from './ReviewState';
import { render } from '@testing-library/react';
describe('ReviewState', () => {
it('should render the text as normal with no options', () => {
const formState = {
name: 'John Doe',
test: 'bob',
};
const { getByRole } = render(
<ReviewState formState={formState} schemas={[]} />,
);
expect(getByRole('row', { name: 'Name John Doe' })).toBeInTheDocument();
expect(getByRole('row', { name: 'Test bob' })).toBeInTheDocument();
});
it('should mask password ui:fields', () => {
const formState = {
name: 'John Doe',
test: 'bob',
};
const schemas = [
{
mergedSchema: {
type: 'object',
properties: {
name: {
type: 'string',
'ui:widget': 'password',
},
},
},
},
];
const { getByRole } = render(
<ReviewState formState={formState} schemas={schemas} />,
);
expect(getByRole('row', { name: 'Name ******' })).toBeInTheDocument();
});
});
@@ -0,0 +1,58 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { StructuredMetadataTable } from '@backstage/core-components';
import { JsonObject } from '@backstage/types';
import { ParsedTemplateSchema } from './useTemplateSchema';
import { Draft07 as JSONSchema } from 'json-schema-library';
interface ReviewStateProps {
schemas: ParsedTemplateSchema[];
formState: JsonObject;
}
export const ReviewState = (props: ReviewStateProps) => {
const reviewData = Object.fromEntries(
Object.entries(props.formState).map(([key, value]) => {
for (const step of props.schemas) {
const parsedSchema = new JSONSchema(step.mergedSchema);
const definitionInSchema = parsedSchema.getSchema(
`#/${key}`,
props.formState,
);
if (definitionInSchema) {
if (definitionInSchema['ui:widget'] === 'password') {
return [key, '******'];
}
const backstageReviewOptions =
definitionInSchema['ui:backstage']?.review;
if (backstageReviewOptions) {
if (backstageReviewOptions.mask) {
return [key, backstageReviewOptions.mask];
}
if (!backstageReviewOptions.show) {
return [];
}
}
}
}
return [key, value];
}),
);
return <StructuredMetadataTable metadata={reviewData} />;
};
@@ -29,6 +29,7 @@ import { FieldExtensionOptions } from '../../../extensions';
import { TemplateParameterSchema } from '../../../types';
import { createAsyncValidators } from './createAsyncValidators';
import { useTemplateSchema } from './useTemplateSchema';
import { ReviewState } from './ReviewState';
const useStyles = makeStyles(theme => ({
backButton: {
@@ -74,7 +75,7 @@ export const Stepper = (props: StepperProps) => {
}, [props.extensions]);
const validation = useMemo(() => {
const { mergedSchema } = steps[activeStep];
const { mergedSchema } = steps[activeStep] ?? {};
return createAsyncValidators(mergedSchema, validators, {
apiHolder,
});
@@ -112,30 +113,37 @@ export const Stepper = (props: StepperProps) => {
<MuiStepLabel>{step.title}</MuiStepLabel>
</MuiStep>
))}
<MuiStep>
<MuiStepLabel>Review</MuiStepLabel>
</MuiStep>
</MuiStepper>
<div className={styles.formWrapper}>
<Form
extraErrors={errors}
formData={formState}
schema={steps[activeStep].schema}
uiSchema={steps[activeStep].uiSchema}
onSubmit={handleNext}
fields={extensions}
showErrorList={false}
>
<div className={styles.footer}>
<Button
onClick={handleBack}
className={styles.backButton}
disabled={activeStep < 1}
>
Back
</Button>
<Button variant="contained" color="primary" type="submit">
{activeStep === steps.length - 1 ? 'Review' : 'Next'}
</Button>
</div>
</Form>
{activeStep < steps.length ? (
<Form
extraErrors={errors}
formData={formState}
schema={steps[activeStep].schema}
uiSchema={steps[activeStep].uiSchema}
onSubmit={handleNext}
fields={extensions}
showErrorList={false}
>
<div className={styles.footer}>
<Button
onClick={handleBack}
className={styles.backButton}
disabled={activeStep < 1}
>
Back
</Button>
<Button variant="contained" color="primary" type="submit">
{activeStep === steps.length - 1 ? 'Review' : 'Next'}
</Button>
</div>
</Form>
) : (
<ReviewState formState={formState} schemas={steps} />
)}
</div>
</>
);
@@ -19,17 +19,16 @@ import { UiSchema } from '@rjsf/core';
import { TemplateParameterSchema } from '../../../types';
import { extractSchemaFromStep } from './schema';
export interface ParsedTemplateSchema {
uiSchema: UiSchema;
mergedSchema: JsonObject;
schema: JsonObject;
title: string;
description?: string;
}
export const useTemplateSchema = (
manifest: TemplateParameterSchema,
): {
steps: {
uiSchema: UiSchema;
mergedSchema: JsonObject;
schema: JsonObject;
title: string;
description?: string;
}[];
} => {
): { steps: ParsedTemplateSchema } => {
const featureFlags = useApi(featureFlagsApiRef);
const steps = manifest.steps.map(({ title, description, schema }) => ({
title,