Sfoglia il codice sorgente

feat(core): Implement `previewCollectionVariants` query in Admin API

Relates to #1530
Michael Bromley 3 anni fa
parent
commit
1c3b38c7f3

File diff suppressed because it is too large
+ 489 - 503
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts


File diff suppressed because it is too large
+ 736 - 751
packages/common/src/generated-shop-types.ts


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

@@ -3725,6 +3725,11 @@ export type PermissionDefinition = {
   assignable: Scalars['Boolean'];
   assignable: Scalars['Boolean'];
 };
 };
 
 
+export type PreviewCollectionVariantsInput = {
+  collectionId: Scalars['ID'];
+  filters: Array<ConfigurableOperationInput>;
+};
+
 /** The price range where the result has more than one price */
 /** The price range where the result has more than one price */
 export type PriceRange = {
 export type PriceRange = {
   __typename?: 'PriceRange';
   __typename?: 'PriceRange';
@@ -4076,6 +4081,8 @@ export type Query = {
   /** Get a Collection either by id or slug. If neither id nor slug is specified, an error will result. */
   /** Get a Collection either by id or slug. If neither id nor slug is specified, an error will result. */
   collection?: Maybe<Collection>;
   collection?: Maybe<Collection>;
   collectionFilters: Array<ConfigurableOperationDefinition>;
   collectionFilters: Array<ConfigurableOperationDefinition>;
+  /** Used for real-time previews of the contents of a Collection */
+  previewCollectionVariants: ProductVariantList;
   countries: CountryList;
   countries: CountryList;
   country?: Maybe<Country>;
   country?: Maybe<Country>;
   customerGroups: CustomerGroupList;
   customerGroups: CustomerGroupList;
@@ -4168,6 +4175,12 @@ export type QueryCollectionArgs = {
 };
 };
 
 
 
 
+export type QueryPreviewCollectionVariantsArgs = {
+  input: PreviewCollectionVariantsInput;
+  options?: Maybe<ProductVariantListOptions>;
+};
+
+
 export type QueryCountriesArgs = {
 export type QueryCountriesArgs = {
   options?: Maybe<CountryListOptions>;
   options?: Maybe<CountryListOptions>;
 };
 };

+ 98 - 0
packages/core/e2e/collection.e2e-spec.ts

@@ -38,6 +38,8 @@ import {
     GetProductsWithVariantIds,
     GetProductsWithVariantIds,
     LanguageCode,
     LanguageCode,
     MoveCollection,
     MoveCollection,
+    PreviewCollectionVariantsQuery,
+    PreviewCollectionVariantsQueryVariables,
     SortOrder,
     SortOrder,
     UpdateCollection,
     UpdateCollection,
     UpdateProduct,
     UpdateProduct,
@@ -1601,6 +1603,87 @@ describe('Collection resolver', () => {
                 // no "Hat"
                 // no "Hat"
             ]);
             ]);
         });
         });
