Explorar el Código

perf(core): Optimize OrderDetail view

Relates to #1727. This commit makes use of the new Fulfillment
API extensions to massively reduce the amount of data needed to
render the Order detail view. In benchmarks of an order with
5000 OrderItems, query time went from 2.6s to 1.9s, and payload from 1.14MB to 367kb.
Michael Bromley hace 3 años
padre
commit
987355cfdb

+ 23 - 11
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -1613,12 +1613,19 @@ export type Fulfillment = Node & {
   createdAt: Scalars['DateTime'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
   orderItems: Array<OrderItem>;
   orderItems: Array<OrderItem>;
+  summary: Array<FulfillmentLineSummary>;
   state: Scalars['String'];
   state: Scalars['String'];
   method: Scalars['String'];
   method: Scalars['String'];
   trackingCode?: Maybe<Scalars['String']>;
   trackingCode?: Maybe<Scalars['String']>;
   customFields?: Maybe<Scalars['JSON']>;
   customFields?: Maybe<Scalars['JSON']>;
 };
 };
 
 
+export type FulfillmentLineSummary = {
+  __typename?: 'FulfillmentLineSummary';
+  orderLine: OrderLine;
+  quantity: Scalars['Int'];
+};
+
 /** Returned when there is an error in transitioning the Fulfillment state */
 /** Returned when there is an error in transitioning the Fulfillment state */
 export type FulfillmentStateTransitionError = ErrorResult & {
 export type FulfillmentStateTransitionError = ErrorResult & {
   __typename?: 'FulfillmentStateTransitionError';
   __typename?: 'FulfillmentStateTransitionError';
@@ -3395,6 +3402,7 @@ export type OrderLine = Node & {
   discounts: Array<Discount>;
   discounts: Array<Discount>;
   taxLines: Array<TaxLine>;
   taxLines: Array<TaxLine>;
   order: Order;
   order: Order;
+  fulfillments?: Maybe<Array<Fulfillment>>;
   customFields?: Maybe<Scalars['JSON']>;
   customFields?: Maybe<Scalars['JSON']>;
 };
 };
 
 
@@ -6299,9 +6307,13 @@ export type OrderFragment = (
 export type FulfillmentFragment = (
 export type FulfillmentFragment = (
   { __typename?: 'Fulfillment' }
   { __typename?: 'Fulfillment' }
   & Pick<Fulfillment, 'id' | 'state' | 'nextStates' | 'createdAt' | 'updatedAt' | 'method' | 'trackingCode'>
   & Pick<Fulfillment, 'id' | 'state' | 'nextStates' | 'createdAt' | 'updatedAt' | 'method' | 'trackingCode'>
-  & { orderItems: Array<(
-    { __typename?: 'OrderItem' }
-    & Pick<OrderItem, 'id'>
+  & { summary: Array<(
+    { __typename?: 'FulfillmentLineSummary' }
+    & Pick<FulfillmentLineSummary, 'quantity'>
+    & { orderLine: (
+      { __typename?: 'OrderLine' }
+      & Pick<OrderLine, 'id'>
+    ) }
   )> }
   )> }
 );
 );
 
 
@@ -6317,13 +6329,12 @@ export type OrderLineFragment = (
   ), discounts: Array<(
   ), discounts: Array<(
     { __typename?: 'Discount' }
     { __typename?: 'Discount' }
     & DiscountFragment
     & DiscountFragment
-  )>, items: Array<(
+  )>, fulfillments?: Maybe<Array<(
+    { __typename?: 'Fulfillment' }
+    & FulfillmentFragment
+  )>>, items: Array<(
     { __typename?: 'OrderItem' }
     { __typename?: 'OrderItem' }
-    & Pick<OrderItem, 'id' | 'unitPrice' | 'unitPriceWithTax' | 'taxRate' | 'refundId' | 'cancelled'>
-    & { fulfillment?: Maybe<(
-      { __typename?: 'Fulfillment' }
-      & FulfillmentFragment
-    )> }
+    & Pick<OrderItem, 'id' | 'refundId' | 'cancelled'>
   )> }
   )> }
 );
 );
 
 
@@ -9719,7 +9730,8 @@ export namespace Order {
 
 
 export namespace Fulfillment {
 export namespace Fulfillment {
   export type Fragment = FulfillmentFragment;
   export type Fragment = FulfillmentFragment;
-  export type OrderItems = NonNullable<(NonNullable<FulfillmentFragment['orderItems']>)[number]>;
+  export type Summary = NonNullable<(NonNullable<FulfillmentFragment['summary']>)[number]>;
+  export type OrderLine = (NonNullable<NonNullable<(NonNullable<FulfillmentFragment['summary']>)[number]>['orderLine']>);
 }
 }
 
 
 export namespace OrderLine {
 export namespace OrderLine {
@@ -9727,8 +9739,8 @@ export namespace OrderLine {
   export type FeaturedAsset = (NonNullable<OrderLineFragment['featuredAsset']>);
   export type FeaturedAsset = (NonNullable<OrderLineFragment['featuredAsset']>);
   export type ProductVariant = (NonNullable<OrderLineFragment['productVariant']>);
   export type ProductVariant = (NonNullable<OrderLineFragment['productVariant']>);
   export type Discounts = NonNullable<(NonNullable<OrderLineFragment['discounts']>)[number]>;
   export type Discounts = NonNullable<(NonNullable<OrderLineFragment['discounts']>)[number]>;
+  export type Fulfillments = NonNullable<(NonNullable<OrderLineFragment['fulfillments']>)[number]>;
   export type Items = NonNullable<(NonNullable<OrderLineFragment['items']>)[number]>;
   export type Items = NonNullable<(NonNullable<OrderLineFragment['items']>)[number]>;
-  export type Fulfillment = (NonNullable<NonNullable<(NonNullable<OrderLineFragment['items']>)[number]>['fulfillment']>);
 }
 }
 
 
 export namespace OrderDetail {
 export namespace OrderDetail {

+ 8 - 8
packages/admin-ui/src/lib/core/src/data/definitions/order-definitions.ts

@@ -83,8 +83,11 @@ export const FULFILLMENT_FRAGMENT = gql`
         createdAt
         createdAt
         updatedAt
         updatedAt
         method
         method
-        orderItems {
-            id
+        summary {
+            orderLine {
+                id
+            }
+            quantity
         }
         }
         trackingCode
         trackingCode
     }
     }
@@ -106,6 +109,9 @@ export const ORDER_LINE_FRAGMENT = gql`
         discounts {
         discounts {
             ...Discount
             ...Discount
         }
         }
+        fulfillments {
+            ...Fulfillment
+        }
         unitPrice
         unitPrice
         unitPriceWithTax
         unitPriceWithTax
         proratedUnitPrice
         proratedUnitPrice
@@ -113,14 +119,8 @@ export const ORDER_LINE_FRAGMENT = gql`
         quantity
         quantity
         items {
         items {
             id
             id
-            unitPrice
-            unitPriceWithTax
-            taxRate
             refundId
             refundId
             cancelled
             cancelled
-            fulfillment {
-                ...Fulfillment
-            }
         }
         }
         linePrice
         linePrice
         lineTax
         lineTax

+ 6 - 1
packages/admin-ui/src/lib/order/src/components/fulfill-order-dialog/fulfill-order-dialog.component.ts

@@ -68,7 +68,12 @@ export class FulfillOrderDialogComponent implements Dialog<FulfillOrderInput>, O
     }
     }
 
 
     getUnfulfilledCount(line: OrderDetail.Lines): number {
     getUnfulfilledCount(line: OrderDetail.Lines): number {
-        const fulfilled = line.items.reduce((sum, item) => sum + (item.fulfillment ? 1 : 0), 0);
+        const fulfilled =
+            line.fulfillments
+                ?.map(f => f.summary)
+                .flat()
+                .filter(row => row.orderLine.id === line.id)
+                .reduce((sum, row) => sum + row.quantity, 0) ?? 0;
         return line.quantity - fulfilled;
         return line.quantity - fulfilled;
     }
     }
 
 

+ 10 - 15
packages/admin-ui/src/lib/order/src/components/fulfillment-detail/fulfillment-detail.component.ts

@@ -31,21 +31,16 @@ export class FulfillmentDetailComponent implements OnInit, OnChanges {
     }
     }
 
 
     get items(): Array<{ name: string; quantity: number }> {
     get items(): Array<{ name: string; quantity: number }> {
-        const itemMap = new Map<string, number>();
-        const fulfillmentItemIds = this.fulfillment?.orderItems.map(i => i.id);
-        for (const line of this.order.lines) {
-            for (const item of line.items) {
-                if (fulfillmentItemIds?.includes(item.id)) {
-                    const count = itemMap.get(line.productVariant.name);
-                    if (count != null) {
-                        itemMap.set(line.productVariant.name, count + 1);
-                    } else {
-                        itemMap.set(line.productVariant.name, 1);
-                    }
-                }
-            }
-        }
-        return Array.from(itemMap.entries()).map(([name, quantity]) => ({ name, quantity }));
+        return (
+            this.fulfillment?.summary.map(row => {
+                return {
+                    name:
+                        this.order.lines.find(line => line.id === row.orderLine.id)?.productVariant.name ??
+                        '',
+                    quantity: row.quantity,
+                };
+            }) ?? []
+        );
     }
     }
 
 
     buildCustomFieldsFormGroup() {
     buildCustomFieldsFormGroup() {

+ 22 - 26
packages/admin-ui/src/lib/order/src/components/line-fulfillment/line-fulfillment.component.ts

@@ -1,5 +1,5 @@
 import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
 import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
-import { OrderDetail } from '@vendure/admin-ui/core';
+import { OrderDetail, OrderDetailFragment } from '@vendure/admin-ui/core';
 import { unique } from '@vendure/common/lib/unique';
 import { unique } from '@vendure/common/lib/unique';
 
 
 export type FulfillmentStatus = 'full' | 'partial' | 'none';
 export type FulfillmentStatus = 'full' | 'partial' | 'none';
@@ -15,7 +15,10 @@ export class LineFulfillmentComponent implements OnChanges {
     @Input() orderState: string;
     @Input() orderState: string;
     fulfilledCount = 0;
     fulfilledCount = 0;
     fulfillmentStatus: FulfillmentStatus;
     fulfillmentStatus: FulfillmentStatus;
-    fulfillments: Array<{ count: number; fulfillment: OrderDetail.Fulfillments }> = [];
+    fulfillments: Array<{
+        count: number;
+        fulfillment: NonNullable<OrderDetailFragment['fulfillments']>[number];
+    }> = [];
 
 
     ngOnChanges(changes: SimpleChanges): void {
     ngOnChanges(changes: SimpleChanges): void {
         if (this.line) {
         if (this.line) {
@@ -29,7 +32,13 @@ export class LineFulfillmentComponent implements OnChanges {
      * Returns the number of items in an OrderLine which are fulfilled.
      * Returns the number of items in an OrderLine which are fulfilled.
      */
      */
     private getDeliveredCount(line: OrderDetail.Lines): number {
     private getDeliveredCount(line: OrderDetail.Lines): number {
-        return line.items.reduce((sum, item) => sum + (item.fulfillment ? 1 : 0), 0);
+        return (
+            line.fulfillments?.reduce(
+                (sum, fulfillment) =>
+                    sum + (fulfillment.summary.find(s => s.orderLine.id === line.id)?.quantity ?? 0),
+                0,
+            ) ?? 0
+        );
     }
     }
 
 
     private getFulfillmentStatus(fulfilledCount: number, lineQuantity: number): FulfillmentStatus {
     private getFulfillmentStatus(fulfilledCount: number, lineQuantity: number): FulfillmentStatus {
@@ -44,28 +53,15 @@ export class LineFulfillmentComponent implements OnChanges {
 
 
     private getFulfillments(
     private getFulfillments(
         line: OrderDetail.Lines,
         line: OrderDetail.Lines,
-    ): Array<{ count: number; fulfillment: OrderDetail.Fulfillments }> {
-        const counts: { [fulfillmentId: string]: number } = {};
-
-        for (const item of line.items) {
-            if (item.fulfillment) {
-                if (counts[item.fulfillment.id] === undefined) {
-                    counts[item.fulfillment.id] = 1;
-                } else {
-                    counts[item.fulfillment.id]++;
-                }
-            }
-        }
-        const all = line.items.reduce((fulfillments, item) => {
-            return item.fulfillment ? [...fulfillments, item.fulfillment] : fulfillments;
-        }, [] as OrderDetail.Fulfillments[]);
-
-        return Object.entries(counts).map(([id, count]) => {
-            return {
-                count,
-                // tslint:disable-next-line:no-non-null-assertion
-                fulfillment: all.find(f => f.id === id)!,
-            };
-        });
+    ): Array<{ count: number; fulfillment: NonNullable<OrderDetailFragment['fulfillments']>[number] }> {
+        return (
+            line.fulfillments?.map(fulfillment => {
+                const summaryLine = fulfillment.summary.find(s => s.orderLine.id === line.id);
+                return {
+                    count: summaryLine?.quantity ?? 0,
+                    fulfillment,
+                };
+            }) ?? []
+        );
     }
     }
 }
 }

+ 15 - 3
packages/admin-ui/src/lib/order/src/components/order-detail/order-detail.component.ts

@@ -8,6 +8,8 @@ import {
     CustomFieldConfig,
     CustomFieldConfig,
     DataService,
     DataService,
     EditNoteDialogComponent,
     EditNoteDialogComponent,
+    FulfillmentFragment,
+    FulfillmentLineSummary,
     GetOrderHistory,
     GetOrderHistory,
     GetOrderQuery,
     GetOrderQuery,
     HistoryEntry,
     HistoryEntry,
@@ -252,9 +254,19 @@ export class OrderDetailComponent
     }
     }
 
 
     canAddFulfillment(order: OrderDetail.Fragment): boolean {
     canAddFulfillment(order: OrderDetail.Fragment): boolean {
-        const allItemsFulfilled = order.lines
-            .reduce((items, line) => [...items, ...line.items], [] as OrderLineFragment['items'])
-            .every(item => !!item.fulfillment || item.cancelled);
+        const allFulfillmentSummaryRows: FulfillmentFragment['summary'] = (order.fulfillments ?? []).reduce(
+            (all, fulfillment) => [...all, ...fulfillment.summary],
+            [] as FulfillmentFragment['summary'],
+        );
+        let allItemsFulfilled = true;
+        for (const line of order.lines) {
+            const totalFulfilledCount = allFulfillmentSummaryRows
+                .filter(row => row.orderLine.id === line.id)
+                .reduce((sum, row) => sum + row.quantity, 0);
+            if (totalFulfilledCount < line.quantity) {
+                allItemsFulfilled = false;
+            }
+        }
         return (
         return (
             !allItemsFulfilled &&
             !allItemsFulfilled &&
             !this.hasUnsettledModifications(order) &&
             !this.hasUnsettledModifications(order) &&