introduce getDeepCount for relations performance

This commit is contained in:
Marc-Roig 2023-01-16 18:20:42 +01:00
parent 25e1435173
commit 27b8b5b353
3 changed files with 251 additions and 18 deletions

View File

@ -4,6 +4,7 @@ const { assoc, has, prop, omit } = require('lodash/fp');
const strapiUtils = require('@strapi/utils');
const { ApplicationError } = require('@strapi/utils').errors;
const { getDeepPopulate, getDeepPopulateDraftCount } = require('./utils/populate');
const { getDeepRelationsCount } = require('./utils/count');
const { sumDraftCounts } = require('./utils/draft');
const { hasDraftAndPublish } = strapiUtils.contentTypes;
@ -26,12 +27,8 @@ const wrapWithEmitEvent = (event, fn) => async (entity, body, model) => {
entry: sanitizedEntity,
});
// If relations were populated, load the entity again without populating them,
// to avoid performance issues
if (isRelationsPopulateEnabled(model)) {
return strapi.entityService.findOne(model, entity.id, {
populate: getCountDeepPopulate(model),
});
return getDeepRelationsCount(entity, model);
}
return result;
@ -64,7 +61,7 @@ const addCreatedByRolesPopulate = (populate) => {
* For performance reasons, it is recommended to set it to false,
*/
const isRelationsPopulateEnabled = () => {
return strapi.config.get('server.relations.populate', false);
return strapi.config.get('server.relations.populate', true);
};
const getCountDeepPopulate = (uid) => getDeepPopulate(uid, { countMany: true, countOne: true });
@ -143,7 +140,7 @@ module.exports = ({ strapi }) => ({
// If relations were populated, load the entity again without populating them,
// to avoid performance issues
if (populateRelations) {
return strapi.entityService.findOne(uid, entity.id, { populate: getCountDeepPopulate(uid) });
return getDeepRelationsCount(entity, uid);
}
return entity;
@ -160,32 +157,27 @@ module.exports = ({ strapi }) => ({
const updatedEntity = await strapi.entityService.update(uid, entity.id, params);
// If relations were populated, load the entity again without populating them,
// to avoid performance issues
if (populateRelations) {
return strapi.entityService.findOne(uid, entity.id, { populate: getCountDeepPopulate(uid) });
return getDeepRelationsCount(updatedEntity, uid);
}
return updatedEntity;
},
async delete(entity, uid) {
let entityToDelete;
const populateRelations = isRelationsPopulateEnabled(uid);
const params = {
populate: populateRelations ? getDeepPopulate(uid, {}) : getCountDeepPopulate(uid),
};
if (populateRelations) {
entityToDelete = await strapi.entityService.findOne(uid, entity.id, {
populate: getCountDeepPopulate(uid),
});
}
const deletedEntity = await strapi.entityService.delete(uid, entity.id, params);
return entityToDelete || deletedEntity;
if (populateRelations) {
return getDeepRelationsCount(deletedEntity, uid);
}
return deletedEntity;
},
// FIXME: handle relations

View File

@ -0,0 +1,182 @@
'use strict';
const { getDeepRelationsCount } = require('../count');
const fakeModels = {
component: {
modelName: 'Fake component model',
attributes: {
componentAttrName: {
type: 'component',
component: 'relationMTM',
},
},
},
dynZone: {
modelName: 'Fake dynamic zone model',
attributes: {
dynZoneAttrName: {
type: 'dynamiczone',
components: ['component'],
},
},
},
relationMTM: {
modelName: 'Fake relation manyToMany 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('Count', () => {
describe('getDeepRelationsCount', () => {
beforeEach(() => {
global.strapi = {
getModel: jest.fn((uid) => fakeModels[uid]),
};
});
afterEach(() => {
jest.clearAllMocks();
});
test('with many to many', () => {
const count = getDeepRelationsCount(
{
relationAttrName: [
{
id: 2,
name: 'rel1',
},
{
id: 7,
name: 'rel2',
},
],
},
'relationMTM'
);
expect(count).toEqual({
relationAttrName: {
count: 2,
},
});
});
test('with one to one', () => {
const count = getDeepRelationsCount(
{
relationAttrName: {
id: 2,
name: 'rel1',
},
},
'relationOTO'
);
expect(count).toEqual({
relationAttrName: {
id: 2,
name: 'rel1',
},
});
});
test('with media', () => {
const mediaEntity = {
mediaAttrName: { id: 1, name: 'img1' },
};
const count = getDeepRelationsCount(mediaEntity, 'media');
expect(count).toEqual(mediaEntity);
});
test('with component', () => {
const count = getDeepRelationsCount(
{
componentAttrName: {
relationAttrName: [
{
id: 2,
name: 'rel1',
},
{
id: 7,
name: 'rel2',
},
],
},
},
'component'
);
expect(count).toEqual({
componentAttrName: {
relationAttrName: {
count: 2,
},
},
});
});
test('with dynamic zone', () => {
const count = getDeepRelationsCount(
{
dynZoneAttrName: [
{
__component: 'component',
componentAttrName: {
relationAttrName: [
{
id: 2,
name: 'rel1',
},
{
id: 7,
name: 'rel2',
},
],
},
},
],
},
'dynZone'
);
expect(count).toEqual({
dynZoneAttrName: [
{
__component: 'component',
componentAttrName: {
relationAttrName: {
count: 2,
},
},
},
],
});
});
});
});

View File

@ -0,0 +1,59 @@
'use strict';
const { merge } = require('lodash/fp');
function getCountForRelation(entity, attributeName) {
const entityAttribute = entity[attributeName];
// Check if is an array? what happens if its a one to one relation?
if (Array.isArray(entityAttribute)) {
return { count: entityAttribute.length };
}
return entityAttribute;
}
function getCountForDZ(attributeName, entity) {
return entity[attributeName].map((component) => {
return getDeepRelationsCount(component, component.__component);
});
}
function getCountFor(attributeName, entity, model) {
const attribute = model.attributes[attributeName];
// Check if attribute is empty
if (!entity[attributeName]) {
return { [attributeName]: entity[attributeName] };
}
switch (attribute?.type) {
case 'relation':
return {
[attributeName]: getCountForRelation(entity, attributeName),
};
case 'component':
return {
[attributeName]: getDeepRelationsCount(entity[attributeName], attribute.component),
};
case 'dynamiczone':
return {
[attributeName]: getCountForDZ(attributeName, entity),
};
default:
return { [attributeName]: entity[attributeName] };
}
}
const getDeepRelationsCount = (entity, uid) => {
const model = strapi.getModel(uid);
return Object.keys(entity).reduce(
(populateAcc, attributeName) => merge(populateAcc, getCountFor(attributeName, entity, model)),
{}
);
};
module.exports = {
getDeepRelationsCount,
};