Merge branch 'main' into chore/tracking-access-CTB

This commit is contained in:
Simone Taeggi 2022-11-17 12:03:00 +01:00
commit f62da9d4a4
5 changed files with 890 additions and 12 deletions

View File

@ -311,6 +311,10 @@ const reducer = (state = initialState, action) =>
toSet.private = rest.private;
}
if (rest.pluginOptions) {
toSet.pluginOptions = rest.pluginOptions;
}
const currentAttributeIndex = updatedAttributes.findIndex(
({ name }) => name === initialAttribute.name
);
@ -323,6 +327,7 @@ const reducer = (state = initialState, action) =>
let oppositeAttributeNameToRemove = null;
let oppositeAttributeNameToUpdate = null;
let oppositeAttributeToCreate = null;
let initialOppositeAttribute = null;
const currentUid = get(state, ['modifiedData', ...pathToDataToEdit, 'uid']);
const didChangeTargetRelation = initialAttribute.target !== rest.target;
@ -378,6 +383,29 @@ const reducer = (state = initialState, action) =>
updatedAttributes.splice(indexToRemove, 1);
}
// In order to preserve plugin options need to get the initial opposite attribute settings
if (!shouldRemoveOppositeAttributeBecauseOfTargetChange) {
const initialTargetContentType = get(state, [
'initialContentTypes',
initialAttribute.target,
]);
if (initialTargetContentType) {
const oppositeAttributeIndex = findAttributeIndex(
initialTargetContentType,
initialAttribute.targetAttribute
);
initialOppositeAttribute = get(state, [
'initialContentTypes',
initialAttribute.target,
'schema',
'attributes',
oppositeAttributeIndex,
]);
}
}
// Create the opposite attribute
if (
shouldCreateOppositeAttributeBecauseOfRelationTypeChange ||
@ -395,6 +423,10 @@ const reducer = (state = initialState, action) =>
oppositeAttributeToCreate.private = rest.private;
}
if (initialOppositeAttribute && initialOppositeAttribute.pluginOptions) {
oppositeAttributeToCreate.pluginOptions = initialOppositeAttribute.pluginOptions;
}
const indexOfInitialAttribute = updatedAttributes.findIndex(
({ name }) => name === initialAttribute.name
);
@ -424,6 +456,10 @@ const reducer = (state = initialState, action) =>
oppositeAttributeToCreate.private = rest.private;
}
if (initialOppositeAttribute && initialOppositeAttribute.pluginOptions) {
oppositeAttributeToCreate.pluginOptions = initialOppositeAttribute.pluginOptions;
}
if (oppositeAttributeNameToUpdate) {
const indexToUpdate = updatedAttributes.findIndex(
({ name }) => name === oppositeAttributeNameToUpdate

View File

@ -1366,5 +1366,851 @@ describe('CTB | components | DataManagerProvider | reducer | EDIT_ATTRIBUTE', ()
expect(reducer(state, action)).toEqual(expected);
});
});
describe('Editing a relation and preserve plugin options', () => {
it('Should save pluginOptions if the relation is a one side relation (oneWay, manyWay)', () => {
const contentTypeUID = 'api::category.category';
const updatedTargetUID = 'api::address.address';
const contentType = {
uid: contentTypeUID,
schema: {
name: 'address',
description: '',
connection: 'default',
collectionName: '',
attributes: [
{ name: 'postal_code', type: 'string' },
{
name: 'one_way',
relation: 'oneToOne',
targetAttribute: null,
target: contentTypeUID,
type: 'relation',
},
{
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
},
{
name: 'cover',
type: 'media',
multiple: false,
required: false,
},
],
},
};
const state = {
...initialState,
components: {},
initialComponents: {},
contentTypes: { [contentTypeUID]: contentType },
initialContentTypes: { [contentTypeUID]: contentType },
modifiedData: {
components: {},
contentType,
},
};
const action = {
type: EDIT_ATTRIBUTE,
attributeToSet: {
relation: 'oneToOne',
targetAttribute: null,
target: updatedTargetUID,
type: 'relation',
name: 'one_way',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
forTarget: 'contentType',
targetUid: contentTypeUID,
initialAttribute: {
relation: 'oneToOne',
targetAttribute: null,
target: contentTypeUID,
type: 'relation',
name: 'one_way',
},
shouldAddComponentToData: false,
};
const expected = {
...initialState,
components: {},
initialComponents: {},
contentTypes: { [contentTypeUID]: contentType },
initialContentTypes: { [contentTypeUID]: contentType },
modifiedData: {
components: {},
contentType: {
...contentType,
schema: {
...contentType.schema,
attributes: [
{ name: 'postal_code', type: 'string' },
{
name: 'one_way',
relation: 'oneToOne',
targetAttribute: null,
target: updatedTargetUID,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
{
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
},
{
name: 'cover',
type: 'media',
multiple: false,
required: false,
},
],
},
},
},
};
expect(reducer(state, action)).toEqual(expected);
});
it('Should preserve plugin options on the opposite attribute if the target is a the same content type and the nature is not a one side relation (oneToOne, ...)', () => {
const contentTypeUID = 'api::address.address';
const contentType = {
uid: contentTypeUID,
schema: {
name: 'address',
description: '',
connection: 'default',
collectionName: '',
attributes: [
{ name: 'postal_code', type: 'string' },
{
name: 'one_to_many',
relation: 'oneToMany',
targetAttribute: 'many_to_one',
target: contentTypeUID,
type: 'relation',
},
{
name: 'many_to_one',
relation: 'manyToOne',
targetAttribute: 'one_to_many',
target: contentTypeUID,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
{
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
},
{
name: 'cover',
type: 'media',
multiple: false,
required: false,
},
],
},
};
const state = {
...initialState,
components: {},
initialComponents: {},
contentTypes: { [contentTypeUID]: contentType },
initialContentTypes: { [contentTypeUID]: contentType },
modifiedData: {
components: {},
contentType,
},
};
const action = {
type: EDIT_ATTRIBUTE,
attributeToSet: {
relation: 'oneToMany',
targetAttribute: 'many_to_one',
target: contentTypeUID,
type: 'relation',
name: 'one_to_many',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
forTarget: 'contentType',
targetUid: contentTypeUID,
initialAttribute: {
relation: 'oneToMany',
targetAttribute: 'many_to_one',
target: contentTypeUID,
type: 'relation',
name: 'one_to_many',
},
shouldAddComponentToData: false,
};
const expected = {
...initialState,
components: {},
initialComponents: {},
contentTypes: { [contentTypeUID]: contentType },
initialContentTypes: { [contentTypeUID]: contentType },
modifiedData: {
components: {},
contentType: {
...contentType,
schema: {
...contentType.schema,
attributes: [
{ name: 'postal_code', type: 'string' },
{
name: 'one_to_many',
relation: 'oneToMany',
targetAttribute: 'many_to_one',
target: contentTypeUID,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
{
name: 'many_to_one',
relation: 'manyToOne',
targetAttribute: 'one_to_many',
target: contentTypeUID,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
{
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
},
{
name: 'cover',
type: 'media',
multiple: false,
required: false,
},
],
},
},
},
};
expect(reducer(state, action)).toEqual(expected);
});
it('Should save pluginOptions if the relation is nested inside a component', () => {
const contentTypeUID = 'api::address.address';
const componentUID = 'default.dish';
const contentType = {
uid: contentTypeUID,
schema: {
name: 'address',
description: '',
connection: 'default',
collectionName: '',
attributes: [
{
name: 'dishes',
component: componentUID,
type: 'component',
repeatable: true,
},
{ name: 'dynamiczone', type: 'dynamiczone', components: [componentUID] },
],
},
};
const component = {
uid: componentUID,
category: 'default',
schema: {
icon: 'book',
name: 'dish',
description: '',
connection: 'default',
collectionName: 'components_dishes',
attributes: [
{
name: 'name',
type: 'string',
required: true,
default: 'My super dish',
},
{
name: 'description',
type: 'text',
},
{
name: 'price',
type: 'float',
},
{
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
},
],
},
};
const state = {
...initialState,
components: { [componentUID]: component },
initialComponents: { [componentUID]: component },
contentTypes: { [contentTypeUID]: contentType },
initialContentTypes: { [contentTypeUID]: contentType },
modifiedData: {
components: { [componentUID]: component },
contentType,
},
};
const action = {
type: EDIT_ATTRIBUTE,
attributeToSet: {
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
forTarget: 'components',
targetUid: componentUID,
initialAttribute: {
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
},
shouldAddComponentToData: false,
};
const expected = {
...initialState,
components: { [componentUID]: component },
initialComponents: { [componentUID]: component },
contentTypes: { [contentTypeUID]: contentType },
initialContentTypes: { [contentTypeUID]: contentType },
modifiedData: {
components: {
[componentUID]: {
...component,
schema: {
...component.schema,
attributes: [
{
name: 'name',
type: 'string',
required: true,
default: 'My super dish',
},
{
name: 'description',
type: 'text',
},
{
name: 'price',
type: 'float',
},
{
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
],
},
},
},
contentType,
},
};
expect(reducer(state, action)).toEqual(expected);
});
it('Should preserve pluginOptions if the relation is nested inside a component', () => {
const contentTypeUID = 'api::address.address';
const componentUID = 'default.dish';
const contentType = {
uid: contentTypeUID,
schema: {
name: 'address',
description: '',
connection: 'default',
collectionName: '',
attributes: [
{
name: 'dishes',
component: componentUID,
type: 'component',
repeatable: true,
},
{ name: 'dynamiczone', type: 'dynamiczone', components: [componentUID] },
],
},
};
const component = {
uid: componentUID,
category: 'default',
schema: {
icon: 'book',
name: 'dish',
description: '',
connection: 'default',
collectionName: 'components_dishes',
attributes: [
{
name: 'name',
type: 'string',
required: true,
default: 'My super dish',
},
{
name: 'description',
type: 'text',
},
{
name: 'price',
type: 'float',
},
{
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
],
},
};
const state = {
...initialState,
components: { [componentUID]: component },
initialComponents: { [componentUID]: component },
contentTypes: { [contentTypeUID]: contentType },
initialContentTypes: { [contentTypeUID]: contentType },
modifiedData: {
components: { [componentUID]: component },
contentType,
},
};
const action = {
type: EDIT_ATTRIBUTE,
attributeToSet: {
name: 'category-new',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
forTarget: 'components',
targetUid: componentUID,
initialAttribute: {
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
shouldAddComponentToData: false,
};
const expected = {
...initialState,
components: { [componentUID]: component },
initialComponents: { [componentUID]: component },
contentTypes: { [contentTypeUID]: contentType },
initialContentTypes: { [contentTypeUID]: contentType },
modifiedData: {
components: {
[componentUID]: {
...component,
schema: {
...component.schema,
attributes: [
{
name: 'name',
type: 'string',
required: true,
default: 'My super dish',
},
{
name: 'description',
type: 'text',
},
{
name: 'price',
type: 'float',
},
{
name: 'category-new',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
],
},
},
},
contentType,
},
};
expect(reducer(state, action)).toEqual(expected);
});
it('Should save pluginOptions if the relation is nested inside a dynamic zone', () => {
const contentTypeUID = 'api::address.address';
const componentUID = 'default.dish';
const contentType = {
uid: contentTypeUID,
schema: {
name: 'address',
description: '',
connection: 'default',
collectionName: '',
attributes: [{ name: 'dynamiczone', type: 'dynamiczone', components: [componentUID] }],
},
};
const component = {
uid: componentUID,
category: 'default',
schema: {
icon: 'book',
name: 'dish',
description: '',
connection: 'default',
collectionName: 'components_dishes',
attributes: [
{
name: 'name',
type: 'string',
required: true,
default: 'My super dish',
},
{
name: 'description',
type: 'text',
},
{
name: 'price',
type: 'float',
},
{
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
},
],
},
};
const state = {
...initialState,
components: { [componentUID]: component },
initialComponents: { [componentUID]: component },
contentTypes: { [contentTypeUID]: contentType },
initialContentTypes: { [contentTypeUID]: contentType },
modifiedData: {
components: { [componentUID]: component },
contentType,
},
};
const action = {
type: EDIT_ATTRIBUTE,
attributeToSet: {
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
forTarget: 'components',
targetUid: componentUID,
initialAttribute: {
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
},
shouldAddComponentToData: false,
};
const expected = {
...initialState,
components: { [componentUID]: component },
initialComponents: { [componentUID]: component },
contentTypes: { [contentTypeUID]: contentType },
initialContentTypes: { [contentTypeUID]: contentType },
modifiedData: {
components: {
[componentUID]: {
...component,
schema: {
...component.schema,
attributes: [
{
name: 'name',
type: 'string',
required: true,
default: 'My super dish',
},
{
name: 'description',
type: 'text',
},
{
name: 'price',
type: 'float',
},
{
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
],
},
},
},
contentType,
},
};
expect(reducer(state, action)).toEqual(expected);
});
it('Should preserve pluginOptions if the relation is nested inside a dynamic zone', () => {
const contentTypeUID = 'api::address.address';
const componentUID = 'default.dish';
const contentType = {
uid: contentTypeUID,
schema: {
name: 'address',
description: '',
connection: 'default',
collectionName: '',
attributes: [{ name: 'dynamiczone', type: 'dynamiczone', components: [componentUID] }],
},
};
const component = {
uid: componentUID,
category: 'default',
schema: {
icon: 'book',
name: 'dish',
description: '',
connection: 'default',
collectionName: 'components_dishes',
attributes: [
{
name: 'name',
type: 'string',
required: true,
default: 'My super dish',
},
{
name: 'description',
type: 'text',
},
{
name: 'price',
type: 'float',
},
{
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
],
},
};
const state = {
...initialState,
components: { [componentUID]: component },
initialComponents: { [componentUID]: component },
contentTypes: { [contentTypeUID]: contentType },
initialContentTypes: { [contentTypeUID]: contentType },
modifiedData: {
components: { [componentUID]: component },
contentType,
},
};
const action = {
type: EDIT_ATTRIBUTE,
attributeToSet: {
name: 'category-new',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
forTarget: 'components',
targetUid: componentUID,
initialAttribute: {
name: 'category',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
shouldAddComponentToData: false,
};
const expected = {
...initialState,
components: { [componentUID]: component },
initialComponents: { [componentUID]: component },
contentTypes: { [contentTypeUID]: contentType },
initialContentTypes: { [contentTypeUID]: contentType },
modifiedData: {
components: {
[componentUID]: {
...component,
schema: {
...component.schema,
attributes: [
{
name: 'name',
type: 'string',
required: true,
default: 'My super dish',
},
{
name: 'description',
type: 'text',
},
{
name: 'price',
type: 'float',
},
{
name: 'category-new',
relation: 'oneToOne',
target: 'api::category.category',
targetAttribute: null,
type: 'relation',
pluginOptions: {
myplugin: {
example: 'first',
},
},
},
],
},
},
},
contentType,
},
};
expect(reducer(state, action)).toEqual(expected);
});
});
});
});

View File

@ -259,6 +259,7 @@ const generateRelation = ({ key, attribute, uid, targetAttribute = {} }) => {
target: uid,
autoPopulate: targetAttribute.autoPopulate,
private: targetAttribute.private || undefined,
pluginOptions: targetAttribute.pluginOptions || undefined,
};
switch (attribute.relation) {

View File

@ -27,7 +27,7 @@
"@babel/cli": "7.18.10",
"@babel/core": "7.18.10",
"@babel/generator": "7.18.7",
"@babel/parser": "7.18.10",
"@babel/parser": "7.18.13",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/plugin-transform-modules-commonjs": "7.18.6",
"@babel/plugin-transform-runtime": "7.18.10",

View File

@ -648,21 +648,16 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/parser@7.18.10", "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.17.9", "@babel/parser@^7.18.10", "@babel/parser@^7.18.5", "@babel/parser@^7.18.9", "@babel/parser@^7.7.0", "@babel/parser@^7.8.3":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.10.tgz#94b5f8522356e69e8277276adf67ed280c90ecc1"
integrity sha512-TYk3OA0HKL6qNryUayb5UUEhM/rkOQozIBEA5ITXh5DWrSp0TlUQXMyZmnWxG/DizSWBeeQ0Zbc5z8UGaaqoeg==
"@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.19.0":
version "7.19.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c"
integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==
"@babel/parser@^7.18.13":
"@babel/parser@7.18.13":
version "7.18.13"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.13.tgz#5b2dd21cae4a2c5145f1fbd8ca103f9313d3b7e4"
integrity sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==
"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.17.9", "@babel/parser@^7.18.10", "@babel/parser@^7.18.13", "@babel/parser@^7.18.5", "@babel/parser@^7.18.9", "@babel/parser@^7.19.0", "@babel/parser@^7.7.0", "@babel/parser@^7.8.3":
version "7.19.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c"
integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"