Browse Source

feat(elasticsearch-plugin): Search by facetValueId allows operator arg

Relates to #357
Michael Bromley 5 years ago
parent
commit
f7f7e5c4eb

+ 41 - 4
packages/elasticsearch-plugin/e2e/elasticsearch-plugin.e2e-spec.ts

@@ -36,7 +36,7 @@ import {
     UpdateProductVariants,
     UpdateTaxRate,
 } from '../../core/e2e/graphql/generated-e2e-admin-types';
-import { SearchProductsShop } from '../../core/e2e/graphql/generated-e2e-shop-types';
+import { LogicalOperator, SearchProductsShop } from '../../core/e2e/graphql/generated-e2e-shop-types';
 import {
     ASSIGN_PRODUCT_TO_CHANNEL,
     CREATE_CHANNEL,
@@ -159,12 +159,13 @@ describe('Elasticsearch plugin', () => {
         ]);
     }
 
-    async function testMatchFacetValueIds(client: SimpleGraphQLClient) {
+    async function testMatchFacetIdsAnd(client: SimpleGraphQLClient) {
         const result = await client.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
             SEARCH_PRODUCTS_SHOP,
             {
                 input: {
                     facetValueIds: ['T_1', 'T_2'],
+                    facetValueOperator: LogicalOperator.AND,
                     groupByProduct: true,
                     sort: {
                         name: SortOrder.ASC,
@@ -182,6 +183,38 @@ describe('Elasticsearch plugin', () => {
         ]);
     }
 
+    async function testMatchFacetIdsOr(client: SimpleGraphQLClient) {
+        const result = await client.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
+            SEARCH_PRODUCTS_SHOP,
+            {
+                input: {
+                    facetValueIds: ['T_1', 'T_5'],
+                    facetValueOperator: LogicalOperator.OR,
+                    groupByProduct: true,
+                    sort: {
+                        name: SortOrder.ASC,
+                    },
+                    take: 20,
+                },
+            },
+        );
+        expect(result.search.items.map(i => i.productName)).toEqual([
+            'Bonsai Tree',
+            'Camera Lens',
+            'Clacky Keyboard',
+            'Curvy Monitor',
+            'Gaming PC',
+            'Hard Drive',
+            'Instant Camera',
+            'Laptop',
+            'Orchid',
+            'SLR Camera',
+            'Spiky Cactus',
+            'Tripod',
+            'USB Cable',
+        ]);
+    }
+
     async function testMatchCollectionId(client: SimpleGraphQLClient) {
         const result = await client.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
             SEARCH_PRODUCTS_SHOP,
@@ -254,7 +287,9 @@ describe('Elasticsearch plugin', () => {
 
         it('matches search term', () => testMatchSearchTerm(shopClient));
 
-        it('matches by facetValueId', () => testMatchFacetValueIds(shopClient));
+        it('matches by facetValueId with AND operator', () => testMatchFacetIdsAnd(shopClient));
+
+        it('matches by facetValueId with OR operator', () => testMatchFacetIdsOr(shopClient));
 
         it('matches by collectionId', () => testMatchCollectionId(shopClient));
 
@@ -404,7 +439,9 @@ describe('Elasticsearch plugin', () => {
 
         it('matches search term', () => testMatchSearchTerm(adminClient));
 
-        it('matches by facetValueId', () => testMatchFacetValueIds(adminClient));
+        it('matches by facetValueId with AND operator', () => testMatchFacetIdsAnd(adminClient));
+
+        it('matches by facetValueId with OR operator', () => testMatchFacetIdsOr(adminClient));
 
         it('matches by collectionId', () => testMatchCollectionId(adminClient));
 

+ 6 - 0
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts

@@ -1736,6 +1736,11 @@ export type LocalizedString = {
     value: Scalars['String'];
 };
 
+export enum LogicalOperator {
+    AND = 'AND',
+    OR = 'OR',
+}
+
 export type LoginResult = {
     __typename?: 'LoginResult';
     user: CurrentUser;
@@ -3085,6 +3090,7 @@ export type Sale = Node &
 export type SearchInput = {
     term?: Maybe<Scalars['String']>;
     facetValueIds?: Maybe<Array<Scalars['ID']>>;
+    facetValueOperator?: Maybe<LogicalOperator>;
     collectionId?: Maybe<Scalars['ID']>;
     groupByProduct?: Maybe<Scalars['Boolean']>;
     take?: Maybe<Scalars['Int']>;

+ 45 - 7
packages/elasticsearch-plugin/src/build-elastic-body.spec.ts

@@ -1,4 +1,4 @@
-import { SortOrder } from '@vendure/common/lib/generated-types';
+import { LogicalOperator, SortOrder } from '@vendure/common/lib/generated-types';
 import { DeepRequired } from '@vendure/core';
 
 import { buildElasticBody } from './build-elastic-body';
@@ -27,11 +27,42 @@ describe('buildElasticBody()', () => {
         });
     });
 
-    it('facetValueIds', () => {
-        const result = buildElasticBody({ facetValueIds: ['1', '2'] }, searchConfig, CHANNEL_ID);
+    it('facetValueIds AND', () => {
+        const result = buildElasticBody(
+            { facetValueIds: ['1', '2'], facetValueOperator: LogicalOperator.AND },
+            searchConfig,
+            CHANNEL_ID,
+        );
+        expect(result.query).toEqual({
+            bool: {
+                filter: [
+                    CHANNEL_ID_TERM,
+                    {
+                        bool: {
+                            must: [{ term: { facetValueIds: '1' } }, { term: { facetValueIds: '2' } }],
+                        },
+                    },
+                ],
+            },
+        });
+    });
+
+    it('facetValueIds OR', () => {
+        const result = buildElasticBody(
+            { facetValueIds: ['1', '2'], facetValueOperator: LogicalOperator.OR },
+            searchConfig,
+            CHANNEL_ID,
+        );
         expect(result.query).toEqual({
             bool: {
-                filter: [CHANNEL_ID_TERM, { term: { facetValueIds: '1' } }, { term: { facetValueIds: '2' } }],
+                filter: [
+                    CHANNEL_ID_TERM,
+                    {
+                        bool: {
+                            should: [{ term: { facetValueIds: '1' } }, { term: { facetValueIds: '2' } }],
+                        },
+                    },
+                ],
             },
         });
     });
@@ -126,8 +157,11 @@ describe('buildElasticBody()', () => {
                     ],
                     filter: [
                         CHANNEL_ID_TERM,
-                        { term: { facetValueIds: '6' } },
-                        { term: { facetValueIds: '7' } },
+                        {
+                            bool: {
+                                should: [{ term: { facetValueIds: '6' } }, { term: { facetValueIds: '7' } }],
+                            },
+                        },
                         { term: { collectionIds: '42' } },
                         { term: { enabled: true } },
                     ],
@@ -308,7 +342,11 @@ describe('buildElasticBody()', () => {
                 bool: {
                     filter: [
                         CHANNEL_ID_TERM,
-                        { term: { facetValueIds: '5' } },
+                        {
+                            bool: {
+                                should: [{ term: { facetValueIds: '5' } }],
+                            },
+                        },
                         { term: { collectionIds: '3' } },
                         {
                             range: {

+ 8 - 4
packages/elasticsearch-plugin/src/build-elastic-body.ts

@@ -1,4 +1,4 @@
-import { PriceRange, SortOrder } from '@vendure/common/lib/generated-types';
+import { LogicalOperator, PriceRange, SortOrder } from '@vendure/common/lib/generated-types';
 import { DeepRequired, ID } from '@vendure/core';
 
 import { SearchConfig } from './options';
@@ -16,6 +16,7 @@ export function buildElasticBody(
     const {
         term,
         facetValueIds,
+        facetValueOperator,
         collectionId,
         groupByProduct,
         skip,
@@ -48,9 +49,12 @@ export function buildElasticBody(
     }
     if (facetValueIds && facetValueIds.length) {
         ensureBoolFilterExists(query);
-        query.bool.filter = query.bool.filter.concat(
-            facetValueIds.map(id => ({ term: { facetValueIds: id } })),
-        );
+        const operator = facetValueOperator === LogicalOperator.AND ? 'must' : 'should';
+        query.bool.filter = query.bool.filter.concat([
+            {
+                bool: { [operator]: facetValueIds.map(id => ({ term: { facetValueIds: id } })) },
+            },
+        ]);
     }
     if (collectionId) {
         ensureBoolFilterExists(query);