Skip to content

Commit bc3a235

Browse files
Merge #427
427: Add populateEntryRule in plugin's settings r=bidoubiwa a=bidoubiwa Fixes: #423 Fixes: #425 By default when fetching an entry from the Strapi database it will not always fetch every nested relationship. Imagine I have a collection that has a relationship with another collection, that itself has another relationship etc.. when fetching with `findOne` for example it will not go as deep. this PR introduces a setting that lets the user give the specific rule to provide to the fetching method to ensure that every nested relation is fetched how the user would like it. Co-authored-by: Charlotte Vermandel <[email protected]> Co-authored-by: cvermand <[email protected]>
2 parents 1f29290 + e814e4e commit bc3a235

File tree

7 files changed

+158
-21
lines changed

7 files changed

+158
-21
lines changed

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,60 @@ module.exports = {
350350

351351
[See resources](./resources/meilisearch-settings) for more settings examples.
352352

353+
#### 👥 Populate entry rule
354+
355+
Content-types in Strapi may have relationships with other content-types (ex: `restaurant` can have a many-to-many relation with `category`). To ensure that these links are fetched and added to an entry correctly from your Strapi database, the correct populate rule must be provided ([see documentation](https://docs-next.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/populate.html#basic-populating)).
356+
357+
To communicate the populate rule, use the `populateEntryRule` setting on the according content-type in the plugin's settings.
358+
359+
**For example**
360+
361+
Imagine my `restaurant` content-type has a relation with a repeatable-component `repeatableComponent` that itself has a relationship with the content-type `categories`.
362+
363+
The following population will ensure that a `restaurant` entry contains even the most nested relation.
364+
365+
```js
366+
module.exports = {
367+
meilisearch: {
368+
config: {
369+
restaurant: {
370+
populateEntryRule: ['repeatableComponent.categories', 'categories'],
371+
}
372+
}
373+
},
374+
}
375+
```
376+
377+
by providing this, the following is indexed in Meilisearch:
378+
379+
```json
380+
{
381+
"id": "restaurant-1",
382+
"title": "The slimmy snail",
383+
// ... other restaurant fields
384+
"repeatableComponent": [
385+
{
386+
"id": 1,
387+
"title": "my repeatable component 1"
388+
"categories": [
389+
{
390+
"id": 3,
391+
"name": "Asian",
392+
// ... other category fields
393+
},
394+
{
395+
"id": 2,
396+
"name": "Healthy",
397+
// ... other category fields
398+
}
399+
],
400+
401+
}
402+
],
403+
404+
}
405+
```
406+
353407
### 🕵️‍♀️ Start Searching <!-- omit in toc -->
354408

355409
Once you have a content-type indexed in Meilisearch, you can [start searching](https://docs.meilisearch.com/learn/getting_started/quick_start.html#search).

server/__tests__/configuration-validation.test.js

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,11 @@ describe('Test plugin configuration', () => {
195195
test('Test settings with function', async () => {
196196
validateConfiguration({
197197
restaurant: {
198-
settings: {},
198+
settings: () => {},
199199
},
200200
})
201201
expect(fakeStrapi.log.warn).toHaveBeenCalledTimes(0)
202-
expect(fakeStrapi.log.error).toHaveBeenCalledTimes(0)
202+
expect(fakeStrapi.log.error).toHaveBeenCalledTimes(1)
203203
})
204204

205205
test('Test settings with undefined', async () => {
@@ -212,6 +212,52 @@ describe('Test plugin configuration', () => {
212212
expect(fakeStrapi.log.error).toHaveBeenCalledTimes(0)
213213
})
214214

215+
test('Test populateEntryRule with wrong type', async () => {
216+
validateConfiguration({
217+
restaurant: {
218+
populateEntryRule: 0,
219+
},
220+
})
221+
expect(fakeStrapi.log.warn).toHaveBeenCalledTimes(0)
222+
expect(fakeStrapi.log.error).toHaveBeenCalledTimes(1)
223+
expect(fakeStrapi.log.error).toHaveBeenCalledWith(
224+
'the "populateEntryRule" param of "restaurant" should be an object/array/string'
225+
)
226+
})
227+
228+
test('Test populateEntryRule with function', async () => {
229+
validateConfiguration({
230+
restaurant: {
231+
populateEntryRule: () => {},
232+
},
233+
})
234+
expect(fakeStrapi.log.warn).toHaveBeenCalledTimes(0)
235+
expect(fakeStrapi.log.error).toHaveBeenCalledTimes(1)
236+
expect(fakeStrapi.log.error).toHaveBeenCalledWith(
237+
'the "populateEntryRule" param of "restaurant" should be an object/array/string'
238+
)
239+
})
240+
241+
test('Test populateEntryRule with empty object', async () => {
242+
validateConfiguration({
243+
restaurant: {
244+
populateEntryRule: {},
245+
},
246+
})
247+
expect(fakeStrapi.log.warn).toHaveBeenCalledTimes(0)
248+
expect(fakeStrapi.log.error).toHaveBeenCalledTimes(0)
249+
})
250+
251+
test('Test populateEntryRule with undefined', async () => {
252+
validateConfiguration({
253+
restaurant: {
254+
populateEntryRule: undefined,
255+
},
256+
})
257+
expect(fakeStrapi.log.warn).toHaveBeenCalledTimes(0)
258+
expect(fakeStrapi.log.error).toHaveBeenCalledTimes(0)
259+
})
260+
215261
test('Test configuration with random field ', async () => {
216262
validateConfiguration({
217263
restaurant: {

server/configuration-validation.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ function validateCollectionConfiguration({ configuration, collection }) {
4747
'transformEntry',
4848
'settings',
4949
'filterEntry',
50+
'populateEntryRule',
5051
]
5152

5253
if (configuration === undefined) {
@@ -98,6 +99,18 @@ function validateCollectionConfiguration({ configuration, collection }) {
9899
delete configuration.settings
99100
}
100101

102+
if (
103+
configuration.populateEntryRule !== undefined &&
104+
!isObject(configuration.populateEntryRule) &&
105+
!Array.isArray(configuration.populateEntryRule) &&
106+
typeof configuration.populateEntryRule !== 'string'
107+
) {
108+
strapi.log.error(
109+
`the "populateEntryRule" param of "${collection}" should be an object/array/string`
110+
)
111+
delete configuration.populateEntryRule
112+
}
113+
101114
Object.keys(configuration).forEach(attribute => {
102115
if (!validApiFields.includes(attribute)) {
103116
strapi.log.warn(

server/services/content-types/content-types.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,16 @@ module.exports = ({ strapi }) => ({
197197
*
198198
* @param {object} options
199199
* @param {string} options.contentType - Name of the content type.
200+
* @param {object} [options.populate] - Relations, components and dynamic zones to populate.
200201
* @param {function} options.callback - Function applied on each entry of the contentType.
201202
*
202203
* @returns {Promise<any[]>} - List of all the returned elements from the callback.
203204
*/
204-
actionInBatches: async function ({ contentType, callback = () => {} }) {
205+
actionInBatches: async function ({
206+
contentType,
207+
callback = () => {},
208+
populate = '*',
209+
}) {
205210
const BATCH_SIZE = 500
206211

207212
// Need total number of entries in contentType
@@ -215,6 +220,7 @@ module.exports = ({ strapi }) => ({
215220
start: index,
216221
limit: BATCH_SIZE,
217222
contentType,
223+
populate,
218224
})) || []
219225

220226
const info = await callback({ entries, contentType })

server/services/lifecycle/lifecycle.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,24 @@ module.exports = ({ strapi }) => {
2525

2626
// Fetch complete entry instead of using result that is possibly
2727
// partial.
28-
const fullEntry = await strapi.entityService.findOne(
29-
contentTypeUid,
30-
result.id,
31-
{ populate: '*' }
32-
)
28+
const entry = await contentTypeService.getEntry({
29+
contentType: contentTypeUid,
30+
id: result.id,
31+
populate: meilisearch.populateEntryRule({ contentType }),
32+
})
3333

3434
meilisearch
3535
.addEntriesToMeilisearch({
3636
contentType: contentTypeUid,
37-
entries: [fullEntry],
37+
entries: [entry],
3838
})
3939
.catch(e => {
4040
strapi.log.error(
4141
`Meilisearch could not add entry with id: ${result.id}: ${e.message}`
4242
)
4343
})
4444
},
45-
afterCreateMany() {
45+
async afterCreateMany() {
4646
strapi.log.error(
4747
`Meilisearch does not work with \`afterCreateMany\` hook as the entries are provided without their id`
4848
)
@@ -55,29 +55,29 @@ module.exports = ({ strapi }) => {
5555

5656
// Fetch complete entry instead of using result that is possibly
5757
// partial.
58-
const fullEntry = await strapi.entityService.findOne(
59-
contentTypeUid,
60-
result.id,
61-
{ populate: '*' }
62-
)
58+
const entry = await contentTypeService.getEntry({
59+
contentType: contentTypeUid,
60+
id: result.id,
61+
populate: meilisearch.populateEntryRule({ contentType }),
62+
})
6363

6464
meilisearch
6565
.updateEntriesInMeilisearch({
6666
contentType: contentTypeUid,
67-
entries: [fullEntry],
67+
entries: [entry],
6868
})
6969
.catch(e => {
7070
strapi.log.error(
7171
`Meilisearch could not update entry with id: ${result.id}: ${e.message}`
7272
)
7373
})
7474
},
75-
afterUpdateMany() {
75+
async afterUpdateMany() {
7676
strapi.log.error(
7777
`Meilisearch could not find an example on how to access the \`afterUpdateMany\` hook. Please consider making an issue to explain your use case`
7878
)
7979
},
80-
afterDelete(event) {
80+
async afterDelete(event) {
8181
const { result, params } = event
8282
const meilisearch = strapi
8383
.plugin('meilisearch')
@@ -106,7 +106,7 @@ module.exports = ({ strapi }) => {
106106
)
107107
})
108108
},
109-
afterDeleteMany() {
109+
async afterDeleteMany() {
110110
strapi.log.error(
111111
`Meilisearch could not find an example on how to access the \`afterDeleteMany\` hook. Please consider making an issue to explain your use case`
112112
)

server/services/meilisearch/config.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ module.exports = ({ strapi }) => {
2323
/**
2424
* Get the name of the index from Meilisearch in which the contentType content is added.
2525
*
26-
* @param contentType - Name of the contentType.
26+
* @param {object} options
27+
* @param {string} options.contentType - ContentType name.
2728
*
2829
* @return {String} - Index name
2930
*/
@@ -34,6 +35,21 @@ module.exports = ({ strapi }) => {
3435
return contentTypeConfig.indexName || collection
3536
},
3637

38+
/**
39+
* Get the populate rule of a content-type that is applied when fetching entries in the Strapi database.
40+
*
41+
* @param {object} options
42+
* @param {string} options.contentType - ContentType name.
43+
*
44+
* @return {String} - Populate rule.
45+
*/
46+
populateEntryRule: function ({ contentType }) {
47+
const collection = contentTypeService.getCollectionName({ contentType })
48+
const contentTypeConfig = meilisearchConfig[collection] || {}
49+
50+
return contentTypeConfig.populate || '*'
51+
},
52+
3753
/**
3854
* Transform contentTypes entries before indexation in Meilisearch.
3955
*
@@ -83,7 +99,7 @@ module.exports = ({ strapi }) => {
8399
* @param {Array<Object>} options.entries - The data to convert. Conversion will use
84100
* the static method `toSearchIndex` defined in the model definition
85101
*
86-
* @return {Array<Object>} - Converted or mapped data
102+
* @return {Promise<Array<Object>>} - Converted or mapped data
87103
*/
88104
filterEntries: async function ({ contentType, entries = [] }) {
89105
const collection = contentTypeService.getCollectionName({ contentType })

server/services/meilisearch/connector.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ module.exports = ({ strapi, adapter, config }) => {
286286
const tasksUids = await contentTypeService.actionInBatches({
287287
contentType,
288288
callback: addDocuments,
289+
populate: config.populateEntryRule({ contentType }),
289290
})
290291

291292
await store.addIndexedContentType({ contentType })
@@ -344,6 +345,7 @@ module.exports = ({ strapi, adapter, config }) => {
344345
await contentTypeService.actionInBatches({
345346
contentType,
346347
callback: deleteEntries,
348+
populate: config.populateEntryRule({ contentType }),
347349
})
348350
} else {
349351
const { apiKey, host } = await store.getCredentials()

0 commit comments

Comments
 (0)