diff --git a/.changeset/two-planets-provide.md b/.changeset/two-planets-provide.md
new file mode 100644
index 0000000000..920eebc01d
--- /dev/null
+++ b/.changeset/two-planets-provide.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder': minor
+---
+
+Implementing review step for the scaffolder under `create/next`
diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.test.tsx
new file mode 100644
index 0000000000..2b2e706102
--- /dev/null
+++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.test.tsx
@@ -0,0 +1,140 @@
+/*
+ * 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';
+import { ParsedTemplateSchema } from './useTemplateSchema';
+
+describe('ReviewState', () => {
+ it('should render the text as normal with no options', () => {
+ const formState = {
+ name: 'John Doe',
+ test: 'bob',
+ };
+
+ const { getByRole } = render(
+ ,
+ );
+
+ 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: ParsedTemplateSchema[] = [
+ {
+ mergedSchema: {
+ type: 'object',
+ properties: {
+ name: {
+ type: 'string',
+ 'ui:widget': 'password',
+ },
+ },
+ },
+ schema: {},
+ title: 'test',
+ uiSchema: {},
+ description: 'asd',
+ },
+ ];
+
+ const { getByRole } = render(
+ ,
+ );
+
+ expect(getByRole('row', { name: 'Name ******' })).toBeInTheDocument();
+ });
+
+ it('should hide from review if show is not set', async () => {
+ const formState = {
+ name: 'John Doe',
+ test: 'bob',
+ };
+
+ const schemas: ParsedTemplateSchema[] = [
+ {
+ mergedSchema: {
+ type: 'object',
+ properties: {
+ name: {
+ type: 'string',
+ 'ui:widget': 'password',
+ 'ui:backstage': {
+ review: {
+ show: false,
+ },
+ },
+ },
+ },
+ },
+ schema: {},
+ title: 'test',
+ uiSchema: {},
+ description: 'asd',
+ },
+ ];
+
+ const { queryByRole } = render(
+ ,
+ );
+
+ expect(
+ await queryByRole('row', { name: 'Name ******' }),
+ ).not.toBeInTheDocument();
+ });
+
+ it('should allow for masking an option with a set text', () => {
+ const formState = {
+ name: 'John Doe',
+ test: 'bob',
+ };
+
+ const schemas: ParsedTemplateSchema[] = [
+ {
+ mergedSchema: {
+ type: 'object',
+ properties: {
+ name: {
+ type: 'string',
+ 'ui:widget': 'password',
+ 'ui:backstage': {
+ review: {
+ mask: 'lols',
+ },
+ },
+ },
+ },
+ },
+ schema: {},
+ title: 'test',
+ uiSchema: {},
+ description: 'asd',
+ },
+ ];
+
+ const { getByRole } = render(
+ ,
+ );
+
+ expect(getByRole('row', { name: 'Name lols' })).toBeInTheDocument();
+ });
+});
diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.tsx
new file mode 100644
index 0000000000..f39a26622a
--- /dev/null
+++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.tsx
@@ -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) {
+ const backstageReviewOptions =
+ definitionInSchema['ui:backstage']?.review;
+
+ if (backstageReviewOptions) {
+ if (backstageReviewOptions.mask) {
+ return [key, backstageReviewOptions.mask];
+ }
+ if (backstageReviewOptions.show === false) {
+ return [];
+ }
+ }
+
+ if (definitionInSchema['ui:widget'] === 'password') {
+ return [key, '******'];
+ }
+ }
+ }
+ return [key, value];
+ }),
+ );
+ return ;
+};
diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx
index 90901f22c5..f605e9c114 100644
--- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx
+++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx
@@ -47,17 +47,17 @@ describe('Stepper', () => {
title: 'React JSON Schema Form Test',
};
- const { getByText } = await renderInTestApp(
+ const { getByRole } = await renderInTestApp(
,
);
- expect(getByText('Next')).toBeInTheDocument();
+ expect(getByRole('button', { name: 'Next' })).toBeInTheDocument();
await act(async () => {
- await fireEvent.click(getByText('Next'));
+ await fireEvent.click(getByRole('button', { name: 'Next' }));
});
- expect(getByText('Review')).toBeInTheDocument();
+ expect(getByRole('button', { name: 'Review' })).toBeInTheDocument();
});
it('should remember the state of the form when cycling through the pages', async () => {
@@ -87,7 +87,7 @@ describe('Stepper', () => {
title: 'React JSON Schema Form Test',
};
- const { getByRole, getByText } = await renderInTestApp(
+ const { getByRole } = await renderInTestApp(
,
);
@@ -96,11 +96,11 @@ describe('Stepper', () => {
});
await act(async () => {
- await fireEvent.click(getByText('Next'));
+ await fireEvent.click(getByRole('button', { name: 'Next' }));
});
await act(async () => {
- await fireEvent.click(getByText('Back'));
+ await fireEvent.click(getByRole('button', { name: 'Back' }));
});
expect(getByRole('textbox', { name: 'name' })).toHaveValue(
diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx
index b85a1e1f5d..f5656092c1 100644
--- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx
+++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx
@@ -29,11 +29,13 @@ 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: {
marginRight: theme.spacing(1),
},
+
footer: {
display: 'flex',
flexDirection: 'row',
@@ -74,8 +76,7 @@ export const Stepper = (props: StepperProps) => {
}, [props.extensions]);
const validation = useMemo(() => {
- const { mergedSchema } = steps[activeStep];
- return createAsyncValidators(mergedSchema, validators, {
+ return createAsyncValidators(steps[activeStep]?.mergedSchema, validators, {
apiHolder,
});
}, [steps, activeStep, validators, apiHolder]);
@@ -104,6 +105,10 @@ export const Stepper = (props: StepperProps) => {
setFormState(current => ({ ...current, ...formData }));
};
+ const handleCreate = () => {
+ // TODO(blam): Create the template in a modal with the ability to view the logs etc.
+ };
+
return (
<>
@@ -112,30 +117,51 @@ export const Stepper = (props: StepperProps) => {
{step.title}
))}
+
+ Review
+
-
+ {activeStep < steps.length ? (
+
+ ) : (
+ <>
+
+
+
+
+
+ >
+ )}
>
);
diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts
index 0482d89503..d0d8590a7b 100644
--- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts
+++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts
@@ -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,