Browse Source

fix(core): EntityHydrator correctly handles custom field relations

Fixes #1284
Michael Bromley 4 years ago
parent
commit
fd3e642b17

+ 29 - 0
packages/core/e2e/entity-hydrator.e2e-spec.ts

@@ -8,7 +8,9 @@ import { initialData } from '../../../e2e-common/e2e-initial-data';
 import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
 
 import { HydrationTestPlugin } from './fixtures/test-plugins/hydration-test-plugin';
+import { UpdateChannel } from './graphql/generated-e2e-admin-types';
 import { AddItemToOrder, UpdatedOrderFragment } from './graphql/generated-e2e-shop-types';
+import { UPDATE_CHANNEL } from './graphql/shared-definitions';
 import { ADD_ITEM_TO_ORDER } from './graphql/shop-definitions';
 
 const orderResultGuard: ErrorResultGuard<UpdatedOrderFragment> = createErrorResultGuard(
@@ -188,6 +190,27 @@ describe('Entity hydration', () => {
 
         expect(hydrateOrderReturnQuantities).toEqual([2]);
     });
+
+    // https://github.com/vendure-ecommerce/vendure/issues/1284
+    it('hydrates custom field relations', async () => {
+        await adminClient.query<UpdateChannel.Mutation, UpdateChannel.Variables>(UPDATE_CHANNEL, {
+            input: {
+                id: 'T_1',
+                customFields: {
+                    thumbId: 'T_2',
+                },
+            },
+        });
+
+        const { hydrateChannel } = await adminClient.query<{
+            hydrateChannel: any;
+        }>(GET_HYDRATED_CHANNEL, {
+            id: 'T_1',
+        });
+
+        expect(hydrateChannel.customFields.thumb).toBeDefined();
+        expect(hydrateChannel.customFields.thumb.id).toBe('T_2');
+    });
 });
 
 function getVariantWithName(product: Product, name: string) {
@@ -221,3 +244,9 @@ const GET_HYDRATED_ORDER_QUANTITIES = gql`
         hydrateOrderReturnQuantities(id: $id)
     }
 `;
+
+const GET_HYDRATED_CHANNEL = gql`
+    query GetHydratedChannel($id: ID!) {
+        hydrateChannel(id: $id)
+    }
+`;

+ 18 - 0
packages/core/e2e/fixtures/test-plugins/hydration-test-plugin.ts

@@ -1,6 +1,8 @@
 /* tslint:disable:no-non-null-assertion */
 import { Args, Query, Resolver } from '@nestjs/graphql';
 import {
+    Asset,
+    ChannelService,
     Ctx,
     EntityHydrator,
     ID,
@@ -19,6 +21,7 @@ export class TestAdminPluginResolver {
     constructor(
         private connection: TransactionalConnection,
         private orderService: OrderService,
+        private channelService: ChannelService,
         private productVariantService: ProductVariantService,
         private entityHydrator: EntityHydrator,
     ) {}
@@ -88,6 +91,16 @@ export class TestAdminPluginResolver {
         });
         return order?.lines.map(line => line.quantity);
     }
+
+    // Test case for https://github.com/vendure-ecommerce/vendure/issues/1284
+    @Query()
+    async hydrateChannel(@Ctx() ctx: RequestContext, @Args() args: { id: ID }) {
+        const channel = await this.channelService.findOne(ctx, args.id);
+        await this.entityHydrator.hydrate(ctx, channel!, {
+            relations: ['customFields.thumb'],
+        });
+        return channel;
+    }
 }
 
 @VendurePlugin({
@@ -101,8 +114,13 @@ export class TestAdminPluginResolver {
                 hydrateProductVariant(id: ID!): JSON
                 hydrateOrder(id: ID!): JSON
                 hydrateOrderReturnQuantities(id: ID!): JSON
+                hydrateChannel(id: ID!): JSON
             }
         `,
     },
+    configuration: config => {
+        config.customFields.Channel.push({ name: 'thumb', type: 'relation', entity: Asset, nullable: true });
+        return config;
+    },
 })
 export class HydrationTestPlugin {}

+ 1 - 0
packages/core/src/common/types/entity-relation-paths.ts

@@ -21,6 +21,7 @@ import { VendureEntity } from '../../entity/base/base.entity';
  * @docsCategory Common
  */
 export type EntityRelationPaths<T extends VendureEntity> =
+    | `customFields.${string}`
     | PathsToStringProps1<T>
     | Join<PathsToStringProps2<T>, '.'>
     | TripleDotPath;

+ 13 - 2
packages/core/src/service/helpers/entity-hydrator/entity-hydrator.service.ts

@@ -22,7 +22,7 @@ import { HydrateOptions } from './entity-hydrator-types';
  *
  * @example
  * ```TypeScript
- * const product = this.productVariantService
+ * const product = await this.productVariantService
  *   .getProductForVariant(ctx, variantId);
  *
  * await this.entityHydrator
@@ -37,6 +37,17 @@ import { HydrateOptions } from './entity-hydrator-types';
  * options is used (see {@link HydrateOptions}), any related ProductVariant will have the correct
  * Channel-specific prices applied to them.
  *
+ * Custom field relations may also be hydrated:
+ *
+ * @example
+ * ```TypeScript
+ * const customer = await this.customerService
+ *   .findOne(ctx, id);
+ *
+ * await this.entityHydrator
+ *   .hydrate(ctx, customer, { relations: ['customFields.avatar' ]});
+ * ```
+ *
  * @docsCategory data-access
  * @since 1.3.0
  */
@@ -156,7 +167,7 @@ export class EntityHydrator {
         const missingRelations: string[] = [];
         for (const relation of options.relations.slice().sort()) {
             if (typeof relation === 'string') {
-                const parts = relation.split('.');
+                const parts = !relation.startsWith('customFields') ? relation.split('.') : [relation];
                 let entity: Record<string, any> | undefined = target;
                 const path = [];
                 for (const part of parts) {

+ 10 - 1
packages/dev-server/test-plugins/entity-hydrate-plugin.ts

@@ -1,6 +1,7 @@
 /* tslint:disable:no-non-null-assertion */
 import { Args, Query, Resolver } from '@nestjs/graphql';
 import {
+    Asset,
     Ctx,
     EntityHydrator,
     ID,
@@ -28,7 +29,7 @@ class TestResolver {
             relations: ['featuredAsset'],
         });
         await this.entityHydrator.hydrate(ctx, product!, {
-            relations: ['facetValues.facet', 'variants.options', 'assets'],
+            relations: ['facetValues.facet', 'customFields.thumb'],
         });
         return product;
     }
@@ -45,5 +46,13 @@ class TestResolver {
         `,
         resolvers: [TestResolver],
     },
+    configuration: config => {
+        config.customFields.Product.push({
+            name: 'thumb',
+            type: 'relation',
+            entity: Asset,
+        });
+        return config;
+    },
 })
 export class EntityHydratePlugin {}