1
0
Эх сурвалжийг харах

feat(core): Add options to string custom field config

Relates to #85
Michael Bromley 6 жил өмнө
parent
commit
bc0813e90d

+ 27 - 1
packages/common/src/generated-shop-types.ts

@@ -1651,7 +1651,14 @@ export type Product = Node & {
     facetValues: Array<FacetValue>;
     translations: Array<ProductTranslation>;
     collections: Array<Collection>;
-    customFields?: Maybe<Scalars['JSON']>;
+    customFields?: Maybe<ProductCustomFields>;
+};
+
+export type ProductCustomFields = {
+    __typename?: 'ProductCustomFields';
+    nickname?: Maybe<Scalars['String']>;
+    localNickname?: Maybe<Scalars['String']>;
+    expires?: Maybe<Scalars['DateTime']>;
 };
 
 export type ProductFilterParameter = {
@@ -1661,6 +1668,9 @@ export type ProductFilterParameter = {
     name?: Maybe<StringOperators>;
     slug?: Maybe<StringOperators>;
     description?: Maybe<StringOperators>;
+    nickname?: Maybe<StringOperators>;
+    localNickname?: Maybe<StringOperators>;
+    expires?: Maybe<DateOperators>;
 };
 
 export type ProductList = PaginatedList & {
@@ -1727,6 +1737,9 @@ export type ProductSortParameter = {
     name?: Maybe<SortOrder>;
     slug?: Maybe<SortOrder>;
     description?: Maybe<SortOrder>;
+    nickname?: Maybe<SortOrder>;
+    localNickname?: Maybe<SortOrder>;
+    expires?: Maybe<SortOrder>;
 };
 
 export type ProductTranslation = {
@@ -1738,6 +1751,12 @@ export type ProductTranslation = {
     name: Scalars['String'];
     slug: Scalars['String'];
     description: Scalars['String'];
+    customFields?: Maybe<ProductTranslationCustomFields>;
+};
+
+export type ProductTranslationCustomFields = {
+    __typename?: 'ProductTranslationCustomFields';
+    localNickname?: Maybe<Scalars['String']>;
 };
 
 export type ProductVariant = Node & {
@@ -2082,6 +2101,13 @@ export type StringCustomFieldConfig = CustomField & {
     label?: Maybe<Array<LocalizedString>>;
     description?: Maybe<Array<LocalizedString>>;
     pattern?: Maybe<Scalars['String']>;
+    options?: Maybe<Array<StringFieldOption>>;
+};
+
+export type StringFieldOption = {
+    __typename?: 'StringFieldOption';
+    value: Scalars['String'];
+    label?: Maybe<Array<LocalizedString>>;
 };
 
 export type StringOperators = {

+ 73 - 33
packages/common/src/generated-types.ts

@@ -471,12 +471,17 @@ export type CreateGroupOptionInput = {
   translations: Array<ProductOptionGroupTranslationInput>,
 };
 
+export type CreateProductCustomFieldsInput = {
+  nickname?: Maybe<Scalars['String']>,
+  expires?: Maybe<Scalars['DateTime']>,
+};
+
 export type CreateProductInput = {
   featuredAssetId?: Maybe<Scalars['ID']>,
   assetIds?: Maybe<Array<Scalars['ID']>>,
   facetValueIds?: Maybe<Array<Scalars['ID']>>,
   translations: Array<ProductTranslationInput>,
-  customFields?: Maybe<Scalars['JSON']>,
+  customFields?: Maybe<CreateProductCustomFieldsInput>,
 };
 
 export type CreateProductOptionGroupInput = {
@@ -1697,6 +1702,8 @@ export type Mutation = {
   refundOrder: Refund,
   settleRefund: Refund,
   addNoteToOrder: Order,
+  /** Update an existing PaymentMethod */
+  updatePaymentMethod: PaymentMethod,
   /** Create a new ProductOptionGroup */
   createProductOptionGroup: ProductOptionGroup,
   /** Update an existing ProductOptionGroup */
@@ -1705,8 +1712,7 @@ export type Mutation = {
   createProductOption: ProductOption,
   /** Create a new ProductOption within a ProductOptionGroup */
   updateProductOption: ProductOption,
-  /** Update an existing PaymentMethod */
-  updatePaymentMethod: PaymentMethod,
+  reindex: JobInfo,
   /** Create a new Product */
   createProduct: Product,
   /** Update an existing Product */
@@ -1723,7 +1729,6 @@ export type Mutation = {
   updateProductVariants: Array<Maybe<ProductVariant>>,
   /** Delete a ProductVariant */
   deleteProductVariant: DeletionResponse,
-  reindex: JobInfo,
   createPromotion: Promotion,
   updatePromotion: Promotion,
   deletePromotion: DeletionResponse,
@@ -1955,6 +1960,11 @@ export type MutationAddNoteToOrderArgs = {
 };
 
 
+export type MutationUpdatePaymentMethodArgs = {
+  input: UpdatePaymentMethodInput
+};
+
+
 export type MutationCreateProductOptionGroupArgs = {
   input: CreateProductOptionGroupInput
 };
@@ -1975,11 +1985,6 @@ export type MutationUpdateProductOptionArgs = {
 };
 
 
-export type MutationUpdatePaymentMethodArgs = {
-  input: UpdatePaymentMethodInput
-};
-
-
 export type MutationCreateProductArgs = {
   input: CreateProductInput
 };
@@ -2358,7 +2363,14 @@ export type Product = Node & {
   translations: Array<ProductTranslation>,
   collections: Array<Collection>,
   enabled: Scalars['Boolean'],
-  customFields?: Maybe<Scalars['JSON']>,
+  customFields?: Maybe<ProductCustomFields>,
+};
+
+export type ProductCustomFields = {
+  __typename?: 'ProductCustomFields',
+  nickname?: Maybe<Scalars['String']>,
+  localNickname?: Maybe<Scalars['String']>,
+  expires?: Maybe<Scalars['DateTime']>,
 };
 
 export type ProductFilterParameter = {
@@ -2369,6 +2381,9 @@ export type ProductFilterParameter = {
   slug?: Maybe<StringOperators>,
   description?: Maybe<StringOperators>,
   enabled?: Maybe<BooleanOperators>,
+  nickname?: Maybe<StringOperators>,
+  localNickname?: Maybe<StringOperators>,
+  expires?: Maybe<DateOperators>,
 };
 
 export type ProductList = PaginatedList & {
@@ -2449,6 +2464,9 @@ export type ProductSortParameter = {
   name?: Maybe<SortOrder>,
   slug?: Maybe<SortOrder>,
   description?: Maybe<SortOrder>,
+  nickname?: Maybe<SortOrder>,
+  localNickname?: Maybe<SortOrder>,
+  expires?: Maybe<SortOrder>,
 };
 
 export type ProductTranslation = {
@@ -2460,6 +2478,16 @@ export type ProductTranslation = {
   name: Scalars['String'],
   slug: Scalars['String'],
   description: Scalars['String'],
+  customFields?: Maybe<ProductTranslationCustomFields>,
+};
+
+export type ProductTranslationCustomFields = {
+  __typename?: 'ProductTranslationCustomFields',
+  localNickname?: Maybe<Scalars['String']>,
+};
+
+export type ProductTranslationCustomFieldsInput = {
+  localNickname?: Maybe<Scalars['String']>,
 };
 
 export type ProductTranslationInput = {
@@ -2468,7 +2496,7 @@ export type ProductTranslationInput = {
   name?: Maybe<Scalars['String']>,
   slug?: Maybe<Scalars['String']>,
   description?: Maybe<Scalars['String']>,
-  customFields?: Maybe<Scalars['JSON']>,
+  customFields?: Maybe<ProductTranslationCustomFieldsInput>,
 };
 
 export type ProductVariant = Node & {
@@ -2619,18 +2647,18 @@ export type Query = {
   facets: FacetList,
   facet?: Maybe<Facet>,
   globalSettings: GlobalSettings,
-  order?: Maybe<Order>,
-  orders: OrderList,
   job?: Maybe<JobInfo>,
   jobs: Array<JobInfo>,
-  productOptionGroups: Array<ProductOptionGroup>,
-  productOptionGroup?: Maybe<ProductOptionGroup>,
+  order?: Maybe<Order>,
+  orders: OrderList,
   paymentMethods: PaymentMethodList,
   paymentMethod?: Maybe<PaymentMethod>,
+  productOptionGroups: Array<ProductOptionGroup>,
+  productOptionGroup?: Maybe<ProductOptionGroup>,
+  search: SearchResponse,
   products: ProductList,
   /** Get a Product either by id or slug. If neither id nor slug is speicified, an error will result. */
   product?: Maybe<Product>,
-  search: SearchResponse,
   promotion?: Maybe<Promotion>,
   promotions: PromotionList,
   adjustmentOperations: AdjustmentOperations,
@@ -2723,6 +2751,16 @@ export type QueryFacetArgs = {
 };
 
 
+export type QueryJobArgs = {
+  jobId: Scalars['String']
+};
+
+
+export type QueryJobsArgs = {
+  input?: Maybe<JobListInput>
+};
+
+
 export type QueryOrderArgs = {
   id: Scalars['ID']
 };
@@ -2733,13 +2771,13 @@ export type QueryOrdersArgs = {
 };
 
 
-export type QueryJobArgs = {
-  jobId: Scalars['String']
+export type QueryPaymentMethodsArgs = {
+  options?: Maybe<PaymentMethodListOptions>
 };
 
 
-export type QueryJobsArgs = {
-  input?: Maybe<JobListInput>
+export type QueryPaymentMethodArgs = {
+  id: Scalars['ID']
 };
 
 
@@ -2755,13 +2793,8 @@ export type QueryProductOptionGroupArgs = {
 };
 
 
-export type QueryPaymentMethodsArgs = {
-  options?: Maybe<PaymentMethodListOptions>
-};
-
-
-export type QueryPaymentMethodArgs = {
-  id: Scalars['ID']
+export type QuerySearchArgs = {
+  input: SearchInput
 };
 
 
@@ -2778,11 +2811,6 @@ export type QueryProductArgs = {
 };
 
 
-export type QuerySearchArgs = {
-  input: SearchInput
-};
-
-
 export type QueryPromotionArgs = {
   id: Scalars['ID']
 };
@@ -3090,6 +3118,13 @@ export type StringCustomFieldConfig = CustomField & {
   label?: Maybe<Array<LocalizedString>>,
   description?: Maybe<Array<LocalizedString>>,
   pattern?: Maybe<Scalars['String']>,
+  options?: Maybe<Array<StringFieldOption>>,
+};
+
+export type StringFieldOption = {
+  __typename?: 'StringFieldOption',
+  value: Scalars['String'],
+  label?: Maybe<Array<LocalizedString>>,
 };
 
 export type StringOperators = {
@@ -3244,6 +3279,11 @@ export type UpdatePaymentMethodInput = {
   configArgs?: Maybe<Array<ConfigArgInput>>,
 };
 
+export type UpdateProductCustomFieldsInput = {
+  nickname?: Maybe<Scalars['String']>,
+  expires?: Maybe<Scalars['DateTime']>,
+};
+
 export type UpdateProductInput = {
   id: Scalars['ID'],
   enabled?: Maybe<Scalars['Boolean']>,
@@ -3251,7 +3291,7 @@ export type UpdateProductInput = {
   assetIds?: Maybe<Array<Scalars['ID']>>,
   facetValueIds?: Maybe<Array<Scalars['ID']>>,
   translations?: Maybe<Array<ProductTranslationInput>>,
-  customFields?: Maybe<Scalars['JSON']>,
+  customFields?: Maybe<UpdateProductCustomFieldsInput>,
 };
 
 export type UpdateProductOptionGroupInput = {

+ 69 - 30
packages/core/e2e/custom-fields.e2e-spec.ts

@@ -49,18 +49,34 @@ describe('Custom fields', () => {
                             min: '2019-01-01T08:30',
                             max: '2019-06-01T08:30',
                         },
-                        { name: 'validateFn1', type: 'string', validate: value => {
+                        {
+                            name: 'validateFn1',
+                            type: 'string',
+                            validate: value => {
                                 if (value !== 'valid') {
                                     return `The value ['${value}'] is not valid`;
                                 }
                             },
                         },
-                        { name: 'validateFn2', type: 'string', validate: value => {
+                        {
+                            name: 'validateFn2',
+                            type: 'string',
+                            validate: value => {
                                 if (value !== 'valid') {
-                                    return [{ languageCode: LanguageCode.en, value: `The value ['${value}'] is not valid` }];
+                                    return [
+                                        {
+                                            languageCode: LanguageCode.en,
+                                            value: `The value ['${value}'] is not valid`,
+                                        },
+                                    ];
                                 }
                             },
                         },
+                        {
+                            name: 'stringWithOptions',
+                            type: 'string',
+                            options: [{ value: 'small' }, { value: 'medium' }, { value: 'large' }],
+                        },
                     ],
                 },
             },
@@ -108,6 +124,7 @@ describe('Custom fields', () => {
                 { name: 'validateDateTime', type: 'datetime' },
                 { name: 'validateFn1', type: 'string' },
                 { name: 'validateFn2', type: 'string' },
+                { name: 'stringWithOptions', type: 'string' },
             ],
         });
     });
@@ -193,19 +210,51 @@ describe('Custom fields', () => {
             }, `The custom field value ['hello'] does not match the pattern [^[0-9][a-z]+$]`),
         );
 
+        it(
+            'invalid string option',
+            assertThrowsWithMessage(async () => {
+                await adminClient.query(gql`
+                    mutation {
+                        updateProduct(input: { id: "T_1", customFields: { stringWithOptions: "tiny" } }) {
+                            id
+                        }
+                    }
+                `);
+            }, `The custom field value ['tiny'] is invalid. Valid options are ['small', 'medium', 'large']`),
+        );
+
+        it(
+            'valid string option', async () => {
+                const { updateProduct } = await adminClient.query(gql`
+                    mutation {
+                        updateProduct(input: { id: "T_1", customFields: { stringWithOptions: "medium" } }) {
+                            id
+                            customFields {
+                                stringWithOptions
+                            }
+                        }
+                    }
+                `);
+                expect(updateProduct.customFields.stringWithOptions).toBe('medium');
+            });
+
         it(
             'invalid localeString',
             assertThrowsWithMessage(async () => {
                 await adminClient.query(gql`
                     mutation {
-                        updateProduct(input: {
-                            id: "T_1"
-                            translations: [{
+                        updateProduct(
+                            input: {
                                 id: "T_1"
-                                languageCode: en,
-                                customFields: { validateLocaleString: "servus" }
-                            }]
-                        }) {
+                                translations: [
+                                    {
+                                        id: "T_1"
+                                        languageCode: en
+                                        customFields: { validateLocaleString: "servus" }
+                                    }
+                                ]
+                            }
+                        ) {
                             id
                         }
                     }
@@ -218,10 +267,7 @@ describe('Custom fields', () => {
             assertThrowsWithMessage(async () => {
                 await adminClient.query(gql`
                     mutation {
-                        updateProduct(input: {
-                            id: "T_1"
-                            customFields: { validateInt: 12 }
-                        }) {
+                        updateProduct(input: { id: "T_1", customFields: { validateInt: 12 } }) {
                             id
                         }
                     }
@@ -234,10 +280,7 @@ describe('Custom fields', () => {
             assertThrowsWithMessage(async () => {
                 await adminClient.query(gql`
                     mutation {
-                        updateProduct(input: {
-                            id: "T_1"
-                            customFields: { validateFloat: 10.6 }
-                        }) {
+                        updateProduct(input: { id: "T_1", customFields: { validateFloat: 10.6 } }) {
                             id
                         }
                     }
@@ -250,10 +293,12 @@ describe('Custom fields', () => {
             assertThrowsWithMessage(async () => {
                 await adminClient.query(gql`
                     mutation {
-                        updateProduct(input: {
-                            id: "T_1"
-                            customFields: { validateDateTime: "2019-01-01T05:25:00.000Z" }
-                        }) {
+                        updateProduct(
+                            input: {
+                                id: "T_1"
+                                customFields: { validateDateTime: "2019-01-01T05:25:00.000Z" }
+                            }
+                        ) {
                             id
                         }
                     }
@@ -266,10 +311,7 @@ describe('Custom fields', () => {
             assertThrowsWithMessage(async () => {
                 await adminClient.query(gql`
                     mutation {
-                        updateProduct(input: {
-                            id: "T_1"
-                            customFields: { validateFn1: "invalid" }
-                        }) {
+                        updateProduct(input: { id: "T_1", customFields: { validateFn1: "invalid" } }) {
                             id
                         }
                     }
@@ -282,10 +324,7 @@ describe('Custom fields', () => {
             assertThrowsWithMessage(async () => {
                 await adminClient.query(gql`
                     mutation {
-                        updateProduct(input: {
-                            id: "T_1"
-                            customFields: { validateFn2: "invalid" }
-                        }) {
+                        updateProduct(input: { id: "T_1", customFields: { validateFn2: "invalid" } }) {
                             id
                         }
                     }

+ 70 - 30
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -471,12 +471,17 @@ export type CreateGroupOptionInput = {
     translations: Array<ProductOptionGroupTranslationInput>;
 };
 
+export type CreateProductCustomFieldsInput = {
+    nickname?: Maybe<Scalars['String']>;
+    expires?: Maybe<Scalars['DateTime']>;
+};
+
 export type CreateProductInput = {
     featuredAssetId?: Maybe<Scalars['ID']>;
     assetIds?: Maybe<Array<Scalars['ID']>>;
     facetValueIds?: Maybe<Array<Scalars['ID']>>;
     translations: Array<ProductTranslationInput>;
-    customFields?: Maybe<Scalars['JSON']>;
+    customFields?: Maybe<CreateProductCustomFieldsInput>;
 };
 
 export type CreateProductOptionGroupInput = {
@@ -1700,6 +1705,8 @@ export type Mutation = {
     refundOrder: Refund;
     settleRefund: Refund;
     addNoteToOrder: Order;
+    /** Update an existing PaymentMethod */
+    updatePaymentMethod: PaymentMethod;
     /** Create a new ProductOptionGroup */
     createProductOptionGroup: ProductOptionGroup;
     /** Update an existing ProductOptionGroup */
@@ -1708,8 +1715,7 @@ export type Mutation = {
     createProductOption: ProductOption;
     /** Create a new ProductOption within a ProductOptionGroup */
     updateProductOption: ProductOption;
-    /** Update an existing PaymentMethod */
-    updatePaymentMethod: PaymentMethod;
+    reindex: JobInfo;
     /** Create a new Product */
     createProduct: Product;
     /** Update an existing Product */
@@ -1726,7 +1732,6 @@ export type Mutation = {
     updateProductVariants: Array<Maybe<ProductVariant>>;
     /** Delete a ProductVariant */
     deleteProductVariant: DeletionResponse;
-    reindex: JobInfo;
     createPromotion: Promotion;
     updatePromotion: Promotion;
     deletePromotion: DeletionResponse;
@@ -1919,6 +1924,10 @@ export type MutationAddNoteToOrderArgs = {
     input: AddNoteToOrderInput;
 };
 
+export type MutationUpdatePaymentMethodArgs = {
+    input: UpdatePaymentMethodInput;
+};
+
 export type MutationCreateProductOptionGroupArgs = {
     input: CreateProductOptionGroupInput;
 };
@@ -1935,10 +1944,6 @@ export type MutationUpdateProductOptionArgs = {
     input: UpdateProductOptionInput;
 };
 
-export type MutationUpdatePaymentMethodArgs = {
-    input: UpdatePaymentMethodInput;
-};
-
 export type MutationCreateProductArgs = {
     input: CreateProductInput;
 };
@@ -2293,7 +2298,14 @@ export type Product = Node & {
     translations: Array<ProductTranslation>;
     collections: Array<Collection>;
     enabled: Scalars['Boolean'];
-    customFields?: Maybe<Scalars['JSON']>;
+    customFields?: Maybe<ProductCustomFields>;
+};
+
+export type ProductCustomFields = {
+    __typename?: 'ProductCustomFields';
+    nickname?: Maybe<Scalars['String']>;
+    localNickname?: Maybe<Scalars['String']>;
+    expires?: Maybe<Scalars['DateTime']>;
 };
 
 export type ProductFilterParameter = {
@@ -2304,6 +2316,9 @@ export type ProductFilterParameter = {
     slug?: Maybe<StringOperators>;
     description?: Maybe<StringOperators>;
     enabled?: Maybe<BooleanOperators>;
+    nickname?: Maybe<StringOperators>;
+    localNickname?: Maybe<StringOperators>;
+    expires?: Maybe<DateOperators>;
 };
 
 export type ProductList = PaginatedList & {
@@ -2384,6 +2399,9 @@ export type ProductSortParameter = {
     name?: Maybe<SortOrder>;
     slug?: Maybe<SortOrder>;
     description?: Maybe<SortOrder>;
+    nickname?: Maybe<SortOrder>;
+    localNickname?: Maybe<SortOrder>;
+    expires?: Maybe<SortOrder>;
 };
 
 export type ProductTranslation = {
@@ -2395,6 +2413,16 @@ export type ProductTranslation = {
     name: Scalars['String'];
     slug: Scalars['String'];
     description: Scalars['String'];
+    customFields?: Maybe<ProductTranslationCustomFields>;
+};
+
+export type ProductTranslationCustomFields = {
+    __typename?: 'ProductTranslationCustomFields';
+    localNickname?: Maybe<Scalars['String']>;
+};
+
+export type ProductTranslationCustomFieldsInput = {
+    localNickname?: Maybe<Scalars['String']>;
 };
 
 export type ProductTranslationInput = {
@@ -2403,7 +2431,7 @@ export type ProductTranslationInput = {
     name?: Maybe<Scalars['String']>;
     slug?: Maybe<Scalars['String']>;
     description?: Maybe<Scalars['String']>;
-    customFields?: Maybe<Scalars['JSON']>;
+    customFields?: Maybe<ProductTranslationCustomFieldsInput>;
 };
 
 export type ProductVariant = Node & {
@@ -2553,18 +2581,18 @@ export type Query = {
     facets: FacetList;
     facet?: Maybe<Facet>;
     globalSettings: GlobalSettings;
-    order?: Maybe<Order>;
-    orders: OrderList;
     job?: Maybe<JobInfo>;
     jobs: Array<JobInfo>;
-    productOptionGroups: Array<ProductOptionGroup>;
-    productOptionGroup?: Maybe<ProductOptionGroup>;
+    order?: Maybe<Order>;
+    orders: OrderList;
     paymentMethods: PaymentMethodList;
     paymentMethod?: Maybe<PaymentMethod>;
+    productOptionGroups: Array<ProductOptionGroup>;
+    productOptionGroup?: Maybe<ProductOptionGroup>;
+    search: SearchResponse;
     products: ProductList;
     /** Get a Product either by id or slug. If neither id nor slug is speicified, an error will result. */
     product?: Maybe<Product>;
-    search: SearchResponse;
     promotion?: Maybe<Promotion>;
     promotions: PromotionList;
     adjustmentOperations: AdjustmentOperations;
@@ -2642,6 +2670,14 @@ export type QueryFacetArgs = {
     languageCode?: Maybe<LanguageCode>;
 };
 
+export type QueryJobArgs = {
+    jobId: Scalars['String'];
+};
+
+export type QueryJobsArgs = {
+    input?: Maybe<JobListInput>;
+};
+
 export type QueryOrderArgs = {
     id: Scalars['ID'];
 };
@@ -2650,12 +2686,12 @@ export type QueryOrdersArgs = {
     options?: Maybe<OrderListOptions>;
 };
 
-export type QueryJobArgs = {
-    jobId: Scalars['String'];
+export type QueryPaymentMethodsArgs = {
+    options?: Maybe<PaymentMethodListOptions>;
 };
 
-export type QueryJobsArgs = {
-    input?: Maybe<JobListInput>;
+export type QueryPaymentMethodArgs = {
+    id: Scalars['ID'];
 };
 
 export type QueryProductOptionGroupsArgs = {
@@ -2668,12 +2704,8 @@ export type QueryProductOptionGroupArgs = {
     languageCode?: Maybe<LanguageCode>;
 };
 
-export type QueryPaymentMethodsArgs = {
-    options?: Maybe<PaymentMethodListOptions>;
-};
-
-export type QueryPaymentMethodArgs = {
-    id: Scalars['ID'];
+export type QuerySearchArgs = {
+    input: SearchInput;
 };
 
 export type QueryProductsArgs = {
@@ -2687,10 +2719,6 @@ export type QueryProductArgs = {
     languageCode?: Maybe<LanguageCode>;
 };
 
-export type QuerySearchArgs = {
-    input: SearchInput;
-};
-
 export type QueryPromotionArgs = {
     id: Scalars['ID'];
 };
@@ -2992,6 +3020,13 @@ export type StringCustomFieldConfig = CustomField & {
     label?: Maybe<Array<LocalizedString>>;
     description?: Maybe<Array<LocalizedString>>;
     pattern?: Maybe<Scalars['String']>;
+    options?: Maybe<Array<StringFieldOption>>;
+};
+
+export type StringFieldOption = {
+    __typename?: 'StringFieldOption';
+    value: Scalars['String'];
+    label?: Maybe<Array<LocalizedString>>;
 };
 
 export type StringOperators = {
@@ -3146,6 +3181,11 @@ export type UpdatePaymentMethodInput = {
     configArgs?: Maybe<Array<ConfigArgInput>>;
 };
 
+export type UpdateProductCustomFieldsInput = {
+    nickname?: Maybe<Scalars['String']>;
+    expires?: Maybe<Scalars['DateTime']>;
+};
+
 export type UpdateProductInput = {
     id: Scalars['ID'];
     enabled?: Maybe<Scalars['Boolean']>;
@@ -3153,7 +3193,7 @@ export type UpdateProductInput = {
     assetIds?: Maybe<Array<Scalars['ID']>>;
     facetValueIds?: Maybe<Array<Scalars['ID']>>;
     translations?: Maybe<Array<ProductTranslationInput>>;
-    customFields?: Maybe<Scalars['JSON']>;
+    customFields?: Maybe<UpdateProductCustomFieldsInput>;
 };
 
 export type UpdateProductOptionGroupInput = {

+ 27 - 1
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -1651,7 +1651,14 @@ export type Product = Node & {
     facetValues: Array<FacetValue>;
     translations: Array<ProductTranslation>;
     collections: Array<Collection>;
-    customFields?: Maybe<Scalars['JSON']>;
+    customFields?: Maybe<ProductCustomFields>;
+};
+
+export type ProductCustomFields = {
+    __typename?: 'ProductCustomFields';
+    nickname?: Maybe<Scalars['String']>;
+    localNickname?: Maybe<Scalars['String']>;
+    expires?: Maybe<Scalars['DateTime']>;
 };
 
 export type ProductFilterParameter = {
@@ -1661,6 +1668,9 @@ export type ProductFilterParameter = {
     name?: Maybe<StringOperators>;
     slug?: Maybe<StringOperators>;
     description?: Maybe<StringOperators>;
+    nickname?: Maybe<StringOperators>;
+    localNickname?: Maybe<StringOperators>;
+    expires?: Maybe<DateOperators>;
 };
 
 export type ProductList = PaginatedList & {
@@ -1727,6 +1737,9 @@ export type ProductSortParameter = {
     name?: Maybe<SortOrder>;
     slug?: Maybe<SortOrder>;
     description?: Maybe<SortOrder>;
+    nickname?: Maybe<SortOrder>;
+    localNickname?: Maybe<SortOrder>;
+    expires?: Maybe<SortOrder>;
 };
 
 export type ProductTranslation = {
@@ -1738,6 +1751,12 @@ export type ProductTranslation = {
     name: Scalars['String'];
     slug: Scalars['String'];
     description: Scalars['String'];
+    customFields?: Maybe<ProductTranslationCustomFields>;
+};
+
+export type ProductTranslationCustomFields = {
+    __typename?: 'ProductTranslationCustomFields';
+    localNickname?: Maybe<Scalars['String']>;
 };
 
 export type ProductVariant = Node & {
@@ -2082,6 +2101,13 @@ export type StringCustomFieldConfig = CustomField & {
     label?: Maybe<Array<LocalizedString>>;
     description?: Maybe<Array<LocalizedString>>;
     pattern?: Maybe<Scalars['String']>;
+    options?: Maybe<Array<StringFieldOption>>;
+};
+
+export type StringFieldOption = {
+    __typename?: 'StringFieldOption';
+    value: Scalars['String'];
+    label?: Maybe<Array<LocalizedString>>;
 };
 
 export type StringOperators = {

+ 23 - 0
packages/core/src/api/common/validate-custom-field-value.spec.ts

@@ -25,6 +25,29 @@ describe('validateCustomFieldValue()', () => {
         });
     });
 
+    describe('string options', () => {
+
+        const validate = (value: string) => () => validateCustomFieldValue({
+            name: 'test',
+            type: 'string',
+            options: [
+                { value: 'small' },
+                { value: 'large' },
+            ],
+        }, value);
+
+        it('passes valid option', () => {
+            expect(validate('small')).not.toThrow();
+            expect(validate('large')).not.toThrow();
+        });
+
+        it('throws on invalid option', () => {
+            expect(validate('SMALL')).toThrowError('error.field-invalid-string-option');
+            expect(validate('')).toThrowError('error.field-invalid-string-option');
+            expect(validate('bad')).toThrowError('error.field-invalid-string-option');
+        });
+    });
+
     describe('int & float', () => {
 
         const validate = (value: number) => () => validateCustomFieldValue({

+ 10 - 0
packages/core/src/api/common/validate-custom-field-value.ts

@@ -59,6 +59,16 @@ function validateStringField(config: StringCustomFieldConfig | LocaleStringCusto
             throw new UserInputError('error.field-invalid-string-pattern', { value, pattern });
         }
     }
+    const options = (config as StringCustomFieldConfig).options;
+    if (options) {
+        const validOptions = options.map(o => o.value);
+        if (!validOptions.includes(value)) {
+            throw new UserInputError('error.field-invalid-string-option', {
+                value,
+                validOptions: validOptions.map(o => `'${o}'`).join(', '),
+            });
+        }
+    }
 }
 
 function validateNumberField(config: IntCustomFieldConfig | FloatCustomFieldConfig, value: number): void {

+ 7 - 0
packages/core/src/api/schema/common/custom-field-types.graphql

@@ -11,7 +11,14 @@ type StringCustomFieldConfig implements CustomField {
     label: [LocalizedString!]
     description: [LocalizedString!]
     pattern: String
+    options: [StringFieldOption!]
 }
+
+type StringFieldOption {
+    value: String!
+    label: [LocalizedString!]
+}
+
 type LocaleStringCustomFieldConfig implements CustomField {
     name: String!
     type: String!

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

@@ -25,6 +25,7 @@
     "field-invalid-datetime-range-min": "The custom field value [{ value }] is less than the minimum [{ min }]",
     "field-invalid-number-range-max": "The custom field value [{ value }] is greater than the maximum [{ max }]",
     "field-invalid-number-range-min": "The custom field value [{ value }] is less than the minimum [{ min }]",
+    "field-invalid-string-option": "The custom field value ['{ value }'] is invalid. Valid options are [{ validOptions }]",
     "field-invalid-string-pattern": "The custom field value ['{ value }'] does not match the pattern [{ pattern }]",
     "forbidden": "You are not currently authorized to perform this action",
     "identifier-change-token-not-recognized": "Identifier change token not recognized",

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
schema-admin.json


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
schema-shop.json


Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно