Pārlūkot izejas kodu

feat(core): Add support for list types in ConfigurableOperationDefs

Relates to #414

BREAKING CHANGE: The `'facetValueIds'` type has been removed from the `ConfigArgType` type, and replaced by `'ID'` and the `list` option. This change only affects you if you have created custom CollectionFilters of PromotionActions/Conditions using the `'facetValueIds'` type for an argument.
Michael Bromley 5 gadi atpakaļ
vecāks
revīzija
669819524b

+ 1 - 1
packages/common/src/shared-types.ts

@@ -107,7 +107,7 @@ export type CustomFieldType = 'string' | 'localeString' | 'int' | 'float' | 'boo
  * @docsCategory common
  * @docsPage Configurable Operations
  */
-export type ConfigArgType = 'string' | 'int' | 'float' | 'boolean' | 'datetime' | 'facetValueIds';
+export type ConfigArgType = 'string' | 'int' | 'float' | 'boolean' | 'datetime' | 'ID';
 
 export type ConfigArgSubset<T extends ConfigArgType> = T;
 

+ 1 - 1
packages/core/e2e/promotion.e2e-spec.ts

@@ -251,7 +251,7 @@ function generateTestAction(code: string): PromotionAction<any> {
     return new PromotionOrderAction({
         code,
         description: [{ languageCode: LanguageCode.en, value: `description for ${code}` }],
-        args: { facetValueIds: { type: 'facetValueIds' } },
+        args: { facetValueIds: { type: 'ID', list: true } },
         execute: (order, args) => {
             return 42;
         },

+ 19 - 11
packages/core/src/api/common/configurable-operation-codec.ts

@@ -36,10 +36,15 @@ export class ConfigurableOperationCodec {
             }
             for (const arg of operationInput.arguments) {
                 const argDef = def.args[arg.name];
-                if (argDef.type === 'facetValueIds' && arg.value) {
-                    const ids = JSON.parse(arg.value) as string[];
-                    const decodedIds = ids.map(id => this.idCodecService.decode(id));
-                    arg.value = JSON.stringify(decodedIds);
+                if (argDef.type === 'ID' && arg.value) {
+                    if (argDef.list === true) {
+                        const ids = JSON.parse(arg.value) as string[];
+                        const decodedIds = ids.map(id => this.idCodecService.decode(id));
+                        arg.value = JSON.stringify(decodedIds);
+                    } else {
+                        const decodedId = this.idCodecService.decode(arg.value);
+                        arg.value = JSON.stringify(decodedId);
+                    }
                 }
             }
         }
@@ -61,19 +66,22 @@ export class ConfigurableOperationCodec {
             }
             for (const arg of operationInput.args) {
                 const argDef = def.args[arg.name];
-                if (argDef.type === 'facetValueIds' && arg.value) {
-                    const ids = JSON.parse(arg.value) as string[];
-                    const encodedIds = ids.map(id => this.idCodecService.encode(id));
-                    arg.value = JSON.stringify(encodedIds);
+                if (argDef.type === 'ID' && arg.value) {
+                    if (argDef.list === true) {
+                        const ids = JSON.parse(arg.value) as string[];
+                        const encodedIds = ids.map(id => this.idCodecService.encode(id));
+                        arg.value = JSON.stringify(encodedIds);
+                    } else {
+                        const encodedId = this.idCodecService.encode(arg.value);
+                        arg.value = JSON.stringify(encodedId);
+                    }
                 }
             }
         }
         return input;
     }
 
-    getAvailableDefsOfType(
-        defType: Type<ConfigurableOperationDef<any>>,
-    ): Array<ConfigurableOperationDef<any>> {
+    getAvailableDefsOfType(defType: Type<ConfigurableOperationDef>): ConfigurableOperationDef[] {
         switch (defType) {
             case CollectionFilter:
                 return this.configService.catalogOptions.collectionFilters;

+ 24 - 14
packages/core/src/common/configurable-operation.ts

@@ -8,7 +8,8 @@ import {
     Maybe,
     StringFieldOption,
 } from '@vendure/common/lib/generated-types';
-import { ConfigArgType } from '@vendure/common/lib/shared-types';
+import { ConfigArgType, ID } from '@vendure/common/lib/shared-types';
+import { assertNever } from '@vendure/common/lib/shared-utils';
 import { simpleDeepClone } from '@vendure/common/lib/simple-deep-clone';
 
 import { RequestContext } from '../api/common/request-context';
@@ -112,8 +113,10 @@ export type ConfigArgValues<T extends ConfigArgs<any>> = {
         ? boolean[]
         : T[K] extends ConfigArgDef<'boolean'>
         ? boolean
-        : T[K] extends ConfigArgDef<'facetValueIds'>
-        ? string[]
+        : T[K] extends ConfigArgListDef<'ID'>
+        ? ID[]
+        : T[K] extends ConfigArgDef<'ID'>
+        ? ID
         : T[K] extends ConfigArgListDef<'string'>
         ? string[]
         : string;
@@ -165,7 +168,7 @@ export interface ConfigurableOperationDefOptions<T extends ConfigArgs<ConfigArgT
  * @docsCategory common
  * @docsPage Configurable Operations
  */
-export class ConfigurableOperationDef<T extends ConfigArgs<ConfigArgType>> {
+export class ConfigurableOperationDef<T extends ConfigArgs<ConfigArgType> = ConfigArgs<ConfigArgType>> {
     get code(): string {
         return this.options.code;
     }
@@ -225,7 +228,8 @@ export class ConfigurableOperationDef<T extends ConfigArgs<ConfigArgType>> {
             if (arg && arg.value != null) {
                 output[arg.name as keyof ConfigArgValues<T>] = coerceValueToType<T>(
                     arg.value,
-                    this.args[arg.name],
+                    this.args[arg.name].type,
+                    this.args[arg.name].list || false,
                 );
             }
         }
@@ -266,24 +270,30 @@ function localizeString(stringArray: LocalizedStringArray, languageCode: Languag
 
 function coerceValueToType<T extends ConfigArgs<any>>(
     value: string,
-    argDef: ConfigArgDef<any>,
+    type: ConfigArgType,
+    isList: boolean,
 ): ConfigArgValues<T>[keyof T] {
-    switch (argDef.type as ConfigArgType) {
+    if (isList) {
+        try {
+            return (JSON.parse(value) as string[]).map(v => coerceValueToType(v, type, false)) as any;
+        } catch (err) {
+            throw new InternalServerError(err.message);
+        }
+    }
+    switch (type) {
         case 'string':
             return value as any;
         case 'int':
             return Number.parseInt(value || '', 10) as any;
+        case 'float':
+            return Number.parseFloat(value || '') as any;
         case 'datetime':
             return Date.parse(value || '') as any;
         case 'boolean':
             return !!(value && (value.toLowerCase() === 'true' || value === '1')) as any;
-        case 'facetValueIds':
-            try {
-                return JSON.parse(value as any);
-            } catch (err) {
-                throw new InternalServerError(err.message);
-            }
+        case 'ID':
+            return value as any;
         default:
-            return (value as string) as any;
+            assertNever(type);
     }
 }

+ 1 - 1
packages/core/src/config/collection/collection-filter.ts

@@ -10,7 +10,7 @@ import {
 } from '../../common/configurable-operation';
 import { ProductVariant } from '../../entity/product-variant/product-variant.entity';
 
-export type CollectionFilterArgType = ConfigArgSubset<'facetValueIds' | 'string' | 'boolean'>;
+export type CollectionFilterArgType = ConfigArgSubset<'ID' | 'string' | 'boolean'>;
 export type CollectionFilterArgs = ConfigArgs<CollectionFilterArgType>;
 
 export type ApplyCollectionFilterFn<T extends CollectionFilterArgs> = (

+ 1 - 1
packages/core/src/config/collection/default-collection-filters.ts

@@ -10,7 +10,7 @@ import { CollectionFilter } from './collection-filter';
  */
 export const facetValueCollectionFilter = new CollectionFilter({
     args: {
-        facetValueIds: { type: 'facetValueIds' },
+        facetValueIds: { type: 'ID', list: true },
         containsAny: { type: 'boolean' },
     },
     code: 'facet-value-filter',

+ 2 - 1
packages/core/src/config/promotion/default-promotion-actions.ts

@@ -28,7 +28,8 @@ export const discountOnItemWithFacets = new PromotionItemAction({
             },
         },
         facets: {
-            type: 'facetValueIds',
+            type: 'ID',
+            list: true,
         },
     },
     async execute(orderItem, orderLine, args, { hasFacetValues }) {

+ 1 - 1
packages/core/src/config/promotion/default-promotion-conditions.ts

@@ -29,7 +29,7 @@ export const atLeastNWithFacets = new PromotionCondition({
     ],
     args: {
         minimum: { type: 'int' },
-        facets: { type: 'facetValueIds' },
+        facets: { type: 'ID', list: true },
     },
     async check(order: Order, args, { hasFacetValues }) {
         let matches = 0;

+ 1 - 1
packages/core/src/config/promotion/promotion-action.ts

@@ -13,7 +13,7 @@ import { Order } from '../../entity/order/order.entity';
 
 import { PromotionUtils } from './promotion-condition';
 
-export type PromotionActionArgType = ConfigArgSubset<'int' | 'facetValueIds'>;
+export type PromotionActionArgType = ConfigArgSubset<'int' | 'ID'>;
 export type PromotionActionArgs = ConfigArgs<PromotionActionArgType>;
 
 /**

+ 1 - 3
packages/core/src/config/promotion/promotion-condition.ts

@@ -10,9 +10,7 @@ import {
 import { OrderLine } from '../../entity';
 import { Order } from '../../entity/order/order.entity';
 
-export type PromotionConditionArgType = ConfigArgSubset<
-    'int' | 'string' | 'datetime' | 'boolean' | 'facetValueIds'
->;
+export type PromotionConditionArgType = ConfigArgSubset<'int' | 'string' | 'datetime' | 'boolean' | 'ID'>;
 export type PromotionConditionArgs = ConfigArgs<PromotionConditionArgType>;
 
 /**

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

@@ -381,7 +381,6 @@ export class CollectionService implements OnModuleInit {
                     args: filter.arguments.map((inputArg, i) => {
                         return {
                             name: inputArg.name,
-                            type: match.args[inputArg.name].type,
                             value: inputArg.value,
                         };
                     }),