Selaa lähdekoodia

feat(server): Add descendantFacetValues property to ProductCategory type

Michael Bromley 7 vuotta sitten
vanhempi
sitoutus
4452220622

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
schema.json


+ 52 - 0
server/e2e/product-category.e2e-spec.ts

@@ -2,6 +2,7 @@ import gql from 'graphql-tag';
 import {
     CreateProductCategory,
     GetAssetList,
+    GetProductCategory,
     LanguageCode,
     MoveProductCategory,
     ProductCategory,
@@ -12,6 +13,7 @@ import { ROOT_CATEGORY_NAME } from 'shared/shared-constants';
 import {
     CREATE_PRODUCT_CATEGORY,
     GET_ASSET_LIST,
+    GET_PRODUCT_CATEGORY,
     MOVE_PRODUCT_CATEGORY,
     UPDATE_PRODUCT_CATEGORY,
 } from '../../admin-ui/src/app/data/definitions/product-definitions';
@@ -69,6 +71,7 @@ describe('ProductCategory resolver', () => {
                 input: {
                     parentId: electronicsCategory.id,
                     translations: [{ languageCode: LanguageCode.en, name: 'Laptops', description: '' }],
+                    facetValueIds: ['T_2'],
                 },
             });
             laptopsCategory = result.createProductCategory;
@@ -83,6 +86,7 @@ describe('ProductCategory resolver', () => {
                 input: {
                     parentId: laptopsCategory.id,
                     translations: [{ languageCode: LanguageCode.en, name: 'Apple', description: '' }],
+                    facetValueIds: ['T_3', 'T_4'],
                 },
             });
             appleCategory = result.createProductCategory;
@@ -90,6 +94,42 @@ describe('ProductCategory resolver', () => {
         });
     });
 
