From f1361176d4a2267e7de9b4b3e2d1cd3301cb5871 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Tue, 11 Jun 2019 14:24:50 +0200 Subject: [PATCH] feat(core): FacetValue Collection filter can specify logical operator Relates to #112 --- .../__snapshots__/collection.e2e-spec.ts.snap | 10 ++ packages/core/e2e/collection.e2e-spec.ts | 108 ++++++++++++++---- .../e2e/default-search-plugin.e2e-spec.ts | 10 ++ packages/core/e2e/shop-catalog.e2e-spec.ts | 5 + .../core/src/common/configurable-operation.ts | 4 +- .../config/collection/collection-filter.ts | 2 +- .../collection/default-collection-filters.ts | 3 +- .../providers/populator/populator.ts | 5 + .../service/services/collection.service.ts | 7 +- 9 files changed, 124 insertions(+), 30 deletions(-) diff --git a/packages/core/e2e/__snapshots__/collection.e2e-spec.ts.snap b/packages/core/e2e/__snapshots__/collection.e2e-spec.ts.snap index c99c46ec19..f384d48fa3 100644 --- a/packages/core/e2e/__snapshots__/collection.e2e-spec.ts.snap +++ b/packages/core/e2e/__snapshots__/collection.e2e-spec.ts.snap @@ -41,6 +41,11 @@ Object { "type": "FACET_VALUE_IDS", "value": "[\\"T_1\\"]", }, + Object { + "name": "containsAny", + "type": "BOOLEAN", + "value": "false", + }, ], "code": "facet-value-filter", "description": "Filter by FacetValues", @@ -97,6 +102,11 @@ Object { "type": "FACET_VALUE_IDS", "value": "[\\"T_3\\"]", }, + Object { + "name": "containsAny", + "type": "BOOLEAN", + "value": "false", + }, ], "code": "facet-value-filter", "description": "Filter by FacetValues", diff --git a/packages/core/e2e/collection.e2e-spec.ts b/packages/core/e2e/collection.e2e-spec.ts index ddfad1fb46..0974ad18fd 100644 --- a/packages/core/e2e/collection.e2e-spec.ts +++ b/packages/core/e2e/collection.e2e-spec.ts @@ -4,10 +4,7 @@ import gql from 'graphql-tag'; import path from 'path'; import { StringOperator } from '../src/common/configurable-operation'; -import { - facetValueCollectionFilter, - variantNameCollectionFilter, -} from '../src/config/collection/default-collection-filters'; +import { facetValueCollectionFilter, variantNameCollectionFilter } from '../src/config/collection/default-collection-filters'; import { TEST_SETUP_TIMEOUT_MS } from './config/test-config'; import { COLLECTION_FRAGMENT, FACET_VALUE_FRAGMENT } from './graphql/fragments'; @@ -107,6 +104,11 @@ describe('Collection resolver', () => { value: `["${getFacetValueId('electronics')}"]`, type: ConfigArgType.FACET_VALUE_IDS, }, + { + name: 'containsAny', + value: `false`, + type: ConfigArgType.BOOLEAN, + }, ], }, ], @@ -138,6 +140,11 @@ describe('Collection resolver', () => { value: `["${getFacetValueId('computers')}"]`, type: ConfigArgType.FACET_VALUE_IDS, }, + { + name: 'containsAny', + value: `false`, + type: ConfigArgType.BOOLEAN, + }, ], }, ], @@ -164,6 +171,11 @@ describe('Collection resolver', () => { value: `["${getFacetValueId('pear')}"]`, type: ConfigArgType.FACET_VALUE_IDS, }, + { + name: 'containsAny', + value: `false`, + type: ConfigArgType.BOOLEAN, + }, ], }, ], @@ -246,7 +258,7 @@ describe('Collection resolver', () => { const { updateCollection } = await client.query< UpdateCollection.Mutation, UpdateCollection.Variables - >(UPDATE_COLLECTION, { + >(UPDATE_COLLECTION, { input: { id: pearCollection.id, assetIds: [assets[1].id], @@ -384,7 +396,7 @@ describe('Collection resolver', () => { const result = await client.query< CreateCollectionSelectVariants.Mutation, CreateCollectionSelectVariants.Variables - >(CREATE_COLLECTION_SELECT_VARIANTS, { + >(CREATE_COLLECTION_SELECT_VARIANTS, { input: { translations: [{ languageCode: LanguageCode.en, name: 'Empty', description: '' }], filters: [], @@ -398,7 +410,7 @@ describe('Collection resolver', () => { const result = await client.query< GetCollectionProducts.Query, GetCollectionProducts.Variables - >(GET_COLLECTION_PRODUCT_VARIANTS, { + >(GET_COLLECTION_PRODUCT_VARIANTS, { id: electronicsCollection.id, }); expect(result.collection!.productVariants.items.map(i => i.name)).toEqual([ @@ -430,7 +442,7 @@ describe('Collection resolver', () => { const result = await client.query< GetCollectionProducts.Query, GetCollectionProducts.Variables - >(GET_COLLECTION_PRODUCT_VARIANTS, { + >(GET_COLLECTION_PRODUCT_VARIANTS, { id: computersCollection.id, }); expect(result.collection!.productVariants.items.map(i => i.name)).toEqual([ @@ -454,14 +466,14 @@ describe('Collection resolver', () => { ]); }); - it('photo and pear', async () => { + it('photo AND pear', async () => { const result = await client.query< CreateCollectionSelectVariants.Mutation, CreateCollectionSelectVariants.Variables - >(CREATE_COLLECTION_SELECT_VARIANTS, { + >(CREATE_COLLECTION_SELECT_VARIANTS, { input: { translations: [ - { languageCode: LanguageCode.en, name: 'Photo Pear', description: '' }, + { languageCode: LanguageCode.en, name: 'Photo AND Pear', description: '' }, ], filters: [ { @@ -474,6 +486,11 @@ describe('Collection resolver', () => { )}"]`, type: ConfigArgType.FACET_VALUE_IDS, }, + { + name: 'containsAny', + value: `false`, + type: ConfigArgType.BOOLEAN, + }, ], }, ], @@ -483,6 +500,48 @@ describe('Collection resolver', () => { 'Instant Camera', ]); }); + + it('photo OR pear', async () => { + const result = await client.query< + CreateCollectionSelectVariants.Mutation, + CreateCollectionSelectVariants.Variables + >(CREATE_COLLECTION_SELECT_VARIANTS, { + input: { + translations: [ + { languageCode: LanguageCode.en, name: 'Photo OR Pear', description: '' }, + ], + filters: [ + { + code: facetValueCollectionFilter.code, + arguments: [ + { + name: 'facetValueIds', + value: `["${getFacetValueId('pear')}", "${getFacetValueId( + 'photo', + )}"]`, + type: ConfigArgType.FACET_VALUE_IDS, + }, + { + name: 'containsAny', + value: `true`, + type: ConfigArgType.BOOLEAN, + }, + ], + }, + ], + } as CreateCollectionInput, + }); + expect(result.createCollection.productVariants.items.map(i => i.name)).toEqual([ + 'Laptop 13 inch 8GB', + 'Laptop 15 inch 8GB', + 'Laptop 13 inch 16GB', + 'Laptop 15 inch 16GB', + 'Instant Camera', + 'Camera Lens', + 'Tripod', + 'SLR Camera', + ]); + }); }); describe('variantName filter', () => { @@ -493,7 +552,7 @@ describe('Collection resolver', () => { const { createCollection } = await client.query< CreateCollection.Mutation, CreateCollection.Variables - >(CREATE_COLLECTION, { + >(CREATE_COLLECTION, { input: { translations: [ { languageCode: LanguageCode.en, name: `${operator} ${term}`, description: '' }, @@ -526,7 +585,7 @@ describe('Collection resolver', () => { const result = await client.query< GetCollectionProducts.Query, GetCollectionProducts.Variables - >(GET_COLLECTION_PRODUCT_VARIANTS, { + >(GET_COLLECTION_PRODUCT_VARIANTS, { id: collection.id, }); expect(result.collection!.productVariants.items.map(i => i.name)).toEqual([ @@ -542,7 +601,7 @@ describe('Collection resolver', () => { const result = await client.query< GetCollectionProducts.Query, GetCollectionProducts.Variables - >(GET_COLLECTION_PRODUCT_VARIANTS, { + >(GET_COLLECTION_PRODUCT_VARIANTS, { id: collection.id, }); expect(result.collection!.productVariants.items.map(i => i.name)).toEqual(['Camera Lens']); @@ -554,7 +613,7 @@ describe('Collection resolver', () => { const result = await client.query< GetCollectionProducts.Query, GetCollectionProducts.Variables - >(GET_COLLECTION_PRODUCT_VARIANTS, { + >(GET_COLLECTION_PRODUCT_VARIANTS, { id: collection.id, }); expect(result.collection!.productVariants.items.map(i => i.name)).toEqual([ @@ -569,7 +628,7 @@ describe('Collection resolver', () => { const result = await client.query< GetCollectionProducts.Query, GetCollectionProducts.Variables - >(GET_COLLECTION_PRODUCT_VARIANTS, { + >(GET_COLLECTION_PRODUCT_VARIANTS, { id: collection.id, }); expect(result.collection!.productVariants.items.map(i => i.name)).toEqual([ @@ -630,7 +689,7 @@ describe('Collection resolver', () => { const result = await client.query< GetCollectionProducts.Query, GetCollectionProducts.Variables - >(GET_COLLECTION_PRODUCT_VARIANTS, { id: pearCollection.id }); + >(GET_COLLECTION_PRODUCT_VARIANTS, { id: pearCollection.id }); expect(result.collection!.productVariants.items.map(i => i.name)).toEqual([ 'Laptop 13 inch 8GB', 'Laptop 15 inch 8GB', @@ -658,7 +717,7 @@ describe('Collection resolver', () => { const result = await client.query< GetCollectionProducts.Query, GetCollectionProducts.Variables - >(GET_COLLECTION_PRODUCT_VARIANTS, { id: pearCollection.id }); + >(GET_COLLECTION_PRODUCT_VARIANTS, { id: pearCollection.id }); expect(result.collection!.productVariants.items.map(i => i.name)).toEqual([ 'Laptop 13 inch 8GB', 'Laptop 15 inch 8GB', @@ -687,7 +746,7 @@ describe('Collection resolver', () => { const result = await client.query< GetCollectionProducts.Query, GetCollectionProducts.Variables - >(GET_COLLECTION_PRODUCT_VARIANTS, { id: pearCollection.id }); + >(GET_COLLECTION_PRODUCT_VARIANTS, { id: pearCollection.id }); expect(result.collection!.productVariants.items.map(i => i.name)).toEqual([ 'Laptop 13 inch 8GB', 'Laptop 15 inch 8GB', @@ -707,13 +766,14 @@ describe('Collection resolver', () => { const result = await client.query< GetCollectionsForProducts.Query, GetCollectionsForProducts.Variables - >(GET_COLLECTIONS_FOR_PRODUCTS, { term: 'camera' }); + >(GET_COLLECTIONS_FOR_PRODUCTS, { term: 'camera' }); expect(result.products.items[0].collections).toEqual([ { id: 'T_3', name: 'Electronics' }, { id: 'T_5', name: 'Pear' }, - { id: 'T_7', name: 'Photo Pear' }, - { id: 'T_8', name: 'contains camera' }, - { id: 'T_10', name: 'endsWith camera' }, + { id: 'T_7', name: 'Photo AND Pear' }, + { id: 'T_8', name: 'Photo OR Pear' }, + { id: 'T_9', name: 'contains camera' }, + { id: 'T_11', name: 'endsWith camera' }, ]); }); }); @@ -725,7 +785,7 @@ describe('Collection resolver', () => { const { collection } = await client.query< GetCollectionProducts.Query, GetCollectionProducts.Variables - >(GET_COLLECTION_PRODUCT_VARIANTS, { + >(GET_COLLECTION_PRODUCT_VARIANTS, { id: pearCollection.id, }); expect(collection!.productVariants.items.map(i => i.name)).toEqual([ diff --git a/packages/core/e2e/default-search-plugin.e2e-spec.ts b/packages/core/e2e/default-search-plugin.e2e-spec.ts index 96913f86cb..da795f333f 100644 --- a/packages/core/e2e/default-search-plugin.e2e-spec.ts +++ b/packages/core/e2e/default-search-plugin.e2e-spec.ts @@ -347,6 +347,11 @@ describe('Default search plugin', () => { value: `["T_4"]`, type: ConfigArgType.FACET_VALUE_IDS, }, + { + name: 'containsAny', + value: `false`, + type: ConfigArgType.BOOLEAN, + }, ], }, ], @@ -396,6 +401,11 @@ describe('Default search plugin', () => { value: `["T_3"]`, type: ConfigArgType.FACET_VALUE_IDS, }, + { + name: 'containsAny', + value: `false`, + type: ConfigArgType.BOOLEAN, + }, ], }, ], diff --git a/packages/core/e2e/shop-catalog.e2e-spec.ts b/packages/core/e2e/shop-catalog.e2e-spec.ts index 97a1988a1a..b8c1890210 100644 --- a/packages/core/e2e/shop-catalog.e2e-spec.ts +++ b/packages/core/e2e/shop-catalog.e2e-spec.ts @@ -251,6 +251,11 @@ describe('Shop catalog', () => { value: `["${category.values[3].id}"]`, type: ConfigArgType.FACET_VALUE_IDS, }, + { + name: 'containsAny', + value: `false`, + type: ConfigArgType.BOOLEAN, + }, ], }, ], diff --git a/packages/core/src/common/configurable-operation.ts b/packages/core/src/common/configurable-operation.ts index 71df413a45..1443109d86 100644 --- a/packages/core/src/common/configurable-operation.ts +++ b/packages/core/src/common/configurable-operation.ts @@ -62,7 +62,7 @@ export function configurableDefToOperation(def: ConfigurableOperationDef): Confi export function argsArrayToHash(args: ConfigArg[]): ConfigArgValues { const output: ConfigArgValues = {} as any; for (const arg of args) { - if (arg.value != null) { + if (arg && arg.value != null) { output[arg.name as keyof ConfigArgValues] = coerceValueToType(arg); } } @@ -81,7 +81,7 @@ function coerceValueToType(arg: ConfigArg): ConfigArgValues[keyof T] { case ConfigArgType.DATETIME: return Date.parse(arg.value || '') as any; case ConfigArgType.BOOLEAN: - return !!arg.value as any; + return !!(arg.value && (arg.value.toLowerCase() === 'true' || arg.value === '1')) as any; case ConfigArgType.FACET_VALUE_IDS: try { return JSON.parse(arg.value as any); diff --git a/packages/core/src/config/collection/collection-filter.ts b/packages/core/src/config/collection/collection-filter.ts index 1a5a1d3e2f..aa95a16eb3 100644 --- a/packages/core/src/config/collection/collection-filter.ts +++ b/packages/core/src/config/collection/collection-filter.ts @@ -9,7 +9,7 @@ import { } from '../../common/configurable-operation'; import { ProductVariant } from '../../entity/product-variant/product-variant.entity'; -export type CollectionFilterArgType = ConfigArgType.FACET_VALUE_IDS | ConfigArgType.STRING | ConfigArgType.STRING_OPERATOR; +export type CollectionFilterArgType = ConfigArgType.FACET_VALUE_IDS | ConfigArgType.STRING | ConfigArgType.STRING_OPERATOR | ConfigArgType.BOOLEAN; export type CollectionFilterArgs = ConfigArgs; export type ApplyCollectionFilterFn = ( diff --git a/packages/core/src/config/collection/default-collection-filters.ts b/packages/core/src/config/collection/default-collection-filters.ts index 8d4625eb1a..4fc51e5be4 100644 --- a/packages/core/src/config/collection/default-collection-filters.ts +++ b/packages/core/src/config/collection/default-collection-filters.ts @@ -9,6 +9,7 @@ import { CollectionFilter } from './collection-filter'; export const facetValueCollectionFilter = new CollectionFilter({ args: { facetValueIds: ConfigArgType.FACET_VALUE_IDS, + containsAny: ConfigArgType.BOOLEAN, }, code: 'facet-value-filter', description: 'Filter by FacetValues', @@ -26,7 +27,7 @@ export const facetValueCollectionFilter = new CollectionFilter({ }), ) .groupBy('productVariant.id') - .having(`COUNT(1) >= :count`, {count: args.facetValueIds.length}); + .having(`COUNT(1) >= :count`, { count: args.containsAny ? 1 : args.facetValueIds.length }); } else { // If no facetValueIds are specified, no ProductVariants will be matched. qb.andWhere('1 = 0'); diff --git a/packages/core/src/data-import/providers/populator/populator.ts b/packages/core/src/data-import/providers/populator/populator.ts index 8377f26c17..25323c49af 100644 --- a/packages/core/src/data-import/providers/populator/populator.ts +++ b/packages/core/src/data-import/providers/populator/populator.ts @@ -117,6 +117,11 @@ export class Populator { type: ConfigArgType.FACET_VALUE_IDS, value: JSON.stringify(facetValueIds), }, + { + name: 'containsAny', + value: `false`, + type: ConfigArgType.BOOLEAN, + }, ], }, ], diff --git a/packages/core/src/service/services/collection.service.ts b/packages/core/src/service/services/collection.service.ts index 5d539bcab5..4e646658be 100644 --- a/packages/core/src/service/services/collection.service.ts +++ b/packages/core/src/service/services/collection.service.ts @@ -386,17 +386,20 @@ export class CollectionService implements OnModuleInit { // Apply any facetValue-based filters if (facetFilters.length) { + const [idsArg, containsAnyArg] = facetFilters[0].args; const mergedArgs = facetFilters .map(f => f.args[0].value) .filter(notNullOrUndefined) .map(value => JSON.parse(value)) .reduce((all, ids) => [...all, ...ids]); + qb = facetValueCollectionFilter.apply(qb, [ { - name: facetFilters[0].args[0].name, - type: facetFilters[0].args[0].type, + name: idsArg.name, + type: idsArg.type, value: JSON.stringify(Array.from(new Set(mergedArgs))), }, + containsAnyArg, ]); }