瀏覽代碼

feat(server): Add collectionIds to search index

Michael Bromley 6 年之前
父節點
當前提交
d86f2ab837

File diff suppressed because it is too large
+ 0 - 0
schema-admin.json


File diff suppressed because it is too large
+ 0 - 0
schema-shop.json


+ 39 - 9
schema.json

@@ -11953,6 +11953,16 @@
             },
             "defaultValue": null
           },
+          {
+            "name": "collectionId",
+            "description": null,
+            "type": {
+              "kind": "SCALAR",
+              "name": "String",
+              "ofType": null
+            },
+            "defaultValue": null
+          },
           {
             "name": "groupByProduct",
             "description": null,
@@ -12333,6 +12343,30 @@
             "isDeprecated": false,
             "deprecationReason": null
           },
+          {
+            "name": "collectionIds",
+            "description": null,
+            "args": [],
+            "type": {
+              "kind": "NON_NULL",
+              "name": null,
+              "ofType": {
+                "kind": "LIST",
+                "name": null,
+                "ofType": {
+                  "kind": "NON_NULL",
+                  "name": null,
+                  "ofType": {
+                    "kind": "SCALAR",
+                    "name": "String",
+                    "ofType": null
+                  }
+                }
+              }
+            },
+            "isDeprecated": false,
+            "deprecationReason": null
+          },
           {
             "name": "score",
             "description": null,
@@ -17478,19 +17512,15 @@
             "name": "translations",
             "description": null,
             "type": {
-              "kind": "NON_NULL",
+              "kind": "LIST",
               "name": null,
               "ofType": {
-                "kind": "LIST",
+                "kind": "NON_NULL",
                 "name": null,
                 "ofType": {
-                  "kind": "NON_NULL",
-                  "name": null,
-                  "ofType": {
-                    "kind": "INPUT_OBJECT",
-                    "name": "CollectionTranslationInput",
-                    "ofType": null
-                  }
+                  "kind": "INPUT_OBJECT",
+                  "name": "CollectionTranslationInput",
+                  "ofType": null
                 }
               }
             },

+ 5 - 5
server/e2e/__snapshots__/collection.e2e-spec.ts.snap

@@ -46,7 +46,7 @@ Object {
       "description": "Filter by FacetValues",
     },
   ],
