diff --git a/.changeset/swift-dots-cough.md b/.changeset/swift-dots-cough.md new file mode 100644 index 0000000000..255ee192a0 --- /dev/null +++ b/.changeset/swift-dots-cough.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': minor +--- + +Improve validation error display text in scaffolder diff --git a/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.test.tsx b/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.test.tsx index c5ddf6854f..d632340031 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.test.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.test.tsx @@ -19,25 +19,88 @@ import { renderInTestApp } from '@backstage/test-utils'; import { ErrorListProps } from '@rjsf/utils'; describe('Error List Template', () => { - const errorList = { - errors: [ - { - stack: 'Test error 1', - }, - { - stack: 'Test error 2', - }, - ], - errorSchema: {}, - } as Partial as ErrorListProps; + describe('should render the error messages', () => { + it('no properties', async () => { + const errorList = { + errors: [ + { + stack: 'Test error 1', + }, + { + stack: 'Test error 2', + }, + ], + errorSchema: {}, + } as Partial as ErrorListProps; - it('should render the error messages', async () => { - const { getByText } = await renderInTestApp( - , - ); + const { getByText } = await renderInTestApp( + , + ); - for (const error of errorList.errors) { - expect(getByText(error.stack)).toBeInTheDocument(); - } + for (const error of errorList.errors) { + expect(getByText(error.stack)).toBeInTheDocument(); + } + }); + + it('properties no title', async () => { + const errorList = { + errors: [ + { + property: '.foo', + stack: 'Test error 1', + message: 'Test error 1', + }, + { + property: '.anExampleTitle', + stack: 'Test error 2', + message: 'Test error 2', + }, + ], + errorSchema: {}, + schema: {}, + } as Partial as ErrorListProps; + + const { getByText } = await renderInTestApp( + , + ); + + expect(getByText("'Foo' Test error 1")).toBeInTheDocument(); + expect(getByText("'An Example Title' Test error 2")).toBeInTheDocument(); + }); + + it('properties with title', async () => { + const errorList = { + errors: [ + { + property: '.foo', + stack: 'Test error 1', + message: 'Test error 1', + }, + { + property: '.bar', + stack: 'Test error 2', + message: 'Test error 2', + }, + ], + errorSchema: {}, + schema: { + properties: { + foo: { + title: 'Hello', + }, + bar: { + title: 'Example Title', + }, + }, + }, + } as Partial as ErrorListProps; + + const { getByText } = await renderInTestApp( + , + ); + + expect(getByText("'Hello' Test error 1")).toBeInTheDocument(); + expect(getByText("'Example Title' Test error 2")).toBeInTheDocument(); + }); }); }); diff --git a/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.tsx b/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.tsx index a5310a5806..6b664fc235 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React from 'react'; -import { ErrorListProps } from '@rjsf/utils'; +import { ErrorListProps, RJSFValidationError } from '@rjsf/utils'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ListItemIcon from '@material-ui/core/ListItemIcon'; @@ -22,6 +22,7 @@ import ListItemText from '@material-ui/core/ListItemText'; import Paper from '@material-ui/core/Paper'; import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'; import ErrorIcon from '@material-ui/icons/Error'; +import startCase from 'lodash/startCase'; const useStyles = makeStyles((_theme: Theme) => createStyles({ @@ -39,9 +40,28 @@ const useStyles = makeStyles((_theme: Theme) => * * @public */ -export const ErrorListTemplate = ({ errors }: ErrorListProps) => { +export const ErrorListTemplate = ({ errors, schema }: ErrorListProps) => { const classes = useStyles(); + function formatErrorMessage(error: RJSFValidationError) { + if (error.property && error.message) { + const propertyName = error.property.startsWith('.') + ? error.property.substring(1) + : error.property; + if (schema.properties && propertyName in schema.properties) { + const property = schema.properties[propertyName]; + + if (typeof property === 'object' && 'title' in property) { + return `'${property.title}' ${error.message}`; + } + } + // fall back to property name + return `'${startCase(propertyName)}' ${error.message}`; + } + // fall back if property does not exist + return error.stack; + } + return ( @@ -52,7 +72,7 @@ export const ErrorListTemplate = ({ errors }: ErrorListProps) => { ))}