mirror of
https://github.com/strapi/strapi.git
synced 2025-12-06 12:01:52 +00:00
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:
commit
058c194967
@ -20,7 +20,7 @@ import { WorkflowAttributes } from '../../components/WorkflowAttributes';
|
|||||||
import { REDUX_NAMESPACE } from '../../constants';
|
import { REDUX_NAMESPACE } from '../../constants';
|
||||||
import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
|
import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
|
||||||
import { reducer, initialState } from '../../reducer';
|
import { reducer, initialState } from '../../reducer';
|
||||||
import { getWorkflowValidationSchema } from '../../utils/getWorkflowValidationSchema';
|
import { validateWorkflow } from '../../utils/validateWorkflow';
|
||||||
|
|
||||||
export function ReviewWorkflowsCreateView() {
|
export function ReviewWorkflowsCreateView() {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
@ -108,7 +108,9 @@ export function ReviewWorkflowsCreateView() {
|
|||||||
submitForm();
|
submitForm();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
validationSchema: getWorkflowValidationSchema({ formatMessage }),
|
validate(values) {
|
||||||
|
return validateWorkflow({ values, formatMessage });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useInjectReducer(REDUX_NAMESPACE, reducer);
|
useInjectReducer(REDUX_NAMESPACE, reducer);
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import { WorkflowAttributes } from '../../components/WorkflowAttributes';
|
|||||||
import { REDUX_NAMESPACE } from '../../constants';
|
import { REDUX_NAMESPACE } from '../../constants';
|
||||||
import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
|
import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
|
||||||
import { reducer, initialState } from '../../reducer';
|
import { reducer, initialState } from '../../reducer';
|
||||||
import { getWorkflowValidationSchema } from '../../utils/getWorkflowValidationSchema';
|
import { validateWorkflow } from '../../utils/validateWorkflow';
|
||||||
|
|
||||||
export function ReviewWorkflowsEditView() {
|
export function ReviewWorkflowsEditView() {
|
||||||
const { workflowId } = useParams();
|
const { workflowId } = useParams();
|
||||||
@ -113,11 +113,11 @@ export function ReviewWorkflowsEditView() {
|
|||||||
if (currentWorkflowHasDeletedServerStages) {
|
if (currentWorkflowHasDeletedServerStages) {
|
||||||
setIsConfirmDeleteDialogOpen(true);
|
setIsConfirmDeleteDialogOpen(true);
|
||||||
} else if (limits?.workflows && meta?.workflowCount > parseInt(limits.workflows, 10)) {
|
} else if (limits?.workflows && meta?.workflowCount > parseInt(limits.workflows, 10)) {
|
||||||
/**
|
/**
|
||||||
* If the current license has a limit, check if the total count of workflows
|
* 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
|
* exceeds that limit and display the limits modal instead of sending the
|
||||||
* update, because it would throw an API error.
|
* update, because it would throw an API error.
|
||||||
*/
|
*/
|
||||||
setShowLimitModal('workflow');
|
setShowLimitModal('workflow');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,7 +134,9 @@ export function ReviewWorkflowsEditView() {
|
|||||||
submitForm();
|
submitForm();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
validationSchema: getWorkflowValidationSchema({ formatMessage }),
|
validate(values) {
|
||||||
|
return validateWorkflow({ values, formatMessage });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useInjectReducer(REDUX_NAMESPACE, reducer);
|
useInjectReducer(REDUX_NAMESPACE, reducer);
|
||||||
|
|||||||
@ -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"",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,7 +1,8 @@
|
|||||||
|
import set from 'lodash/set';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
export function getWorkflowValidationSchema({ formatMessage }) {
|
export async function validateWorkflow({ values, formatMessage }) {
|
||||||
return yup.object({
|
const schema = yup.object({
|
||||||
contentTypes: yup.array().of(yup.string()),
|
contentTypes: yup.array().of(yup.string()),
|
||||||
name: yup
|
name: yup
|
||||||
.string()
|
.string()
|
||||||
@ -32,6 +33,20 @@ export function getWorkflowValidationSchema({ formatMessage }) {
|
|||||||
id: 'Settings.review-workflows.validation.stage.max-length',
|
id: 'Settings.review-workflows.validation.stage.max-length',
|
||||||
defaultMessage: 'Name can not be longer than 255 characters',
|
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
|
color: yup
|
||||||
.string()
|
.string()
|
||||||
@ -46,4 +61,20 @@ export function getWorkflowValidationSchema({ formatMessage }) {
|
|||||||
)
|
)
|
||||||
.min(1),
|
.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user