diff --git a/packages/core/database/lib/query/helpers/populate/apply.js b/packages/core/database/lib/query/helpers/populate/apply.js index 1138bab261..fee3feeeaa 100644 --- a/packages/core/database/lib/query/helpers/populate/apply.js +++ b/packages/core/database/lib/query/helpers/populate/apply.js @@ -490,7 +490,7 @@ const morphToMany = async (input, ctx) => { const qb = db.entityManager.createQueryBuilder(type); const rows = await qb - .init(on && type in on ? on[type] : typePopulate) + .init(on?.[type] ?? typePopulate) .addSelect(`${qb.alias}.${idColumn.referencedColumn}`) .where({ [idColumn.referencedColumn]: ids }) .execute({ mapResults: false }); @@ -561,7 +561,7 @@ const morphToOne = async (input, ctx) => { const qb = db.entityManager.createQueryBuilder(type); const rows = await qb - .init(on && type in on ? on[type] : typePopulate) + .init(on?.[type] ?? typePopulate) .addSelect(`${qb.alias}.${idColumn.referencedColumn}`) .where({ [idColumn.referencedColumn]: ids }) .execute({ mapResults: false }); diff --git a/packages/core/strapi/tests/api/populate/filtering/index.test.api.js b/packages/core/strapi/tests/api/populate/filtering/index.test.api.js index 24931e882f..44c39d34e7 100644 --- a/packages/core/strapi/tests/api/populate/filtering/index.test.api.js +++ b/packages/core/strapi/tests/api/populate/filtering/index.test.api.js @@ -315,4 +315,64 @@ describe('Populate filters', () => { expect(body.data[0].attributes.third).toBeUndefined(); }); }); + + describe('Populate a dynamic zone', () => { + test('Populate every components in the dynamic zone', async () => { + const qs = { + populate: { + dz: '*', + }, + }; + + const { status, body } = await rq.get(`/${schemas.contentTypes.b.pluralName}`, { qs }); + + expect(status).toBe(200); + expect(body.data).toHaveLength(2); + expect(body.data[0].attributes.dz).toHaveLength(3); + expect(body.data[1].attributes.dz).toHaveLength(1); + }); + + test('Populate only one component type using fragment', async () => { + const qs = { + populate: { + dz: { + on: { + 'default.foo': true, + }, + }, + }, + }; + + const { status, body } = await rq.get(`/${schemas.contentTypes.b.pluralName}`, { qs }); + + expect(status).toBe(200); + expect(body.data).toHaveLength(2); + expect(body.data[0].attributes.dz).toHaveLength(2); + expect(body.data[1].attributes.dz).toHaveLength(0); + }); + + test('Populate the dynamic zone with filters in fragments', async () => { + const qs = { + populate: { + dz: { + on: { + 'default.foo': { + filters: { number: { $lt: 2 } }, + }, + 'default.bar': { + filters: { title: { $contains: 'another' } }, + }, + }, + }, + }, + }; + + const { status, body } = await rq.get(`/${schemas.contentTypes.b.pluralName}`, { qs }); + + expect(status).toBe(200); + expect(body.data).toHaveLength(2); + expect(body.data[0].attributes.dz).toHaveLength(1); + expect(body.data[1].attributes.dz).toHaveLength(1); + }); + }); }); diff --git a/packages/core/utils/lib/convert-query-params.js b/packages/core/utils/lib/convert-query-params.js index cd274b97d7..182a1ff8bd 100644 --- a/packages/core/utils/lib/convert-query-params.js +++ b/packages/core/utils/lib/convert-query-params.js @@ -186,7 +186,15 @@ const convertPopulateObject = (populate, schema) => { return acc; } - if (typeof subPopulate === 'object' && 'on' in subPopulate) { + // Allow adding a 'on' strategy to populate queries for polymorphic relations, media and dynamic zones + const isAllowedAttributeForFragmentPopulate = + attribute.type === 'dynamiczone' || + attribute.type === 'media' || + (attribute.relation && attribute.relation.startsWith('morphTo')); + + const hasFragmentPopulateDefined = typeof subPopulate === 'object' && 'on' in subPopulate; + + if (isAllowedAttributeForFragmentPopulate && hasFragmentPopulateDefined) { return { ...acc, [key]: { @@ -201,8 +209,8 @@ const convertPopulateObject = (populate, schema) => { }; } - // TODO: Deprecated way of handling dynamic zone populate queries. It's kept as is, - // as removing it could break existing user queries but should be removed in V5. + // TODO: This is a query's populate fallback for DynamicZone and is kept for legacy purpose. + // Removing it could break existing user queries but it should be removed in V5. if (attribute.type === 'dynamiczone') { const populates = attribute.components .map((uid) => strapi.getModel(uid))