Merge pull request #3712 from strapi/ctm/edit-formadata-upgrade

Ctm/edit formadata upgrade
This commit is contained in:
Alexandre BODIN 2019-08-02 09:36:59 +02:00 committed by GitHub
commit 00f0a536a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 526 additions and 473 deletions

View File

@ -1,6 +1,6 @@
import React, { memo, useEffect, useState, useReducer } from 'react';
import PropTypes from 'prop-types';
import { cloneDeep, get, isEmpty } from 'lodash';
import { cloneDeep, get } from 'lodash';
import {
BackHeader,
getQueryParameters,
@ -24,7 +24,7 @@ import createYupSchema from './utils/schema';
import {
getMediaAttributes,
cleanData,
associateFilesToData,
mapDataKeysToFilesToUpload,
} from './utils/formatData';
const getRequestUrl = path => `/${pluginId}/explorer/${path}`;
@ -289,121 +289,46 @@ function EditView({
emitEvent('willSaveEntry');
// Create an object containing all the paths of the media fields
const filesMap = getMediaAttributes(layout, groupLayoutsData);
// Create the formdata to upload all the files
const formDatas = Object.keys(filesMap).reduce((acc, current) => {
const keys = current.split('.');
const isMultiple = get(filesMap, [current, 'multiple'], false);
const isGroup = get(filesMap, [current, 'isGroup'], false);
const isRepeatable = get(filesMap, [current, 'repeatable'], false);
const getFilesToUpload = path => {
const value = get(modifiedData, path, []) || [];
return value.filter(file => {
return file instanceof File;
});
};
const getFileToUpload = path => {
const file = get(modifiedData, [...path, 0], '');
if (file instanceof File) {
return [file];
}
return [];
};
if (!isRepeatable) {
const currentFilesToUpload = isMultiple
? getFilesToUpload(keys)
: getFileToUpload([...keys]);
if (!isEmpty(currentFilesToUpload)) {
acc[current] = currentFilesToUpload.reduce((acc2, curr) => {
acc2.append('files', curr);
return acc2;
}, new FormData());
}
}
if (isGroup && isRepeatable) {
const [key, targetKey] = current.split('.');
const groupData = get(modifiedData, [key], []);
const groupFiles = groupData.reduce((acc1, current, index) => {
const files = isMultiple
? getFileToUpload([key, index, targetKey])
: getFileToUpload([key, index, targetKey]);
if (!isEmpty(files)) {
const toFormData = files.reduce((acc2, curr) => {
acc2.append('files', curr);
return acc2;
}, new FormData());
acc1[`${key}.${index}.${targetKey}`] = toFormData;
}
return acc1;
}, {});
return { ...acc, ...groupFiles };
}
return acc;
}, {});
// Change the request helper default headers so we can pass a FormData
const headers = { 'X-Forwarded-Host': 'strapi' };
// Upload the files
const mapUploadedFiles = Object.keys(formDatas).reduce(
async (acc, current) => {
const collection = await acc;
try {
const uploadedFiles = await request(
'/upload',
{
method: 'POST',
body: formDatas[current],
headers,
signal: submitSignal,
},
false,
false
);
collection[current] = uploadedFiles;
return collection;
} catch (err) {
console.log('upload error', err);
strapi.notification.error(`${pluginId}.notification.upload.error`);
}
},
Promise.resolve({})
);
// Create an object that maps the keys with the related files to upload
const filesToUpload = mapDataKeysToFilesToUpload(filesMap, modifiedData);
const cleanedData = cleanData(
cloneDeep(modifiedData),
layout,
groupLayoutsData
);
const cleanedDataWithUploadedFiles = associateFilesToData(
cleanedData,
filesMap,
await mapUploadedFiles
);
const formData = new FormData();
formData.append('data', JSON.stringify(cleanedData));
Object.keys(filesToUpload).forEach(key => {
const files = filesToUpload[key];
files.forEach(file => {
formData.append(`files.${key}`, file);
});
});
// Change the request helper default headers so we can pass a FormData
const headers = { 'X-Forwarded-Host': 'strapi' };
const method = isCreatingEntry ? 'POST' : 'PUT';
const endPoint = isCreatingEntry ? slug : `${slug}/${id}`;
try {
// Time to actually send the data
await request(getRequestUrl(endPoint), {
method,
params: { source },
body: cleanedDataWithUploadedFiles,
signal: submitSignal,
});
await request(
getRequestUrl(endPoint),
{
method,
headers,
params: { source },
body: formData,
signal: submitSignal,
},
false,
false
);
emitEvent('didSaveEntry');
redirectToPreviousPage();
} catch (err) {
@ -546,7 +471,7 @@ function EditView({
}}
groupValue={groupValue}
key={key}
isRepeatable={group.repeatable}
isRepeatable={group.repeatable || false}
name={name}
modifiedData={modifiedData}
moveGroupField={(dragIndex, overIndex, name) => {

View File

@ -65,43 +65,47 @@ function reducer(state, action) {
.update('initialData', () => fromJS(action.data))
.update('modifiedData', () => fromJS(action.data))
.update('isLoading', () => false);
case 'GET_GROUP_LAYOUTS_SUCCEEDED':
case 'GET_GROUP_LAYOUTS_SUCCEEDED': {
const addTempIdToGroupData = obj => {
const { defaultGroupValues } = action;
if (action.isCreatingEntry === true) {
return obj.keySeq().reduce((acc, current) => {
if (defaultGroupValues[current]) {
return acc.set(
current,
fromJS(defaultGroupValues[current].toSet)
);
}
return acc;
}, obj);
} else {
return obj.keySeq().reduce((acc, current) => {
if (defaultGroupValues[current] && List.isList(obj.get(current))) {
const formatted = obj.get(current).reduce((acc2, curr, index) => {
return acc2.set(index, curr.set('_temp__id', index));
}, List([]));
return acc.set(current, formatted);
}
return acc;
}, obj);
}
};
return state
.update('groupLayoutsData', () => fromJS(action.groupLayouts))
.update('defaultGroupValues', () => fromJS(action.defaultGroupValues))
.update('modifiedData', obj => {
const { defaultGroupValues } = action;
if (action.isCreatingEntry === true) {
return obj.keySeq().reduce((acc, current) => {
if (defaultGroupValues[current]) {
return acc.set(
current,
fromJS(defaultGroupValues[current].toSet)
);
}
return acc;
}, obj);
} else {
return obj.keySeq().reduce((acc, current) => {
if (
defaultGroupValues[current] &&
List.isList(obj.get(current))
) {
const formatted = obj
.get(current)
.reduce((acc2, curr, index) => {
return acc2.set(index, curr.set('_temp__id', index));
}, List([]));
return acc.set(current, formatted);
}
return acc;
}, obj);
}
return addTempIdToGroupData(obj);
})
.update('initialData', obj => {
return addTempIdToGroupData(obj);
})
.update('isLoadingForLayouts', () => false);
}
case 'INIT':
return initialState
.set('initialData', fromJS(action.data))
@ -112,11 +116,24 @@ function reducer(state, action) {
.delete(action.dragIndex)
.insert(action.overIndex, list.get(action.dragIndex));
});
case 'ON_CHANGE':
return state.updateIn(
case 'ON_CHANGE': {
let newState = state;
const [nonRepeatableGroupKey] = action.keys;
if (
action.keys.length === 2 &&
state.getIn(['modifiedData', nonRepeatableGroupKey]) === null
) {
newState = state.updateIn(['modifiedData', nonRepeatableGroupKey], () =>
fromJS({})
);
}
return newState.updateIn(
['modifiedData', ...action.keys],
() => action.value
);
}
case 'ON_REMOVE_FIELD':
return state
.removeIn(['modifiedData', ...action.keys])

View File

@ -0,0 +1,126 @@
const ctLayout = {
schema: {
attributes: {
bool: { type: 'boolean' },
content: { type: 'richtext' },
created_at: { type: 'timestamp' },
date: { type: 'date' },
enum: { type: 'enumeration', enum: ['un', 'deux'] },
fb_cta: {
required: true,
type: 'group',
group: 'cta_facebook',
repeatable: false,
},
id: { type: 'integer' },
ingredients: {
type: 'group',
group: 'ingredients',
repeatable: true,
min: 1,
max: 10,
},
json: { type: 'json' },
linkedTags: {
attribute: 'tag',
collection: 'tag',
column: 'id',
isVirtual: true,
relationType: 'manyWay',
targetModel: 'tag',
type: 'relation',
},
mainIngredient: {
type: 'group',
group: 'ingredients',
repeatable: false,
},
mainTag: {
model: 'tag',
type: 'relation',
targetModel: 'tag',
relationType: 'oneWay',
},
manyTags: {
attribute: 'tag',
collection: 'tag',
column: 'id',
dominant: true,
isVirtual: true,
relationType: 'manyToMany',
targetModel: 'tag',
type: 'relation',
via: 'linkedArticles',
},
number: { type: 'integer' },
pic: { type: 'media', multiple: false, required: false },
pictures: { type: 'media', multiple: true, required: false },
published: { type: 'boolean' },
title: {
type: 'string',
default: 'test',
required: true,
unique: true,
},
updated_at: { type: 'timestampUpdate' },
},
},
};
const groupLayouts = {
cta_facebook: {
schema: {
attributes: {
description: { type: 'text' },
id: { type: 'integer' },
title: { type: 'string' },
},
},
},
ingredients: {
schema: {
attributes: {
testMultiple: { type: 'media', multiple: true },
test: { type: 'media', multiple: false },
id: { type: 'integer' },
name: { type: 'string' },
},
},
},
};
const simpleCtLayout = {
uid: 'simple',
schema: {
attributes: {
title: {
type: 'string',
default: 'test',
},
article: {
type: 'relation',
relationType: 'oneToOne',
targetModel: 'article',
},
articles: {
type: 'relation',
relationType: 'manyToMany',
targetModel: 'article',
},
picture: {
type: 'media',
multiple: false,
},
pictures: {
type: 'media',
multiple: true,
},
},
},
// We don't need this key for the test
layouts: {},
// We don't need this key for the test
settings: {},
};
export { ctLayout, groupLayouts, simpleCtLayout };

View File

@ -1,100 +1,212 @@
import { getMediaAttributes } from '../utils/formatData';
import {
cleanData,
getMediaAttributes,
helperCleanData,
} from '../utils/formatData';
import { ctLayout, groupLayouts, simpleCtLayout } from './data';
describe('getMediasAttributes util', () => {
let ctLayout;
let groupLayouts;
describe('Content Manager | EditView | utils | cleanData', () => {
let simpleContentTypeLayout;
let contentTypeLayout;
let grpLayouts;
beforeEach(() => {
ctLayout = {
schema: {
attributes: {
bool: { type: 'boolean' },
content: { type: 'richtext' },
created_at: { type: 'timestamp' },
date: { type: 'date' },
enum: { type: 'enumeration', enum: Array(2) },
fb_cta: {
required: true,
type: 'group',
group: 'cta_facebook',
repeatable: false,
},
id: { type: 'integer' },
ingredients: {
type: 'group',
group: 'ingredients',
repeatable: true,
min: 1,
max: 10,
},
json: { type: 'json' },
linkedTags: {
attribute: 'tag',
collection: 'tag',
column: 'id',
isVirtual: true,
relationType: 'manyWay',
targetModel: 'tag',
type: 'relation',
},
mainIngredient: {
type: 'group',
group: 'ingredients',
repeatable: false,
},
mainTag: {
model: 'tag',
type: 'relation',
targetModel: 'tag',
relationType: 'oneWay',
},
manyTags: {
attribute: 'tag',
collection: 'tag',
column: 'id',
dominant: true,
isVirtual: true,
relationType: 'manyToMany',
targetModel: 'tag',
type: 'relation',
via: 'linkedArticles',
},
number: { type: 'integer' },
pic: { type: 'media', multiple: false, required: false },
pictures: { type: 'media', multiple: true, required: false },
published: { type: 'boolean' },
title: {
type: 'string',
default: 'soupette',
required: true,
unique: true,
},
updated_at: { type: 'timestampUpdate' },
},
simpleContentTypeLayout = simpleCtLayout;
contentTypeLayout = ctLayout;
grpLayouts = groupLayouts;
});
it('should format de data correctly if the content type has no group and no file has been added', () => {
const data = {
title: 'test',
article: {
id: 1,
name: 'test',
},
articles: [
{
id: 1,
name: 'test',
},
{
id: 2,
name: 'test1',
},
],
picture: {
id: 4,
url: '/something-test',
ext: 'unknown',
},
pictures: [
{
id: 1,
url: '/something',
ext: 'jpg',
},
{
id: 2,
url: '/something-else',
ext: 'png',
},
],
};
const expected = {
title: 'test',
article: 1,
articles: [1, 2],
picture: 4,
pictures: [1, 2],
};
groupLayouts = {
cta_facebook: {
schema: {
attributes: {
description: { type: 'text' },
id: { type: 'integer' },
title: { type: 'string' },
},
},
expect(cleanData(data, simpleContentTypeLayout, grpLayouts)).toEqual(
expected
);
});
it('should format the datac correctly when there is a repeatable and a non repeatable field', () => {
const data = {
bool: 'test',
content: 'test',
date: null,
enum: 'un',
fb_cta: {
description: 'something cool',
title: 'test',
},
ingredients: {
schema: {
attributes: {
testMultiple: { type: 'media', multiple: true },
test: { type: 'media', multiple: false },
id: { type: 'integer' },
name: { type: 'string' },
},
ingredients: [
{
testMultiple: [
{
id: 3,
url: '/test-test',
},
new File([''], 'test', { type: 'text/html' }),
],
test: null,
name: 'Super name',
},
],
linkedTags: [
{
name: 'test',
id: 1,
},
],
mainIngredient: {
name: 'another name',
},
mainTag: {
name: 'test1',
id: 2,
},
manyTags: [
{
name: 'test4',
id: 4,
},
],
number: 1,
pic: new File([''], 'test1', { type: 'text/html' }),
pictures: [
{
id: 1,
url: '/test',
},
new File([''], 'test2', { type: 'text/html' }),
],
published: true,
title: 'test',
};
const expected = {
bool: 'test',
content: 'test',
date: null,
enum: 'un',
fb_cta: {
description: 'something cool',
title: 'test',
},
ingredients: [
{
testMultiple: [3],
test: null,
name: 'Super name',
},
],
linkedTags: [1],
mainIngredient: {
name: 'another name',
},
mainTag: 2,
manyTags: [4],
number: 1,
pic: null,
pictures: [1],
published: true,
title: 'test',
};
expect(cleanData(data, contentTypeLayout, groupLayouts)).toEqual(expected);
});
});
describe('Content Manager | EditView | utils | helperCleanData', () => {
let data;
beforeEach(() => {
data = {
test: 'something',
object: {
id: 1,
test: 'test',
other: 'test',
},
array: [
{
id: 2,
test: 'test',
other: 'test',
},
{
id: 3,
test: 'test1',
other: 'test1',
},
{
id: 4,
test: 'test2',
other: 'test2',
},
],
other: 'super cool',
};
});
it('should return the value if it is not an object', () => {
expect(helperCleanData(data.test, 'id')).toEqual('something');
});
it('should return the id of an object', () => {
expect(helperCleanData(data.object, 'id')).toEqual(1);
});
it('should return an array with the ids of each elements if an array of objects is given', () => {
expect(helperCleanData(data.array, 'id')).toEqual([2, 3, 4]);
});
it('should return an array with the objects if the key does not exist', () => {
expect(helperCleanData(data.array, 'something')).toEqual(data.array);
});
});
describe('Content Manager | EditView | utils | getMediasAttributes', () => {
let contentTypeLayout;
let grpLayouts;
beforeEach(() => {
contentTypeLayout = ctLayout;
grpLayouts = groupLayouts;
});
it('should return an array containing the paths of all the medias attributes', () => {
@ -119,6 +231,8 @@ describe('getMediasAttributes util', () => {
pictures: { multiple: true, isGroup: false, repeatable: false },
};
expect(getMediaAttributes(ctLayout, groupLayouts)).toMatchObject(expected);
expect(getMediaAttributes(contentTypeLayout, grpLayouts)).toMatchObject(
expected
);
});
});

View File

@ -1,4 +1,74 @@
import { cloneDeep, get, isArray, isObject, set, unset } from 'lodash';
import { get, isArray, isEmpty, isObject } from 'lodash';
export const cleanData = (retrievedData, ctLayout, groupLayouts) => {
const getType = (schema, attrName) =>
get(schema, ['attributes', attrName, 'type'], '');
const getOtherInfos = (schema, arr) =>
get(schema, ['attributes', ...arr], '');
const recursiveCleanData = (data, layout) => {
return Object.keys(data).reduce((acc, current) => {
const attrType = getType(layout.schema, current);
const value = get(data, current);
const group = getOtherInfos(layout.schema, [current, 'group']);
const isRepeatable = getOtherInfos(layout.schema, [
current,
'repeatable',
]);
let cleanedData;
switch (attrType) {
case 'json':
cleanedData = value;
break;
case 'date':
cleanedData =
value && value._isAMomentObject === true
? value.toISOString()
: value;
break;
case 'media':
if (getOtherInfos(layout.schema, [current, 'multiple']) === true) {
cleanedData = value
? helperCleanData(
value.filter(file => !(file instanceof File)),
'id'
)
: null;
} else {
cleanedData =
get(value, 0) instanceof File ? null : get(value, 'id', null);
}
break;
case 'group':
if (isRepeatable) {
cleanedData = value
? value.map(data => {
delete data._temp__id;
const subCleanedData = recursiveCleanData(
data,
groupLayouts[group]
);
return subCleanedData;
})
: value;
} else {
cleanedData = recursiveCleanData(value, groupLayouts[group]);
}
break;
default:
cleanedData = helperCleanData(value, 'id');
}
acc[current] = cleanedData;
return acc;
}, {});
};
return recursiveCleanData(retrievedData, ctLayout);
};
export const getMediaAttributes = (ctLayout, groupLayouts) => {
const getMedia = (
@ -37,23 +107,7 @@ export const getMediaAttributes = (ctLayout, groupLayouts) => {
return getMedia(ctLayout);
};
export const getFilesToUpload = (data, prefix = '') => {
return Object.keys(data).reduce((acc, current) => {
if (isObject(data[current]) && !isArray(data[current])) {
return getFilesToUpload(data[current], current);
}
if (get(data, [current]) instanceof File) {
const path = prefix !== '' ? `${prefix}.${current}` : current;
acc[path] = data[current];
}
return acc;
}, {});
};
const helperCleanData = (value, key) => {
export const helperCleanData = (value, key) => {
if (isArray(value)) {
return value.map(obj => (obj[key] ? obj[key] : obj));
} else if (isObject(value)) {
@ -63,95 +117,57 @@ const helperCleanData = (value, key) => {
}
};
export const cleanData = (retrievedData, ctLayout, groupLayouts) => {
const getType = (schema, attrName) =>
get(schema, ['attributes', attrName, 'type'], '');
const getOtherInfos = (schema, arr) =>
get(schema, ['attributes', ...arr], '');
export const mapDataKeysToFilesToUpload = (filesMap, data) => {
return Object.keys(filesMap).reduce((acc, current) => {
const keys = current.split('.');
const isMultiple = get(filesMap, [current, 'multiple'], false);
const isGroup = get(filesMap, [current, 'isGroup'], false);
const isRepeatable = get(filesMap, [current, 'repeatable'], false);
const recursiveCleanData = (data, layout) => {
return Object.keys(data).reduce((acc, current) => {
const attrType = getType(layout.schema, current);
const value = get(data, current);
const group = getOtherInfos(layout.schema, [current, 'group']);
const isRepeatable = getOtherInfos(layout.schema, [
current,
'repeatable',
]);
let cleanedData;
const getFilesToUpload = path => {
const value = get(data, path, []) || [];
switch (attrType) {
case 'json':
cleanedData = value;
break;
case 'date':
cleanedData =
value && value._isAMomentObject === true
? value.toISOString()
: value;
break;
case 'media':
if (getOtherInfos(layout.schema, [current, 'multiple'])) {
cleanedData = value
? value.filter(file => !(file instanceof File))
: null;
} else {
cleanedData =
get(value, 0) instanceof File ? '' : get(value, 'id', null);
}
break;
case 'group':
if (isRepeatable) {
cleanedData = value
? value.map(data => {
delete data._temp__id;
const subCleanedData = recursiveCleanData(
data,
groupLayouts[group]
);
return subCleanedData;
})
: value;
} else {
cleanedData = recursiveCleanData(value, groupLayouts[group]);
}
break;
default:
cleanedData = helperCleanData(value, 'id');
return value.filter(file => {
return file instanceof File;
});
};
const getFileToUpload = path => {
const file = get(data, [...path, 0], '');
if (file instanceof File) {
return [file];
}
acc[current] = cleanedData;
return [];
};
return acc;
}, {});
};
if (!isRepeatable) {
const currentFilesToUpload = isMultiple
? getFilesToUpload(keys)
: getFileToUpload([...keys]);
return recursiveCleanData(retrievedData, ctLayout);
};
export const associateFilesToData = (data, filesMap, uploadedFiles) => {
const ret = cloneDeep(data);
Object.keys(uploadedFiles).forEach(key => {
const keys = key.split('.');
const filesMapKey =
keys.length > 2
? [keys[0], keys[2]]
: [keys[0], keys[1]].filter(k => !!k);
const isMultiple = get(filesMap, [...filesMapKey, 'multiple'], false);
const cleanedValue = get(uploadedFiles, key, []).map(v =>
helperCleanData(v, 'id')
);
if (isMultiple) {
const previousFiles = get(data, key, []);
set(ret, key, [...previousFiles, ...cleanedValue]);
} else {
unset(ret, key);
set(ret, key, cleanedValue[0] || null);
if (!isEmpty(currentFilesToUpload)) {
acc[current] = currentFilesToUpload;
}
}
});
return ret;
if (isGroup && isRepeatable) {
const [key, targetKey] = current.split('.');
const groupData = get(data, [key], []);
const groupFiles = groupData.reduce((acc1, current, index) => {
const files = isMultiple
? getFileToUpload([key, index, targetKey])
: getFileToUpload([key, index, targetKey]);
if (!isEmpty(files)) {
acc1[`${key}.${index}.${targetKey}`] = files;
}
return acc1;
}, {});
return { ...acc, ...groupFiles };
}
return acc;
}, {});
};

View File

@ -1,15 +0,0 @@
import { isPlainObject, isFunction } from 'lodash';
export const bindLayout = function (object) {
return Object.keys(object).reduce((acc, current) => {
if (isPlainObject(object[current])) {
acc[current] = bindLayout.call(this, object[current]);
} else if (isFunction(object[current])) {
acc[current] = object[current].bind(this);
} else {
acc[current] = object[current];
}
return acc;
}, {});
};

View File

@ -1,130 +0,0 @@
import {
findIndex,
forEach,
includes,
isArray,
isBoolean,
isEmpty,
isNaN,
isNull,
isNumber,
isObject,
isUndefined,
map,
mapKeys,
reject,
} from 'lodash';
/* eslint-disable consistent-return */
export function getValidationsFromForm(form, formValidations) {
map(form, (value, key) => {
// Check if the object
if (isObject(value) && !isArray(value)) {
forEach(value, subValue => {
// Check if it has nestedInputs
if (isArray(subValue) && value.type !== 'select') {
return getValidationsFromForm(subValue, formValidations);
}
});
}
if (isArray(value) && value.type !== 'select') {
return getValidationsFromForm(form[key], formValidations);
}
// Push the target and the validation
if (value.name) {
formValidations.push({
name: value.name,
validations: value.validations,
});
}
});
return formValidations;
}
export function checkFormValidity(formData, formValidations) {
const errors = [];
forEach(formData, (value, key) => {
const validationValue =
formValidations[findIndex(formValidations, ['name', key])];
if (!isUndefined(validationValue)) {
const inputErrors = validate(value, validationValue.validations);
if (!isEmpty(inputErrors)) {
errors.push({ name: key, errors: inputErrors });
}
}
});
return errors;
}
function validate(value, validations) {
let errors = [];
// Handle i18n
const requiredError = { id: 'content-manager.error.validation.required' };
mapKeys(validations, (validationValue, validationKey) => {
switch (validationKey) {
case 'max':
if (parseInt(value, 10) > validationValue) {
errors.push({ id: 'content-manager.error.validation.max' });
}
break;
case 'min':
if (parseInt(value, 10) < validationValue) {
errors.push({ id: 'content-manager.error.validation.min' });
}
break;
case 'maxLength':
if (value && value.length > validationValue) {
errors.push({ id: 'content-manager.error.validation.maxLength' });
}
break;
case 'minLength':
if (value && value.length < validationValue) {
errors.push({ id: 'content-manager.error.validation.minLength' });
}
break;
case 'required':
if (validationValue === true && value.length === 0) {
errors.push({ id: 'content-manager.error.validation.required' });
}
break;
case 'regex':
if (!new RegExp(validationValue).test(value)) {
errors.push({ id: 'content-manager.error.validation.regex' });
}
break;
case 'type':
if (validationValue === 'json') {
try {
if (
isObject(value) ||
isBoolean(value) ||
isNumber(value) ||
isArray(value) ||
isNaN(value) ||
isNull(value)
) {
value = JSON.parse(JSON.stringify(value));
} else {
errors.push({ id: 'content-manager.error.validation.json' });
}
} catch (err) {
errors.push({ id: 'content-manager.error.validation.json' });
}
}
break;
default:
}
});
if (includes(errors, requiredError)) {
errors = reject(errors, error => error !== requiredError);
}
return errors;
}