-  "id": "T_2",
+  "id": "T_3",
   "languageCode": "en",
   "name": "Electronics",
   "parent": Object {
@@ -56,7 +56,7 @@ Object {
   "translations": Array [
     Object {
       "description": "",
-      "id": "T_1",
+      "id": "T_3",
       "languageCode": "en",
       "name": "Electronics",
     },
@@ -101,17 +101,17 @@ Object {
       "description": "Filter by FacetValues",
     },
   ],
-  "id": "T_4",
+  "id": "T_5",
   "languageCode": "en",
   "name": "Pear",
   "parent": Object {
-    "id": "T_3",
+    "id": "T_4",
     "name": "Computers",
   },
   "translations": Array [
     Object {
       "description": "Apple stuff ",
-      "id": "T_4",
+      "id": "T_5",
       "languageCode": "en",
       "name": "Pear",
     },

+ 152 - 9
server/e2e/default-search-plugin.e2e-spec.ts

@@ -1,8 +1,20 @@
 import path from 'path';
 
-import { SEARCH_PRODUCTS } from '../../admin-ui/src/app/data/definitions/product-definitions';
-import { SearchProducts } from '../../shared/generated-types';
+import {
+    CREATE_COLLECTION,
+    UPDATE_COLLECTION,
+} from '../../admin-ui/src/app/data/definitions/collection-definitions';
+import { SEARCH_PRODUCTS, UPDATE_PRODUCT } from '../../admin-ui/src/app/data/definitions/product-definitions';
+import {
+    ConfigArgType,
+    CreateCollection,
+    LanguageCode,
+    SearchProducts,
+    UpdateCollection,
+    UpdateProduct,
+} from '../../shared/generated-types';
 import { SimpleGraphQLClient } from '../mock-data/simple-graphql-client';
+import { facetValueCollectionFilter } from '../src/config/collection/default-collection-filters';
 import { DefaultSearchPlugin } from '../src/plugin/default-search-plugin/default-search-plugin';
 
 import { TEST_SETUP_TIMEOUT_MS } from './config/test-config';
@@ -81,6 +93,32 @@ describe('Default search plugin', () => {
         ]);
     }
 
+    async function testMatchCollectionId(client: SimpleGraphQLClient) {
+        const result = await client.query<SearchProducts.Query, SearchProducts.Variables>(SEARCH_PRODUCTS, {
+            input: {
+                collectionId: 'T_2',
+                groupByProduct: true,
+            },
+        });
+        expect(result.search.items.map(i => i.productName)).toEqual([
+            'Spiky Cactus',
+            'Orchid',
+            'Bonsai Tree',
+        ]);
+    }
+
+    describe('shop api', () => {
+        it('group by product', () => testGroupByProduct(shopClient));
+
+        it('no grouping', () => testNoGrouping(shopClient));
+
+        it('matches search term', () => testMatchSearchTerm(shopClient));
+
+        it('matches by facetId', () => testMatchFacetIds(shopClient));
+
+        it('matches by collectionId', () => testMatchCollectionId(shopClient));
+    });
+
     describe('admin api', () => {
         it('group by product', () => testGroupByProduct(adminClient));
 
@@ -89,15 +127,120 @@ describe('Default search plugin', () => {
         it('matches search term', () => testMatchSearchTerm(adminClient));
 
         it('matches by facetId', () => testMatchFacetIds(adminClient));
-    });
 
-    describe('shop api', () => {
-        it('group by product', () => testGroupByProduct(shopClient));
-
-        it('no grouping', () => testNoGrouping(shopClient));
+        it('matches by collectionId', () => testMatchCollectionId(adminClient));
+
+        it('updates index when a Product is changed', async () => {
+            await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
+                input: {
+                    id: 'T_1',
+                    facetValueIds: [],
+                },
+            });
+
+            const result = await adminClient.query<SearchProducts.Query, SearchProducts.Variables>(
+                SEARCH_PRODUCTS,
+                {
+                    input: {
+                        facetIds: ['T_2'],
+                        groupByProduct: true,
+                    },
+                },
+            );
+            expect(result.search.items.map(i => i.productName)).toEqual([
+                'Curvy Monitor',
+                'Gaming PC',
+                'Hard Drive',
+                'Clacky Keyboard',
+                'USB Cable',
+            ]);
+        });
 
-        it('matches search term', () => testMatchSearchTerm(shopClient));
+        it('updates index when a Collection is changed', async () => {
+            await adminClient.query<UpdateCollection.Mutation, UpdateCollection.Variables>(
+                UPDATE_COLLECTION,
+                {
+                    input: {
+                        id: 'T_2',
+                        filters: [
+                            {
+                                code: facetValueCollectionFilter.code,
+                                arguments: [
+                                    {
+                                        name: 'facetValueIds',
+                                        value: `["T_4"]`,
+                                        type: ConfigArgType.FACET_VALUE_IDS,
+                                    },
+                                ],
+                            },
+                        ],
+                    },
+                },
+            );
+
+            const result = await adminClient.query<SearchProducts.Query, SearchProducts.Variables>(
+                SEARCH_PRODUCTS,
+                {
+                    input: {
+                        collectionId: 'T_2',
+                        groupByProduct: true,
+                    },
+                },
+            );
+            expect(result.search.items.map(i => i.productName)).toEqual([
+                'Road Bike',
+                'Skipping Rope',
+                'Boxing Gloves',
+                'Tent',
+                'Cruiser Skateboard',
+                'Football',
+                'Running Shoe',
+            ]);
+        });
 
-        it('matches by facetId', () => testMatchFacetIds(shopClient));
+        it('updates index when a Collection created', async () => {
+            const { createCollection } = await adminClient.query<
+                CreateCollection.Mutation,
+                CreateCollection.Variables
+            >(CREATE_COLLECTION, {
+                input: {
+                    translations: [
+                        {
+                            languageCode: LanguageCode.en,
+                            name: 'Photo',
+                            description: '',
+                        },
+                    ],
+                    filters: [
+                        {
+                            code: facetValueCollectionFilter.code,
+                            arguments: [
+                                {
+                                    name: 'facetValueIds',
+                                    value: `["T_3"]`,
+                                    type: ConfigArgType.FACET_VALUE_IDS,
+                                },
+                            ],
+                        },
+                    ],
+                },
+            });
+
+            const result = await adminClient.query<SearchProducts.Query, SearchProducts.Variables>(
+                SEARCH_PRODUCTS,
+                {
+                    input: {
+                        collectionId: createCollection.id,
+                        groupByProduct: true,
+                    },
+                },
+            );
+            expect(result.search.items.map(i => i.productName)).toEqual([
+                'Instant Camera',
+                'Camera Lens',
+                'Tripod',
+                'SLR Camera',
+            ]);
+        });
     });
 });

