Merge pull request #12183 from strapi/content-15/fix-non-localized-media-not-populated-in-new-locale

Add all non-localized fields (media, compo, dz) in /get-non-localized-fields response
This commit is contained in:
Alexandre BODIN 2022-02-08 09:28:10 +01:00 committed by GitHub
commit 21bf31b5a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 264 additions and 22 deletions

View File

@ -6,6 +6,7 @@ const {
getPrivateAttributes,
getVisibleAttributes,
getNonWritableAttributes,
getScalarAttributes,
constants,
} = require('../content-types');
@ -196,4 +197,57 @@ describe('Content types utils', () => {
expect(isTypedAttribute({ type: 'test' }, 'other-type')).toBe(false);
});
});
describe('getScalarAttributes', () => {
test('returns only scalar attributes', () => {
const schema = {
attributes: {
mediaField: { type: 'media' },
componentField: { type: 'component' },
relationField: { type: 'relation' },
dynamiczoneField: { type: 'dynamiczone' },
stringField: { type: 'string' },
textField: { type: 'text' },
richtextField: { type: 'richtext' },
enumerationField: { type: 'enumeration' },
emailField: { type: 'email' },
passwordField: { type: 'password' },
uidField: { type: 'uid' },
dateField: { type: 'date' },
timeField: { type: 'time' },
datetimeField: { type: 'datetime' },
timestampField: { type: 'timestamp' },
integerField: { type: 'integer' },
bigintegerField: { type: 'biginteger' },
floatField: { type: 'float' },
decimalField: { type: 'decimal' },
booleanField: { type: 'boolean' },
arrayField: { type: 'array' },
jsonField: { type: 'json' },
},
};
const scalarAttributes = getScalarAttributes(schema);
expect(scalarAttributes).toEqual([
'stringField',
'textField',
'richtextField',
'enumerationField',
'emailField',
'passwordField',
'uidField',
'dateField',
'timeField',
'datetimeField',
'timestampField',
'integerField',
'bigintegerField',
'floatField',
'decimalField',
'booleanField',
'arrayField',
'jsonField',
]);
});
});
});

View File

@ -104,13 +104,21 @@ const isPrivateAttribute = (model = {}, attributeName) => {
};
const isScalarAttribute = attribute => {
return !['component', 'relation', 'dynamiczone'].includes(attribute.type);
return !['media', 'component', 'relation', 'dynamiczone'].includes(attribute.type);
};
const isMediaAttribute = attr => {
return attr.type === 'media';
const getScalarAttributes = schema => {
return _.reduce(
schema.attributes,
(acc, attr, attrName) => {
if (isScalarAttribute(attr)) acc.push(attrName);
return acc;
},
[]
);
};
const isMediaAttribute = attribute => attribute.type === 'media';
const isRelationalAttribute = attribute => attribute.type === 'relation';
const isComponentAttribute = attribute => ['component', 'dynamiczone'].includes(attribute.type);
@ -144,6 +152,7 @@ module.exports = {
isPrivateAttribute,
constants,
getNonWritableAttributes,
getScalarAttributes,
getWritableAttributes,
isWritableAttribute,
getNonVisibleAttributes,

View File

@ -7,9 +7,9 @@ const ctService = require('../../services/content-types')();
describe('i18n - Controller - content-types', () => {
describe('getNonLocalizedAttributes', () => {
beforeEach(() => {
const getModel = () => {};
const contentType = () => ({});
global.strapi = {
getModel,
contentType,
plugins: { i18n: { services: { 'content-types': ctService } } },
admin: { services: { constants: { READ_ACTION: 'read', CREATE_ACTION: 'create' } } },
};
@ -42,10 +42,10 @@ describe('i18n - Controller - content-types', () => {
test('entity not found', async () => {
const notFound = jest.fn();
const findOne = jest.fn(() => Promise.resolve(undefined));
const getModel = jest.fn(() => ({ pluginOptions: { i18n: { localized: true } } }));
const contentType = jest.fn(() => ({ pluginOptions: { i18n: { localized: true } } }));
global.strapi.query = () => ({ findOne });
global.strapi.getModel = getModel;
global.strapi.contentType = contentType;
const ctx = {
state: { user: {} },
request: {
@ -87,10 +87,10 @@ describe('i18n - Controller - content-types', () => {
const findOne = jest.fn(() => Promise.resolve(entity));
const findMany = jest.fn(() => Promise.resolve(permissions));
const getModel = jest.fn(() => model);
const contentType = jest.fn(() => model);
global.strapi.query = () => ({ findOne });
global.strapi.getModel = getModel;
global.strapi.contentType = contentType;
global.strapi.admin.services.permission = { findMany };
const ctx = {
state: { user: { roles: [{ id: 1 }, { id: 2 }] } },

View File

@ -1,11 +1,12 @@
'use strict';
const { pick, uniq, prop, getOr, flatten, pipe, map } = require('lodash/fp');
const { pick, uniq, prop, getOr, flatten, pipe, map, difference } = require('lodash/fp');
const { contentTypes: contentTypesUtils } = require('@strapi/utils');
const { ApplicationError } = require('@strapi/utils').errors;
const { getService } = require('../utils');
const { validateGetNonLocalizedAttributesInput } = require('../validation/content-types');
const { getScalarAttributes } = contentTypesUtils;
const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants;
const getLocalesProperty = getOr([], 'properties.locales');
@ -20,10 +21,18 @@ module.exports = {
await validateGetNonLocalizedAttributesInput({ model, id, locale });
const modelDef = strapi.getModel(model);
const { copyNonLocalizedAttributes, isLocalizedContentType } = getService('content-types');
const {
copyNonLocalizedAttributes,
isLocalizedContentType,
getNonLocalizedAttributes,
} = getService('content-types');
const { READ_ACTION, CREATE_ACTION } = strapi.admin.services.constants;
const modelDef = strapi.contentType(model);
const scalarAttributes = getScalarAttributes(modelDef);
const nonLocalizedAttributes = getNonLocalizedAttributes(modelDef);
const attributesToPopulate = difference(nonLocalizedAttributes, scalarAttributes);
if (!isLocalizedContentType(modelDef)) {
throw new ApplicationError('model.not.localized');
}
@ -32,7 +41,7 @@ module.exports = {
const entity = await strapi
.query(model)
.findOne({ where: params, populate: ['localizations'] });
.findOne({ where: params, populate: [...attributesToPopulate, 'localizations'] });
if (!entity) {
return ctx.notFound();

View File

@ -5,7 +5,6 @@ const { pick, pipe, has, prop, isNil, cloneDeep, isArray } = require('lodash/fp'
const {
isRelationalAttribute,
getVisibleAttributes,
isMediaAttribute,
isTypedAttribute,
} = require('@strapi/utils').contentTypes;
const { ApplicationError } = require('@strapi/utils').errors;
@ -82,12 +81,10 @@ const getAndValidateRelatedEntity = async (relatedEntityId, model, locale) => {
* @param {*} attribute
* @returns
*/
const isLocalizedAttribute = (model, attributeName) => {
const attribute = model.attributes[attributeName];
const isLocalizedAttribute = attribute => {
return (
hasLocalizedOption(attribute) ||
(isRelationalAttribute(attribute) && !isMediaAttribute(attribute)) ||
isRelationalAttribute(attribute) ||
isTypedAttribute(attribute, 'uid')
);
};
@ -108,7 +105,7 @@ const isLocalizedContentType = model => {
*/
const getNonLocalizedAttributes = model => {
return getVisibleAttributes(model).filter(
attributeName => !isLocalizedAttribute(model, attributeName)
attrName => !isLocalizedAttribute(model.attributes[attrName])
);
};
@ -170,8 +167,8 @@ const copyNonLocalizedAttributes = (model, entry) => {
* @returns {string[]}
*/
const getLocalizedAttributes = model => {
return getVisibleAttributes(model).filter(attributeName =>
isLocalizedAttribute(model, attributeName)
return getVisibleAttributes(model).filter(attrName =>
isLocalizedAttribute(model.attributes[attrName])
);
};

View File

@ -9,7 +9,7 @@ const validateGetNonLocalizedAttributesSchema = yup
.shape({
model: yup.string().required(),
id: yup.mixed().when('model', {
is: model => get('kind', strapi.getModel(model)) === 'singleType',
is: model => get('kind', strapi.contentType(model)) === 'singleType',
then: yup.strapiID().nullable(),
otherwise: yup.strapiID().required(),
}),

View File

@ -0,0 +1,173 @@
'use strict';
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createTestBuilder } = require('../../../../../test/helpers/builder');
let strapi;
let rq;
const compoModel = {
collectionName: 'components_default_simples',
displayName: 'simple',
description: '',
icon: 'ambulance',
attributes: {
name: {
type: 'string',
required: true,
},
test: {
type: 'string',
},
},
};
const categoryModel = {
kind: 'collectionType',
collectionName: 'categories',
displayName: 'Category',
singularName: 'category',
pluralName: 'categories',
description: '',
name: 'Category',
options: {
draftAndPublish: false,
},
pluginOptions: {
i18n: {
localized: true,
},
},
attributes: {
name: {
type: 'string',
},
},
};
const dogSchema = {
kind: 'collectionType',
collectionName: 'dogs',
displayName: 'Dog',
singularName: 'dog',
pluralName: 'dogs',
options: {
draftAndPublish: false,
},
pluginOptions: {
i18n: {
localized: true,
},
},
attributes: {
name: {
type: 'string',
pluginOptions: {
i18n: {
localized: true,
},
},
},
description: {
type: 'string',
},
categories: {
type: 'relation',
relation: 'manyToMany',
target: 'api::category.category',
targetAttribute: 'dogs',
},
myCompo: {
type: 'component',
repeatable: false,
component: 'default.simple',
},
myDz: {
type: 'dynamiczone',
components: ['default.simple'],
},
},
};
const dogs = [
{
name: 'Pilou',
description: 'A good girl',
myCompo: { name: 'my compo' },
myDz: [{ name: 'my compo', __component: 'default.simple' }],
},
];
const categories = [{ name: 'Labrador' }];
const data = {};
describe('i18n - Content API', () => {
const builder = createTestBuilder();
beforeAll(async () => {
await builder
.addComponent(compoModel)
.addContentTypes([categoryModel, dogSchema])
.addFixtures('plugin::i18n.locale', [
{
name: 'French (fr)',
code: 'fr',
},
])
.addFixtures(dogSchema.singularName, dogs)
.addFixtures(categoryModel.singularName, categories)
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
data.dogs = await builder.sanitizedFixturesFor(dogSchema.singularName, strapi);
data.categories = await builder.sanitizedFixturesFor(categoryModel.singularName, strapi);
const { body } = await rq({
method: 'PUT',
url: `/content-manager/collection-types/api::dog.dog/${data.dogs[0].id}`,
body: {
categories: [data.categories[0].id],
},
});
data.dogs[0] = body;
});
afterAll(async () => {
await strapi.destroy();
await builder.cleanup();
});
describe('Test content-types', () => {
describe('getNonLocalizedAttributes', () => {
test('Get non localized attributes (including compo and dz)', async () => {
const res = await rq({
method: 'POST',
url: '/i18n/content-manager/actions/get-non-localized-fields',
body: {
id: data.dogs[0].id,
locale: 'fr',
model: 'api::dog.dog',
},
});
expect(res.body).toMatchObject({
nonLocalizedFields: {
description: 'A good girl',
myCompo: { name: 'my compo', test: null },
myDz: [
{
__component: 'default.simple',
name: 'my compo',
test: null,
},
],
},
localizations: [{ id: 1, locale: 'en' }],
});
});
});
});
});