Browse Source

fix(core): Fix filtering by facet value uuid in DefaultSearchPlugin

Fixes #1341
Michael Bromley 4 years ago
parent
commit
53babf6541

+ 111 - 0
packages/core/e2e/default-search-plugin-uuids.e2e-spec.ts

@@ -0,0 +1,111 @@
+/* tslint:disable:no-non-null-assertion */
+import { DefaultJobQueuePlugin, DefaultSearchPlugin, mergeConfig, UuidIdStrategy } from '@vendure/core';
+import { createTestEnvironment, registerInitializer, SqljsInitializer } from '@vendure/testing';
+import path from 'path';
+
+import { initialData } from '../../../e2e-common/e2e-initial-data';
+import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
+
+import { FacetValueFragment, GetFacetList } from './graphql/generated-e2e-admin-types';
+import {
+    SearchProductsShop,
+    SearchProductsShopQueryVariables,
+    SortOrder,
+} from './graphql/generated-e2e-shop-types';
+import { GET_FACET_LIST } from './graphql/shared-definitions';
+import { SEARCH_PRODUCTS_SHOP } from './graphql/shop-definitions';
+import { awaitRunningJobs } from './utils/await-running-jobs';
+
+registerInitializer('sqljs', new SqljsInitializer(path.join(__dirname, '__data__'), 1000));
+
+describe('Default search plugin with UUIDs', () => {
+    const { server, adminClient, shopClient } = createTestEnvironment(
+        mergeConfig(testConfig(), {
+            plugins: [DefaultSearchPlugin.init({ indexStockStatus: true }), DefaultJobQueuePlugin],
+            entityOptions: {
+                entityIdStrategy: new UuidIdStrategy(),
+            },
+        }),
+    );
+
+    let plantsFacetValue: FacetValueFragment;
+    let furnitureFacetValue: FacetValueFragment;
+    let photoFacetValue: FacetValueFragment;
+    let electronicsFacetValue: FacetValueFragment;
+
+    beforeAll(async () => {
+        await server.init({
+            initialData,
+            productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-default-search.csv'),
+            customerCount: 1,
+        });
+        await adminClient.asSuperAdmin();
+
+        // A precaution against a race condition in which the index
+        // rebuild is not completed in time for the first test.
+        await new Promise(resolve => setTimeout(resolve, 5000));
+
+        const { facets } = await adminClient.query<GetFacetList.Query, GetFacetList.Variables>(
+            GET_FACET_LIST,
+            {
+                options: {
+                    sort: {
+                        name: SortOrder.ASC,
+                    },
+                },
+            },
+        );
+        plantsFacetValue = facets.items[0].values.find(v => v.code === 'plants')!;
+        furnitureFacetValue = facets.items[0].values.find(v => v.code === 'furniture')!;
+        photoFacetValue = facets.items[0].values.find(v => v.code === 'photo')!;
+        electronicsFacetValue = facets.items[0].values.find(v => v.code === 'electronics')!;
+    }, TEST_SETUP_TIMEOUT_MS);
+
+    afterAll(async () => {
+        await awaitRunningJobs(adminClient);
+        await server.destroy();
+    });
+
+    it('can filter by facetValueIds', async () => {
+        const result = await shopClient.query<SearchProductsShop.Query, SearchProductsShopQueryVariables>(
+            SEARCH_PRODUCTS_SHOP,
+            {
+                input: {
+                    facetValueIds: [plantsFacetValue.id],
+                    groupByProduct: true,
+                    sort: { name: SortOrder.ASC },
+                },
+            },
+        );
+        expect(result.search.items.map(i => i.productName)).toEqual([
+            'Bonsai Tree',
+            'Orchid',
+            'Spiky Cactus',
+        ]);
+    });
+
+    it('can filter by facetValueFilters', async () => {
+        const { facets } = await adminClient.query<GetFacetList.Query, GetFacetList.Variables>(
+            GET_FACET_LIST,
+        );
+        const result = await shopClient.query<SearchProductsShop.Query, SearchProductsShopQueryVariables>(
+            SEARCH_PRODUCTS_SHOP,
+            {
+                input: {
+                    facetValueFilters: [
+                        { and: electronicsFacetValue.id },
+                        { or: [plantsFacetValue.id, photoFacetValue.id] },
+                    ],
+                    sort: { name: SortOrder.ASC },
+                    groupByProduct: true,
+                },
+            },
+        );
+        expect(result.search.items.map(i => i.productName)).toEqual([
+            'Camera Lens',
+            'Instant Camera',
+            'Slr Camera',
+            'Tripod',
+        ]);
+    });
+});

+ 4 - 3
packages/core/src/plugin/default-search-plugin/search-strategy/mysql-search-strategy.ts

@@ -13,6 +13,7 @@ import { getFieldsToSelect } from './search-strategy-common';
 import {
     createCollectionIdCountMap,
     createFacetIdCountMap,
+    createPlaceholderFromId,
     mapToSearchResult,
 } from './search-strategy-utils';
 
