mirror of
https://github.com/strapi/strapi.git
synced 2025-08-10 09:47:46 +00:00
Merge branch 'master' into colors-fix
This commit is contained in:
commit
026078566a
@ -28,6 +28,7 @@ import retrieveComponentsFromSchema from './utils/retrieveComponentsFromSchema';
|
|||||||
import retrieveNestedComponents from './utils/retrieveNestedComponents';
|
import retrieveNestedComponents from './utils/retrieveNestedComponents';
|
||||||
import { retrieveComponentsThatHaveComponents } from './utils/retrieveComponentsThatHaveComponents';
|
import { retrieveComponentsThatHaveComponents } from './utils/retrieveComponentsThatHaveComponents';
|
||||||
import { getComponentsToPost, formatMainDataType, sortContentType } from './utils/cleanData';
|
import { getComponentsToPost, formatMainDataType, sortContentType } from './utils/cleanData';
|
||||||
|
import validateSchema from './utils/validateSchema';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ADD_ATTRIBUTE,
|
ADD_ATTRIBUTE,
|
||||||
@ -440,6 +441,21 @@ const DataManagerProvider = ({
|
|||||||
initialData.contentType
|
initialData.contentType
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isValidSchema = validateSchema(contentType);
|
||||||
|
|
||||||
|
if (!isValidSchema) {
|
||||||
|
toggleNotification({
|
||||||
|
type: 'warning',
|
||||||
|
message: {
|
||||||
|
id: getTrad('notification.error.dynamiczone-min.validation'),
|
||||||
|
defaultMessage:
|
||||||
|
'At least one component is required in a dynamic zone to be able to save a content type',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
body.contentType = contentType;
|
body.contentType = contentType;
|
||||||
|
|
||||||
trackUsage('willSaveContentType');
|
trackUsage('willSaveContentType');
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
const validateSchema = schema => {
|
||||||
|
const dynamicZoneAttributes = Object.values(schema.attributes).filter(
|
||||||
|
({ type }) => type === 'dynamiczone'
|
||||||
|
);
|
||||||
|
|
||||||
|
return dynamicZoneAttributes.every(
|
||||||
|
({ components }) => Array.isArray(components) && components.length > 0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default validateSchema;
|
@ -166,6 +166,7 @@
|
|||||||
"modelPage.attribute.relation-polymorphic": "Relation (polymorphic)",
|
"modelPage.attribute.relation-polymorphic": "Relation (polymorphic)",
|
||||||
"modelPage.attribute.relationWith": "Relation with",
|
"modelPage.attribute.relationWith": "Relation with",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
|
"notification.error.dynamiczone-min.validation": "At least one component is required in a dynamic zone to be able to save a content type",
|
||||||
"notification.info.autoreaload-disable": "The autoReload feature is required to use this plugin. Start your server with `strapi develop`",
|
"notification.info.autoreaload-disable": "The autoReload feature is required to use this plugin. Start your server with `strapi develop`",
|
||||||
"notification.info.creating.notSaved": "Please save your work before creating a new collection type or component",
|
"notification.info.creating.notSaved": "Please save your work before creating a new collection type or component",
|
||||||
"plugin.description.long": "Modelize the data structure of your API. Create new fields and relations in just a minute. The files are automatically created and updated in your project.",
|
"plugin.description.long": "Modelize the data structure of your API. Create new fields and relations in just a minute. The files are automatically created and updated in your project.",
|
||||||
|
@ -43,6 +43,42 @@ describe('Type validators', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Dynamiczone type validator', () => {
|
||||||
|
test('Components cannot be empty', () => {
|
||||||
|
const attributes = {
|
||||||
|
dz: {
|
||||||
|
type: 'dynamiczone',
|
||||||
|
components: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const validator = getTypeValidator(attributes.dz, {
|
||||||
|
types: ['dynamiczone'],
|
||||||
|
modelType: 'collectionType',
|
||||||
|
attributes,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(validator.isValidSync(attributes.dz)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Components must have at least one item', () => {
|
||||||
|
const attributes = {
|
||||||
|
dz: {
|
||||||
|
type: 'dynamiczone',
|
||||||
|
components: ['compoA', 'compoB'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const validator = getTypeValidator(attributes.dz, {
|
||||||
|
types: ['dynamiczone'],
|
||||||
|
modelType: 'collectionType',
|
||||||
|
attributes,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(validator.isValidSync(attributes.dz)).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('UID type validator', () => {
|
describe('UID type validator', () => {
|
||||||
test('Target field can be null', () => {
|
test('Target field can be null', () => {
|
||||||
const attributes = {
|
const attributes = {
|
||||||
|
@ -258,7 +258,8 @@ const getTypeShape = (attribute, { modelType, attributes } = {}) => {
|
|||||||
components: yup
|
components: yup
|
||||||
.array()
|
.array()
|
||||||
.of(yup.string().required())
|
.of(yup.string().required())
|
||||||
.test('isArray', '${path} must be an array', value => Array.isArray(value)),
|
.test('isArray', '${path} must be an array', value => Array.isArray(value))
|
||||||
|
.min(1),
|
||||||
min: yup.number(),
|
min: yup.number(),
|
||||||
max: yup.number(),
|
max: yup.number(),
|
||||||
};
|
};
|
||||||
|
@ -45,7 +45,7 @@ const Content = ({ appLocales, currentLocale, localizations, readPermissions })
|
|||||||
const toggleNotification = useNotification();
|
const toggleNotification = useNotification();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { allLayoutData, slug } = useCMEditViewDataManager();
|
const { allLayoutData, initialData, slug } = useCMEditViewDataManager();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [value, setValue] = useState(options[0]?.value || '');
|
const [value, setValue] = useState(options[0]?.value || '');
|
||||||
@ -59,12 +59,15 @@ const Content = ({ appLocales, currentLocale, localizations, readPermissions })
|
|||||||
|
|
||||||
const requestURL = `/content-manager/collection-types/${slug}/${value}`;
|
const requestURL = `/content-manager/collection-types/${slug}/${value}`;
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
const { data: response } = await axiosInstance.get(requestURL);
|
const { data: response } = await axiosInstance.get(requestURL);
|
||||||
|
|
||||||
const cleanedData = cleanData(response, allLayoutData, localizations);
|
const cleanedData = cleanData(response, allLayoutData, localizations);
|
||||||
|
['createdBy', 'updatedBy', 'publishedAt', 'id', 'createdAt'].forEach(key => {
|
||||||
|
if (!initialData[key]) return;
|
||||||
|
cleanedData[key] = initialData[key];
|
||||||
|
});
|
||||||
|
|
||||||
dispatch({ type: 'ContentManager/CrudReducer/GET_DATA_SUCCEEDED', data: cleanedData });
|
dispatch({ type: 'ContentManager/CrudReducer/GET_DATA_SUCCEEDED', data: cleanedData });
|
||||||
|
|
||||||
|
@ -13,15 +13,7 @@ const cleanData = (data, { contentType, components }, initialLocalizations) => {
|
|||||||
|
|
||||||
dataWithoutPasswordsAndRelations.localizations = initialLocalizations;
|
dataWithoutPasswordsAndRelations.localizations = initialLocalizations;
|
||||||
|
|
||||||
const fieldsToRemove = [
|
const fieldsToRemove = ['createdBy', 'updatedBy', 'publishedAt', 'id', 'updatedAt', 'createdAt'];
|
||||||
'createdBy',
|
|
||||||
'updatedBy',
|
|
||||||
'publishedAt',
|
|
||||||
'id',
|
|
||||||
'_id',
|
|
||||||
'updatedAt',
|
|
||||||
'createdAt',
|
|
||||||
];
|
|
||||||
|
|
||||||
const cleanedClonedData = contentManagementUtilRemoveFieldsFromData(
|
const cleanedClonedData = contentManagementUtilRemoveFieldsFromData(
|
||||||
dataWithoutPasswordsAndRelations,
|
dataWithoutPasswordsAndRelations,
|
||||||
|
@ -113,9 +113,8 @@ const getNonLocalizedAttributes = model => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const removeId = value => {
|
const removeId = value => {
|
||||||
if (typeof value === 'object' && (has('id', value) || has('_id', value))) {
|
if (typeof value === 'object' && has('id', value)) {
|
||||||
delete value.id;
|
delete value.id;
|
||||||
delete value._id;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ describe('i18n - Relation-list route', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(res.body).toHaveLength(1);
|
expect(res.body).toHaveLength(1);
|
||||||
expect(res.body[0]).toStrictEqual(pick(['_id', 'id', 'name'], data.products[1]));
|
expect(res.body[0]).toStrictEqual(pick(['id', 'name'], data.products[1]));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can filter on any locale', async () => {
|
test('Can filter on any locale', async () => {
|
||||||
@ -125,6 +125,6 @@ describe('i18n - Relation-list route', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(res.body).toHaveLength(1);
|
expect(res.body).toHaveLength(1);
|
||||||
expect(res.body[0]).toStrictEqual(pick(['_id', 'id', 'name'], data.products[0]));
|
expect(res.body[0]).toStrictEqual(pick(['id', 'name'], data.products[0]));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user