瀏覽代碼

feat(core): Support "required" field in ConfigArgs

Relates to #643
Michael Bromley 5 年之前
父節點
當前提交
99403850b2

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

@@ -2466,6 +2466,7 @@ export type ConfigArgDefinition = {
     name: Scalars['String'];
     type: Scalars['String'];
     list: Scalars['Boolean'];
+    required: Scalars['Boolean'];
     label?: Maybe<Scalars['String']>;
     description?: Maybe<Scalars['String']>;
     ui?: Maybe<Scalars['JSON']>;
@@ -3486,6 +3487,7 @@ export type OrderAddress = {
     country?: Maybe<Scalars['String']>;
     countryCode?: Maybe<Scalars['String']>;
     phoneNumber?: Maybe<Scalars['String']>;
+    customFields?: Maybe<Scalars['JSON']>;
 };
 
 export type OrderList = PaginatedList & {

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

@@ -647,6 +647,7 @@ export type ConfigArgDefinition = {
     name: Scalars['String'];
     type: Scalars['String'];
     list: Scalars['Boolean'];
+    required: Scalars['Boolean'];
     label?: Maybe<Scalars['String']>;
     description?: Maybe<Scalars['String']>;
     ui?: Maybe<Scalars['JSON']>;
@@ -1799,6 +1800,7 @@ export type OrderAddress = {
     country?: Maybe<Scalars['String']>;
     countryCode?: Maybe<Scalars['String']>;
     phoneNumber?: Maybe<Scalars['String']>;
+    customFields?: Maybe<Scalars['JSON']>;
 };
 
 export type OrderList = PaginatedList & {

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

@@ -2651,6 +2651,7 @@ export type ConfigArgDefinition = {
   name: Scalars['String'];
   type: Scalars['String'];
   list: Scalars['Boolean'];
+  required: Scalars['Boolean'];
   label?: Maybe<Scalars['String']>;
   description?: Maybe<Scalars['String']>;
   ui?: Maybe<Scalars['JSON']>;
@@ -3690,6 +3691,7 @@ export type OrderAddress = {
   country?: Maybe<Scalars['String']>;
   countryCode?: Maybe<Scalars['String']>;
   phoneNumber?: Maybe<Scalars['String']>;
+  customFields?: Maybe<Scalars['JSON']>;
 };
 
 export type OrderList = PaginatedList & {

+ 103 - 0
packages/core/e2e/configurable-operation.e2e-spec.ts

@@ -0,0 +1,103 @@
+import { LanguageCode, mergeConfig, ShippingEligibilityChecker } from '@vendure/core';
+import { createTestEnvironment } from '@vendure/testing';
+import path from 'path';
+
+import { initialData } from '../../../e2e-common/e2e-initial-data';
+import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
+import { UPDATE_SHIPPING_METHOD } from '../../admin-ui/src/lib/core/src/data/definitions/shipping-definitions';
+
+import { UpdateShippingMethod } from './graphql/generated-e2e-admin-types';
+import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
+
+const testShippingEligibilityChecker = new ShippingEligibilityChecker({
+    code: 'test-checker',
+    description: [{ languageCode: LanguageCode.en, value: 'test checker' }],
+    args: {
+        optional: {
+            required: false,
+            type: 'string',
+        },
+        required: {
+            required: true,
+            type: 'string',
+        },
+    },
+    check: ctx => true,
+});
+
+describe('Configurable operations', () => {
+    const { server, adminClient, shopClient } = createTestEnvironment(
+        mergeConfig(testConfig, {
+            shippingOptions: {
+                shippingEligibilityCheckers: [testShippingEligibilityChecker],
+            },
+        }),
+    );
+
+    beforeAll(async () => {
+        await server.init({
+            initialData,
+            productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
+            customerCount: 1,
+        });
+        await adminClient.asSuperAdmin();
+    }, TEST_SETUP_TIMEOUT_MS);
+
+    afterAll(async () => {
+        await server.destroy();
+    });
+
+    describe('required args', () => {
+        it('allows empty optional arg', async () => {
+            const { updateShippingMethod } = await adminClient.query<
+                UpdateShippingMethod.Mutation,
+                UpdateShippingMethod.Variables
+            >(UPDATE_SHIPPING_METHOD, {
+                input: {
+                    id: 'T_1',
+                    checker: {
+                        code: testShippingEligibilityChecker.code,
+                        arguments: [
+                            { name: 'optional', value: 'null' },
+                            { name: 'required', value: '"foo"' },
+                        ],
+                    },
+                    translations: [],
+                },
+            });
+
+            expect(updateShippingMethod.checker.args).toEqual([
+                {
+                    name: 'optional',
+                    value: 'null',
+                },
+                {
+                    name: 'required',
+                    value: '"foo"',
+                },
+            ]);
+        });
+
+        it(
+            'throws if a required arg is null',
+            assertThrowsWithMessage(async () => {
+                await adminClient.query<UpdateShippingMethod.Mutation, UpdateShippingMethod.Variables>(
+                    UPDATE_SHIPPING_METHOD,
+                    {
+                        input: {
+                            id: 'T_1',
+                            checker: {
+                                code: testShippingEligibilityChecker.code,
+                                arguments: [
+                                    { name: 'optional', value: 'null' },
+                                    { name: 'required', value: 'null' },
+                                ],
+                            },
+                            translations: [],
+                        },
+                    },
+                );
+            }, "The argument 'required' is required, but the value is [null]"),
+        );
+    });
+});

+ 8 - 0
packages/core/e2e/graphql/fragments.ts

@@ -611,9 +611,17 @@ export const SHIPPING_METHOD_FRAGMENT = gql`
         description
         calculator {
             code
+            args {
+                name
+                value
+            }
         }
         checker {
             code
+            args {
+                name
+                value
+            }
         }
     }
 `;

+ 22 - 14
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -2466,6 +2466,7 @@ export type ConfigArgDefinition = {
     name: Scalars['String'];
     type: Scalars['String'];
     list: Scalars['Boolean'];
+    required: Scalars['Boolean'];
     label?: Maybe<Scalars['String']>;
     description?: Maybe<Scalars['String']>;
     ui?: Maybe<Scalars['JSON']>;
@@ -3486,6 +3487,7 @@ export type OrderAddress = {
     country?: Maybe<Scalars['String']>;
     countryCode?: Maybe<Scalars['String']>;
     phoneNumber?: Maybe<Scalars['String']>;
+    customFields?: Maybe<Scalars['JSON']>;
 };
 
 export type OrderList = PaginatedList & {
@@ -5094,8 +5096,8 @@ export type ProductWithOptionsFragment = Pick<Product, 'id'> & {
 };
 
 export type ShippingMethodFragment = Pick<ShippingMethod, 'id' | 'code' | 'name' | 'description'> & {
-    calculator: Pick<ConfigurableOperation, 'code'>;
-    checker: Pick<ConfigurableOperation, 'code'>;
+    calculator: Pick<ConfigurableOperation, 'code'> & { args: Array<Pick<ConfigArg, 'name' | 'value'>> };
+    checker: Pick<ConfigurableOperation, 'code'> & { args: Array<Pick<ConfigArg, 'name' | 'value'>> };
 };
 
 export type CreateAdministratorMutationVariables = Exact<{
@@ -5689,6 +5691,12 @@ export type GetOrderHistoryQuery = {
     >;
 };
 
+export type UpdateShippingMethodMutationVariables = Exact<{
+    input: UpdateShippingMethodInput;
+}>;
+
+export type UpdateShippingMethodMutation = { updateShippingMethod: ShippingMethodFragment };
+
 export type CancelJobMutationVariables = Exact<{
     id: Scalars['ID'];
 }>;
@@ -5995,12 +6003,6 @@ export type GetShippingMethodQueryVariables = Exact<{
 
 export type GetShippingMethodQuery = { shippingMethod?: Maybe<ShippingMethodFragment> };
 
-export type UpdateShippingMethodMutationVariables = Exact<{
-    input: UpdateShippingMethodInput;
-}>;
-
-export type UpdateShippingMethodMutation = { updateShippingMethod: ShippingMethodFragment };
-
 export type DeleteShippingMethodMutationVariables = Exact<{
     id: Scalars['ID'];
 }>;
@@ -7105,7 +7107,13 @@ export namespace ProductWithOptions {
 export namespace ShippingMethod {
     export type Fragment = ShippingMethodFragment;
     export type Calculator = NonNullable<ShippingMethodFragment['calculator']>;
+    export type Args = NonNullable<
+        NonNullable<NonNullable<ShippingMethodFragment['calculator']>['args']>[number]
+    >;
     export type Checker = NonNullable<ShippingMethodFragment['checker']>;
+    export type _Args = NonNullable<
+        NonNullable<NonNullable<ShippingMethodFragment['checker']>['args']>[number]
+    >;
 }
 
 export namespace CreateAdministrator {
@@ -7686,6 +7694,12 @@ export namespace GetOrderHistory {
     >;
 }
 
+export namespace UpdateShippingMethod {
+    export type Variables = UpdateShippingMethodMutationVariables;
+    export type Mutation = UpdateShippingMethodMutation;
+    export type UpdateShippingMethod = NonNullable<UpdateShippingMethodMutation['updateShippingMethod']>;
+}
+
 export namespace CancelJob {
     export type Variables = CancelJobMutationVariables;
     export type Mutation = CancelJobMutation;
@@ -8013,12 +8027,6 @@ export namespace GetShippingMethod {
     export type ShippingMethod = NonNullable<GetShippingMethodQuery['shippingMethod']>;
 }
 
-export namespace UpdateShippingMethod {
-    export type Variables = UpdateShippingMethodMutationVariables;
-    export type Mutation = UpdateShippingMethodMutation;
-    export type UpdateShippingMethod = NonNullable<UpdateShippingMethodMutation['updateShippingMethod']>;
-}
-
 export namespace DeleteShippingMethod {
     export type Variables = DeleteShippingMethodMutationVariables;
     export type Mutation = DeleteShippingMethodMutation;

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

@@ -622,6 +622,7 @@ export type ConfigArgDefinition = {
     name: Scalars['String'];
     type: Scalars['String'];
     list: Scalars['Boolean'];
+    required: Scalars['Boolean'];
     label?: Maybe<Scalars['String']>;
     description?: Maybe<Scalars['String']>;
     ui?: Maybe<Scalars['JSON']>;
@@ -1746,6 +1747,7 @@ export type OrderAddress = {
     country?: Maybe<Scalars['String']>;
     countryCode?: Maybe<Scalars['String']>;
     phoneNumber?: Maybe<Scalars['String']>;
+    customFields?: Maybe<Scalars['JSON']>;
 };
 
 export type OrderList = PaginatedList & {

+ 9 - 0
packages/core/e2e/graphql/shared-definitions.ts

@@ -813,3 +813,12 @@ export const GET_ORDER_HISTORY = gql`
         }
     }
 `;
+
+export const UPDATE_SHIPPING_METHOD = gql`
+    mutation UpdateShippingMethod($input: UpdateShippingMethodInput!) {
+        updateShippingMethod(input: $input) {
+            ...ShippingMethod
+        }
+    }
+    ${SHIPPING_METHOD_FRAGMENT}
+`;

+ 1 - 10
packages/core/e2e/shipping-method.e2e-spec.ts

@@ -26,7 +26,7 @@ import {
     TestShippingMethod,
     UpdateShippingMethod,
 } from './graphql/generated-e2e-admin-types';
-import { CREATE_SHIPPING_METHOD } from './graphql/shared-definitions';
+import { CREATE_SHIPPING_METHOD, UPDATE_SHIPPING_METHOD } from './graphql/shared-definitions';
 
 const TEST_METADATA = {
     foo: 'bar',
@@ -349,15 +349,6 @@ const GET_SHIPPING_METHOD = gql`
     ${SHIPPING_METHOD_FRAGMENT}
 `;
 
-const UPDATE_SHIPPING_METHOD = gql`
-    mutation UpdateShippingMethod($input: UpdateShippingMethodInput!) {
-        updateShippingMethod(input: $input) {
-            ...ShippingMethod
-        }
-    }
-    ${SHIPPING_METHOD_FRAGMENT}
-`;
-
 const DELETE_SHIPPING_METHOD = gql`
     mutation DeleteShippingMethod($id: ID!) {
         deleteShippingMethod(id: $id) {

+ 1 - 0
packages/core/src/api/schema/common/common-types.graphql

@@ -38,6 +38,7 @@ type ConfigArgDefinition {
     name: String!
     type: String!
     list: Boolean!
+    required: Boolean!
     label: String
     description: String
     ui: JSON

+ 2 - 0
packages/core/src/common/configurable-operation.ts

@@ -53,6 +53,7 @@ export type UiComponentConfig =
 
 export interface ConfigArgCommonDef<T extends ConfigArgType> {
     type: T;
+    required?: boolean;
     list?: boolean;
     label?: LocalizedStringArray;
     description?: LocalizedStringArray;
@@ -360,6 +361,7 @@ export class ConfigurableOperationDef<T extends ConfigArgs = ConfigArgs> {
                         name,
                         type: arg.type,
                         list: arg.list ?? false,
+                        required: arg.required ?? true,
                         ui: arg.ui,
                         label: arg.label && localizeString(arg.label, ctx.languageCode),
                         description: arg.description && localizeString(arg.description, ctx.languageCode),

+ 2 - 8
packages/core/src/config/fulfillment/manual-fulfillment-handler.ts

@@ -8,17 +8,11 @@ export const manualFulfillmentHandler = new FulfillmentHandler({
     args: {
         method: {
             type: 'string',
-            config: {
-                options: [
-                    { value: 'next_day' },
-                    { value: 'first_class' },
-                    { value: 'priority' },
-                    { value: 'standard' },
-                ],
-            },
+            required: false,
         },
         trackingCode: {
             type: 'string',
+            required: false,
         },
     },
     createFulfillment: (ctx, orders, orderItems, args) => {

+ 1 - 0
packages/core/src/i18n/messages/en.json

@@ -10,6 +10,7 @@
     "cannot-transition-fulfillment-from-to": "Cannot transition Fulfillment from \"{ fromState }\" to \"{ toState }\"",
     "collection-id-or-slug-must-be-provided": "Either the Collection id or slug must be provided",
     "collection-id-slug-mismatch": "The provided id and slug refer to different Collections",
+    "configurable-argument-is-required": "The argument '{ name }' is required, but the value is [{ value }]",
     "country-code-not-valid": "The countryCode \"{ countryCode }\" was not recognized",
     "customer-does-not-belong-to-customer-group": "Customer does not belong to this CustomerGroup",
     "default-channel-not-found": "Default channel not found",

+ 26 - 3
packages/core/src/service/helpers/config-arg/config-arg.service.ts

@@ -61,17 +61,18 @@ export class ConfigArgService {
         return match as ConfigDefTypeMap[T];
     }
 
+    /**
+     * Parses and validates the input to a ConfigurableOperation.
+     */
     parseInput(defType: ConfigDefType, input: ConfigurableOperationInput): ConfigurableOperation {
         const match = this.getByCode(defType, input.code);
+        this.validateRequiredFields(input, match);
         return {
             code: input.code,
             args: input.arguments,
         };
     }
 
-    /**
-     * Converts the input values of the "create" and "update" mutations into the format expected by the ShippingMethod entity.
-     */
     private parseOperationArgs(
         input: ConfigurableOperationInput,
         checkerOrCalculator: ShippingEligibilityChecker | ShippingCalculator,
@@ -82,4 +83,26 @@ export class ConfigArgService {
         };
         return output;
     }
+
+    private validateRequiredFields(input: ConfigurableOperationInput, def: ConfigurableOperationDef) {
+        for (const [name, argDef] of Object.entries(def.args)) {
+            if (argDef.required) {
+                const inputArg = input.arguments.find(a => a.name === name);
+                let val: unknown;
+                if (inputArg) {
+                    try {
+                        val = JSON.parse(inputArg?.value);
+                    } catch (e) {
+                        // ignore
+                    }
+                }
+                if (!val) {
+                    throw new UserInputError('error.configurable-argument-is-required', {
+                        name,
+                        value: String(val),
+                    });
+                }
+            }
+        }
+    }
 }

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

@@ -2466,6 +2466,7 @@ export type ConfigArgDefinition = {
     name: Scalars['String'];
     type: Scalars['String'];
     list: Scalars['Boolean'];
+    required: Scalars['Boolean'];
     label?: Maybe<Scalars['String']>;
     description?: Maybe<Scalars['String']>;
     ui?: Maybe<Scalars['JSON']>;
@@ -3486,6 +3487,7 @@ export type OrderAddress = {
     country?: Maybe<Scalars['String']>;
     countryCode?: Maybe<Scalars['String']>;
     phoneNumber?: Maybe<Scalars['String']>;
+    customFields?: Maybe<Scalars['JSON']>;
 };
 
 export type OrderList = PaginatedList & {

文件差異過大導致無法顯示
+ 0 - 0
schema-admin.json


文件差異過大導致無法顯示
+ 0 - 0
schema-shop.json


部分文件因文件數量過多而無法顯示