+ 1 - 1
server/e2e/fixtures/e2e-initial-data.ts

@@ -19,5 +19,5 @@ export const initialData: InitialData = {
         { name: 'United Kingdom', code: 'GB', zone: 'Europe' },
         { name: 'United States of America', code: 'US', zone: 'Americas' },
     ],
-    collections: [],
+    collections: [{ name: 'Plants', facetNames: ['plants'] }],
 };

+ 1 - 1
server/src/api/schema/admin-api/collection.api.graphql

@@ -45,5 +45,5 @@ input UpdateCollectionInput {
     parentId: ID
     assetIds: [ID!]
     filters: [ConfigurableOperationInput!]
-    translations: [CollectionTranslationInput!]!
+    translations: [CollectionTranslationInput!]
 }

+ 1 - 0
server/src/api/schema/common/common-types.graphql

@@ -126,6 +126,7 @@ input DateOperators {
 input SearchInput {
     term: String
     facetIds: [String!]
+    collectionId: String
     groupByProduct: Boolean
     take: Int
     skip: Int

+ 1 - 0
server/src/api/schema/type/product-search.type.graphql

@@ -22,5 +22,6 @@ type SearchResult {
     description: String!
     facetIds: [String!]!
     facetValueIds: [String!]!
+    collectionIds: [String!]!
     score: Float!
 }

+ 1 - 1
server/src/entity/subscribers.ts

@@ -26,7 +26,7 @@ export class CalculatedPropertySubscriber implements EntitySubscriberInterface {
                 for (const property of prototype[CALCULATED_PROPERTIES]) {
                     const getterDescriptor = Object.getOwnPropertyDescriptor(prototype, property);
                     const getFn = getterDescriptor && getterDescriptor.get;
-                    if (getFn) {
+                    if (getFn && !entity.hasOwnProperty(property)) {
                         const boundGetFn = getFn.bind(entity);
                         Object.defineProperties(entity, {
                             [property]: {

+ 20 - 0
server/src/event-bus/events/collection-modification-event.ts

@@ -0,0 +1,20 @@
+import { ID } from '../../../../shared/shared-types';
+import { RequestContext } from '../../api/common/request-context';
+import { Collection } from '../../entity';
+import { VendureEvent } from '../vendure-event';
+
+/**
+ * @description
+ * This event is fired whenever a Collection is modified in some way. The `productVariantIds`
+ * argument is an array of ids of all ProductVariants which:
+ *
+ * 1. were part of this collection prior to modification and are no longer
+ * 2. are now part of this collection after modification but were not before
+ *
+ * @docsCategory events
+ */
+export class CollectionModificationEvent extends VendureEvent {
+    constructor(public ctx: RequestContext, public collection: Collection, public productVariantIds: ID[]) {
+        super();
+    }
+}

+ 2 - 2
server/src/plugin/default-search-plugin/fulltext-search.resolver.ts

@@ -19,7 +19,7 @@ export class ShopFulltextSearchResolver implements Omit<BaseSearchResolver, 'rei
 
     @Query()
     @Allow(Permission.Public)
-    @Decode('facetIds')
+    @Decode('facetIds', 'collectionId')
     async search(
         @Ctx() ctx: RequestContext,
         @Args() args: SearchQueryArgs,
@@ -42,7 +42,7 @@ export class AdminFulltextSearchResolver implements BaseSearchResolver {
 
     @Query()
     @Allow(Permission.ReadCatalog)
-    @Decode('facetIds')
+    @Decode('facetIds', 'collectionId')
     async search(
         @Ctx() ctx: RequestContext,
         @Args() args: SearchQueryArgs,

+ 40 - 17
server/src/plugin/default-search-plugin/fulltext-search.service.ts

@@ -13,6 +13,7 @@ import { Translated } from '../../common/types/locale-types';
 import { FacetValue, Product, ProductVariant } from '../../entity';
 import { EventBus } from '../../event-bus/event-bus';
 import { CatalogModificationEvent } from '../../event-bus/events/catalog-modification-event';
+import { CollectionModificationEvent } from '../../event-bus/events/collection-modification-event';
 import { translateDeep } from '../../service/helpers/utils/translate-entity';
 import { FacetValueService } from '../../service/services/facet-value.service';
 
@@ -39,6 +40,7 @@ export class FulltextSearchService {
         'featuredAsset',
         'facetValues',
         'facetValues.facet',
+        'collections',
     ];
 
     constructor(
@@ -48,24 +50,14 @@ export class FulltextSearchService {
     ) {
         eventBus.subscribe(CatalogModificationEvent, event => {
             if (event.entity instanceof Product || event.entity instanceof ProductVariant) {
-                return this.update(event.ctx, event.entity);
+                return this.updateProductOrVariant(event.ctx, event.entity);
             }
         });
-        switch (this.connection.options.type) {
-            case 'mysql':
-            case 'mariadb':
-                this.searchStrategy = new MysqlSearchStrategy(connection);
-                break;
-            case 'sqlite':
-            case 'sqljs':
-                this.searchStrategy = new SqliteSearchStrategy(connection);
-                break;
-            case 'postgres':
-                this.searchStrategy = new PostgresSearchStrategy(connection);
-                break;
-            default:
-                throw new InternalServerError(`error.database-not-supported-by-default-search-plugin`);
-        }
+        eventBus.subscribe(CollectionModificationEvent, event => {
+            return this.updateVariantsById(event.ctx, event.productVariantIds);
+        });
+
+        this.setSearchStrategy();
     }
 
     /**
@@ -111,7 +103,7 @@ export class FulltextSearchService {
     /**
      * Updates the search index only for the affected entities.
      */
-    async update(ctx: RequestContext, updatedEntity: Product | ProductVariant) {
+    async updateProductOrVariant(ctx: RequestContext, updatedEntity: Product | ProductVariant) {
         let updatedVariants: ProductVariant[] = [];
         let removedVariantIds: ID[] = [];
         if (updatedEntity instanceof Product) {
@@ -145,6 +137,15 @@ export class FulltextSearchService {
         }
     }
 
+    async updateVariantsById(ctx: RequestContext, ids: ID[]) {
+        if (ids.length) {
+            const updatedVariants = await this.connection.getRepository(ProductVariant).findByIds(ids, {
+                relations: this.variantRelations,
+            });
+            await this.saveSearchIndexItems(ctx.languageCode, updatedVariants);
+        }
+    }
+
     /**
      * Checks to see if the index is empty, and if so triggers a re-index operation.
      */
@@ -159,6 +160,27 @@ export class FulltextSearchService {
         }
     }
 
+    /**
+     * Sets the SearchStrategy appropriate to th configured database type.
+     */
+    private setSearchStrategy() {
+        switch (this.connection.options.type) {
+            case 'mysql':
+            case 'mariadb':
+                this.searchStrategy = new MysqlSearchStrategy(this.connection);
+                break;
+            case 'sqlite':
+            case 'sqljs':
+                this.searchStrategy = new SqliteSearchStrategy(this.connection);
+                break;
+            case 'postgres':
+                this.searchStrategy = new PostgresSearchStrategy(this.connection);
+                break;
+            default:
+                throw new InternalServerError(`error.database-not-supported-by-default-search-plugin`);
+        }
+    }
+
     /**
      * Add or update items in the search index
      */
@@ -181,6 +203,7 @@ export class FulltextSearchService {
                         productVariantPreview: v.featuredAsset ? v.featuredAsset.preview : '',
                         facetIds: this.getFacetIds(v),
                         facetValueIds: this.getFacetValueIds(v),
+                        collectionIds: v.collections.map(c => c.id.toString()),
                     }),
             );
         await this.connection.getRepository(SearchIndexItem).save(items);

+ 3 - 0
server/src/plugin/default-search-plugin/search-index-item.entity.ts

@@ -52,6 +52,9 @@ export class SearchIndexItem {
     @Column('simple-array')
     facetValueIds: string[];
 
+    @Column('simple-array')
+    collectionIds: string[];
+
     @Column()
     productPreview: string;
 

+ 1 - 0
server/src/plugin/default-search-plugin/search-strategy/map-to-search-result.ts

@@ -16,6 +16,7 @@ export function mapToSearchResult(raw: any, currencyCode: CurrencyCode): SearchR
         description: raw.si_description,
         facetIds: raw.si_facetIds.split(',').map(x => x.trim()),
         facetValueIds: raw.si_facetValueIds.split(',').map(x => x.trim()),
+        collectionIds: raw.si_collectionIds.split(',').map(x => x.trim()),
         productPreview: raw.si_productPreview,
         productVariantPreview: raw.si_productVariantPreview,
         score: raw.score || 0,

+ 4 - 1
server/src/plugin/default-search-plugin/search-strategy/mysql-search-strategy.ts

@@ -71,7 +71,7 @@ export class MysqlSearchStrategy implements SearchStrategy {
         qb: SelectQueryBuilder<SearchIndexItem>,
         input: SearchInput,
     ): SelectQueryBuilder<SearchIndexItem> {
-        const { term, facetIds } = input;
+        const { term, facetIds, collectionId } = input;
 
         qb.where('1 = 1');
         if (term && term.length > this.minTermLength) {
@@ -100,6 +100,9 @@ export class MysqlSearchStrategy implements SearchStrategy {
                 qb.andWhere(`FIND_IN_SET(:${placeholder}, facetValueIds)`, { [placeholder]: id });
             }
         }
+        if (collectionId) {
+            qb.andWhere(`FIND_IN_SET (:collectionId, collectionIds)`, { collectionId });
+        }
         if (input.groupByProduct === true) {
             qb.groupBy('productId');
         }

+ 6 - 2
server/src/plugin/default-search-plugin/search-strategy/postgres-search-strategy.ts

@@ -79,7 +79,7 @@ export class PostgresSearchStrategy implements SearchStrategy {
         input: SearchInput,
         forceGroup: boolean = false,
     ): SelectQueryBuilder<SearchIndexItem> {
-        const { term, facetIds } = input;
+        const { term, facetIds, collectionId } = input;
         // join multiple words with the logical AND operator
         const termLogicalAnd = term ? term.trim().replace(/\s+/, ' & ') : '';
 
@@ -116,6 +116,9 @@ export class PostgresSearchStrategy implements SearchStrategy {
                 });
             }
         }
+        if (collectionId) {
+            qb.andWhere(`:collectionId = ANY (string_to_array(si.collectionIds, ','))`, { collectionId });
+        }
         if (input.groupByProduct === true) {
             qb.groupBy('si.productId');
         }
@@ -140,6 +143,7 @@ export class PostgresSearchStrategy implements SearchStrategy {
             'description',
             'facetIds',
             'facetValueIds',
+            'collectionIds',
             'productPreview',
             'productVariantPreview',
         ]
@@ -147,7 +151,7 @@ export class PostgresSearchStrategy implements SearchStrategy {
                 const qualifiedName = `si.${col}`;
                 const alias = `si_${col}`;
                 if (groupByProduct && col !== 'productId') {
-                    if (col === 'facetIds' || col === 'facetValueIds') {
+                    if (col === 'facetIds' || col === 'facetValueIds' || col === 'collectionIds') {
                         return `string_agg(${qualifiedName}, ',') as "${alias}"`;
                     } else {
                         return `MIN(${qualifiedName}) as "${alias}"`;

+ 6 - 1
server/src/plugin/default-search-plugin/search-strategy/sqlite-search-strategy.ts

@@ -72,7 +72,7 @@ export class SqliteSearchStrategy implements SearchStrategy {
         qb: SelectQueryBuilder<SearchIndexItem>,
         input: SearchInput,
     ): SelectQueryBuilder<SearchIndexItem> {
-        const { term, facetIds } = input;
+        const { term, facetIds, collectionId } = input;
 
         qb.where('1 = 1');
         if (term && term.length > this.minTermLength) {
@@ -104,6 +104,11 @@ export class SqliteSearchStrategy implements SearchStrategy {
                 });
             }
         }
+        if (collectionId) {
+            qb.andWhere(`(',' || collectionIds || ',') LIKE :collectionId`, {
+                collectionId: `%,${collectionId},%`,
+            });
+        }
         if (input.groupByProduct === true) {
             qb.groupBy('productId');
         }

+ 43 - 5
server/src/service/services/collection.service.ts

@@ -25,6 +25,7 @@ import { Collection } from '../../entity/collection/collection.entity';
 import { ProductVariant } from '../../entity/product-variant/product-variant.entity';
 import { EventBus } from '../../event-bus/event-bus';
 import { CatalogModificationEvent } from '../../event-bus/events/catalog-modification-event';
+import { CollectionModificationEvent } from '../../event-bus/events/collection-modification-event';
 import { AssetUpdater } from '../helpers/asset-updater/asset-updater';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { TranslatableSaver } from '../helpers/translatable-saver/translatable-saver';
@@ -50,9 +51,14 @@ export class CollectionService implements OnModuleInit {
 
     onModuleInit() {
         this.eventBus.subscribe(CatalogModificationEvent, async event => {
-            const collections = await this.connection.getRepository(Collection).find();
+            const collections = await this.connection.getRepository(Collection).find({
+                relations: ['productVariants'],
+            });
             for (const collection of collections) {
-                await this.applyCollectionFilters(collection);
+                const affectedVariantIds = await this.applyCollectionFilters(collection);
+                this.eventBus.publish(
+                    new CollectionModificationEvent(event.ctx, collection, affectedVariantIds),
+                );
             }
         });
     }
@@ -167,7 +173,8 @@ export class CollectionService implements OnModuleInit {
                 await this.assetUpdater.updateEntityAssets(coll, input);
             },
         });
-        await this.applyCollectionFilters(collection);
+        const affectedVariantIds = await this.applyCollectionFilters(collection);
+        this.eventBus.publish(new CollectionModificationEvent(ctx, collection, affectedVariantIds));
         return assertFound(this.findOne(ctx, collection.id));
     }
 
@@ -183,9 +190,11 @@ export class CollectionService implements OnModuleInit {
                 await this.assetUpdater.updateEntityAssets(coll, input);
             },
         });
+        let affectedVariantIds: ID[] = [];
         if (input.filters) {
-            await this.applyCollectionFilters(collection);
+            affectedVariantIds = await this.applyCollectionFilters(collection);
         }
+        this.eventBus.publish(new CollectionModificationEvent(ctx, collection, affectedVariantIds));
         return assertFound(this.findOne(ctx, collection.id));
     }
 
@@ -256,18 +265,47 @@ export class CollectionService implements OnModuleInit {
         return filters;
     }
 
-    private async applyCollectionFilters(collection: Collection) {
+    /**
+     * Applies the CollectionFilters and returns an array of all affected ProductVariant ids.
+     */
+    private async applyCollectionFilters(collection: Collection): Promise<ID[]> {
         const ancestorFilters = await this.getAncestors(collection.id).then(ancestors =>
             ancestors.reduce(
                 (filters, c) => [...filters, ...(c.filters || [])],
                 [] as ConfigurableOperation[],
             ),
         );
+        const preIds = await this.getCollectionProductVariantIds(collection);
         collection.productVariants = await this.getFilteredProductVariants([
             ...ancestorFilters,
             ...(collection.filters || []),
         ]);
         await this.connection.getRepository(Collection).save(collection);
+        const postIds = collection.productVariants.map(v => v.id);
+
+        const preIdsSet = new Set(preIds);
+        const postIdsSet = new Set(postIds);
+        const difference = [
+            ...preIds.filter(id => !postIdsSet.has(id)),
+            ...postIds.filter(id => !preIdsSet.has(id)),
+        ];
+        return difference;
+    }
+
+    /**
+     * Returns the IDs of the Collection's ProductVariants.
+     */
+    private async getCollectionProductVariantIds(collection: Collection): Promise<ID[]> {
+        if (collection.productVariants) {
+            return collection.productVariants.map(v => v.id);
+        } else {
+            const productVariants = await this.connection
+                .getRepository(ProductVariant)
+                .createQueryBuilder('variant')
+                .innerJoin('variant.collections', 'collection', 'collection.id = :id', { id: collection.id })
+                .getMany();
+            return productVariants.map(v => v.id);
+        }
     }
 
     /**

+ 2 - 8
server/src/service/services/product-variant.service.ts

@@ -93,11 +93,9 @@ export class ProductVariantService {
         collectionId: ID,
         options: ListQueryOptions<ProductVariant>,
     ): Promise<PaginatedList<Translated<ProductVariant>>> {
-        const relations = ['product', 'product.featuredAsset', 'taxCategory'];
-
         return this.listQueryBuilder
             .build(ProductVariant, options, {
-                relations,
+                relations: ['taxCategory'],
                 channelId: ctx.channelId,
             })
             .leftJoin('productvariant.collections', 'collection')
@@ -106,11 +104,7 @@ export class ProductVariantService {
             .then(async ([variants, totalItems]) => {
                 const items = variants.map(variant => {
                     const variantWithPrices = this.applyChannelPriceAndTax(variant, ctx);
-                    return translateDeep(variantWithPrices, ctx.languageCode, [
-                        'options',
-                        'facetValues',
-                        ['facetValues', 'facet'],
-                    ]);
+                    return translateDeep(variantWithPrices, ctx.languageCode);
                 });
                 return {
                     items,

+ 5 - 1
shared/generated-shop-types.ts

@@ -1,5 +1,5 @@
 // tslint:disable
-// Generated in 2019-03-07T11:46:05+01:00
+// Generated in 2019-03-08T12:05:06+01:00
 export type Maybe<T> = T | null;
 
 export interface OrderListOptions {
@@ -233,6 +233,8 @@ export interface SearchInput {
 
     facetIds?: Maybe<string[]>;
 
+    collectionId?: Maybe<string>;
+
     groupByProduct?: Maybe<boolean>;
 
     take?: Maybe<number>;
@@ -1552,6 +1554,8 @@ export interface SearchResult {
 
     facetValueIds: string[];
 
+    collectionIds: string[];
+
     score: number;
 }
 

+ 6 - 2
shared/generated-types.ts

@@ -1,5 +1,5 @@
 // tslint:disable
-// Generated in 2019-03-07T11:46:05+01:00
+// Generated in 2019-03-08T12:05:07+01:00
 export type Maybe<T> = T | null;
 
 
@@ -440,6 +440,8 @@ export interface SearchInput {
   
   facetIds?: Maybe<string[]>;
   
+  collectionId?: Maybe<string>;
+  
   groupByProduct?: Maybe<boolean>;
   
   take?: Maybe<number>;
@@ -762,7 +764,7 @@ export interface UpdateCollectionInput {
   
   filters?: Maybe<ConfigurableOperationInput[]>;
   
-  translations: CollectionTranslationInput[];
+  translations?: Maybe<CollectionTranslationInput[]>;
   
   customFields?: Maybe<Json>;
 }
@@ -5460,6 +5462,8 @@ export interface SearchResult {
   
   facetValueIds: string[];
   
+  collectionIds: string[];
+  
   score: number;
 }
 

Some files were not shown because too many files changed in this diff