Browse Source

fix(core): Fix loading eager custom fields for `order` query

Relates to #1664
Michael Bromley 3 years ago
parent
commit
93b8601934

+ 24 - 0
packages/core/e2e/custom-field-relations.e2e-spec.ts

@@ -645,6 +645,30 @@ describe('Custom field relations', () => {
 
                 assertCustomFieldIds(setOrderCustomFields.customFields, 'T_1', ['T_1', 'T_2']);
             });
+
+            // https://github.com/vendure-ecommerce/vendure/issues/1664#issuecomment-1320872627
+            it('admin order query with eager-loaded custom field relation', async () => {
+                const { order } = await adminClient.query(gql`
+                    query {
+                        order(id: 1) {
+                            id
+                            customFields {
+                                productOwner {
+                                    id
+                                }
+                            }
+                        }
+                    }
+                `);
+
+                // we're just making sure it does not throw here.
+                expect(order).toEqual({
+                    customFields: {
+                        productOwner: null,
+                    },
+                    id: 'T_1',
+                });
+            });
         });
 
         describe('OrderLine entity', () => {

+ 10 - 0
packages/core/e2e/fixtures/test-plugins/issue-1636-1664/issue-1636-1664-plugin.ts

@@ -140,6 +140,16 @@ const profileType = gql`
             eager: true, // needs to be eager to enable indexing of profile attributes like name, etc.
         });
 
+        config.customFields.Order.push({
+            name: 'productOwner',
+            nullable: true,
+            type: 'relation',
+            entity: User,
+            public: false,
+            eager: true,
+            readonly: true,
+        });
+
         return config;
     },
 })

+ 3 - 3
packages/core/src/connection/remove-custom-fields-with-eager-relations.ts

@@ -35,10 +35,10 @@ import { Logger } from '../config/index';
  * TODO: Ideally create a minimal reproduction case and report in the TypeORM repo for an upstream fix.
  */
 
-export function removeCustomFieldsWithEagerRelations(
+export function removeCustomFieldsWithEagerRelations<T extends string>(
     qb: SelectQueryBuilder<any>,
-    relations: string[] = [],
-): string[] {
+    relations: T[] = [],
+): T[] {
     let resultingRelations = relations;
     const mainAlias = qb.expressionMap.mainAlias;
     const customFieldsMetadata = mainAlias?.metadata.embeddeds.find(

+ 3 - 1
packages/core/src/service/services/order.service.ts

@@ -77,6 +77,7 @@ import { grossPriceOf, netPriceOf } from '../../common/tax-utils';
 import { ListQueryOptions, PaymentMetadata } from '../../common/types/common-types';
 import { assertFound, idsAreEqual } from '../../common/utils';
 import { ConfigService } from '../../config/config.service';
+import { removeCustomFieldsWithEagerRelations } from '../../connection/remove-custom-fields-with-eager-relations';
 import { TransactionalConnection } from '../../connection/transactional-connection';
 import { Customer } from '../../entity/customer/customer.entity';
 import { Fulfillment } from '../../entity/fulfillment/fulfillment.entity';
@@ -219,7 +220,7 @@ export class OrderService {
         relations?: RelationPaths<Order>,
     ): Promise<Order | undefined> {
         const qb = this.connection.getRepository(ctx, Order).createQueryBuilder('order');
-        const effectiveRelations = relations ?? [
+        let effectiveRelations = relations ?? [
             'channels',
             'customer',
             'customer.user',
@@ -242,6 +243,7 @@ export class OrderService {
         ) {
             effectiveRelations.push('lines.productVariant.taxCategory');
         }
+        effectiveRelations = removeCustomFieldsWithEagerRelations(qb, effectiveRelations);
         FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, {
             relations: effectiveRelations,
         });

+ 0 - 1
packages/core/src/service/services/product.service.ts

@@ -20,7 +20,6 @@ import { ProductOptionInUseError } from '../../common/error/generated-graphql-ad
 import { ListQueryOptions } from '../../common/types/common-types';
 import { Translated } from '../../common/types/locale-types';
 import { assertFound, idsAreEqual } from '../../common/utils';
-import { removeCustomFieldsWithEagerRelations } from '../../connection/remove-custom-fields-with-eager-relations';
 import { TransactionalConnection } from '../../connection/transactional-connection';
 import { Channel } from '../../entity/channel/channel.entity';
 import { FacetValue } from '../../entity/facet-value/facet-value.entity';

+ 74 - 1
packages/dev-server/test-plugins/issue-1664/issue-1664.ts

@@ -1,8 +1,15 @@
 import { OnApplicationBootstrap } from '@nestjs/common';
+import { DEFAULT_CHANNEL_CODE } from '@vendure/common/lib/shared-constants';
 import {
     Asset,
+    Channel,
+    CustomOrderFields,
+    CustomProductFields,
+    Order,
+    OrderService,
     PluginCommonModule,
     Product,
+    RequestContext,
     TransactionalConnection,
     User,
     VendurePlugin,
@@ -12,6 +19,15 @@ import gql from 'graphql-tag';
 import { ProfileAsset } from './profile-asset.entity';
 import { Profile } from './profile.entity';
 
+declare module '@vendure/core' {
+    interface CustomOrderFields {
+        productOwner: User;
+    }
+    interface CustomProductFields {
+        owner: User;
+    }
+}
+
 const schema = gql`
     type Profile implements Node {
         id: ID!
@@ -52,6 +68,16 @@ const schema = gql`
     shopApiExtensions: { schema, resolvers: [] },
     adminApiExtensions: { schema, resolvers: [] },
     configuration: config => {
+        // Order
+        config.customFields.Order.push({
+            name: 'productOwner', // because orders are always c2c (and should be stored redundant in case product get's deleted)
+            nullable: true,
+            type: 'relation',
+            entity: User,
+            public: false,
+            eager: true,
+            readonly: true,
+        });
         config.customFields.Product.push({
             name: 'owner',
             nullable: true,
@@ -76,9 +102,14 @@ const schema = gql`
     },
 })
 export class Test1664Plugin implements OnApplicationBootstrap {
-    constructor(private connection: TransactionalConnection) {}
+    constructor(private connection: TransactionalConnection, private orderService: OrderService) {}
 
     async onApplicationBootstrap() {
+        await this.createDummyProfiles();
+        await this.createDummyOrder();
+    }
+
+    async createDummyProfiles() {
         const profilesCount = await this.connection.rawConnection.getRepository(Profile).count();
         if (0 < profilesCount) {
             return;
@@ -111,4 +142,46 @@ export class Test1664Plugin implements OnApplicationBootstrap {
             await this.connection.rawConnection.getRepository(Product).save(product);
         }
     }
+
+    async createDummyOrder() {
+        const orderCount = await this.connection.rawConnection.getRepository(Order).count();
+        if (0 < orderCount) {
+            return;
+        }
+
+        const defaultChannel = await this.connection.getRepository(Channel).findOne({
+            relations: ['defaultShippingZone', 'defaultTaxZone'],
+            where: {
+                code: DEFAULT_CHANNEL_CODE,
+            },
+        });
+
+        if (!defaultChannel) {
+            throw new Error(`Channel with code ${DEFAULT_CHANNEL_CODE} could not be found.`);
+        }
+
+        const ctx = new RequestContext({
+            apiType: 'shop',
+            authorizedAsOwnerOnly: false,
+            channel: defaultChannel,
+            isAuthorized: true,
+            languageCode: defaultChannel.defaultLanguageCode,
+        });
+
+        // Create order
+        const users = await this.connection.rawConnection.getRepository(User).find();
+        // tslint:disable-next-line:no-non-null-assertion
+        const customer = users[1]!;
+        const created = await this.orderService.create(ctx, customer.id);
+
+        // Add products
+        const products = await this.connection.rawConnection
+            .getRepository(Product)
+            .find({ relations: ['variants'] });
+        const product = products[0];
+        await this.orderService.addItemToOrder(ctx, created.id, product.variants[0].id, 1);
+        // Add the product owner to order
+        const productOwner = product.customFields.owner;
+        await this.orderService.updateCustomFields(ctx, created.id, { productOwner });
+    }
 }