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

fix(core): Correctly resolve translatable custom field relations

Michael Bromley 3 лет назад
Родитель
Сommit
354932c07a

+ 79 - 65
packages/core/e2e/custom-field-relations.e2e-spec.ts

@@ -93,6 +93,7 @@ customFieldConfig.Product?.push(
 );
 
 const testResolverSpy = jest.fn();
+
 @Resolver()
 class TestResolver1636 {
     constructor(private connection: TransactionalConnection) {}
@@ -185,6 +186,72 @@ describe('Custom field relations', () => {
 
     describe('special data resolution', () => {
         let productId: string;
+        const productCustomFieldRelationsSelection = `
+        id
+        customFields {
+            cfCollection {
+                languageCode
+                name
+            }
+            cfCountry {
+                languageCode
+                name
+            }
+            cfFacetValue {
+                languageCode
+                name
+            }
+            cfFacet {
+                languageCode
+                name
+            }
+            cfProductOptionGroup {
+                languageCode
+                name
+            }
+            cfProductOption {
+                languageCode
+                name
+            }
+            cfProductVariant {
+                languageCode
+                name
+            }
+            cfProduct {
+                languageCode
+                name
+            }
+            cfShippingMethod {
+                name
+            }
+        }`;
+
+        function assertTranslatableCustomFieldValues(product: { customFields: any }) {
+            expect(product.customFields.cfCollection).toEqual({
+                languageCode: 'en',
+                name: '__root_collection__',
+            });
+            expect(product.customFields.cfCountry).toEqual({ languageCode: 'en', name: 'Australia' });
+            expect(product.customFields.cfFacetValue).toEqual({
+                languageCode: 'en',
+                name: 'electronics',
+            });
+            expect(product.customFields.cfFacet).toEqual({ languageCode: 'en', name: 'category' });
+            expect(product.customFields.cfProductOptionGroup).toEqual({
+                languageCode: 'en',
+                name: 'screen size',
+            });
+            expect(product.customFields.cfProductOption).toEqual({
+                languageCode: 'en',
+                name: '13 inch',
+            });
+            expect(product.customFields.cfProductVariant).toEqual({
+                languageCode: 'en',
+                name: 'Laptop 13 inch 8GB',
+            });
+            expect(product.customFields.cfProduct).toEqual({ languageCode: 'en', name: 'Laptop' });
+            expect(product.customFields.cfShippingMethod).toEqual({ name: 'Standard Shipping' });
+        }
 
         it('translatable entities get translated', async () => {
             const { createProduct } = await adminClient.query(gql`
@@ -211,75 +278,22 @@ describe('Custom field relations', () => {
                                 cfShippingMethodId: "T_1"
                             }
                         }
-                    ) {
-                        id
-                        customFields {
-                            cfCollection {
-                                languageCode
-                                name
-                            }
-                            cfCountry {
-                                languageCode
-                                name
-                            }
-                            cfFacetValue {
-                                languageCode
-                                name
-                            }
-                            cfFacet {
-                                languageCode
-                                name
-                            }
-                            cfProductOptionGroup {
-                                languageCode
-                                name
-                            }
-                            cfProductOption {
-                                languageCode
-                                name
-                            }
-                            cfProductVariant {
-                                languageCode
-                                name
-                            }
-                            cfProduct {
-                                languageCode
-                                name
-                            }
-                            cfShippingMethod {
-                                name
-                            }
-                        }
-                    }
+                    ) { ${productCustomFieldRelationsSelection} }
                 }
             `);
 
             productId = createProduct.id;
+            assertTranslatableCustomFieldValues(createProduct);
+        });
 
-            expect(createProduct.customFields.cfCollection).toEqual({
-                languageCode: 'en',
-                name: '__root_collection__',
-            });
-            expect(createProduct.customFields.cfCountry).toEqual({ languageCode: 'en', name: 'Australia' });
-            expect(createProduct.customFields.cfFacetValue).toEqual({
-                languageCode: 'en',
-                name: 'electronics',
-            });
-            expect(createProduct.customFields.cfFacet).toEqual({ languageCode: 'en', name: 'category' });
-            expect(createProduct.customFields.cfProductOptionGroup).toEqual({
-                languageCode: 'en',
-                name: 'screen size',
-            });
-            expect(createProduct.customFields.cfProductOption).toEqual({
-                languageCode: 'en',
-                name: '13 inch',
-            });
-            expect(createProduct.customFields.cfProductVariant).toEqual({
-                languageCode: 'en',
-                name: 'Laptop 13 inch 8GB',
-            });
-            expect(createProduct.customFields.cfProduct).toEqual({ languageCode: 'en', name: 'Laptop' });
-            expect(createProduct.customFields.cfShippingMethod).toEqual({ name: 'Standard Shipping' });
+        it('translatable entities get translated on findOneInChannel', async () => {
+            const { product } = await adminClient.query(gql`
+                query {
+                    product(id: "${productId}") { ${productCustomFieldRelationsSelection} }
+                }
+            `);
+
+            assertTranslatableCustomFieldValues(product);
         });
 
         it('ProductVariant prices get resolved', async () => {
@@ -979,7 +993,7 @@ describe('Custom field relations', () => {
                             ${customFieldsSelection}
                         }
                     }
-                            `);
+                `);
 
                 assertCustomFieldIds(updateAsset.customFields, 'T_2', ['T_3', 'T_4']);
             });

+ 2 - 0
packages/core/src/api/api-internal-modules.ts

@@ -72,6 +72,7 @@ import {
 import { RefundEntityResolver } from './resolvers/entity/refund-entity.resolver';
 import { RoleEntityResolver } from './resolvers/entity/role-entity.resolver';
 import { ShippingLineEntityResolver } from './resolvers/entity/shipping-line-entity.resolver';
+import { ShippingMethodEntityResolver } from './resolvers/entity/shipping-method-entity.resolver';
 import { TaxRateEntityResolver } from './resolvers/entity/tax-rate-entity.resolver';
 import { UserEntityResolver } from './resolvers/entity/user-entity.resolver';
 import { ShopAuthResolver } from './resolvers/shop/shop-auth.resolver';
@@ -136,6 +137,7 @@ export const entityResolvers = [
     ShippingLineEntityResolver,
     UserEntityResolver,
     TaxRateEntityResolver,
+    ShippingMethodEntityResolver,
 ];
 
 export const adminEntityResolvers = [

+ 5 - 0
packages/core/src/api/resolvers/entity/collection-entity.resolver.ts

@@ -39,6 +39,11 @@ export class CollectionEntityResolver {
         return this.localeStringHydrator.hydrateLocaleStringField(ctx, collection, 'description');
     }
 
+    @ResolveField()
+    languageCode(@Ctx() ctx: RequestContext, @Parent() collection: Collection): Promise<string> {
+        return this.localeStringHydrator.hydrateLocaleStringField(ctx, collection, 'languageCode');
+    }
+
     @ResolveField()
     async productVariants(
         @Ctx() ctx: RequestContext,

+ 5 - 0
packages/core/src/api/resolvers/entity/country-entity.resolver.ts

@@ -13,4 +13,9 @@ export class CountryEntityResolver {
     name(@Ctx() ctx: RequestContext, @Parent() country: Country): Promise<string> {
         return this.localeStringHydrator.hydrateLocaleStringField(ctx, country, 'name');
     }
+
+    @ResolveField()
+    languageCode(@Ctx() ctx: RequestContext, @Parent() country: Country): Promise<string> {
+        return this.localeStringHydrator.hydrateLocaleStringField(ctx, country, 'languageCode');
+    }
 }

+ 5 - 0
packages/core/src/api/resolvers/entity/facet-entity.resolver.ts

@@ -21,6 +21,11 @@ export class FacetEntityResolver {
         return this.localeStringHydrator.hydrateLocaleStringField(ctx, facetValue, 'name');
     }
 
+    @ResolveField()
+    languageCode(@Ctx() ctx: RequestContext, @Parent() facetValue: FacetValue): Promise<string> {
+        return this.localeStringHydrator.hydrateLocaleStringField(ctx, facetValue, 'languageCode');
+    }
+
     @ResolveField()
     async values(@Ctx() ctx: RequestContext, @Parent() facet: Facet): Promise<FacetValue[]> {
         if (facet.values) {

+ 5 - 0
packages/core/src/api/resolvers/entity/facet-value-entity.resolver.ts

@@ -21,6 +21,11 @@ export class FacetValueEntityResolver {
         return this.localeStringHydrator.hydrateLocaleStringField(ctx, facetValue, 'name');
     }
 
+    @ResolveField()
+    languageCode(@Ctx() ctx: RequestContext, @Parent() facetValue: FacetValue): Promise<string> {
+        return this.localeStringHydrator.hydrateLocaleStringField(ctx, facetValue, 'languageCode');
+    }
+
     @ResolveField()
     async facet(@Ctx() ctx: RequestContext, @Parent() facetValue: FacetValue): Promise<Facet | undefined> {
         if (facetValue.facet) {

+ 5 - 0
packages/core/src/api/resolvers/entity/product-entity.resolver.ts

@@ -50,6 +50,11 @@ export class ProductEntityResolver {
         return this.localeStringHydrator.hydrateLocaleStringField(ctx, product, 'description');
     }
 
+    @ResolveField()
+    languageCode(@Ctx() ctx: RequestContext, @Parent() product: Product): Promise<string> {
+        return this.localeStringHydrator.hydrateLocaleStringField(ctx, product, 'languageCode');
+    }
+
     @ResolveField()
     async variants(
         @Ctx() ctx: RequestContext,

+ 5 - 0
packages/core/src/api/resolvers/entity/product-option-entity.resolver.ts

@@ -25,6 +25,11 @@ export class ProductOptionEntityResolver {
         return this.localeStringHydrator.hydrateLocaleStringField(ctx, productOption, 'name');
     }
 
+    @ResolveField()
+    languageCode(@Ctx() ctx: RequestContext, @Parent() productOption: ProductOption): Promise<string> {
+        return this.localeStringHydrator.hydrateLocaleStringField(ctx, productOption, 'languageCode');
+    }
+
     @ResolveField()
     @Allow(Permission.ReadCatalog, Permission.Public, Permission.ReadProduct)
     async group(

+ 5 - 0
packages/core/src/api/resolvers/entity/product-option-group-entity.resolver.ts

@@ -22,6 +22,11 @@ export class ProductOptionGroupEntityResolver {
         return this.localeStringHydrator.hydrateLocaleStringField(ctx, optionGroup, 'name');
     }
 
+    @ResolveField()
+    languageCode(@Ctx() ctx: RequestContext, @Parent() optionGroup: ProductOptionGroup): Promise<string> {
+        return this.localeStringHydrator.hydrateLocaleStringField(ctx, optionGroup, 'languageCode');
+    }
+
     @ResolveField()
     @Allow(Permission.ReadCatalog, Permission.Public, Permission.ReadProduct)
     async options(

+ 8 - 0
packages/core/src/api/resolvers/entity/product-variant-entity.resolver.ts

@@ -32,6 +32,14 @@ export class ProductVariantEntityResolver {
         return this.localeStringHydrator.hydrateLocaleStringField(ctx, productVariant, 'name');
     }
 
+    @ResolveField()
+    async languageCode(
+        @Ctx() ctx: RequestContext,
+        @Parent() productVariant: ProductVariant,
+    ): Promise<string> {
+        return this.localeStringHydrator.hydrateLocaleStringField(ctx, productVariant, 'languageCode');
+    }
+
     @ResolveField()
     async price(@Ctx() ctx: RequestContext, @Parent() productVariant: ProductVariant): Promise<number> {
         return this.productVariantService.hydratePriceFields(ctx, productVariant, 'price');

+ 21 - 0
packages/core/src/api/resolvers/entity/shipping-method-entity.resolver.ts

@@ -0,0 +1,21 @@
+import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
+
+import { ShippingMethod } from '../../../entity/index';
+import { LocaleStringHydrator } from '../../../service/helpers/locale-string-hydrator/locale-string-hydrator';
+import { RequestContext } from '../../common/request-context';
+import { Ctx } from '../../decorators/request-context.decorator';
+
+@Resolver('ShippingMethod')
+export class ShippingMethodEntityResolver {
+    constructor(private localeStringHydrator: LocaleStringHydrator) {}
+
+    @ResolveField()
+    name(@Ctx() ctx: RequestContext, @Parent() shippingMethod: ShippingMethod): Promise<string> {
+        return this.localeStringHydrator.hydrateLocaleStringField(ctx, shippingMethod, 'name');
+    }
+
+    @ResolveField()
+    description(@Ctx() ctx: RequestContext, @Parent() shippingMethod: ShippingMethod): Promise<string> {
+        return this.localeStringHydrator.hydrateLocaleStringField(ctx, shippingMethod, 'description');
+    }
+}

+ 9 - 3
packages/core/src/service/helpers/locale-string-hydrator/locale-string-hydrator.ts

@@ -1,4 +1,5 @@
 import { Injectable } from '@nestjs/common';
+import { LanguageCode } from '@vendure/common/lib/generated-types';
 
 import { RequestContext } from '../../../api/common/request-context';
 import { RequestContextCacheService } from '../../../cache/request-context-cache.service';
@@ -19,10 +20,10 @@ export class LocaleStringHydrator {
         private requestCache: RequestContextCacheService,
     ) {}
 
-    async hydrateLocaleStringField<T extends VendureEntity & Translatable>(
+    async hydrateLocaleStringField<T extends VendureEntity & Translatable & { languageCode?: LanguageCode }>(
         ctx: RequestContext,
         entity: T,
-        fieldName: TranslatableKeys<T>,
+        fieldName: TranslatableKeys<T> | 'languageCode',
     ): Promise<string> {
         if (entity[fieldName]) {
             // Already hydrated, so return the value
@@ -61,7 +62,12 @@ export class LocaleStringHydrator {
         if (entity.translations.length) {
             const translated = translateDeep(entity, ctx.languageCode);
             for (const localeStringProp of Object.keys(entity.translations[0])) {
-                if (localeStringProp === 'base' || localeStringProp === 'languageCode') {
+                if (
+                    localeStringProp === 'base' ||
+                    localeStringProp === 'id' ||
+                    localeStringProp === 'createdAt' ||
+                    localeStringProp === 'updatedAt'
+                ) {
                     continue;
                 }
                 if (localeStringProp === 'customFields') {