@@ -185,7 +186,7 @@ export class MysqlSearchStrategy implements SearchStrategy {
             qb.andWhere(
                 new Brackets(qb1 => {
                     for (const id of facetValueIds) {
-                        const placeholder = '_' + id;
+                        const placeholder = createPlaceholderFromId(id);
                         const clause = `FIND_IN_SET(:${placeholder}, facetValueIds)`;
                         const params = { [placeholder]: id };
                         if (facetValueOperator === LogicalOperator.AND) {
@@ -207,14 +208,14 @@ export class MysqlSearchStrategy implements SearchStrategy {
                                     throw new UserInputError('error.facetfilterinput-invalid-input');
                                 }
                                 if (facetValueFilter.and) {
-                                    const placeholder = '_' + facetValueFilter.and;
+                                    const placeholder = createPlaceholderFromId(facetValueFilter.and);
                                     const clause = `FIND_IN_SET(:${placeholder}, facetValueIds)`;
                                     const params = { [placeholder]: facetValueFilter.and };
                                     qb2.where(clause, params);
                                 }
                                 if (facetValueFilter.or?.length) {
                                     for (const id of facetValueFilter.or) {
-                                        const placeholder = '_' + id;
+                                        const placeholder = createPlaceholderFromId(id);
                                         const clause = `FIND_IN_SET(:${placeholder}, facetValueIds)`;
                                         const params = { [placeholder]: id };
                                         qb2.orWhere(clause, params);

+ 4 - 3
packages/core/src/plugin/default-search-plugin/search-strategy/postgres-search-strategy.ts

@@ -13,6 +13,7 @@ import { getFieldsToSelect } from './search-strategy-common';
 import {
     createCollectionIdCountMap,
     createFacetIdCountMap,
+    createPlaceholderFromId,
     mapToSearchResult,
 } from './search-strategy-utils';
 
@@ -189,7 +190,7 @@ export class PostgresSearchStrategy implements SearchStrategy {
             qb.andWhere(
                 new Brackets(qb1 => {
                     for (const id of facetValueIds) {
-                        const placeholder = '_' + id;
+                        const placeholder = createPlaceholderFromId(id);
                         const clause = `:${placeholder} = ANY (string_to_array(si.facetValueIds, ','))`;
                         const params = { [placeholder]: id };
                         if (facetValueOperator === LogicalOperator.AND) {
@@ -211,14 +212,14 @@ export class PostgresSearchStrategy implements SearchStrategy {
                                     throw new UserInputError('error.facetfilterinput-invalid-input');
                                 }
                                 if (facetValueFilter.and) {
-                                    const placeholder = '_' + facetValueFilter.and;
+                                    const placeholder = createPlaceholderFromId(facetValueFilter.and);
                                     const clause = `:${placeholder} = ANY (string_to_array(si.facetValueIds, ','))`;
                                     const params = { [placeholder]: facetValueFilter.and };
                                     qb2.where(clause, params);
                                 }
                                 if (facetValueFilter.or?.length) {
                                     for (const id of facetValueFilter.or) {
-                                        const placeholder = '_' + id;
+                                        const placeholder = createPlaceholderFromId(id);
                                         const clause = `:${placeholder} = ANY (string_to_array(si.facetValueIds, ','))`;
                                         const params = { [placeholder]: id };
                                         qb2.orWhere(clause, params);

+ 4 - 0
packages/core/src/plugin/default-search-plugin/search-strategy/search-strategy-utils.ts

@@ -106,3 +106,7 @@ function parseFocalPoint(focalPoint: any): Coordinate | undefined {
     }
     return;
 }
+
+export function createPlaceholderFromId(id: ID): string {
+    return '_' + id.toString().replace(/-/g, '_');
+}

+ 4 - 3
packages/core/src/plugin/default-search-plugin/search-strategy/sqlite-search-strategy.ts

@@ -12,6 +12,7 @@ import { SearchStrategy } from './search-strategy';
 import {
     createCollectionIdCountMap,
     createFacetIdCountMap,
+    createPlaceholderFromId,
     mapToSearchResult,
 } from './search-strategy-utils';
 
@@ -172,7 +173,7 @@ export class SqliteSearchStrategy implements SearchStrategy {
             qb.andWhere(
                 new Brackets(qb1 => {
                     for (const id of facetValueIds) {
-                        const placeholder = '_' + id;
+                        const placeholder = createPlaceholderFromId(id);
                         const clause = `(',' || facetValueIds || ',') LIKE :${placeholder}`;
                         const params = { [placeholder]: `%,${id},%` };
                         if (facetValueOperator === LogicalOperator.AND) {
@@ -194,14 +195,14 @@ export class SqliteSearchStrategy implements SearchStrategy {
                                     throw new UserInputError('error.facetfilterinput-invalid-input');
                                 }
                                 if (facetValueFilter.and) {
-                                    const placeholder = '_' + facetValueFilter.and;
+                                    const placeholder = createPlaceholderFromId(facetValueFilter.and);
                                     const clause = `(',' || facetValueIds || ',') LIKE :${placeholder}`;
                                     const params = { [placeholder]: `%,${facetValueFilter.and},%` };
                                     qb2.where(clause, params);
                                 }
                                 if (facetValueFilter.or?.length) {
                                     for (const id of facetValueFilter.or) {
-                                        const placeholder = '_' + id;
+                                        const placeholder = createPlaceholderFromId(id);
                                         const clause = `(',' || facetValueIds || ',') LIKE :${placeholder}`;
                                         const params = { [placeholder]: `%,${id},%` };
                                         qb2.orWhere(clause, params);