Browse Source

fix(core): Fix PaginatedList filtering by calculated property

An error occurred when filtering using complex _and, _or boolean
filter expressions in combination with calculated properties
on entities.
Michael Bromley 9 months ago
parent
commit
79669789f9

+ 34 - 0
packages/core/e2e/list-query-builder.e2e-spec.ts

@@ -1115,6 +1115,23 @@ describe('ListQueryBuilder', () => {
             expect(getItemLabels(testEntities.items)).toEqual(['A', 'B']);
         });
 
+        it('filter by calculated property with boolean expression', async () => {
+            const { testEntities } = await adminClient.query(GET_LIST, {
+                options: {
+                    filter: {
+                        _and: [
+                            {
+                                descriptionLength: {
+                                    lt: 12,
+                                },
+                            },
+                        ],
+                    },
+                },
+            });
+            expect(getItemLabels(testEntities.items)).toEqual(['A', 'B']);
+        });
+
         it('filter by calculated property with join', async () => {
             const { testEntities } = await adminClient.query(GET_LIST, {
                 options: {
@@ -1128,6 +1145,23 @@ describe('ListQueryBuilder', () => {
             expect(getItemLabels(testEntities.items)).toEqual(['A', 'B', 'E']);
         });
 
+        it('filter by calculated property with join and boolean expression', async () => {
+            const { testEntities } = await adminClient.query(GET_LIST, {
+                options: {
+                    filter: {
+                        _and: [
+                            {
+                                price: {
+                                    lt: 14,
+                                },
+                            },
+                        ],
+                    },
+                },
+            });
+            expect(getItemLabels(testEntities.items)).toEqual(['A', 'B', 'E']);
+        });
+
         it('sort by simple calculated property', async () => {
             const { testEntities } = await adminClient.query(GET_LIST, {
                 options: {

+ 26 - 0
packages/core/e2e/order.e2e-spec.ts

@@ -221,6 +221,32 @@ describe('Orders resolver', () => {
             expect(result.orders.items.map(o => o.id).sort()).toEqual(['T_1', 'T_2']);
         });
 
+        it('filtering by total', async () => {
+            const result = await adminClient.query<
+                Codegen.GetOrderListQuery,
+                Codegen.GetOrderListQueryVariables
+            >(GET_ORDERS_LIST, {
+                options: {
+                    filter: { total: { gt: 1000_00 } },
+                },
+            });
+            expect(result.orders.items.map(o => o.id).sort()).toEqual(['T_1', 'T_2']);
+        });
+
+        it('filtering by total using boolean expression', async () => {
+            const result = await adminClient.query<
+                Codegen.GetOrderListQuery,
+                Codegen.GetOrderListQueryVariables
+            >(GET_ORDERS_LIST, {
+                options: {
+                    filter: {
+                        _and: [{ total: { gt: 1000_00 } }],
+                    },
+                },
+            });
+            expect(result.orders.items.map(o => o.id).sort()).toEqual(['T_1', 'T_2']);
+        });
+
         it('order', async () => {
             const result = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
                 GET_ORDER,

+ 29 - 4
packages/core/src/service/helpers/list-query-builder/list-query-builder.ts

@@ -505,10 +505,7 @@ export class ListQueryBuilder implements OnApplicationBootstrap {
         options: ListQueryOptions<T>,
     ) {
         const calculatedColumns = getCalculatedColumns(entity);
-        const filterAndSortFields = unique([
-            ...Object.keys(options.filter || {}),
-            ...Object.keys(options.sort || {}),
-        ]);
+        const filterAndSortFields = this.getFilterAndSortFields(options);
         const alias = getEntityAlias(this.connection.rawConnection, entity);
         for (const field of filterAndSortFields) {
             const calculatedColumnDef = calculatedColumns.find(c => c.name === field);
@@ -534,6 +531,34 @@ export class ListQueryBuilder implements OnApplicationBootstrap {
         }
     }
 
+    private getFilterAndSortFields<T extends VendureEntity>(options: ListQueryOptions<T>): string[] {
+        const sortFields = Object.keys(options.sort || {});
+        // filter fields can be immediate children of the filter object
+        // or nested inside _and or _or
+        const filterFields = this.getFilterFields(options.filter);
+        return unique([...sortFields, ...filterFields]);
+    }
+
+    private getFilterFields<T extends VendureEntity>(
+        filter?: NullOptionals<FilterParameter<T>> | null,
+    ): string[] {
+        if (!filter) {
+            return [];
+        }
+        const filterFields: string[] = [];
+        for (const key in filter) {
+            if (key === '_and' || key === '_or') {
+                const value = filter[key] as Array<FilterParameter<T>>;
+                for (const condition of value) {
+                    filterFields.push(...this.getFilterFields(condition));
+                }
+            } else if (filter[key as keyof FilterParameter<T>]) {
+                filterFields.push(key);
+            }
+        }
+        return unique(filterFields);
+    }
+
     /**
      * @description
      * If this entity is Translatable, and we are sorting on one of the translatable fields,