Merge pull request #17311 from strapi/fix/validate-unique-stage-names

Validate all stages of a workflow have a unique name
This commit is contained in:
Gustav Hansen 2023-07-14 15:49:55 +02:00 committed by GitHub
commit 058c194967
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 194 additions and 11 deletions

View File

@ -20,7 +20,7 @@ import { WorkflowAttributes } from '../../components/WorkflowAttributes';
import { REDUX_NAMESPACE } from '../../constants';
import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
import { reducer, initialState } from '../../reducer';
import { getWorkflowValidationSchema } from '../../utils/getWorkflowValidationSchema';
import { validateWorkflow } from '../../utils/validateWorkflow';
export function ReviewWorkflowsCreateView() {
const { formatMessage } = useIntl();
@ -108,7 +108,9 @@ export function ReviewWorkflowsCreateView() {
submitForm();
}
},
validationSchema: getWorkflowValidationSchema({ formatMessage }),
validate(values) {
return validateWorkflow({ values, formatMessage });
},
});
useInjectReducer(REDUX_NAMESPACE, reducer);

View File

@ -25,7 +25,7 @@ import { WorkflowAttributes } from '../../components/WorkflowAttributes';
import { REDUX_NAMESPACE } from '../../constants';
import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
import { reducer, initialState } from '../../reducer';
import { getWorkflowValidationSchema } from '../../utils/getWorkflowValidationSchema';
import { validateWorkflow } from '../../utils/validateWorkflow';
export function ReviewWorkflowsEditView() {
const { workflowId } = useParams();
@ -113,11 +113,11 @@ export function ReviewWorkflowsEditView() {
if (currentWorkflowHasDeletedServerStages) {
setIsConfirmDeleteDialogOpen(true);
} else if (limits?.workflows && meta?.workflowCount > parseInt(limits.workflows, 10)) {
/**
* If the current license has a limit, check if the total count of workflows
* exceeds that limit and display the limits modal instead of sending the
* update, because it would throw an API error.
*/
/**
* If the current license has a limit, check if the total count of workflows
* exceeds that limit and display the limits modal instead of sending the
* update, because it would throw an API error.
*/
setShowLimitModal('workflow');
/**
@ -134,7 +134,9 @@ export function ReviewWorkflowsEditView() {
submitForm();
}
},
validationSchema: getWorkflowValidationSchema({ formatMessage }),
validate(values) {
return validateWorkflow({ values, formatMessage });
},
});
useInjectReducer(REDUX_NAMESPACE, reducer);

View File

@ -0,0 +1,148 @@
import { validateWorkflow } from '../validateWorkflow';
const generateStringWithLength = (length) => new Array(length + 1).join('_');
const formatMessage = (message) => message.defaultMessage;
const setup = (values) => validateWorkflow({ values, formatMessage });
describe('Settings | Review Workflows | validateWorkflow()', () => {
test('name: valid input', async () => {
expect(await setup({ name: 'short name' })).toEqual(true);
});
test('name: empty', async () => {
expect(await setup({ name: '' })).toMatchInlineSnapshot(`
{
"name": "name is a required field",
}
`);
});
test('name: too long', async () => {
expect(await setup({ name: generateStringWithLength(256) })).toMatchInlineSnapshot(`
{
"name": "Name can not be longer than 255 characters",
}
`);
});
test('contentTypes: valid input', async () => {
expect(await setup({ name: 'name', contentTypes: ['1'] })).toEqual(true);
});
test('stages: empty array', async () => {
expect(
await setup({
name: 'name',
stages: [],
})
).toMatchInlineSnapshot(`
{
"stages": "stages field must have at least 1 items",
}
`);
});
test('stages.name: valid input', async () => {
expect(
await setup({
name: 'name',
stages: [
{
name: 'stage-1',
color: '#ffffff',
},
],
})
).toEqual(true);
});
test('stages.name: duplicated name', async () => {
expect(
await setup({
name: 'name',
stages: [
{
name: 'stage-1',
color: '#ffffff',
},
{
name: 'stage-1',
color: '#ffffff',
},
],
})
).toMatchInlineSnapshot(`
{
"stages": [
{
"name": "Stage name must be unique",
},
{
"name": "Stage name must be unique",
},
],
}
`);
});
test('stages.name: name too long', async () => {
expect(
await setup({
name: 'name',
stages: [
{
name: generateStringWithLength(256),
color: '#ffffff',
},
],
})
).toMatchInlineSnapshot(`
{
"stages": [
{
"name": "Name can not be longer than 255 characters",
},
],
}
`);
});
test('stages.color: valid input', async () => {
expect(
await setup({
name: 'name',
stages: [
{
name: 'stage-1',
color: '#ffffff',
},
],
})
).toEqual(true);
});
test('stages.color: invalid hex code', async () => {
expect(
await setup({
name: 'name',
stages: [
{
name: 'stage-1',
color: 'non-hex-code',
},
],
})
).toMatchInlineSnapshot(`
{
"stages": [
{
"color": "stages[0].color must match the following: "/^#(?:[0-9a-fA-F]{3}){1,2}$/i"",
},
],
}
`);
});
});

View File

@ -1,7 +1,8 @@
import set from 'lodash/set';
import * as yup from 'yup';
export function getWorkflowValidationSchema({ formatMessage }) {
return yup.object({
export async function validateWorkflow({ values, formatMessage }) {
const schema = yup.object({
contentTypes: yup.array().of(yup.string()),
name: yup
.string()
@ -32,6 +33,20 @@ export function getWorkflowValidationSchema({ formatMessage }) {
id: 'Settings.review-workflows.validation.stage.max-length',
defaultMessage: 'Name can not be longer than 255 characters',
})
)
.test(
'unique-name',
formatMessage({
id: 'Settings.review-workflows.validation.stage.duplicate',
defaultMessage: 'Stage name must be unique',
}),
function (stageName) {
const {
options: { context },
} = this;
return context.stages.filter((stage) => stage.name === stageName).length === 1;
}
),
color: yup
.string()
@ -46,4 +61,20 @@ export function getWorkflowValidationSchema({ formatMessage }) {
)
.min(1),
});
try {
await schema.validate(values, { abortEarly: false, context: values });
return true;
} catch (error) {
let errors = {};
if (error instanceof yup.ValidationError) {
error.inner.forEach((error) => {
set(errors, error.path, error.message);
});
}
return errors;
}
}