Parcourir la source

Merge branch 'minor' into major

Michael Bromley il y a 3 ans
Parent
commit
62cf50cd46

+ 28 - 0
CHANGELOG.md

@@ -1,3 +1,31 @@
+## 1.9.0 (2022-12-01)
+
+
+#### Fixes
+
+* **admin-ui** Add links according to Unsplash guidelines (#1911) ([e703304](https://github.com/vendure-ecommerce/vendure/commit/e703304)), closes [#1911](https://github.com/vendure-ecommerce/vendure/issues/1911)
+* **core** Fix creation of superadmin ([416e03a](https://github.com/vendure-ecommerce/vendure/commit/416e03a))
+* **core** Importing custom boolean field works as intended (#1908) ([5114563](https://github.com/vendure-ecommerce/vendure/commit/5114563)), closes [#1908](https://github.com/vendure-ecommerce/vendure/issues/1908)
+
+#### Features
+
+* **admin-ui** Add default component for custom history entries ([cd8d5a2](https://github.com/vendure-ecommerce/vendure/commit/cd8d5a2)), closes [#1694](https://github.com/vendure-ecommerce/vendure/issues/1694)
+* **admin-ui** Allow custom components for Customer history timeline ([eeba323](https://github.com/vendure-ecommerce/vendure/commit/eeba323)), closes [#1694](https://github.com/vendure-ecommerce/vendure/issues/1694) [#432](https://github.com/vendure-ecommerce/vendure/issues/432)
+* **admin-ui** Allow custom components for Order history timeline ([fc7bcf1](https://github.com/vendure-ecommerce/vendure/commit/fc7bcf1)), closes [#1694](https://github.com/vendure-ecommerce/vendure/issues/1694) [#432](https://github.com/vendure-ecommerce/vendure/issues/432)
+* **admin-ui** Login UI refresh (#1862) ([72febce](https://github.com/vendure-ecommerce/vendure/commit/72febce)), closes [#1862](https://github.com/vendure-ecommerce/vendure/issues/1862)
+* **core** Add facetValues list query ([ddab719](https://github.com/vendure-ecommerce/vendure/commit/ddab719)), closes [#1404](https://github.com/vendure-ecommerce/vendure/issues/1404)
+* **core** Custom Order/Customer history entries can be defined ([d9e1770](https://github.com/vendure-ecommerce/vendure/commit/d9e1770)), closes [#1694](https://github.com/vendure-ecommerce/vendure/issues/1694) [#432](https://github.com/vendure-ecommerce/vendure/issues/432)
+* **core** Expose config for internal TaxRate cache ([454e905](https://github.com/vendure-ecommerce/vendure/commit/454e905)), closes [#1856](https://github.com/vendure-ecommerce/vendure/issues/1856)
+* **core** Implement ActiveOrderStrategy ([e62009f](https://github.com/vendure-ecommerce/vendure/commit/e62009f)), closes [#1858](https://github.com/vendure-ecommerce/vendure/issues/1858)
+* **core** Implement null filters on PaginatedList queries ([3906cbf](https://github.com/vendure-ecommerce/vendure/commit/3906cbf)), closes [#1490](https://github.com/vendure-ecommerce/vendure/issues/1490)
+* **elasticsearch-plugin** Independently access customMappings (#1909) ([6c1c83a](https://github.com/vendure-ecommerce/vendure/commit/6c1c83a)), closes [#1909](https://github.com/vendure-ecommerce/vendure/issues/1909)
+* **email-plugin** Add support for AWS SES transport (#1877) ([e516660](https://github.com/vendure-ecommerce/vendure/commit/e516660)), closes [#1877](https://github.com/vendure-ecommerce/vendure/issues/1877)
+* **payments-plugin** Use Mollie's Order API (#1884) ([56b8646](https://github.com/vendure-ecommerce/vendure/commit/56b8646)), closes [#1884](https://github.com/vendure-ecommerce/vendure/issues/1884)
+
+#### Perf
+
+* **admin-ui** Lazy-load facet values for selector component ([3350608](https://github.com/vendure-ecommerce/vendure/commit/3350608)), closes [#1404](https://github.com/vendure-ecommerce/vendure/issues/1404)
+
 ## <small>1.8.5 (2022-11-28)</small>
 
 

+ 6 - 0
docs/content/storefront/order-workflow/_index.md

@@ -225,3 +225,9 @@ query OrderByCode($code: String!) {
   }
 }
 ```
+
+## ActiveOrderStrategy
+
+In the above examples, the active Order is always associated with the current session and is therefore implicit - which is why there is no need to pass an ID to each of the above operations.
+
+Sometimes you _do_ want to be able to explicitly specify the Order you wish to operate on. In this case you need to define a custom [ActiveOrderStrategy]({{< relref "active-order-strategy" >}}).

+ 1 - 1
packages/admin-ui-plugin/src/plugin.ts

@@ -246,7 +246,7 @@ export class AdminUiPlugin implements NestModule {
                 'hideVersion',
                 AdminUiPlugin.options.adminUiConfig?.hideVersion || false,
             ),
-            loginImage: AdminUiPlugin.options.adminUiConfig?.loginImage,
+            loginImageUrl: AdminUiPlugin.options.adminUiConfig?.loginImageUrl,
             cancellationReasons: propOrDefault('cancellationReasons', undefined),
         };
     }

+ 1 - 1
packages/admin-ui/src/lib/login/src/components/login/login.component.html

@@ -6,7 +6,7 @@
                     {{ 'common.login-image-title' | translate }}
                 </div>
                 <div class="login-wrapper-image-copyright">
-                    <p *ngIf="imageCreator" class="creator">Photo by  {{ imageCreator }} on Unsplash</p>
+                    <p *ngIf="imageCreator" class="creator">Photo by  <a [href]="imageCreatorUrl" target="_blank">{{ imageCreator }}</a> on <a [href]="imageUnsplashUrl" target="_blank">Unsplash</a></p>
                     <p *ngIf="imageLocation" class="location">{{ imageLocation }}</p>
                 </div>
             </div>

+ 5 - 0
packages/admin-ui/src/lib/login/src/components/login/login.component.scss

@@ -78,6 +78,11 @@
                         color: white;
                         margin: 0 !important;
                     }
+                    
+                    a{
+                        color: white;
+                        text-decoration: underline;
+                    }
                 }
             }
         }

+ 7 - 3
packages/admin-ui/src/lib/login/src/components/login/login.component.ts

@@ -17,10 +17,12 @@ export class LoginComponent {
     brand = getAppConfig().brand;
     hideVendureBranding = getAppConfig().hideVendureBranding;
     hideVersion = getAppConfig().hideVersion;
-    customImageUrl = getAppConfig().loginImage;
+    customImageUrl = getAppConfig().loginImageUrl;
     imageUrl = '';
+    imageUnsplashUrl = '';
     imageLocation = '';
     imageCreator = '';
+    imageCreatorUrl = '';
 
     constructor(private authService: AuthService, private router: Router, private httpClient: HttpClient) {
         if (this.customImageUrl) {
@@ -55,13 +57,15 @@ export class LoginComponent {
             });
     }
 
-    updateImage(res) {
+    updateImage(res: any) {
         const user: any = (res as any).user;
         const location: any = (res as any).location;
 
-        this.imageUrl = (res as any).urls.regular;
+        this.imageUrl = res.urls.regular;
         this.imageCreator = user.name;
         this.imageLocation = location.name;
+        this.imageCreatorUrl = user.links.html;
+        this.imageUnsplashUrl = res.links.html;
     }
 
     /**

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

@@ -305,11 +305,11 @@ export interface AdminUiConfig {
     hideVersion?: boolean;
     /**
      * @description
-     * The custom login image
+     * A url of a custom image to be used on the login screen, to override the images provided by Vendure's login image server.
      *
      * @since 1.9.0
      */
-    loginImage?: string;
+    loginImageUrl?: string;
     /**
      * @description
      * Allows you to provide default reasons for a refund or cancellation. This will be used in the

+ 11 - 0
packages/core/e2e/__snapshots__/import.e2e-spec.ts.snap

@@ -49,6 +49,7 @@ Object {
     Object {
       "assets": Array [],
       "customFields": Object {
+        "valid": null,
         "weight": 100,
       },
       "featuredAsset": null,
@@ -69,6 +70,7 @@ Object {
     Object {
       "assets": Array [],
       "customFields": Object {
+        "valid": null,
         "weight": 100,
       },
       "featuredAsset": null,
@@ -89,6 +91,7 @@ Object {
     Object {
       "assets": Array [],
       "customFields": Object {
+        "valid": null,
         "weight": 100,
       },
       "featuredAsset": null,
@@ -137,6 +140,7 @@ Object {
     Object {
       "assets": Array [],
       "customFields": Object {
+        "valid": null,
         "weight": 300,
       },
       "featuredAsset": null,
@@ -198,6 +202,7 @@ Object {
         },
       ],
       "customFields": Object {
+        "valid": null,
         "weight": 200,
       },
       "featuredAsset": Object {
@@ -230,6 +235,7 @@ Object {
         },
       ],
       "customFields": Object {
+        "valid": null,
         "weight": 200,
       },
       "featuredAsset": Object {
@@ -291,6 +297,7 @@ Object {
     Object {
       "assets": Array [],
       "customFields": Object {
+        "valid": true,
         "weight": 500,
       },
       "featuredAsset": null,
@@ -311,6 +318,7 @@ Object {
     Object {
       "assets": Array [],
       "customFields": Object {
+        "valid": false,
         "weight": 500,
       },
       "featuredAsset": null,
@@ -331,6 +339,7 @@ Object {
     Object {
       "assets": Array [],
       "customFields": Object {
+        "valid": null,
         "weight": 500,
       },
       "featuredAsset": null,
@@ -351,6 +360,7 @@ Object {
     Object {
       "assets": Array [],
       "customFields": Object {
+        "valid": true,
         "weight": 500,
       },
       "featuredAsset": null,
@@ -371,6 +381,7 @@ Object {
     Object {
       "assets": Array [],
       "customFields": Object {
+        "valid": false,
         "weight": null,
       },
       "featuredAsset": null,

+ 12 - 12
packages/core/e2e/fixtures/product-import.csv

@@ -1,13 +1,13 @@
-name                   ,slug                   ,description                         ,assets             ,facets                           ,optionGroups ,optionValues    ,sku   ,price,taxCategory,stockOnHand,trackInventory,variantAssets  ,variantFacets          ,product:pageType,variant:weight,product:owner,product:keywords,product:localName
-Perfect Paper Stretcher,perfect-paper-stretcher,A great device for stretching paper.,"pps1.jpg|pps2.jpg",                                 ,size         ,Half Imperial   ,PPS12 ,45.3 ,standard   ,0          ,false         ,               ,Brand:KB|Type:Accessory,default         ,100           ,"{""id"": 1}",paper|stretching|watercolor,localPPS
-                       ,                       ,                                    ,                   ,                                 ,             ,Quarter Imperial,PPS14 ,32.5 ,standard   ,0          ,false         ,               ,Brand:KB|Type:Accessory,                ,100           ,"{""id"": 1}",,
-                       ,                       ,                                    ,                   ,                                 ,             ,Full Imperial   ,PPSF  ,59.5 ,standard   ,-10        ,false         ,               ,Brand:KB|Type:Accessory,                ,100           ,"{""id"": 1}",,
-Mabef M/02 Studio Easel,                       ,Mabef description                   ,                   ,                                 ,             ,                ,M02   ,910.7,standard   ,100        ,false         ,               ,Brand:Mabef|Type:Easel ,expanded        ,300           ,"{""id"": 1}",,localMabef
-Giotto Mega Pencils    ,                       ,Really mega pencils                 ,                   ,                                 ,box size     ,Box of 8        ,225400,4.16 ,standard   ,           ,false         ,"box-of-8.jpg" ,Collection:Xmas Sale   ,default         ,200           ,"{""id"": 1}",,localGiotto
-                       ,                       ,                                    ,                   ,                                 ,             ,Box of 12       ,225600,6.24 ,standard   ,           ,false         ,"box-of-12.jpg",Collection:Xmas Sale   ,                ,200           ,"{""id"": 1}",,
+name                   ,slug                   ,description                         ,assets             ,facets                           ,optionGroups ,optionValues    ,sku   ,price,taxCategory,stockOnHand,trackInventory,variantAssets  ,variantFacets          ,product:pageType,variant:weight,product:owner,product:keywords           ,product:localName,variant:valid
+Perfect Paper Stretcher,perfect-paper-stretcher,A great device for stretching paper.,"pps1.jpg|pps2.jpg",                                 ,size         ,Half Imperial   ,PPS12 ,45.3 ,standard   ,0          ,false         ,               ,Brand:KB|Type:Accessory,default         ,100           ,"{""id"": 1}",paper|stretching|watercolor,localPPS         ,
+                       ,                       ,                                    ,                   ,                                 ,             ,Quarter Imperial,PPS14 ,32.5 ,standard   ,0          ,false         ,               ,Brand:KB|Type:Accessory,                ,100           ,"{""id"": 1}",                           ,                 ,
+                       ,                       ,                                    ,                   ,                                 ,             ,Full Imperial   ,PPSF  ,59.5 ,standard   ,-10        ,false         ,               ,Brand:KB|Type:Accessory,                ,100           ,"{""id"": 1}",                           ,                 ,
+Mabef M/02 Studio Easel,                       ,Mabef description                   ,                   ,                                 ,             ,                ,M02   ,910.7,standard   ,100        ,false         ,               ,Brand:Mabef|Type:Easel ,expanded        ,300           ,"{""id"": 1}",                           ,localMabef       ,
+Giotto Mega Pencils    ,                       ,Really mega pencils                 ,                   ,                                 ,box size     ,Box of 8        ,225400,4.16 ,standard   ,           ,false         ,"box-of-8.jpg" ,Collection:Xmas Sale   ,default         ,200           ,"{""id"": 1}",                           ,localGiotto      ,
+                       ,                       ,                                    ,                   ,                                 ,             ,Box of 12       ,225600,6.24 ,standard   ,           ,false         ,"box-of-12.jpg",Collection:Xmas Sale   ,                ,200           ,"{""id"": 1}",                           ,                 ,
 
-Artists Smock          ,                       ,Keeps the paint off the clothes     ,                   ,Material:Denim|Collection:clothes,"size|colour","small|beige"   ,10112 ,11.99,reduced    ,           ,false         ,               ,                       ,default         ,500           ,"{""id"": 1}",apron|clothing,localSmock
-                       ,                       ,                                    ,                   ,                                 ,             ,"large|beige"   ,10113 ,11.99,reduced    ,           ,false         ,               ,                       ,default         ,500           ,"{""id"": 1}",,
-                       ,                       ,                                    ,                   ,                                 ,             ,"small|navy"    ,10114 ,11.99,reduced    ,           ,false         ,               ,                       ,default         ,500           ,"{""id"": 1}",,
-                       ,                       ,                                    ,                   ,                                 ,             ,"large|navy"    ,10115 ,11.99,reduced    ,           ,false         ,               ,                       ,default         ,500           ,"{""id"": 1}",,
-                       ,                       ,                                    ,                   ,                                 ,             ,"large|navy"    ,10115 ,11.99,reduced    ,           ,false         ,               ,                       ,default         ,              ,"{""id"": 1}",,
+Artists Smock          ,                       ,Keeps the paint off the clothes     ,                   ,Material:Denim|Collection:clothes,"size|colour","small|beige"   ,10112 ,11.99,reduced    ,           ,false         ,               ,                       ,default         ,500           ,"{""id"": 1}",apron|clothing             ,localSmock       ,true
+                       ,                       ,                                    ,                   ,                                 ,             ,"large|beige"   ,10113 ,11.99,reduced    ,           ,false         ,               ,                       ,default         ,500           ,"{""id"": 1}",                           ,                 ,false
+                       ,                       ,                                    ,                   ,                                 ,             ,"small|navy"    ,10114 ,11.99,reduced    ,           ,false         ,               ,                       ,default         ,500           ,"{""id"": 1}",                           ,                 ,
+                       ,                       ,                                    ,                   ,                                 ,             ,"large|navy"    ,10115 ,11.99,reduced    ,           ,false         ,               ,                       ,default         ,500           ,"{""id"": 1}",                           ,                 ,true
+                       ,                       ,                                    ,                   ,                                 ,             ,"large|navy"    ,10115 ,11.99,reduced    ,           ,false         ,               ,                       ,default         ,              ,"{""id"": 1}",                           ,                 ,false

+ 2 - 3
packages/core/e2e/fixtures/test-plugins/token-active-order-plugin.ts

@@ -12,10 +12,9 @@ import {
     RequestContext,
     Transaction,
     TransactionalConnection,
+    UserInputError,
     VendurePlugin,
 } from '@vendure/core';
-import { CustomOrderFields } from '@vendure/core/dist/entity/custom-entity-fields';
-import { UserInputError } from 'apollo-server-express';
 import gql from 'graphql-tag';
 
 declare module '@vendure/core/dist/entity/custom-entity-fields' {
@@ -24,7 +23,7 @@ declare module '@vendure/core/dist/entity/custom-entity-fields' {
     }
 }
 
-class TokenActiveOrderStrategy implements ActiveOrderStrategy {
+class TokenActiveOrderStrategy implements ActiveOrderStrategy<{ token: string }> {
     readonly name = 'orderToken';
 
     private connection: TransactionalConnection;

+ 11 - 2
packages/core/e2e/import.e2e-spec.ts

@@ -35,7 +35,10 @@ describe('Import resolver', () => {
                     type: 'localeString',
                 },
             ],
-            ProductVariant: [{ type: 'int', name: 'weight' }],
+            ProductVariant: [
+                { type: 'boolean', name: 'valid' },
+                { type: 'int', name: 'weight' },
+            ],
         },
     });
 
@@ -82,7 +85,7 @@ describe('Import resolver', () => {
         });
 
         expect(result.importProducts.errors).toEqual([
-            'Invalid Record Length: header length is 19, got 1 on line 8',
+            'Invalid Record Length: header length is 20, got 1 on line 8',
         ]);
         expect(result.importProducts.imported).toBe(4);
         expect(result.importProducts.processed).toBe(4);
@@ -176,6 +179,7 @@ describe('Import resolver', () => {
                                     }
                                 }
                                 customFields {
+                                    valid
                                     weight
                                 }
                             }
@@ -238,10 +242,15 @@ describe('Import resolver', () => {
         expect(smock.customFields.owner.id).toBe('T_1');
 
         // Import non-list custom fields
+        expect(smock.variants[0].customFields.valid).toEqual(true);
         expect(smock.variants[0].customFields.weight).toEqual(500);
+        expect(smock.variants[1].customFields.valid).toEqual(false);
         expect(smock.variants[1].customFields.weight).toEqual(500);
+        expect(smock.variants[2].customFields.valid).toEqual(null);
         expect(smock.variants[2].customFields.weight).toEqual(500);
+        expect(smock.variants[3].customFields.valid).toEqual(true);
         expect(smock.variants[3].customFields.weight).toEqual(500);
+        expect(smock.variants[4].customFields.valid).toEqual(false);
         expect(smock.variants[4].customFields.weight).toEqual(null);
 
         // Import list custom fields

+ 4 - 1
packages/core/e2e/populate.e2e-spec.ts

@@ -57,7 +57,10 @@ describe('populate() function', () => {
                     type: 'localeString',
                 },
             ],
-            ProductVariant: [{ type: 'int', name: 'weight' }],
+            ProductVariant: [
+                { type: 'boolean', name: 'valid' },
+                { type: 'int', name: 'weight' },
+            ],
         },
     });
 

+ 117 - 10
packages/core/src/config/order/active-order-strategy.ts

@@ -14,9 +14,118 @@ export const ACTIVE_ORDER_INPUT_FIELD_NAME = 'activeOrderInput';
  * and set it on the current Session, and then read the session to obtain the active Order.
  * This behaviour is defined by the {@link DefaultActiveOrderStrategy}.
  *
+ * The `InputType` generic argument should correspond to the input type defined by the
+ * `defineInputType()` method.
+ *
+ * When `defineInputType()` is used, then the following Shop API operations will receive an additional
+ * `activeOrderInput` argument allowing the active order input to be specified:
+ *
+ * - `activeOrder`
+ * - `eligibleShippingMethods`
+ * - `eligiblePaymentMethods`
+ * - `nextOrderStates`
+ * - `addItemToOrder`
+ * - `adjustOrderLine`
+ * - `removeOrderLine`
+ * - `removeAllOrderLines`
+ * - `applyCouponCode`
+ * - `removeCouponCode`
+ * - `addPaymentToOrder`
+ * - `setCustomerForOrder`
+ * - `setOrderShippingAddress`
+ * - `setOrderBillingAddress`
+ * - `setOrderShippingMethod`
+ * - `setOrderCustomFields`
+ * - `transitionOrderToState`
+ *
+ * @example
+ * ```GraphQL {hl_lines=[5]}
+ * mutation AddItemToOrder {
+ *   addItemToOrder(
+ *     productVariantId: 42,
+ *     quantity: 1,
+ *     activeOrderInput: { token: "123456" }
+ *   ) {
+ *     ...on Order {
+ *       id
+ *       # ...etc
+ *     }
+ *   }
+ * }
+ * ```
+ *
+ * @example
+ * ```TypeScript
+ * import { ID } from '\@vendure/common/lib/shared-types';
+ * import {
+ *   ActiveOrderStrategy,
+ *   CustomerService,
+ *   idsAreEqual,
+ *   Injector,
+ *   Order,
+ *   OrderService,
+ *   RequestContext,
+ *   TransactionalConnection,
+ * } from '\@vendure/core';
+ * import gql from 'graphql-tag';
+ *
+ * // This strategy assumes a "orderToken" custom field is defined on the Order
+ * // entity, and uses that token to perform a lookup to determine the active Order.
+ * //
+ * // Additionally, it does _not_ define a `createActiveOrder()` method, which
+ * // means that a custom mutation would be required to create the initial Order in
+ * // the first place and set the "orderToken" custom field.
+ * class TokenActiveOrderStrategy implements ActiveOrderStrategy<{ token: string }> {
+ *   readonly name = 'orderToken';
+ *
+ *   private connection: TransactionalConnection;
+ *   private orderService: OrderService;
+ *
+ *   init(injector: Injector) {
+ *     this.connection = injector.get(TransactionalConnection);
+ *     this.orderService = injector.get(OrderService);
+ *   }
+ *
+ *   defineInputType = () => gql`
+ *     input OrderTokenActiveOrderInput {
+ *       token: String
+ *     }
+ *   `;
+ *
+ *   async determineActiveOrder(ctx: RequestContext, input: { token: string }) {
+ *     const qb = this.connection
+ *       .getRepository(ctx, Order)
+ *       .createQueryBuilder('order')
+ *       .leftJoinAndSelect('order.customer', 'customer')
+ *       .leftJoinAndSelect('customer.user', 'user')
+ *       .where('order.customFields.orderToken = :orderToken', { orderToken: input.token });
+ *
+ *     const order = await qb.getOne();
+ *     if (!order) {
+ *       return;
+ *     }
+ *     // Ensure the active user is the owner of this Order
+ *     const orderUserId = order.customer && order.customer.user && order.customer.user.id;
+ *     if (order.customer && idsAreEqual(orderUserId, ctx.activeUserId)) {
+ *       return order;
+ *     }
+ *   }
+ * }
+ *
+ * // in vendure-config.ts
+ * export const config = {
+ *   // ...
+ *   orderOptions: {
+ *     activeOrderStrategy: new TokenActiveOrderStrategy(),
+ *   },
+ * }
+ * ```
+ *
  * @since 1.9.0
+ * @docsCategory orders
  */
-export interface ActiveOrderStrategy extends InjectableStrategy {
+export interface ActiveOrderStrategy<InputType extends Record<string, any> | void = void>
+    extends InjectableStrategy {
     /**
      * @description
      * The name of the strategy, e.g. "orderByToken", which will also be used as the
@@ -26,10 +135,8 @@ export interface ActiveOrderStrategy extends InjectableStrategy {
 
     /**
      * @description
-     * Defines the type of the GraphQL Input object expected by the `authenticate`
-     * mutation. The final input object will be a map, with the key being the name
-     * of the strategy. The shape of the input object should match the generic `Data`
-     * type argument.
+     * Defines the type of the GraphQL Input object expected by the `activeOrderInput`
+     * input argument.
      *
      * @example
      * For example, given the following:
@@ -44,8 +151,8 @@ export interface ActiveOrderStrategy extends InjectableStrategy {
      * }
      * ```
      *
-     * assuming the strategy name is "my_auth", then the resulting call to `authenticate`
-     * would look like:
+     * assuming the strategy name is "orderByToken", then the resulting call to `activeOrder` (or any of the other
+     * affected Shop API operations) would look like:
      *
      * ```GraphQL
      * activeOrder(activeOrderInput: {
@@ -70,15 +177,15 @@ export interface ActiveOrderStrategy extends InjectableStrategy {
      * If automatic creation of an Order does not make sense in your strategy, then leave this method
      * undefined. You'll then need to take care of creating an order manually by defining a custom mutation.
      */
-    createActiveOrder?: (ctx: RequestContext, inputs: any) => Promise<Order>;
+    createActiveOrder?: (ctx: RequestContext, input: InputType) => Promise<Order>;
 
     /**
      * @description
      * This method is used to determine the active Order based on the current RequestContext in addition to any
      * input values provided, as defined by the `defineInputType` method of this strategy.
      *
-     * Note that this method is invoked frequently so you should aim to keep it efficient. The returned Order,
+     * Note that this method is invoked frequently, so you should aim to keep it efficient. The returned Order,
      * for example, does not need to have its various relations joined.
      */
-    determineActiveOrder(ctx: RequestContext, inputs: any): Promise<Order | undefined>;
+    determineActiveOrder(ctx: RequestContext, input: InputType): Promise<Order | undefined>;
 }

+ 1 - 2
packages/core/src/config/order/default-active-order-strategy.ts

@@ -3,8 +3,6 @@ import { InternalServerError } from '../../common/error/errors';
 import { Injector } from '../../common/injector';
 import { TransactionalConnection } from '../../connection/transactional-connection';
 import { Order } from '../../entity/order/order.entity';
-// import { OrderService } from '../../service/services/order.service';
-// import { SessionService } from '../../service/services/session.service';
 
 import { ActiveOrderStrategy } from './active-order-strategy';
 
@@ -15,6 +13,7 @@ import { ActiveOrderStrategy } from './active-order-strategy';
  * session which is part of the RequestContext.
  *
  * @since 1.9.0
+ * @docsCategory orders
  */
 export class DefaultActiveOrderStrategy implements ActiveOrderStrategy {
     private connection: TransactionalConnection;

+ 1 - 1
packages/core/src/config/vendure-config.ts

@@ -569,7 +569,7 @@ export interface OrderOptions {
      * @since 1.9.0
      * @default DefaultActiveOrderStrategy
      */
-    activeOrderStrategy?: ActiveOrderStrategy | ActiveOrderStrategy[];
+    activeOrderStrategy?: ActiveOrderStrategy<any> | Array<ActiveOrderStrategy<any>>;
 }
 
 /**

+ 3 - 1
packages/core/src/data-import/providers/importer/importer.ts

@@ -364,11 +364,13 @@ export class Importer {
     }
 
     private processCustomFieldValues(customFields: { [field: string]: string }, config: CustomFieldConfig[]) {
-        const processed: { [field: string]: string | string[] | undefined } = {};
+        const processed: { [field: string]: string | string[] | boolean | undefined } = {};
         for (const fieldDef of config) {
             const value = customFields[fieldDef.name];
             if (fieldDef.list === true) {
                 processed[fieldDef.name] = value?.split('|').filter(val => val.trim() !== '');
+            } else if (fieldDef.type === 'boolean') {
+                processed[fieldDef.name] = value ? value.toLowerCase() === 'true' : undefined;
             } else {
                 processed[fieldDef.name] = value ? value : undefined;
             }

+ 2 - 2
packages/core/src/service/helpers/active-order/active-order.service.ts

@@ -87,7 +87,7 @@ export class ActiveOrderService {
     ): Promise<Order>;
     async getActiveOrder(
         ctx: RequestContext,
-        input: { [strategyName: string]: any } | undefined,
+        input: { [strategyName: string]: Record<string, any> | undefined } | undefined,
         createIfNotExists = false,
     ): Promise<Order | undefined> {
         let order: any;
@@ -103,7 +103,7 @@ export class ActiveOrderService {
                     break;
                 }
                 if (createIfNotExists && typeof strategy.createActiveOrder === 'function') {
-                    order = await strategy.createActiveOrder(ctx, input);
+                    order = await strategy.createActiveOrder(ctx, strategyInput);
                 }
                 if (order) {
                     break;

+ 0 - 1
packages/core/vendure-import-error.log

@@ -1 +0,0 @@
-Invalid Record Length: header length is 19, got 1 on line 8

+ 15 - 0
packages/elasticsearch-plugin/e2e/elasticsearch-plugin.e2e-spec.ts

@@ -1358,10 +1358,19 @@ describe('Elasticsearch plugin', () => {
             search(input: { take: 1, groupByProduct: false, sort: { name: ASC } }) {
                 items {
                   productVariantName
+                  customProductVariantMappings {
+                    inStock
+                  }
+                  customProductMappings {
+                    answer
+                  }
                   customMappings {
                     ...on CustomProductVariantMappings {
                       inStock
                     }
+                    ...on CustomProductMappings {
+                        answer
+                    }
                   }
                 }
               }
@@ -1370,6 +1379,12 @@ describe('Elasticsearch plugin', () => {
 
             expect(search.items[0]).toEqual({
                 productVariantName: 'Bonsai Tree',
+                customProductVariantMappings: {
+                    inStock: false,
+                },
+                customProductMappings: {
+                    answer: 42,
+                },
                 customMappings: {
                     inStock: false,
                 },

+ 7 - 3
packages/elasticsearch-plugin/src/api/api-extensions.ts

@@ -126,19 +126,23 @@ function generateCustomMappingTypes(options: ElasticsearchOptions): DocumentNode
                 union CustomMappings = CustomProductMappings | CustomProductVariantMappings
 
                 extend type SearchResult {
-                    customMappings: CustomMappings!
+                    customMappings: CustomMappings! @deprecated(reason: "Use customProductMappings or customProductVariantMappings")
+                    customProductMappings: CustomProductMappings!
+                    customProductVariantMappings: CustomProductVariantMappings!
                 }
             `;
         } else if (productMappings.length) {
             sdl += `
                 extend type SearchResult {
-                    customMappings: CustomProductMappings!
+                    customMappings: CustomProductMappings! @deprecated(reason: "Use customProductMappings or customProductVariantMappings")
+                    customProductMappings: CustomProductMappings!
                 }
             `;
         } else if (variantMappings.length) {
             sdl += `
                 extend type SearchResult {
-                    customMappings: CustomProductVariantMappings!
+                    customMappings: CustomProductVariantMappings! @deprecated(reason: "Use customProductMappings or customProductVariantMappings")
+                    customProductVariantMappings: CustomProductVariantMappings!
                 }
             `;
         }

+ 29 - 9
packages/elasticsearch-plugin/src/elasticsearch.service.ts

@@ -505,6 +505,7 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
         ElasticsearchService.addCustomMappings(
             result,
             source,
+            this.options.customProductMappings,
             this.options.customProductVariantMappings,
             false,
         );
@@ -547,7 +548,13 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
             inStock: source.productInStock,
             score: hit._score || 0,
         };
-        ElasticsearchService.addCustomMappings(result, source, this.options.customProductMappings, true);
+        ElasticsearchService.addCustomMappings(
+            result,
+            source,
+            this.options.customProductMappings,
+            this.options.customProductVariantMappings,
+            true,
+        );
         ElasticsearchService.addScriptMappings(
             result,
             fields,
@@ -581,18 +588,31 @@ export class ElasticsearchService implements OnModuleInit, OnModuleDestroy {
     private static addCustomMappings(
         result: any,
         source: any,
-        mappings: { [fieldName: string]: CustomMapping<any> },
+        productMappings: { [fieldName: string]: CustomMapping<any> },
+        variantMappings: { [fieldName: string]: CustomMapping<any> },
         groupByProduct: boolean,
     ): any {
-        const customMappings = Object.keys(mappings);
-        if (customMappings.length) {
+        const productCustomMappings = Object.keys(productMappings);
+        if (productCustomMappings.length) {
             const customMappingsResult: any = {};
-            for (const name of customMappings) {
-                customMappingsResult[name] = (source as any)[
-                    groupByProduct ? `product-${name}` : `variant-${name}`
-                ];
+            for (const name of productCustomMappings) {
+                customMappingsResult[name] = (source as any)[`product-${name}`];
+            }
+            (result as any).customProductMappings = customMappingsResult;
+            if (groupByProduct) {
+                (result as any).customMappings = customMappingsResult;
+            }
+        }
+        const variantCustomMappings = Object.keys(variantMappings);
+        if (variantCustomMappings.length) {
+            const customMappingsResult: any = {};
+            for (const name of variantCustomMappings) {
+                customMappingsResult[name] = (source as any)[`variant-${name}`];
+            }
+            (result as any).customProductVariantMappings = customMappingsResult;
+            if (!groupByProduct) {
+                (result as any).customMappings = customMappingsResult;
             }
-            (result as any).customMappings = customMappingsResult;
         }
         return result;
     }

+ 12 - 3
packages/elasticsearch-plugin/src/options.ts

@@ -165,7 +165,7 @@ export interface ElasticsearchOptions {
      * @description
      * Custom mappings may be defined which will add the defined data to the
      * Elasticsearch index and expose that data via the SearchResult GraphQL type,
-     * adding a new `customMappings` field.
+     * adding a new `customMappings`, `customProductMappings` & `customProductVariantMappings` fields.
      *
      * The `graphQlType` property may be one of `String`, `Int`, `Float`, `Boolean`, `ID` or list
      * versions thereof (`[String!]` etc) and can be appended with a `!` to indicate non-nullable fields.
@@ -175,7 +175,8 @@ export interface ElasticsearchOptions {
      * parsed to the elasticsearch index.
      *
      * This config option defines custom mappings which are accessible when the "groupByProduct"
-     * input options is set to `true`.
+     * input options is set to `true`. In addition, custom variant mappings can be accessed by using
+     * the `customProductVariantMappings` field, which is always available.
      *
      * @example
      * ```TypeScript
@@ -205,6 +206,10 @@ export interface ElasticsearchOptions {
      *         items {
      *             productId
      *             productName
+     *             customProductMappings {
+     *                 variantCount
+     *                 reviewRating
+     *             }
      *             customMappings {
      *                 ...on CustomProductMappings {
      *                     variantCount
@@ -222,7 +227,8 @@ export interface ElasticsearchOptions {
     /**
      * @description
      * This config option defines custom mappings which are accessible when the "groupByProduct"
-     * input options is set to `false`.
+     * input options is set to `false`. In addition, custom product mappings can be accessed by using
+     * the `customProductMappings` field, which is always available.
      *
      * @example
      * ```SDL
@@ -232,6 +238,9 @@ export interface ElasticsearchOptions {
      *         items {
      *             productId
      *             productName
+     *             customProductVariantMappings {
+     *                 weight
+     *             }
      *             customMappings {
      *                 ...on CustomProductVariantMappings {
      *                     weight