+
+        describe('previewCollectionVariants', () => {
+            it('returns correct contents', async () => {
+                const { previewCollectionVariants } = await adminClient.query<
+                    PreviewCollectionVariantsQuery,
+                    PreviewCollectionVariantsQueryVariables
+                >(PREVIEW_COLLECTION_VARIANTS, {
+                    input: {
+                        collectionId: electronicsCollection.id,
+                        filters: [
+                            {
+                                code: facetValueCollectionFilter.code,
+                                arguments: [
+                                    {
+                                        name: 'facetValueIds',
+                                        value: `["${getFacetValueId('electronics')}","${getFacetValueId(
+                                            'pear',
+                                        )}"]`,
+                                    },
+                                    {
+                                        name: 'containsAny',
+                                        value: `false`,
+                                    },
+                                ],
+                            },
+                        ],
+                    },
+                });
+                expect(previewCollectionVariants.items.map(i => i.name).sort()).toEqual([
+                    'Curvy Monitor 24 inch',
+                    'Curvy Monitor 27 inch',
+                    'Gaming PC i7-8700 240GB SSD',
+                    'Instant Camera',
+                    'Laptop 13 inch 16GB',
+                    'Laptop 13 inch 8GB',
+                    'Laptop 15 inch 16GB',
+                    'Laptop 15 inch 8GB',
+                ]);
+            });
+
+            it('works with list options', async () => {
+                const { previewCollectionVariants } = await adminClient.query<
+                    PreviewCollectionVariantsQuery,
+                    PreviewCollectionVariantsQueryVariables
+                >(PREVIEW_COLLECTION_VARIANTS, {
+                    input: {
+                        collectionId: electronicsCollection.id,
+                        filters: [
+                            {
+                                code: facetValueCollectionFilter.code,
+                                arguments: [
+                                    {
+                                        name: 'facetValueIds',
+                                        value: `["${getFacetValueId('electronics')}"]`,
+                                    },
+                                    {
+                                        name: 'containsAny',
+                                        value: `false`,
+                                    },
+                                ],
+                            },
+                        ],
+                    },
+                    options: {
+                        sort: {
+                            name: SortOrder.ASC,
+                        },
+                        filter: {
+                            name: {
+                                contains: 'mon',
+                            },
+                        },
+                        take: 5,
+                    },
+                });
+                expect(previewCollectionVariants.items).toEqual([
+                    { id: 'T_5', name: 'Curvy Monitor 24 inch' },
+                    { id: 'T_6', name: 'Curvy Monitor 27 inch' },
+                ]);
+            });
+        });
     });
     });
 
 
     describe('Product collections property', () => {
     describe('Product collections property', () => {
@@ -1924,3 +2007,18 @@ const GET_COLLECTION_NESTED_PARENTS = gql`
         }
         }
     }
     }
 `;
 `;
+
+const PREVIEW_COLLECTION_VARIANTS = gql`
+    query PreviewCollectionVariants(
+        $input: PreviewCollectionVariantsInput!
+        $options: ProductVariantListOptions
+    ) {
+        previewCollectionVariants(input: $input, options: $options) {
+            items {
+                id
+                name
+            }
+            totalItems
+        }
+    }
+`;

File diff suppressed because it is too large
+ 489 - 503
packages/core/e2e/graphql/generated-e2e-admin-types.ts


File diff suppressed because it is too large
+ 699 - 714
packages/core/e2e/graphql/generated-e2e-shop-types.ts


+ 12 - 0
packages/core/src/api/resolvers/admin/collection.resolver.ts

@@ -9,6 +9,7 @@ import {
     Permission,
     Permission,
     QueryCollectionArgs,
     QueryCollectionArgs,
     QueryCollectionsArgs,
     QueryCollectionsArgs,
+    QueryPreviewCollectionVariantsArgs,
 } from '@vendure/common/lib/generated-types';
 } from '@vendure/common/lib/generated-types';
 import { PaginatedList } from '@vendure/common/lib/shared-types';
 import { PaginatedList } from '@vendure/common/lib/shared-types';
 
 
@@ -77,6 +78,17 @@ export class CollectionResolver {
         return this.encodeFilters(collection);
         return this.encodeFilters(collection);
     }
     }
 
 
+    @Query()
+    @Allow(Permission.ReadCatalog, Permission.ReadCollection)
+    previewCollectionVariants(
+        @Ctx() ctx: RequestContext,
+        @Args() args: QueryPreviewCollectionVariantsArgs,
+        @Relations(Collection) relations: RelationPaths<Collection>,
+    ) {
+        this.configurableOperationCodec.decodeConfigurableOperationIds(CollectionFilter, args.input.filters);
+        return this.collectionService.previewCollectionVariants(ctx, args.input, args.options || undefined);
+    }
+
     @Transaction()
     @Transaction()
     @Mutation()
     @Mutation()
     @Allow(Permission.CreateCatalog, Permission.CreateCollection)
     @Allow(Permission.CreateCatalog, Permission.CreateCollection)

+ 7 - 0
packages/core/src/api/schema/admin-api/collection.api.graphql

@@ -3,6 +3,8 @@ type Query {
     "Get a Collection either by id or slug. If neither id nor slug is specified, an error will result."
     "Get a Collection either by id or slug. If neither id nor slug is specified, an error will result."
     collection(id: ID, slug: String): Collection
     collection(id: ID, slug: String): Collection
     collectionFilters: [ConfigurableOperationDefinition!]!
     collectionFilters: [ConfigurableOperationDefinition!]!
+    "Used for real-time previews of the contents of a Collection"
+    previewCollectionVariants(input: PreviewCollectionVariantsInput!, options: ProductVariantListOptions): ProductVariantList!
 }
 }
 
 
 type Mutation {
 type Mutation {
@@ -52,6 +54,11 @@ input CreateCollectionInput {
     translations: [CreateCollectionTranslationInput!]!
     translations: [CreateCollectionTranslationInput!]!
 }
 }
 
 
+input PreviewCollectionVariantsInput {
+    collectionId: ID!
+    filters: [ConfigurableOperationInput!]!
+}
+
 input UpdateCollectionInput {
 input UpdateCollectionInput {
     id: ID!
     id: ID!
     isPrivate: Boolean
     isPrivate: Boolean

+ 5 - 3
packages/core/src/config/catalog/default-collection-filters.ts

@@ -91,14 +91,16 @@ export const variantNameCollectionFilter = new CollectionFilter({
     code: 'variant-name-filter',
     code: 'variant-name-filter',
     description: [{ languageCode: LanguageCode.en, value: 'Filter by ProductVariant name' }],
     description: [{ languageCode: LanguageCode.en, value: 'Filter by ProductVariant name' }],
     apply: (qb, args) => {
     apply: (qb, args) => {
-        const translationAlias = `variant_name_filter_translation`;
+        let translationAlias = `variant_name_filter_translation`;
         const nanoid = customAlphabet('123456789abcdefghijklmnopqrstuvwxyz', 6);
         const nanoid = customAlphabet('123456789abcdefghijklmnopqrstuvwxyz', 6);
         const termName = `term_${nanoid()}`;
         const termName = `term_${nanoid()}`;
-        const hasJoinOnTranslations = !!qb.expressionMap.joinAttributes.find(
+        const translationsJoin = qb.expressionMap.joinAttributes.find(
             ja => ja.entityOrProperty === 'productVariant.translations',
             ja => ja.entityOrProperty === 'productVariant.translations',
         );
         );
-        if (!hasJoinOnTranslations) {
+        if (!translationsJoin) {
             qb.leftJoin('productVariant.translations', translationAlias);
             qb.leftJoin('productVariant.translations', translationAlias);
+        } else {
+            translationAlias = translationsJoin.alias.name;
         }
         }
         const LIKE = qb.connection.options.type === 'postgres' ? 'ILIKE' : 'LIKE';
         const LIKE = qb.connection.options.type === 'postgres' ? 'ILIKE' : 'LIKE';
         switch (args.operator) {
         switch (args.operator) {

+ 39 - 1
packages/core/src/service/services/collection.service.ts

@@ -6,6 +6,7 @@ import {
     DeletionResponse,
     DeletionResponse,
     DeletionResult,
     DeletionResult,
     MoveCollectionInput,
     MoveCollectionInput,
+    PreviewCollectionVariantsInput,
     UpdateCollectionInput,
     UpdateCollectionInput,
 } from '@vendure/common/lib/generated-types';
 } from '@vendure/common/lib/generated-types';
 import { pick } from '@vendure/common/lib/pick';
 import { pick } from '@vendure/common/lib/pick';
@@ -361,6 +362,43 @@ export class CollectionService implements OnModuleInit {
             });
             });
     }
     }
 
 
+    async previewCollectionVariants(
+        ctx: RequestContext,
+        input: PreviewCollectionVariantsInput,
+        options?: ListQueryOptions<ProductVariant>,
+        relations?: RelationPaths<Collection>,
+    ): Promise<PaginatedList<ProductVariant>> {
+        const filters = this.getCollectionFiltersFromInput(input);
+        const ancestorFilters = await this.getAncestors(input.collectionId).then(ancestors =>
+            ancestors.reduce(
+                (_filters, c) => [..._filters, ...(c.filters || [])],
+                [] as ConfigurableOperation[],
+            ),
+        );
+        const applicableFilters = filters.concat(ancestorFilters);
+        let qb = this.listQueryBuilder.build(ProductVariant, options, {
+            relations: relations ?? ['taxCategory'],
+            channelId: ctx.channelId,
+            where: { deletedAt: null },
+            ctx,
+            entityAlias: 'productVariant',
+        });
+
+        const { collectionFilters } = this.configService.catalogOptions;
+        for (const filterType of collectionFilters) {
+            const filtersOfType = applicableFilters.filter(f => f.code === filterType.code);
+            if (filtersOfType.length) {
+                for (const filter of filtersOfType) {
+                    qb = filterType.apply(qb, filter.args);
+                }
+            }
+        }
+        return qb.getManyAndCount().then(([items, totalItems]) => ({
+            items,
+            totalItems,
+        }));
+    }
+
     async create(ctx: RequestContext, input: CreateCollectionInput): Promise<Translated<Collection>> {
     async create(ctx: RequestContext, input: CreateCollectionInput): Promise<Translated<Collection>> {
         await this.slugValidator.validateSlugs(ctx, input, CollectionTranslation);
         await this.slugValidator.validateSlugs(ctx, input, CollectionTranslation);
         const collection = await this.translatableSaver.create({
         const collection = await this.translatableSaver.create({
@@ -480,7 +518,7 @@ export class CollectionService implements OnModuleInit {
     }
     }
 
 
     private getCollectionFiltersFromInput(
     private getCollectionFiltersFromInput(
-        input: CreateCollectionInput | UpdateCollectionInput,
+        input: CreateCollectionInput | UpdateCollectionInput | PreviewCollectionVariantsInput,
     ): ConfigurableOperation[] {
     ): ConfigurableOperation[] {
         const filters: ConfigurableOperation[] = [];
         const filters: ConfigurableOperation[] = [];
         if (input.filters) {
         if (input.filters) {

File diff suppressed because it is too large
+ 489 - 503
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts


File diff suppressed because it is too large
+ 489 - 503
packages/payments-plugin/e2e/graphql/generated-admin-types.ts


File diff suppressed because it is too large
+ 699 - 714
packages/payments-plugin/e2e/graphql/generated-shop-types.ts


File diff suppressed because it is too large
+ 736 - 751
packages/payments-plugin/src/mollie/graphql/generated-shop-types.ts


File diff suppressed because it is too large
+ 0 - 0
schema-admin.json


Some files were not shown because too many files changed in this diff