mirror of
https://github.com/strapi/strapi.git
synced 2025-09-25 08:19:07 +00:00
Merge pull request #14977 from strapi/refactor/deep-populate-entity-manager
refactor(entity-manager): getDeepPopulate
This commit is contained in:
commit
ed5c21e5c0
@ -62,40 +62,40 @@ module.exports = ({ strapi }) => ({
|
||||
return assoc(`${CREATED_BY_ATTRIBUTE}.roles`, roles, entity);
|
||||
},
|
||||
|
||||
find(opts, uid, populate) {
|
||||
const params = { ...opts, populate: getDeepPopulate(uid, populate) };
|
||||
find(opts, uid) {
|
||||
const params = { ...opts, populate: getDeepPopulate(uid) };
|
||||
|
||||
return strapi.entityService.findMany(uid, params);
|
||||
},
|
||||
|
||||
findPage(opts, uid, populate) {
|
||||
const params = { ...opts, populate: getDeepPopulate(uid, populate, { maxLevel: 1 }) };
|
||||
findPage(opts, uid) {
|
||||
const params = { ...opts, populate: getDeepPopulate(uid, { maxLevel: 1 }) };
|
||||
|
||||
return strapi.entityService.findPage(uid, params);
|
||||
},
|
||||
|
||||
findWithRelationCountsPage(opts, uid, populate) {
|
||||
const counterPopulate = getDeepPopulate(uid, populate, { countMany: true, maxLevel: 1 });
|
||||
findWithRelationCountsPage(opts, uid) {
|
||||
const counterPopulate = getDeepPopulate(uid, { countMany: true, maxLevel: 1 });
|
||||
const params = { ...opts, populate: addCreatedByRolesPopulate(counterPopulate) };
|
||||
|
||||
return strapi.entityService.findWithRelationCountsPage(uid, params);
|
||||
},
|
||||
|
||||
findOneWithCreatorRolesAndCount(id, uid, populate) {
|
||||
const counterPopulate = getDeepPopulate(uid, populate, { countMany: true, countOne: true });
|
||||
findOneWithCreatorRolesAndCount(id, uid) {
|
||||
const counterPopulate = getDeepPopulate(uid, { countMany: true, countOne: true });
|
||||
const params = { populate: addCreatedByRolesPopulate(counterPopulate) };
|
||||
|
||||
return strapi.entityService.findOne(uid, id, params);
|
||||
},
|
||||
|
||||
async findOne(id, uid, populate) {
|
||||
const params = { populate: getDeepPopulate(uid, populate) };
|
||||
async findOne(id, uid) {
|
||||
const params = { populate: getDeepPopulate(uid) };
|
||||
|
||||
return strapi.entityService.findOne(uid, id, params);
|
||||
},
|
||||
|
||||
async findOneWithCreatorRoles(id, uid, populate) {
|
||||
const entity = await this.findOne(id, uid, populate);
|
||||
async findOneWithCreatorRoles(id, uid) {
|
||||
const entity = await this.findOne(id, uid);
|
||||
|
||||
if (!entity) {
|
||||
return entity;
|
||||
@ -114,7 +114,7 @@ module.exports = ({ strapi }) => ({
|
||||
|
||||
const params = {
|
||||
data: publishData,
|
||||
populate: getDeepPopulate(uid, null, { countMany: true, countOne: true }),
|
||||
populate: getDeepPopulate(uid, { countMany: true, countOne: true }),
|
||||
};
|
||||
|
||||
return strapi.entityService.create(uid, params);
|
||||
@ -125,14 +125,14 @@ module.exports = ({ strapi }) => ({
|
||||
|
||||
const params = {
|
||||
data: publishData,
|
||||
populate: getDeepPopulate(uid, null, { countMany: true, countOne: true }),
|
||||
populate: getDeepPopulate(uid, { countMany: true, countOne: true }),
|
||||
};
|
||||
|
||||
return strapi.entityService.update(uid, entity.id, params);
|
||||
},
|
||||
|
||||
delete(entity, uid) {
|
||||
const params = { populate: getDeepPopulate(uid, null, { countMany: true, countOne: true }) };
|
||||
const params = { populate: getDeepPopulate(uid, { countMany: true, countOne: true }) };
|
||||
|
||||
return strapi.entityService.delete(uid, entity.id, params);
|
||||
},
|
||||
@ -161,7 +161,7 @@ module.exports = ({ strapi }) => ({
|
||||
|
||||
const params = {
|
||||
data,
|
||||
populate: getDeepPopulate(uid, null, { countMany: true, countOne: true }),
|
||||
populate: getDeepPopulate(uid, { countMany: true, countOne: true }),
|
||||
};
|
||||
|
||||
return strapi.entityService.update(uid, entity.id, params);
|
||||
@ -176,7 +176,7 @@ module.exports = ({ strapi }) => ({
|
||||
|
||||
const params = {
|
||||
data,
|
||||
populate: getDeepPopulate(uid, null, { countMany: true, countOne: true }),
|
||||
populate: getDeepPopulate(uid, { countMany: true, countOne: true }),
|
||||
};
|
||||
|
||||
return strapi.entityService.update(uid, entity.id, params);
|
||||
|
@ -0,0 +1,152 @@
|
||||
'use strict';
|
||||
|
||||
const { getDeepPopulate } = require('../populate');
|
||||
|
||||
describe('Populate', () => {
|
||||
const fakeModels = {
|
||||
empty: {
|
||||
modelName: 'Fake empty model',
|
||||
attributes: {},
|
||||
},
|
||||
component: {
|
||||
modelName: 'Fake component model',
|
||||
attributes: {
|
||||
componentAttrName: {
|
||||
type: 'component',
|
||||
component: 'empty',
|
||||
},
|
||||
},
|
||||
},
|
||||
dynZone: {
|
||||
modelName: 'Fake dynamic zone model',
|
||||
attributes: {
|
||||
dynZoneAttrName: {
|
||||
type: 'dynamiczone',
|
||||
components: ['empty', 'component'],
|
||||
},
|
||||
},
|
||||
},
|
||||
relationOTM: {
|
||||
modelName: 'Fake relation oneToMany model',
|
||||
attributes: {
|
||||
relationAttrName: {
|
||||
type: 'relation',
|
||||
relation: 'oneToMany',
|
||||
},
|
||||
},
|
||||
},
|
||||
relationOTO: {
|
||||
modelName: 'Fake relation oneToOne model',
|
||||
attributes: {
|
||||
relationAttrName: {
|
||||
type: 'relation',
|
||||
relation: 'oneToOne',
|
||||
},
|
||||
},
|
||||
},
|
||||
media: {
|
||||
modelName: 'Fake media model',
|
||||
attributes: {
|
||||
mediaAttrName: {
|
||||
type: 'media',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('getDeepPopulate', () => {
|
||||
beforeEach(() => {
|
||||
global.strapi = {
|
||||
getModel: jest.fn((uid) => fakeModels[uid]),
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('with empty model', async () => {
|
||||
const uid = 'empty';
|
||||
|
||||
const result = getDeepPopulate(uid);
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
test('with component model', async () => {
|
||||
const uid = 'component';
|
||||
|
||||
const result = getDeepPopulate(uid);
|
||||
|
||||
expect(result).toEqual({
|
||||
componentAttrName: { populate: {} },
|
||||
});
|
||||
});
|
||||
|
||||
test('with dynamic zone model', async () => {
|
||||
const uid = 'dynZone';
|
||||
|
||||
const result = getDeepPopulate(uid);
|
||||
|
||||
expect(result).toEqual({
|
||||
dynZoneAttrName: {
|
||||
populate: {
|
||||
componentAttrName: {
|
||||
populate: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('with relation model - oneToMany', async () => {
|
||||
const uid = 'relationOTM';
|
||||
|
||||
const result = getDeepPopulate(uid);
|
||||
|
||||
expect(result).toEqual({
|
||||
relationAttrName: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('with relation model - oneToMany - with countMany', async () => {
|
||||
const uid = 'relationOTM';
|
||||
|
||||
const result = getDeepPopulate(uid, { countMany: true });
|
||||
|
||||
expect(result).toEqual({
|
||||
relationAttrName: { count: true },
|
||||
});
|
||||
});
|
||||
|
||||
test('with relation model - oneToOne', async () => {
|
||||
const uid = 'relationOTO';
|
||||
|
||||
const result = getDeepPopulate(uid);
|
||||
|
||||
expect(result).toEqual({
|
||||
relationAttrName: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('with relation model - oneToOne - with countOne', async () => {
|
||||
const uid = 'relationOTO';
|
||||
|
||||
const result = getDeepPopulate(uid, { countOne: true });
|
||||
|
||||
expect(result).toEqual({
|
||||
relationAttrName: { count: true },
|
||||
});
|
||||
});
|
||||
|
||||
test('with media model', async () => {
|
||||
const uid = 'media';
|
||||
|
||||
const result = getDeepPopulate(uid);
|
||||
|
||||
expect(result).toEqual({
|
||||
mediaAttrName: { populate: 'folder' },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -7,70 +7,123 @@ const { hasDraftAndPublish, isVisibleAttribute } = strapiUtils.contentTypes;
|
||||
const { isAnyToMany } = strapiUtils.relations;
|
||||
const { PUBLISHED_AT_ATTRIBUTE } = strapiUtils.contentTypes.constants;
|
||||
|
||||
/**
|
||||
* Populate the model for relation
|
||||
* @param {Object} attribute - Attribute containing a relation
|
||||
* @param {String} attribute.relation - type of relation
|
||||
* @param model - Model of the populated entity
|
||||
* @param attributeName
|
||||
* @param {Object} options - Options to apply while populating
|
||||
* @param {Boolean} options.countMany
|
||||
* @param {Boolean} options.countOne
|
||||
* @returns {true|{count: true}}
|
||||
*/
|
||||
function getPopulateForRelation(attribute, model, attributeName, { countMany, countOne }) {
|
||||
const isManyRelation = isAnyToMany(attribute);
|
||||
|
||||
// always populate createdBy, updatedBy, localizations etc.
|
||||
if (!isVisibleAttribute(model, attributeName)) {
|
||||
return true;
|
||||
}
|
||||
if ((isManyRelation && countMany) || (!isManyRelation && countOne)) {
|
||||
return { count: true };
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the model for Dynamic Zone components
|
||||
* @param {Object} attribute - Attribute containing the components
|
||||
* @param {String[]} attribute.components - IDs of components
|
||||
* @param {Object} options - Options to apply while populating
|
||||
* @param {Boolean} options.countMany
|
||||
* @param {Boolean} options.countOne
|
||||
* @param {Number} options.maxLevel
|
||||
* @param {Number} level
|
||||
* @returns {{populate: Object}}
|
||||
*/
|
||||
function getPopulateForDZ(attribute, options, level) {
|
||||
const populatedComponents = (attribute.components || []).map((componentUID) =>
|
||||
getDeepPopulate(componentUID, options, level + 1)
|
||||
);
|
||||
|
||||
return { populate: populatedComponents.reduce(merge, {}) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the populated value based on the type of the attribute
|
||||
* @param {String} attributeName - Name of the attribute
|
||||
* @param {Object} model - Model of the populated entity
|
||||
* @param {Object} model.attributes
|
||||
* @param {Object} options - Options to apply while populating
|
||||
* @param {Boolean} options.countMany
|
||||
* @param {Boolean} options.countOne
|
||||
* @param {Number} options.maxLevel
|
||||
* @param {Number} level
|
||||
* @returns {Object}
|
||||
*/
|
||||
function getPopulateFor(attributeName, model, options, level) {
|
||||
const attribute = model.attributes[attributeName];
|
||||
|
||||
switch (attribute.type) {
|
||||
case 'relation':
|
||||
return {
|
||||
[attributeName]: getPopulateForRelation(attribute, model, attributeName, options),
|
||||
};
|
||||
case 'component':
|
||||
return {
|
||||
[attributeName]: {
|
||||
populate: getDeepPopulate(attribute.component, options, level + 1),
|
||||
},
|
||||
};
|
||||
case 'media':
|
||||
return {
|
||||
[attributeName]: { populate: 'folder' },
|
||||
};
|
||||
case 'dynamiczone':
|
||||
return {
|
||||
[attributeName]: getPopulateForDZ(attribute, options, level),
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deeply populate a model based on UID
|
||||
* @param {String} uid - Unique identifier of the model
|
||||
* @param {Object} [options] - Options to apply while populating
|
||||
* @param {Boolean} [options.countMany=false]
|
||||
* @param {Boolean} [options.countOne=false]
|
||||
* @param {Number} [options.maxLevel=Infinity]
|
||||
* @param {Number} [level=1] - Current level of nested call
|
||||
* @returns {Object}
|
||||
*/
|
||||
const getDeepPopulate = (
|
||||
uid,
|
||||
populate,
|
||||
{ countMany = false, countOne = false, maxLevel = Infinity } = {},
|
||||
level = 1
|
||||
) => {
|
||||
if (populate) {
|
||||
return populate;
|
||||
}
|
||||
|
||||
if (level > maxLevel) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const model = strapi.getModel(uid);
|
||||
|
||||
return Object.keys(model.attributes).reduce((populateAcc, attributeName) => {
|
||||
const attribute = model.attributes[attributeName];
|
||||
|
||||
if (attribute.type === 'relation') {
|
||||
const isManyRelation = isAnyToMany(attribute);
|
||||
// always populate createdBy, updatedBy, localizations etc.
|
||||
if (!isVisibleAttribute(model, attributeName)) {
|
||||
populateAcc[attributeName] = true;
|
||||
} else if ((isManyRelation && countMany) || (!isManyRelation && countOne)) {
|
||||
populateAcc[attributeName] = { count: true };
|
||||
} else {
|
||||
populateAcc[attributeName] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (attribute.type === 'component') {
|
||||
populateAcc[attributeName] = {
|
||||
populate: getDeepPopulate(
|
||||
attribute.component,
|
||||
null,
|
||||
{ countOne, countMany, maxLevel },
|
||||
level + 1
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (attribute.type === 'media') {
|
||||
populateAcc[attributeName] = { populate: 'folder' };
|
||||
}
|
||||
|
||||
if (attribute.type === 'dynamiczone') {
|
||||
populateAcc[attributeName] = {
|
||||
populate: (attribute.components || []).reduce((acc, componentUID) => {
|
||||
return merge(
|
||||
acc,
|
||||
getDeepPopulate(componentUID, null, { countOne, countMany, maxLevel }, level + 1)
|
||||
);
|
||||
}, {}),
|
||||
};
|
||||
}
|
||||
|
||||
return populateAcc;
|
||||
}, {});
|
||||
return Object.keys(model.attributes).reduce(
|
||||
(populateAcc, attributeName) =>
|
||||
merge(
|
||||
populateAcc,
|
||||
getPopulateFor(attributeName, model, { countMany, countOne, maxLevel }, level)
|
||||
),
|
||||
{}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* getDeepPopulateDraftCount works recursively on the attributes of a model
|
||||
* creating a populate object to count all the unpublished relations within the model
|
||||
* creating a populated object to count all the unpublished relations within the model
|
||||
* These relations can be direct to this content type or contained within components/dynamic zones
|
||||
* @param {String} uid of the model
|
||||
* @returns {Object} result
|
||||
|
Loading…
x
Reference in New Issue
Block a user