+    describe('productCategory query', () => {
+        it('returns a category', async () => {
+            const result = await client.query<GetProductCategory.Query, GetProductCategory.Variables>(
+                GET_PRODUCT_CATEGORY,
+                { id: laptopsCategory.id },
+            );
+            if (!result.productCategory) {
+                fail(`did not return the category`);
+                return;
+            }
+            expect(result.productCategory.id).toBe(laptopsCategory.id);
+        });
+
+        it('resolves descendantFacetValues 1 level deep', async () => {
+            const result = await client.query(GET_DECENDANT_FACET_VALUES, { id: laptopsCategory.id });
+            if (!result.productCategory) {
+                fail(`did not return the category`);
+                return;
+            }
+            expect(result.productCategory.descendantFacetValues.map(v => v.id)).toEqual(['T_3', 'T_4']);
+        });
+
+        it('resolves descendantFacetValues 2 levels deep', async () => {
+            const result = await client.query(GET_DECENDANT_FACET_VALUES, { id: electronicsCategory.id });
+            if (!result.productCategory) {
+                fail(`did not return the category`);
+                return;
+            }
+            expect(result.productCategory.descendantFacetValues.map(v => v.id)).toEqual([
+                'T_2',
+                'T_3',
+                'T_4',
+            ]);
+        });
+    });
+
     describe('updateProductCategory', () => {
         it('updates the details', async () => {
             const result = await client.query<
@@ -238,3 +278,15 @@ const GET_CATEGORIES = gql`
         }
     }
 `;
+
+const GET_DECENDANT_FACET_VALUES = gql`
+    query GetDescendantFacetValues($id: ID!) {
+        productCategory(id: $id) {
+            id
+            descendantFacetValues {
+                id
+                name
+            }
+        }
+    }
+`;

+ 19 - 2
server/src/api/resolvers/product-category.resolver.ts

@@ -1,4 +1,4 @@
-import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
+import { Args, Mutation, Parent, Query, ResolveProperty, Resolver } from '@nestjs/graphql';
 import {
     CreateProductCategoryMutationArgs,
     MoveProductCategoryMutationArgs,
@@ -10,8 +10,11 @@ import {
 import { PaginatedList } from 'shared/shared-types';
 
 import { Translated } from '../../common/types/locale-types';
+import { FacetValue } from '../../entity/facet-value/facet-value.entity';
 import { ProductCategory } from '../../entity/product-category/product-category.entity';
+import { FacetValueService } from '../../service/services/facet-value.service';
 import { ProductCategoryService } from '../../service/services/product-category.service';
+import { IdCodecService } from '../common/id-codec.service';
 import { RequestContext } from '../common/request-context';
 import { Allow } from '../decorators/allow.decorator';
 import { Decode } from '../decorators/decode.decorator';
@@ -19,7 +22,11 @@ import { Ctx } from '../decorators/request-context.decorator';
 
 @Resolver('ProductCategory')
 export class ProductCategoryResolver {
-    constructor(private productCategoryService: ProductCategoryService) {}
+    constructor(
+        private productCategoryService: ProductCategoryService,
+        private facetValueService: FacetValueService,
+        private idCodecService: IdCodecService,
+    ) {}
 
     @Query()
     @Allow(Permission.ReadCatalog, Permission.Public)
@@ -39,6 +46,16 @@ export class ProductCategoryResolver {
         return this.productCategoryService.findOne(ctx, args.id);
     }
 
+    @ResolveProperty()
+    async descendantFacetValues(
+        @Ctx() ctx: RequestContext,
+        @Parent() category: ProductCategory,
+    ): Promise<Array<Translated<FacetValue>>> {
+        const categoryId = this.idCodecService.decode(category.id);
+        const descendants = await this.productCategoryService.getDescendants(ctx, categoryId);
+        return this.facetValueService.findByCategoryIds(ctx, descendants.map(d => d.id));
+    }
+
     @Mutation()
     @Allow(Permission.CreateCatalog)
     @Decode('assetIds', 'featuredAssetId', 'parentId', 'facetValueIds')

+ 0 - 1
server/src/api/resolvers/product.resolver.ts

@@ -1,7 +1,6 @@
 import { Args, Mutation, Parent, Query, ResolveProperty, Resolver } from '@nestjs/graphql';
 import {
     AddOptionGroupToProductMutationArgs,
-    ApplyFacetValuesToProductVariantsMutationArgs,
     CreateProductMutationArgs,
     GenerateVariantsForProductMutationArgs,
     Permission,

+ 1 - 0
server/src/entity/product-category/product-category.graphql

@@ -11,6 +11,7 @@ type ProductCategory implements Node {
     parent: ProductCategory!
     children: [ProductCategory!]
     facetValues: [FacetValue!]!
+    descendantFacetValues: [FacetValue!]!
     translations: [ProductCategoryTranslation!]!
 }
 

+ 17 - 0
server/src/service/services/facet-value.service.ts

@@ -16,6 +16,7 @@ import { FacetValueTranslation } from '../../entity/facet-value/facet-value-tran
 import { FacetValue } from '../../entity/facet-value/facet-value.entity';
 import { Facet } from '../../entity/facet/facet.entity';
 
+import { RequestContext } from '../../api/common/request-context';
 import { TranslatableSaver } from '../helpers/translatable-saver/translatable-saver';
 import { translateDeep } from '../helpers/utils/translate-entity';
 
@@ -46,6 +47,22 @@ export class FacetValueService {
         return this.connection.getRepository(FacetValue).findByIds(ids, { relations: ['facet'] });
     }
 
+    async findByCategoryIds(ctx: RequestContext, ids: ID[]): Promise<Array<Translated<FacetValue>>> {
+        const facetValues = await this.connection
+            .getRepository(FacetValue)
+            .createQueryBuilder('facetValue')
+            .leftJoinAndSelect(
+                'product_category_facet_values_facet_value',
+                'joinTable',
+                'joinTable.facetValueId = facetValue.id',
+            )
+            .where('joinTable.productCategoryId IN (:...ids)', { ids })
+            .getMany();
+        return this.findByIds(facetValues.map(v => v.id)).then(values =>
+            values.map(value => translateDeep(value, ctx.languageCode)),
+        );
+    }
+
     async create(
         facet: Facet,
         input: CreateFacetValueInput | CreateFacetValueWithFacetInput,

+ 11 - 0
server/src/service/services/product-category.service.ts

@@ -86,6 +86,17 @@ export class ProductCategoryService {
         }
     }
 
+    /**
+     * Returns the descendants of a ProductCategory as a flat array.
+     */
+    async getDescendants(ctx: RequestContext, rootId: ID): Promise<Array<Translated<ProductCategory>>> {
+        const descendants = await this.connection
+            .getTreeRepository(ProductCategory)
+            .findDescendants(new ProductCategory({ id: rootId }));
+        // Note: the result includes the root category itself, so we filter this out.
+        return descendants.filter(d => d.id !== rootId).map(d => translateDeep(d, ctx.languageCode));
+    }
+
     async create(
         ctx: RequestContext,
         input: CreateProductCategoryInput,

+ 7 - 0
shared/generated-types.ts

@@ -491,6 +491,7 @@ export interface ProductCategory extends Node {
     parent: ProductCategory;
     children?: ProductCategory[] | null;
     facetValues: FacetValue[];
+    descendantFacetValues: FacetValue[];
     translations: ProductCategoryTranslation[];
     customFields?: Json | null;
 }
@@ -3507,6 +3508,7 @@ export namespace ProductCategoryResolvers {
         parent?: ParentResolver<ProductCategory, any, Context>;
         children?: ChildrenResolver<ProductCategory[] | null, any, Context>;
         facetValues?: FacetValuesResolver<FacetValue[], any, Context>;
+        descendantFacetValues?: DescendantFacetValuesResolver<FacetValue[], any, Context>;
         translations?: TranslationsResolver<ProductCategoryTranslation[], any, Context>;
         customFields?: CustomFieldsResolver<Json | null, any, Context>;
     }
@@ -3543,6 +3545,11 @@ export namespace ProductCategoryResolvers {
         Parent,
         Context
     >;
+    export type DescendantFacetValuesResolver<R = FacetValue[], Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
     export type TranslationsResolver<
         R = ProductCategoryTranslation[],
         Parent = any,

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä