Make last step (a summary page of scaffolder template) configurable.
Signed-off-by: bnechyporenko <bnechyporenko@bol.com>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2020 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 { Box, Button, Paper, Typography } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { Content, StructuredMetadataTable } from '@backstage/core-components';
|
||||
import { Step } from './types';
|
||||
import { UiSchema } from '@rjsf/core';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
|
||||
export type LastStepFormProps = {
|
||||
disableButtons: boolean;
|
||||
finishButtonLabel?: string;
|
||||
formData: Record<string, any>;
|
||||
handleBack: () => void;
|
||||
handleCreate: () => void;
|
||||
handleReset: () => void;
|
||||
onFinish?: () => Promise<void>;
|
||||
steps: Step[];
|
||||
};
|
||||
|
||||
export function getUiSchemasFromSteps(steps: Step[]): UiSchema[] {
|
||||
const uiSchemas: Array<UiSchema> = [];
|
||||
steps.forEach(step => {
|
||||
const schemaProps = step.schema.properties as JsonObject;
|
||||
for (const key in schemaProps) {
|
||||
if (schemaProps.hasOwnProperty(key)) {
|
||||
const uiSchema = schemaProps[key] as UiSchema;
|
||||
uiSchema.name = key;
|
||||
uiSchemas.push(uiSchema);
|
||||
}
|
||||
}
|
||||
});
|
||||
return uiSchemas;
|
||||
}
|
||||
|
||||
export function getReviewData(formData: Record<string, any>, steps: Step[]) {
|
||||
const uiSchemas = getUiSchemasFromSteps(steps);
|
||||
const reviewData: Record<string, any> = {};
|
||||
for (const key in formData) {
|
||||
if (formData.hasOwnProperty(key)) {
|
||||
const uiSchema = uiSchemas.find(us => us.name === key);
|
||||
|
||||
if (!uiSchema) {
|
||||
reviewData[key] = formData[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (uiSchema['ui:widget'] === 'password') {
|
||||
reviewData[key] = '******';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!uiSchema['ui:backstage'] || !uiSchema['ui:backstage'].review) {
|
||||
reviewData[key] = formData[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
const review = uiSchema['ui:backstage'].review as JsonObject;
|
||||
if (review.mask) {
|
||||
reviewData[key] = review.mask;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!review.show) {
|
||||
continue;
|
||||
}
|
||||
reviewData[key] = formData[key];
|
||||
}
|
||||
}
|
||||
|
||||
return reviewData;
|
||||
}
|
||||
|
||||
export const LastStepForm = (props: LastStepFormProps) => {
|
||||
const {
|
||||
disableButtons,
|
||||
finishButtonLabel,
|
||||
formData,
|
||||
handleBack,
|
||||
handleCreate,
|
||||
handleReset,
|
||||
onFinish,
|
||||
steps,
|
||||
} = props;
|
||||
return (
|
||||
<Content>
|
||||
<Paper square elevation={0}>
|
||||
<Typography variant="h6">Review and create</Typography>
|
||||
<StructuredMetadataTable
|
||||
dense
|
||||
metadata={getReviewData(formData, steps)}
|
||||
/>
|
||||
<Box mb={4} />
|
||||
<Button onClick={handleBack} disabled={disableButtons}>
|
||||
Back
|
||||
</Button>
|
||||
<Button onClick={handleReset} disabled={disableButtons}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleCreate}
|
||||
disabled={!onFinish || disableButtons}
|
||||
>
|
||||
{finishButtonLabel ?? 'Create'}
|
||||
</Button>
|
||||
</Paper>
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
||||
export const lastStepFormComponent = (props: LastStepFormProps) => (
|
||||
<LastStepForm {...props} />
|
||||
);
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getReviewData } from './MultistepJsonForm';
|
||||
import { getReviewData } from './LastStepForm';
|
||||
|
||||
describe('MultistepJsonForm', () => {
|
||||
const formDataMock = {
|
||||
|
||||
@@ -13,11 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Paper,
|
||||
Step as StepUI,
|
||||
StepContent,
|
||||
StepLabel,
|
||||
@@ -26,23 +23,20 @@ import {
|
||||
} from '@material-ui/core';
|
||||
import {
|
||||
errorApiRef,
|
||||
useApi,
|
||||
featureFlagsApiRef,
|
||||
useApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { FormProps, IChangeEvent, UiSchema, withTheme } from '@rjsf/core';
|
||||
import { FormProps, IChangeEvent, withTheme } from '@rjsf/core';
|
||||
import { Theme as MuiTheme } from '@rjsf/material-ui';
|
||||
import React, { useState } from 'react';
|
||||
import { transformSchemaToProps } from './schema';
|
||||
import { Content, StructuredMetadataTable } from '@backstage/core-components';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import * as fieldOverrides from './FieldOverrides';
|
||||
import { LayoutOptions } from '../../layouts';
|
||||
import { Step } from './types';
|
||||
import { useScaffolderPluginOptions } from '../../options';
|
||||
|
||||
const Form = withTheme(MuiTheme);
|
||||
type Step = {
|
||||
schema: JsonObject;
|
||||
title: string;
|
||||
} & Partial<Omit<FormProps<any>, 'schema'>>;
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
@@ -59,59 +53,6 @@ type Props = {
|
||||
layouts: LayoutOptions[];
|
||||
};
|
||||
|
||||
export function getUiSchemasFromSteps(steps: Step[]): UiSchema[] {
|
||||
const uiSchemas: Array<UiSchema> = [];
|
||||
steps.forEach(step => {
|
||||
const schemaProps = step.schema.properties as JsonObject;
|
||||
for (const key in schemaProps) {
|
||||
if (schemaProps.hasOwnProperty(key)) {
|
||||
const uiSchema = schemaProps[key] as UiSchema;
|
||||
uiSchema.name = key;
|
||||
uiSchemas.push(uiSchema);
|
||||
}
|
||||
}
|
||||
});
|
||||
return uiSchemas;
|
||||
}
|
||||
|
||||
export function getReviewData(formData: Record<string, any>, steps: Step[]) {
|
||||
const uiSchemas = getUiSchemasFromSteps(steps);
|
||||
const reviewData: Record<string, any> = {};
|
||||
for (const key in formData) {
|
||||
if (formData.hasOwnProperty(key)) {
|
||||
const uiSchema = uiSchemas.find(us => us.name === key);
|
||||
|
||||
if (!uiSchema) {
|
||||
reviewData[key] = formData[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (uiSchema['ui:widget'] === 'password') {
|
||||
reviewData[key] = '******';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!uiSchema['ui:backstage'] || !uiSchema['ui:backstage'].review) {
|
||||
reviewData[key] = formData[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
const review = uiSchema['ui:backstage'].review as JsonObject;
|
||||
if (review.mask) {
|
||||
reviewData[key] = review.mask;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!review.show) {
|
||||
continue;
|
||||
}
|
||||
reviewData[key] = formData[key];
|
||||
}
|
||||
}
|
||||
|
||||
return reviewData;
|
||||
}
|
||||
|
||||
export const MultistepJsonForm = (props: Props) => {
|
||||
const {
|
||||
formData,
|
||||
@@ -134,7 +75,7 @@ export const MultistepJsonForm = (props: Props) => {
|
||||
if (filteredStep.schema.properties) {
|
||||
filteredStep.schema.properties = Object.fromEntries(
|
||||
Object.entries(filteredStep.schema.properties).filter(
|
||||
([key, value]) => {
|
||||
([key, value]: [string, any]) => {
|
||||
if (value[featureFlagKey]) {
|
||||
if (featureFlagApi.isActive(value[featureFlagKey])) {
|
||||
return true;
|
||||
@@ -189,6 +130,8 @@ export const MultistepJsonForm = (props: Props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const { lastStepFormComponent } = useScaffolderPluginOptions();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stepper activeStep={activeStep} orientation="vertical">
|
||||
@@ -231,32 +174,16 @@ export const MultistepJsonForm = (props: Props) => {
|
||||
);
|
||||
})}
|
||||
</Stepper>
|
||||
{activeStep === steps.length && (
|
||||
<Content>
|
||||
<Paper square elevation={0}>
|
||||
<Typography variant="h6">Review and create</Typography>
|
||||
<StructuredMetadataTable
|
||||
dense
|
||||
metadata={getReviewData(formData, steps)}
|
||||
/>
|
||||
<Box mb={4} />
|
||||
<Button onClick={handleBack} disabled={disableButtons}>
|
||||
Back
|
||||
</Button>
|
||||
<Button onClick={handleReset} disabled={disableButtons}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleCreate}
|
||||
disabled={!onFinish || disableButtons}
|
||||
>
|
||||
{finishButtonLabel ?? 'Create'}
|
||||
</Button>
|
||||
</Paper>
|
||||
</Content>
|
||||
)}
|
||||
{activeStep === steps.length &&
|
||||
lastStepFormComponent({
|
||||
disableButtons,
|
||||
handleBack,
|
||||
handleCreate,
|
||||
handleReset,
|
||||
finishButtonLabel,
|
||||
formData,
|
||||
steps,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,3 +14,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { MultistepJsonForm } from './MultistepJsonForm';
|
||||
export { lastStepFormComponent, LastStepForm } from './LastStepForm';
|
||||
export type { LastStepFormProps } from './LastStepForm';
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2020 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 { FormProps } from '@rjsf/core';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
|
||||
export type Step = {
|
||||
schema: JsonObject;
|
||||
title: string;
|
||||
} & Partial<Omit<FormProps<any>, 'schema'>>;
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2020 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 { usePluginOptions } from '@backstage/core-plugin-api';
|
||||
import { ReactElement } from 'react';
|
||||
import { LastStepFormProps } from './components/MultistepJsonForm';
|
||||
|
||||
export type ScaffolderPluginOptions = {
|
||||
lastStepFormComponent: (props: LastStepFormProps) => ReactElement;
|
||||
};
|
||||
|
||||
/** @ignore */
|
||||
export type ScaffolderInputPluginOptionsOptions = {
|
||||
lastStepFormComponent: (props: LastStepFormProps) => ReactElement;
|
||||
};
|
||||
|
||||
export const useScaffolderPluginOptions = () =>
|
||||
usePluginOptions<ScaffolderPluginOptions>();
|
||||
@@ -20,7 +20,7 @@ import { EntityPicker } from './components/fields/EntityPicker/EntityPicker';
|
||||
import { entityNamePickerValidation } from './components/fields/EntityNamePicker';
|
||||
import { EntityNamePicker } from './components/fields/EntityNamePicker/EntityNamePicker';
|
||||
import { OwnerPicker } from './components/fields/OwnerPicker/OwnerPicker';
|
||||
import { repoPickerValidation } from './components/fields/RepoUrlPicker';
|
||||
import { repoPickerValidation } from './components';
|
||||
import { RepoUrlPicker } from './components/fields/RepoUrlPicker/RepoUrlPicker';
|
||||
import { createScaffolderFieldExtension } from './extensions';
|
||||
import {
|
||||
@@ -39,6 +39,11 @@ import {
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { OwnedEntityPicker } from './components/fields/OwnedEntityPicker/OwnedEntityPicker';
|
||||
import { EntityTagsPicker } from './components/fields/EntityTagsPicker/EntityTagsPicker';
|
||||
import {
|
||||
ScaffolderInputPluginOptionsOptions,
|
||||
ScaffolderPluginOptions,
|
||||
} from './options';
|
||||
import { lastStepFormComponent } from './components/MultistepJsonForm';
|
||||
|
||||
/**
|
||||
* The main plugin export for the scaffolder.
|
||||
@@ -71,6 +76,14 @@ export const scaffolderPlugin = createPlugin({
|
||||
registerComponent: registerComponentRouteRef,
|
||||
viewTechDoc: viewTechDocRouteRef,
|
||||
},
|
||||
__experimentalConfigure(
|
||||
options?: ScaffolderInputPluginOptionsOptions,
|
||||
): ScaffolderPluginOptions {
|
||||
const defaultOptions = {
|
||||
lastStepFormComponent,
|
||||
};
|
||||
return { ...defaultOptions, ...options };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -100,7 +113,7 @@ export const EntityNamePickerFieldExtension = scaffolderPlugin.provide(
|
||||
|
||||
/**
|
||||
* The field extension which provides the ability to select a RepositoryUrl.
|
||||
* Currently this is an encoded URL that looks something like the following `github.com?repo=myRepoName&owner=backstage`.
|
||||
* Currently, this is an encoded URL that looks something like the following `github.com?repo=myRepoName&owner=backstage`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
Reference in New Issue
Block a user