Просмотр исходного кода

fix(core): Merge relations in customFields correctly (#2062)

Jonas Osburg 2 лет назад
Родитель
Сommit
aeb06e3bba

+ 55 - 4
packages/core/e2e/fixtures/test-plugins/list-query-plugin.ts

@@ -6,6 +6,7 @@ import {
     Ctx,
     Customer,
     CustomerService,
+    HasCustomFields,
     ListQueryBuilder,
     LocaleString,
     Order,
@@ -21,12 +22,30 @@ import {
     VendurePlugin,
 } from '@vendure/core';
 import gql from 'graphql-tag';
-import { Column, Entity, JoinColumn, JoinTable, ManyToOne, OneToMany, OneToOne } from 'typeorm';
+import { Column, Entity, JoinColumn, JoinTable, ManyToOne, OneToMany, OneToOne, Relation } from 'typeorm';
 
 import { Calculated } from '../../../src/common/calculated-decorator';
 
 @Entity()
-export class TestEntity extends VendureEntity implements Translatable {
+export class CustomFieldRelationTestEntity extends VendureEntity {
+    constructor(input: Partial<CustomFieldRelationTestEntity>) {
+        super(input);
+    }
+
+    @Column()
+    data: string;
+
+    @ManyToOne(() => TestEntity)
+    parent: Relation<TestEntity>;
+}
+
+class TestEntityCustomFields {
+    @OneToMany(() => CustomFieldRelationTestEntity, child => child.parent)
+    relation: Relation<CustomFieldRelationTestEntity[]>;
+}
+
+@Entity()
+export class TestEntity extends VendureEntity implements Translatable, HasCustomFields {
     constructor(input: Partial<TestEntity>) {
         super(input);
     }
@@ -106,6 +125,9 @@ export class TestEntity extends VendureEntity implements Translatable {
 
     @Column({ nullable: true })
     nullableDate: Date;
+
+    @Column(() => TestEntityCustomFields)
+    customFields: TestEntityCustomFields;
 }
 
 @Entity()
@@ -120,6 +142,8 @@ export class TestEntityTranslation extends VendureEntity implements Translation<
 
     @ManyToOne(type => TestEntity, base => base.translations)
     base: TestEntity;
+
+    customFields: {};
 }
 
 @Entity()
@@ -147,7 +171,7 @@ export class ListQueryResolver {
         return this.listQueryBuilder
             .build(TestEntity, args.options, {
                 ctx,
-                relations: ['orderRelation', 'orderRelation.customer'],
+                relations: ['orderRelation', 'orderRelation.customer', 'customFields.relation'],
                 customPropertyMap: {
                     customerLastName: 'orderRelation.customer.lastName',
                 },
@@ -193,6 +217,15 @@ const apiExtensions = gql`
         name: String!
     }
 
+    type CustomFieldRelationTestEntity implements Node {
+        id: ID!
+        data: String!
+    }
+
+    type TestEntityCustomFields {
+        relation: [CustomFieldRelationTestEntity!]!
+    }
+
     type TestEntity implements Node {
         id: ID!
         createdAt: DateTime!
@@ -213,6 +246,7 @@ const apiExtensions = gql`
         nullableNumber: Int
         nullableId: ID
         nullableDate: DateTime
+        customFields: TestEntityCustomFields!
     }
 
     type TestEntityList implements PaginatedList {
@@ -238,7 +272,7 @@ const apiExtensions = gql`
 
 @VendurePlugin({
     imports: [PluginCommonModule],
-    entities: [TestEntity, TestEntityPrice, TestEntityTranslation],
+    entities: [TestEntity, TestEntityPrice, TestEntityTranslation, CustomFieldRelationTestEntity],
     adminApiExtensions: {
         schema: apiExtensions,
         resolvers: [ListQueryResolver],
@@ -334,6 +368,12 @@ export class ListQueryPlugin implements OnApplicationBootstrap {
                 F: { [LanguageCode.de]: 'baum' },
             };
 
+            const nestedData: Record<string, Array<{ data: string }>> = {
+                A: [{ data: 'A' }],
+                B: [{ data: 'B' }],
+                C: [{ data: 'C' }],
+            };
+
             for (const testEntity of testEntities) {
                 await this.connection.getRepository(TestEntityPrice).save([
                     new TestEntityPrice({
@@ -360,6 +400,17 @@ export class ListQueryPlugin implements OnApplicationBootstrap {
                         );
                     }
                 }
+
+                if (nestedData[testEntity.label]) {
+                    for (const nestedContent of nestedData[testEntity.label]) {
+                        await this.connection.getRepository(CustomFieldRelationTestEntity).save(
+                            new CustomFieldRelationTestEntity({
+                                parent: testEntity,
+                                data: nestedContent.data,
+                            }),
+                        );
+                    }
+                }
             }
         } else {
             const testEntities = await this.connection.getRepository(TestEntity).find();

+ 39 - 0
packages/core/e2e/list-query-builder.e2e-spec.ts

@@ -1246,6 +1246,28 @@ describe('ListQueryBuilder', () => {
             ]);
         });
     });
+
+    describe('relations in customFields', () => {
+        it('should resolve relations in customFields successfully', async () => {
+            const { testEntities } = await shopClient.query(GET_LIST_WITH_CUSTOM_FIELD_RELATION, {
+                options: {
+                    filter: {
+                        label: { eq: 'A' },
+                    },
+                },
+            });
+
+            expect(testEntities.items).toEqual([
+                {
+                    id: 'T_1',
+                    label: 'A',
+                    customFields: {
+                        relation: [{ id: 'T_1', data: 'A' }],
+                    },
+                },
+            ]);
+        });
+    });
 });
 
 const GET_LIST = gql`
@@ -1311,3 +1333,20 @@ const GET_ARRAY_LIST = gql`
         }
     }
 `;
+
+const GET_LIST_WITH_CUSTOM_FIELD_RELATION = gql`
+    query GetTestWithCustomFieldRelation($options: TestEntityListOptions) {
+        testEntities(options: $options) {
+            items {
+                id
+                label
+                customFields {
+                    relation {
+                        id
+                        data
+                    }
+                }
+            }
+        }
+    }
+`;

+ 24 - 1
packages/core/src/service/helpers/list-query-builder/list-query-builder.ts

@@ -492,12 +492,35 @@ export class ListQueryBuilder implements OnApplicationBootstrap {
         for (const entry of entitiesIdsWithRelations) {
             const finalEntity = entityMap.get(entry.entity.id);
             if (finalEntity) {
-                finalEntity[entry.relation] = entry.entity[entry.relation];
+                this.assignDeep(entry.relation, entry.entity, finalEntity);
             }
         }
         return Array.from(entityMap.values());
     }
 
+    private assignDeep<T>(relation: string | keyof T, source: T, target: T) {
+        if (typeof relation === 'string') {
+            const parts = relation.split('.');
+            let resolvedTarget: any = target;
+            let resolvedSource: any = source;
+
+            for (const part of parts.slice(0, parts.length - 1)) {
+                if (!resolvedTarget[part]) {
+                    resolvedTarget[part] = {};
+                }
+                if (!resolvedSource[part]) {
+                    return;
+                }
+                resolvedTarget = resolvedTarget[part];
+                resolvedSource = resolvedSource[part];
+            }
+
+            resolvedTarget[parts[parts.length - 1]] = resolvedSource[parts[parts.length - 1]];
+        } else {
+            target[relation] = source[relation];
+        }
+    }
+
     /**
      * If a customPropertyMap is provided, we need to take the path provided and convert it to the actual
      * relation aliases being used by the SelectQueryBuilder.