Browse Source

fix(elasticsearch-plugin): Support hydration of custom field relations

Fixes #1638.
Michael Bromley 3 years ago
parent
commit
a75390ea06

+ 63 - 0
packages/elasticsearch-plugin/e2e/elasticsearch-plugin.e2e-spec.ts

@@ -4,6 +4,7 @@ import { pick } from '@vendure/common/lib/pick';
 import {
     DefaultJobQueuePlugin,
     DefaultLogger,
+    FacetValue,
     facetValueCollectionFilter,
     LogLevel,
     mergeConfig,
@@ -39,6 +40,8 @@ import {
     UpdateCollection,
     UpdateProduct,
     UpdateProductVariants,
+    UpdateProductVariantsMutation,
+    UpdateProductVariantsMutationVariables,
     UpdateTaxRate,
 } from '../../core/e2e/graphql/generated-e2e-admin-types';
 import { SearchProductsShop } from '../../core/e2e/graphql/generated-e2e-shop-types';
@@ -115,11 +118,21 @@ const INDEX_PREFIX = 'e2e-tests';
 describe('Elasticsearch plugin', () => {
     const { server, adminClient, shopClient } = createTestEnvironment(
         mergeConfig(testConfig(), {
+            customFields: {
+                ProductVariant: [
+                    {
+                        name: 'material',
+                        type: 'relation',
+                        entity: FacetValue,
+                    },
+                ],
+            },
             plugins: [
                 ElasticsearchPlugin.init({
                     indexPrefix: INDEX_PREFIX,
                     port: elasticsearchPort,
                     host: elasticsearchHost,
+                    hydrateProductVariantRelations: ['customFields.material'],
                     customProductVariantMappings: {
                         inStock: {
                             graphQlType: 'Boolean!',
@@ -127,6 +140,19 @@ describe('Elasticsearch plugin', () => {
                                 return variant.stockOnHand > 0;
                             },
                         },
+                        materialCode: {
+                            graphQlType: 'String!',
+                            valueFn: variant => {
+                                const materialFacetValue: FacetValue | undefined = (
+                                    variant.customFields as any
+                                ).material;
+                                let result = '';
+                                if (materialFacetValue) {
+                                    result = `${materialFacetValue.code}`;
+                                }
+                                return result;
+                            },
+                        },
                     },
                     customProductMappings: {
                         answer: {
@@ -1347,6 +1373,43 @@ describe('Elasticsearch plugin', () => {
             });
         });
 
+        // https://github.com/vendure-ecommerce/vendure/issues/1638
+        it('hydrates variant custom field relation', async () => {
+            const { updateProductVariants } = await adminClient.query<
+                UpdateProductVariantsMutation,
+                UpdateProductVariantsMutationVariables
+            >(UPDATE_PRODUCT_VARIANTS, {
+                input: [
+                    {
+                        id: 'T_20',
+                        customFields: {
+                            materialId: 'T_1',
+                        },
+                    },
+                ],
+            });
+            expect(updateProductVariants[0]!.id).toBe('T_20');
+
+            await adminClient.query<Reindex.Mutation>(REINDEX);
+            await awaitRunningJobs(adminClient);
+            const query = `{
+            search(input: { groupByProduct: false, term: "tripod" }) {
+                items {
+                  productVariantName
+                  productVariantId
+                  customMappings {
+                    ...on CustomProductVariantMappings {
+                      materialCode
+                    }
+                  }
+                }
+              }
+            }`;
+            const { search } = await shopClient.query(gql(query));
+
+            expect(search.items.map((i: any) => i.customMappings)).toEqual([{ materialCode: 'electronics' }]);
+        });
+
         it('product mappings', async () => {
             const query = `{
             search(input: { take: 1, groupByProduct: true, sort: { name: ASC } }) {

+ 16 - 4
packages/elasticsearch-plugin/src/indexing/indexer.controller.ts

@@ -11,6 +11,7 @@ import {
     EntityRelationPaths,
     FacetValue,
     ID,
+    InternalServerError,
     LanguageCode,
     Logger,
     Product,
@@ -96,11 +97,11 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
 
     onModuleInit(): any {
         this.client = getClient(this.options);
-        this.productRelations = this.getReindexRelationsRelations(
+        this.productRelations = this.getReindexRelations(
             defaultProductRelations,
             this.options.hydrateProductRelations,
         );
-        this.variantRelations = this.getReindexRelationsRelations(
+        this.variantRelations = this.getReindexRelations(
             defaultVariantRelations,
             this.options.hydrateProductVariantRelations,
         );
@@ -633,13 +634,24 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
      * throw an error trying to join a 2nd-level deep relation without the first level also
      * being joined.
      */
-    private getReindexRelationsRelations<T extends Product | ProductVariant>(
+    private getReindexRelations<T extends Product | ProductVariant>(
         defaultRelations: Array<EntityRelationPaths<T>>,
         hydratedRelations: Array<EntityRelationPaths<T>>,
     ): Array<EntityRelationPaths<T>> {
         const uniqueRelations = unique([...defaultRelations, ...hydratedRelations]);
         for (const relation of hydratedRelations) {
-            const path = relation.split('.');
+            let path = relation.split('.');
+            if (path[0] === 'customFields') {
+                if (2 < path.length) {
+                    throw new InternalServerError(
+                        [
+                            `hydrateProductRelations / hydrateProductVariantRelations does not currently support nested custom field relations`,
+                            `Received: "${relation}"`,
+                        ].join('\n'),
+                    );
+                }
+                path = [path.join('.')];
+            }
             const pathToPart: string[] = [];
             for (const part of path) {
                 pathToPart.push(part);

+ 1 - 1
packages/elasticsearch-plugin/src/options.ts

@@ -275,7 +275,7 @@ export interface ElasticsearchOptions {
      *       graphQlType: '[String!]',
      *       // Here we can be sure that the `product.assets` array is populated
      *       // with an Asset object
-     *       valueFn: (product) => product.assets.map(asset => asset.preview),
+     *       valueFn: (product) => product.assets.map(a => a.asset.preview),
      *     }
      *   }
      * }