Explorar el Código

feat(core): Add Product.variantList field

Relates to #1110
Michael Bromley hace 4 años
padre
commit
438ac468d7

+ 7 - 0
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts

@@ -3351,7 +3351,10 @@ export type Product = Node & {
     description: Scalars['String'];
     featuredAsset?: Maybe<Asset>;
     assets: Array<Asset>;
+    /** Returns all ProductVariants */
     variants: Array<ProductVariant>;
+    /** Returns a paginated, sortable, filterable list of ProductVariants */
+    variantList: ProductVariantList;
     optionGroups: Array<ProductOptionGroup>;
     facetValues: Array<FacetValue>;
     translations: Array<ProductTranslation>;
@@ -3359,6 +3362,10 @@ export type Product = Node & {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type ProductVariantListArgs = {
+    options?: Maybe<ProductVariantListOptions>;
+};
+
 export type ProductFilterParameter = {
     enabled?: Maybe<BooleanOperators>;
     createdAt?: Maybe<DateOperators>;

+ 7 - 0
packages/common/src/generated-shop-types.ts

@@ -2303,7 +2303,10 @@ export type Product = Node & {
     description: Scalars['String'];
     featuredAsset?: Maybe<Asset>;
     assets: Array<Asset>;
+    /** Returns all ProductVariants */
     variants: Array<ProductVariant>;
+    /** Returns a paginated, sortable, filterable list of ProductVariants */
+    variantList: ProductVariantList;
     optionGroups: Array<ProductOptionGroup>;
     facetValues: Array<FacetValue>;
     translations: Array<ProductTranslation>;
@@ -2311,6 +2314,10 @@ export type Product = Node & {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type ProductVariantListArgs = {
+    options?: Maybe<ProductVariantListOptions>;
+};
+
 export type ProductFilterParameter = {
     createdAt?: Maybe<DateOperators>;
     updatedAt?: Maybe<DateOperators>;

+ 8 - 0
packages/common/src/generated-types.ts

@@ -3526,7 +3526,10 @@ export type Product = Node & {
   description: Scalars['String'];
   featuredAsset?: Maybe<Asset>;
   assets: Array<Asset>;
+  /** Returns all ProductVariants */
   variants: Array<ProductVariant>;
+  /** Returns a paginated, sortable, filterable list of ProductVariants */
+  variantList: ProductVariantList;
   optionGroups: Array<ProductOptionGroup>;
   facetValues: Array<FacetValue>;
   translations: Array<ProductTranslation>;
@@ -3534,6 +3537,11 @@ export type Product = Node & {
   customFields?: Maybe<Scalars['JSON']>;
 };
 
+
+export type ProductVariantListArgs = {
+  options?: Maybe<ProductVariantListOptions>;
+};
+
 export type ProductFilterParameter = {
   enabled?: Maybe<BooleanOperators>;
   createdAt?: Maybe<DateOperators>;

+ 34 - 0
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -3351,7 +3351,10 @@ export type Product = Node & {
     description: Scalars['String'];
     featuredAsset?: Maybe<Asset>;
     assets: Array<Asset>;
+    /** Returns all ProductVariants */
     variants: Array<ProductVariant>;
+    /** Returns a paginated, sortable, filterable list of ProductVariants */
+    variantList: ProductVariantList;
     optionGroups: Array<ProductOptionGroup>;
     facetValues: Array<FacetValue>;
     translations: Array<ProductTranslation>;
@@ -3359,6 +3362,10 @@ export type Product = Node & {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type ProductVariantListArgs = {
+    options?: Maybe<ProductVariantListOptions>;
+};
+
 export type ProductFilterParameter = {
     enabled?: Maybe<BooleanOperators>;
     createdAt?: Maybe<DateOperators>;
@@ -6502,6 +6509,19 @@ export type GetProductVariantListQuery = {
     };
 };
 
+export type GetProductWithVariantListQueryVariables = Exact<{
+    id?: Maybe<Scalars['ID']>;
+    variantListOptions?: Maybe<ProductVariantListOptions>;
+}>;
+
+export type GetProductWithVariantListQuery = {
+    product?: Maybe<
+        Pick<Product, 'id'> & {
+            variantList: Pick<ProductVariantList, 'totalItems'> & { items: Array<ProductVariantFragment> };
+        }
+    >;
+};
+
 export type DeletePromotionMutationVariables = Exact<{
     id: Scalars['ID'];
 }>;
@@ -8824,6 +8844,20 @@ export namespace GetProductVariantList {
     >;
 }
 
+export namespace GetProductWithVariantList {
+    export type Variables = GetProductWithVariantListQueryVariables;
+    export type Query = GetProductWithVariantListQuery;
+    export type Product = NonNullable<GetProductWithVariantListQuery['product']>;
+    export type VariantList = NonNullable<
+        NonNullable<GetProductWithVariantListQuery['product']>['variantList']
+    >;
+    export type Items = NonNullable<
+        NonNullable<
+            NonNullable<NonNullable<GetProductWithVariantListQuery['product']>['variantList']>['items']
+        >[number]
+    >;
+}
+
 export namespace DeletePromotion {
     export type Variables = DeletePromotionMutationVariables;
     export type Mutation = DeletePromotionMutation;

+ 7 - 0
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -2226,7 +2226,10 @@ export type Product = Node & {
     description: Scalars['String'];
     featuredAsset?: Maybe<Asset>;
     assets: Array<Asset>;
+    /** Returns all ProductVariants */
     variants: Array<ProductVariant>;
+    /** Returns a paginated, sortable, filterable list of ProductVariants */
+    variantList: ProductVariantList;
     optionGroups: Array<ProductOptionGroup>;
     facetValues: Array<FacetValue>;
     translations: Array<ProductTranslation>;
@@ -2234,6 +2237,10 @@ export type Product = Node & {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type ProductVariantListArgs = {
+    options?: Maybe<ProductVariantListOptions>;
+};
+
 export type ProductFilterParameter = {
     createdAt?: Maybe<DateOperators>;
     updatedAt?: Maybe<DateOperators>;

+ 128 - 1
packages/core/e2e/product.e2e-spec.ts

@@ -8,7 +8,7 @@ import path from 'path';
 import { initialData } from '../../../e2e-common/e2e-initial-data';
 import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
 
-import { PRODUCT_WITH_OPTIONS_FRAGMENT } from './graphql/fragments';
+import { PRODUCT_VARIANT_FRAGMENT, PRODUCT_WITH_OPTIONS_FRAGMENT } from './graphql/fragments';
 import {
     AddOptionGroupToProduct,
     CreateProduct,
@@ -23,6 +23,7 @@ import {
     GetProductSimple,
     GetProductVariant,
     GetProductVariantList,
+    GetProductWithVariantList,
     GetProductWithVariants,
     LanguageCode,
     ProductVariantFragment,
@@ -544,6 +545,117 @@ describe('Product resolver', () => {
                 expect(product?.variants.length).toBe(4);
             });
         });
+
+        describe('product.variants', () => {
+            it('returns product variants', async () => {
+                const { product } = await adminClient.query<
+                    GetProductWithVariants.Query,
+                    GetProductWithVariants.Variables
+                >(GET_PRODUCT_WITH_VARIANTS, {
+                    id: 'T_1',
+                });
+
+                expect(product?.variants.length).toBe(4);
+            });
+
+            it('returns product variants in existing language', async () => {
+                const { product } = await adminClient.query<
+                    GetProductWithVariants.Query,
+                    GetProductWithVariants.Variables
+                >(
+                    GET_PRODUCT_WITH_VARIANTS,
+                    {
+                        id: 'T_1',
+                    },
+                    { languageCode: LanguageCode.en },
+                );
+
+                expect(product?.variants.length).toBe(4);
+            });
+
+            it('returns product variants in non-existing language', async () => {
+                const { product } = await adminClient.query<
+                    GetProductWithVariants.Query,
+                    GetProductWithVariants.Variables
+                >(
+                    GET_PRODUCT_WITH_VARIANTS,
+                    {
+                        id: 'T_1',
+                    },
+                    { languageCode: LanguageCode.ru },
+                );
+
+                expect(product?.variants.length).toBe(4);
+            });
+        });
+
+        describe('product.variantList', () => {
+            it('returns product variants', async () => {
+                const { product } = await adminClient.query<
+                    GetProductWithVariantList.Query,
+                    GetProductWithVariantList.Variables
+                >(GET_PRODUCT_WITH_VARIANT_LIST, {
+                    id: 'T_1',
+                });
+
+                expect(product?.variantList.items.length).toBe(4);
+                expect(product?.variantList.totalItems).toBe(4);
+            });
+
+            it('returns product variants in existing language', async () => {
+                const { product } = await adminClient.query<
+                    GetProductWithVariantList.Query,
+                    GetProductWithVariantList.Variables
+                >(
+                    GET_PRODUCT_WITH_VARIANT_LIST,
+                    {
+                        id: 'T_1',
+                    },
+                    { languageCode: LanguageCode.en },
+                );
+
+                expect(product?.variantList.items.length).toBe(4);
+            });
+
+            it('returns product variants in non-existing language', async () => {
+                const { product } = await adminClient.query<
+                    GetProductWithVariantList.Query,
+                    GetProductWithVariantList.Variables
+                >(
+                    GET_PRODUCT_WITH_VARIANT_LIST,
+                    {
+                        id: 'T_1',
+                    },
+                    { languageCode: LanguageCode.ru },
+                );
+
+                expect(product?.variantList.items.length).toBe(4);
+            });
+
+            it('filter & sort', async () => {
+                const { product } = await adminClient.query<
+                    GetProductWithVariantList.Query,
+                    GetProductWithVariantList.Variables
+                >(GET_PRODUCT_WITH_VARIANT_LIST, {
+                    id: 'T_1',
+                    variantListOptions: {
+                        filter: {
+                            name: {
+                                contains: '15',
+                            },
+                        },
+                        sort: {
+                            price: SortOrder.DESC,
+                        },
+                    },
+                });
+
+                expect(product?.variantList.items.map(i => i.name)).toEqual([
+                    'Laptop 15 inch 16GB',
+                    'Laptop 15 inch 8GB',
+                ]);
+            });
+        });
     });
 
     describe('productVariants list query', () => {
@@ -1813,3 +1925,18 @@ export const GET_PRODUCT_VARIANT_LIST = gql`
         }
     }
 `;
+
+export const GET_PRODUCT_WITH_VARIANT_LIST = gql`
+    query GetProductWithVariantList($id: ID, $variantListOptions: ProductVariantListOptions) {
+        product(id: $id) {
+            id
+            variantList(options: $variantListOptions) {
+                items {
+                    ...ProductVariant
+                }
+                totalItems
+            }
+        }
+    }
+    ${PRODUCT_VARIANT_FRAGMENT}
+`;

+ 13 - 3
packages/core/src/api/resolvers/entity/product-entity.resolver.ts

@@ -1,5 +1,7 @@
-import { Info, Parent, ResolveField, Resolver } from '@nestjs/graphql';
+import { Args, Info, Parent, ResolveField, Resolver } from '@nestjs/graphql';
+import { ProductVariantListOptions } from '@vendure/common/lib/generated-types';
 import { DEFAULT_CHANNEL_CODE } from '@vendure/common/lib/shared-constants';
+import { PaginatedList } from '@vendure/common/lib/shared-types';
 
 import { Translated } from '../../../common/types/locale-types';
 import { idsAreEqual } from '../../../common/utils';
@@ -51,10 +53,18 @@ export class ProductEntityResolver {
     async variants(
         @Ctx() ctx: RequestContext,
         @Parent() product: Product,
-        @Api() apiType: ApiType,
     ): Promise<Array<Translated<ProductVariant>>> {
         const { items: variants } = await this.productVariantService.getVariantsByProductId(ctx, product.id);
-        return variants.filter(v => (apiType === 'admin' ? true : v.enabled));
+        return variants;
+    }
+
+    @ResolveField()
+    async variantList(
+        @Ctx() ctx: RequestContext,
+        @Parent() product: Product,
+        @Args() args: { options: ProductVariantListOptions },
+    ): Promise<PaginatedList<ProductVariant>> {
+        return this.productVariantService.getVariantsByProductId(ctx, product.id, args.options);
     }
 
     @ResolveField()

+ 0 - 8
packages/core/src/api/schema/common/collection.type.graphql

@@ -37,11 +37,3 @@ type CollectionList implements PaginatedList {
     items: [Collection!]!
     totalItems: Int!
 }
-
-
-type ProductVariantList implements PaginatedList {
-    items: [ProductVariant!]!
-    totalItems: Int!
-}
-
-input ProductVariantListOptions

+ 3 - 0
packages/core/src/api/schema/common/product.type.graphql

@@ -8,7 +8,10 @@ type Product implements Node {
     description: String!
     featuredAsset: Asset
     assets: [Asset!]!
+    "Returns all ProductVariants"
     variants: [ProductVariant!]!
+    "Returns a paginated, sortable, filterable list of ProductVariants"
+    variantList(options: ProductVariantListOptions): ProductVariantList!
     optionGroups: [ProductOptionGroup!]!
     facetValues: [FacetValue!]!
     translations: [ProductTranslation!]!

+ 3 - 0
packages/core/src/api/schema/shop-api/shop.api.graphql

@@ -187,6 +187,9 @@ input OrderListOptions
 # generated by generateListOptions function
 input ProductListOptions
 
+# generated by generateListOptions function
+input ProductVariantListOptions
+
 union UpdateOrderItemsResult =
       Order
     | OrderModificationError

+ 23 - 19
packages/core/src/service/services/product-variant.service.ts

@@ -154,7 +154,7 @@ export class ProductVariantService {
             'featuredAsset',
         ];
 
-        return this.listQueryBuilder
+        const qb = this.listQueryBuilder
             .build(ProductVariant, options, {
                 relations,
                 orderBy: { id: 'ASC' },
@@ -166,25 +166,29 @@ export class ProductVariantService {
             })
             .innerJoinAndSelect('productvariant.product', 'product', 'product.id = :productId', {
                 productId,
-            })
-            .getManyAndCount()
-            .then(async ([variants, totalItems]) => {
-                const items = await Promise.all(
-                    variants.map(async variant => {
-                        const variantWithPrices = await this.applyChannelPriceAndTax(variant, ctx);
-                        return translateDeep(variantWithPrices, ctx.languageCode, [
-                            'options',
-                            'facetValues',
-                            ['facetValues', 'facet'],
-                        ]);
-                    }),
-                );
-
-                return {
-                    items,
-                    totalItems,
-                };
             });
+
+        if (ctx.apiType === 'shop') {
+            qb.andWhere('productvariant.enabled = :enabled', { enabled: true });
+        }
+
+        return qb.getManyAndCount().then(async ([variants, totalItems]) => {
+            const items = await Promise.all(
+                variants.map(async variant => {
+                    const variantWithPrices = await this.applyChannelPriceAndTax(variant, ctx);
+                    return translateDeep(variantWithPrices, ctx.languageCode, [
+                        'options',
+                        'facetValues',
+                        ['facetValues', 'facet'],
+                    ]);
+                }),
+            );
+
+            return {
+                items,
+                totalItems,
+            };
+        });
     }
 
     getVariantsByCollectionId(

+ 7 - 0
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts

@@ -3351,7 +3351,10 @@ export type Product = Node & {
     description: Scalars['String'];
     featuredAsset?: Maybe<Asset>;
     assets: Array<Asset>;
+    /** Returns all ProductVariants */
     variants: Array<ProductVariant>;
+    /** Returns a paginated, sortable, filterable list of ProductVariants */
+    variantList: ProductVariantList;
     optionGroups: Array<ProductOptionGroup>;
     facetValues: Array<FacetValue>;
     translations: Array<ProductTranslation>;
@@ -3359,6 +3362,10 @@ export type Product = Node & {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type ProductVariantListArgs = {
+    options?: Maybe<ProductVariantListOptions>;
+};
+
 export type ProductFilterParameter = {
     enabled?: Maybe<BooleanOperators>;
     createdAt?: Maybe<DateOperators>;

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
schema-admin.json


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
schema-shop.json


Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio