Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement configuration options to limit depth of certain fields on s… #49

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Strapi plugin populate-deep

This plugin allows for easier population of deep content structures using the rest API.

# Installation
Expand All @@ -7,7 +8,6 @@ This plugin allows for easier population of deep content structures using the re

`yarn add strapi-plugin-populate-deep`


# Usages

## Examples
Expand All @@ -28,25 +28,39 @@ Populate a request with the a custom depth

The default max depth is 5 levels deep.

The populate deep option is available for all collections and single types using the findOne and findMany methods.
The populate deep option is available for all collections and single types using the `findOne` and `findMany` methods.

# Configuration

The default depth can be customized via the plugin config. To do so create or edit you plugins.js file.
The default depth and custom depth for certain fields can be customized via the plugin config. To do so create or edit you `plugins.js` file.

To avoid cyclic population you might need to ignore certain fields.

To avoid too much data, especially when you are working with parent-child relationships, adjust the depth of population of these fields using the `fields` option.

To find out what collections and fields are populated, set the debug option to true.

## Example configuration

`config/plugins.js`

```
```js
module.exports = ({ env }) => ({
'strapi-plugin-populate-deep': {
config: {
defaultDepth: 3, // Default is 5
ignore: ['field1', 'collectionName.field2'], // default is []
fields: [
{ collectionName: 'collectionName', field: 'field1', depth: 1 },
{ field: 'field2', depth: 3 }, // for all collections
],
debug: true, // default is false
skipCreatorFields: true, // default is false, skips all fields of model admin::user
}
},
});
```

# Contributions

The original idea for getting the populate structure was created by [tomnovotny7](https://github.com/tomnovotny7) and can be found in [this](https://github.com/strapi/strapi/issues/11836) github thread
10 changes: 9 additions & 1 deletion server/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ module.exports = ({ strapi }) => {
if (event.action === 'beforeFindMany' || event.action === 'beforeFindOne') {
const populate = event.params?.populate;
const defaultDepth = strapi.plugin('strapi-plugin-populate-deep')?.config('defaultDepth') || 5
const ignore = strapi.plugin('strapi-plugin-populate-deep')?.config('ignore') || []
const debug = strapi.plugin('strapi-plugin-populate-deep')?.config('debug') || false

/** @type {import('./helpers').FieldPopulateConfiguration[]} */
const fields = strapi.plugin('strapi-plugin-populate-deep')?.config('fields') || {}

if (populate && populate[0] === 'deep') {
const depth = populate[1] ?? defaultDepth
const modelObject = getFullPopulateObject(event.model.uid, depth, []);
debug && console.debug(`[strapi-plugin-populate-deep] DEBUG Deep populating for model ${event.model.uid}`, {
depth, ignore, fields
})
const modelObject = getFullPopulateObject(event.model.uid, depth, ignore, fields, debug);
event.params.populate = modelObject.populate
}
}
Expand Down
50 changes: 41 additions & 9 deletions server/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,23 @@ const getModelPopulationAttributes = (model) => {
return model.attributes;
};

const getFullPopulateObject = (modelUid, maxDepth = 20, ignore) => {
/**
* @typedef {Object} FieldPopulateConfiguration
* @property {string?} collectionName - optional, if not given, it will be considered for all collections
* @property {string} field
* @property {number} depth
* @exports FieldPopulateConfiguration
*/

/**
* @param {string} modelUid
* @param {number} maxDepth
* @param {string[]?} ignore
* @param {FieldPopulateConfiguration[]?} fieldPopulateConfigurations
* @param {boolean?} debug
* @returns
*/
const getFullPopulateObject = (modelUid, maxDepth = 20, ignore, fieldPopulateConfigurations, debug = false) => {
const skipCreatorFields = strapi.plugin('strapi-plugin-populate-deep')?.config('skipCreatorFields');

if (maxDepth <= 1) {
Expand All @@ -21,25 +37,41 @@ const getFullPopulateObject = (modelUid, maxDepth = 20, ignore) => {

const populate = {};
const model = strapi.getModel(modelUid);
if (ignore && !ignore.includes(model.collectionName)) ignore.push(model.collectionName)

for (const [key, value] of Object.entries(
getModelPopulationAttributes(model)
)) {
if (ignore?.includes(key)) continue
if (ignore?.includes(key) || ignore?.includes(model.collectionName + '.' + key)) {
debug && console.debug(`[strapi-plugin-populate-deep] DEBUG Ignoring collectionName: ${model.collectionName}, field: ${key}`)
continue
}

let depth = maxDepth
if (fieldPopulateConfigurations) {
debug && console.debug(`[strapi-plugin-populate-deep] DEBUG collectionName: ${model.collectionName}, field: ${key}`)
const fieldPopulateConfiguration = fieldPopulateConfigurations.find(f => f.field === key && (!f.collectionName || f.collectionName === model.collectionName))
if (fieldPopulateConfiguration) {
debug && console.debug(`[strapi-plugin-populate-deep] DEBUG Overriding depth for collectionName: ${model.collectionName}, field: ${key} to ${fieldPopulateConfiguration.depth}`)
depth = fieldPopulateConfiguration.depth
}
}

if (value) {
if (value.type === "component") {
populate[key] = getFullPopulateObject(value.component, maxDepth - 1);
} else if (value.type === "dynamiczone") {
populate[key] = getFullPopulateObject(value.component, depth - 1, ignore, fieldPopulateConfigurations, debug);
} else if (value.type === "dynamiczone" && depth > 1) {
const dynamicPopulate = value.components.reduce((prev, cur) => {
const curPopulate = getFullPopulateObject(cur, maxDepth - 1);
const curPopulate = getFullPopulateObject(cur, depth - 1, ignore, fieldPopulateConfigurations, debug);
return curPopulate === true ? prev : merge(prev, curPopulate);
}, {});
populate[key] = isEmpty(dynamicPopulate) ? true : dynamicPopulate;
} else if (value.type === "relation") {
} else if (value.type === "relation" && depth > 1) {
const relationPopulate = getFullPopulateObject(
value.target,
(key === 'localizations') && maxDepth > 2 ? 1 : maxDepth - 1,
ignore
(key === 'localizations') && depth > 2 ? 1 : depth - 1,
ignore,
fieldPopulateConfigurations,
debug,
);
if (relationPopulate) {
populate[key] = relationPopulate;
Expand Down