Browse Source

Merge branch 'master' into minor

Michael Bromley 10 months ago
parent
commit
478458ed3e

+ 1 - 0
e2e-common/e2e-initial-data.ts

@@ -12,6 +12,7 @@ export const initialData: InitialData = {
     shippingMethods: [
         { name: 'Standard Shipping', price: 500 },
         { name: 'Express Shipping', price: 1000 },
+        { name: 'Express Shipping (Taxed)', price: 1000, taxRate: 20 },
     ],
     paymentMethods: [],
     countries: [

+ 2 - 0
packages/admin-ui/src/lib/catalog/src/components/product-options-editor/product-options-editor.component.html

@@ -78,9 +78,11 @@
                         id="edit-options-list"
                         *ngIf="getOptions(optionGroup) as options"
                         [items]="options"
+                        [trackByPath]="'value.id'"
                         [itemsPerPage]="paginationSettings[optionGroup.value.id]?.itemsPerPage"
                         [currentPage]="paginationSettings[optionGroup.value.id]?.currentPage"
                         (pageChange)="paginationSettings[optionGroup.value.id].currentPage = $event"
+                        (itemsPerPageChange)="paginationSettings[optionGroup.value.id].itemsPerPage = $event"
                         [totalItems]="options.length"
                     >
                         <vdr-dt2-column [heading]="'common.id' | translate" id="id" [hiddenByDefault]="true">

+ 1 - 1
packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.html

@@ -106,7 +106,7 @@
                                   totalItems: totalItems
                               };
                     index as i;
-                    trackBy: trackByFn
+                    trackBy: trackByFn.bind(this)
                 "
             >
                 <td *ngIf="selectionManager" class="selection-col" [class.active]="activeIndex === i">

+ 4 - 5
packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.ts

@@ -114,6 +114,7 @@ export class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDe
     @Input() emptyStateLabel: string;
     @Input() filters: DataTableFilterCollection;
     @Input() activeIndex = -1;
+    @Input() trackByPath = 'id';
     @Output() pageChange = new EventEmitter<number>();
     @Output() itemsPerPageChange = new EventEmitter<number>();
     @Output() visibleColumnsChange = new EventEmitter<Array<DataTable2ColumnComponent<T>>>();
@@ -296,11 +297,9 @@ export class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDe
     }
 
     trackByFn(index: number, item: any) {
-        if ((item as any).id != null) {
-            return (item as any).id;
-        } else {
-            return index;
-        }
+        return this.trackByPath.split('.').reduce((accu, val) => {
+            return accu && accu[val];
+        }, item) ?? index;
     }
 
     onToggleAllClick() {

+ 5 - 0
packages/core/e2e/active-order-strategy.e2e-spec.ts

@@ -446,6 +446,11 @@ describe('custom ActiveOrderStrategy', () => {
                     name: 'Express Shipping',
                     priceWithTax: 1000,
                 },
+                {
+                    id: 'T_3',
+                    name: 'Express Shipping (Taxed)',
+                    priceWithTax: 1200,
+                },
             ]);
         });
 

+ 9 - 0
packages/core/e2e/draft-order.e2e-spec.ts

@@ -382,6 +382,15 @@ describe('Draft Orders resolver', () => {
                 price: 1000,
                 priceWithTax: 1000,
             },
+            {
+                code: 'express-shipping-taxed',
+                description: '',
+                id: 'T_3',
+                metadata: null,
+                name: 'Express Shipping (Taxed)',
+                price: 1000,
+                priceWithTax: 1200,
+            },
         ]);
     });
 

+ 20 - 2
packages/core/e2e/fixtures/test-plugins/with-job-queue.ts

@@ -1,5 +1,5 @@
 import { Controller, Get, OnModuleInit } from '@nestjs/common';
-import { JobQueue, JobQueueService, PluginCommonModule, VendurePlugin } from '@vendure/core';
+import { JobQueue, JobQueueService, Logger, PluginCommonModule, VendurePlugin } from '@vendure/core';
 import { Subject } from 'rxjs';
 import { take } from 'rxjs/operators';
 
@@ -17,12 +17,20 @@ class TestController implements OnModuleInit {
                     await new Promise(resolve => setTimeout(resolve, 50));
                     return job.data.returnValue;
                 } else {
+                    const interval = setInterval(() => {
+                        Logger.info(`Job is running...`);
+                        if (job.state === 'CANCELLED') {
+                            clearInterval(interval);
+                            PluginWithJobQueue.jobSubject.next();
+                        }
+                    }, 500);
                     return PluginWithJobQueue.jobSubject
                         .pipe(take(1))
                         .toPromise()
                         .then(() => {
                             PluginWithJobQueue.jobHasDoneWork = true;
-                            return job.data.returnValue;
+                            clearInterval(interval);
+                            return 'job result';
                         });
                 }
             },
@@ -43,6 +51,16 @@ class TestController implements OnModuleInit {
             .toPromise()
             .then(update => update?.result);
     }
+
+    @Get('subscribe-timeout')
+    async runJobAndSubscribeTimeout() {
+        const job = await this.queue.add({});
+        const result = await job
+            .updates({ timeoutMs: 50 })
+            .toPromise()
+            .then(update => update?.result);
+        return result;
+    }
 }
 
 @VendurePlugin({

+ 15 - 0
packages/core/e2e/job-queue.e2e-spec.ts

@@ -159,6 +159,21 @@ describe('JobQueue', () => {
         const jobs = await getJobsInTestQueue(JobState.RUNNING);
         expect(jobs.items.length).toBe(0);
     });
+
+    it('subscribable that times out', async () => {
+        const restControllerUrl = `http://localhost:${activeConfig.apiOptions.port}/run-job/subscribe-timeout`;
+        const result = await adminClient.fetch(restControllerUrl);
+
+        expect(result.status).toBe(200);
+        expect(await result.text()).toBe('Job subscription timed out. The job may still be running');
+        const jobs = await getJobsInTestQueue(JobState.RUNNING);
+        expect(jobs.items.length).toBe(1);
+    });
+
+    it('server still running after timeout', async () => {
+        const jobs = await getJobsInTestQueue(JobState.RUNNING);
+        expect(jobs.items.length).toBe(1);
+    });
 });
 
 function sleep(ms: number): Promise<void> {

+ 1 - 0
packages/core/e2e/order-multi-vendor.e2e-spec.ts

@@ -169,6 +169,7 @@ describe('Multi-vendor orders', () => {
             'alices-wares-shipping',
             'bobs-parts-shipping',
             'express-shipping',
+            'express-shipping-taxed',
             'standard-shipping',
         ]);
 

+ 2 - 2
packages/core/e2e/order-multiple-shipping.e2e-spec.ts

@@ -140,8 +140,8 @@ describe('Multiple shipping orders', () => {
             },
         });
 
-        expect(result1.createShippingMethod.id).toBe('T_3');
-        expect(result2.createShippingMethod.id).toBe('T_4');
+        expect(result1.createShippingMethod.id).toBe('T_4');
+        expect(result2.createShippingMethod.id).toBe('T_5');
         lessThan100MethodId = result1.createShippingMethod.id;
         greaterThan100MethodId = result2.createShippingMethod.id;
     });

+ 10 - 10
packages/core/e2e/order.e2e-spec.ts

@@ -1170,7 +1170,7 @@ describe('Orders resolver', () => {
                 customers[0].emailAddress,
                 password,
             );
-            await proceedToArrangingPayment(shopClient);
+            await proceedToArrangingPayment(shopClient, 2);
             const order = await addPaymentToOrder(shopClient, failsToSettlePaymentMethod);
             orderGuard.assertSuccess(order);
 
@@ -1290,7 +1290,7 @@ describe('Orders resolver', () => {
         });
 
         it('cannot cancel from ArrangingPayment state', async () => {
-            await proceedToArrangingPayment(shopClient);
+            await proceedToArrangingPayment(shopClient, 2);
             const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
                 GET_ORDER,
                 {
@@ -1708,7 +1708,7 @@ describe('Orders resolver', () => {
             >(REFUND_ORDER, {
                 input: {
                     lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
-                    shipping: order!.shipping,
+                    shipping: order!.shippingWithTax,
                     adjustment: 0,
                     reason: 'foo',
                     paymentId,
@@ -1716,7 +1716,7 @@ describe('Orders resolver', () => {
             });
             refundGuard.assertSuccess(refundOrder);
 
-            expect(refundOrder.shipping).toBe(order!.shipping);
+            expect(refundOrder.shipping).toBe(order!.shippingWithTax);
             expect(refundOrder.items).toBe(order!.subTotalWithTax);
             expect(refundOrder.total).toBe(order!.totalWithTax);
             expect(refundOrder.transactionId).toBe(null);
@@ -1815,7 +1815,7 @@ describe('Orders resolver', () => {
                 customers[0].emailAddress,
                 password,
             );
-            await proceedToArrangingPayment(shopClient);
+            await proceedToArrangingPayment(shopClient, 2);
             const order = await addPaymentToOrder(shopClient, singleStageRefundFailingPaymentMethod);
             orderGuard.assertSuccess(order);
 
@@ -1827,7 +1827,7 @@ describe('Orders resolver', () => {
             >(REFUND_ORDER, {
                 input: {
                     lines: order.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
-                    shipping: order.shipping,
+                    shipping: order.shippingWithTax,
                     adjustment: 0,
                     reason: 'foo',
                     paymentId: order.payments![0].id,
@@ -1843,7 +1843,7 @@ describe('Orders resolver', () => {
             >(REFUND_ORDER, {
                 input: {
                     lines: order.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
-                    shipping: order.shipping,
+                    shipping: order.shippingWithTax,
                     adjustment: 0,
                     reason: 'foo',
                     paymentId: order.payments![0].id,
@@ -1862,7 +1862,7 @@ describe('Orders resolver', () => {
                 customers[0].emailAddress,
                 password,
             );
-            await proceedToArrangingPayment(shopClient);
+            await proceedToArrangingPayment(shopClient, 2);
             const order = await addPaymentToOrder(shopClient, singleStageRefundablePaymentMethod);
             orderGuard.assertSuccess(order);
 
@@ -1886,7 +1886,7 @@ describe('Orders resolver', () => {
             >(REFUND_ORDER, {
                 input: {
                     lines: order.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
-                    shipping: order.shipping,
+                    shipping: order.shippingWithTax,
                     adjustment: 0,
                     reason: 'foo',
                     paymentId: order.payments![0].id,
@@ -2181,7 +2181,7 @@ describe('Orders resolver', () => {
         });
 
         it('adds a partial payment', async () => {
-            await proceedToArrangingPayment(shopClient);
+            await proceedToArrangingPayment(shopClient, 2);
             const { addPaymentToOrder: order } = await shopClient.query<
                 CodegenShop.AddPaymentToOrderMutation,
                 CodegenShop.AddPaymentToOrderMutationVariables

+ 18 - 9
packages/core/e2e/shipping-method.e2e-spec.ts

@@ -154,9 +154,10 @@ describe('ShippingMethod resolver', () => {
         const { shippingMethods } = await adminClient.query<Codegen.GetShippingMethodListQuery>(
             GET_SHIPPING_METHOD_LIST,
         );
-        expect(shippingMethods.totalItems).toEqual(2);
+        expect(shippingMethods.totalItems).toEqual(3);
         expect(shippingMethods.items[0].code).toBe('standard-shipping');
         expect(shippingMethods.items[1].code).toBe('express-shipping');
+        expect(shippingMethods.items[2].code).toBe('express-shipping-taxed');
     });
 
     it('shippingMethod', async () => {
@@ -195,7 +196,7 @@ describe('ShippingMethod resolver', () => {
         });
 
         expect(createShippingMethod).toEqual({
-            id: 'T_3',
+            id: 'T_4',
             code: 'new-method',
             name: 'new method',
             description: '',
@@ -268,7 +269,7 @@ describe('ShippingMethod resolver', () => {
 
         expect(testEligibleShippingMethods).toEqual([
             {
-                id: 'T_3',
+                id: 'T_4',
                 name: 'new method',
                 description: '',
                 price: 100,
@@ -292,6 +293,14 @@ describe('ShippingMethod resolver', () => {
                 priceWithTax: 1000,
                 metadata: null,
             },
+            {
+                id: 'T_3',
+                name: 'Express Shipping (Taxed)',
+                description: '',
+                price: 1000,
+                priceWithTax: 1200,
+                metadata: null,
+            },
         ]);
     });
 
@@ -301,7 +310,7 @@ describe('ShippingMethod resolver', () => {
             Codegen.UpdateShippingMethodMutationVariables
         >(UPDATE_SHIPPING_METHOD, {
             input: {
-                id: 'T_3',
+                id: 'T_4',
                 translations: [{ languageCode: LanguageCode.en, name: 'changed method', description: '' }],
             },
         });
@@ -313,13 +322,13 @@ describe('ShippingMethod resolver', () => {
         const listResult1 = await adminClient.query<Codegen.GetShippingMethodListQuery>(
             GET_SHIPPING_METHOD_LIST,
         );
-        expect(listResult1.shippingMethods.items.map(i => i.id)).toEqual(['T_1', 'T_2', 'T_3']);
+        expect(listResult1.shippingMethods.items.map(i => i.id)).toEqual(['T_1', 'T_2', 'T_3', 'T_4']);
 
         const { deleteShippingMethod } = await adminClient.query<
             Codegen.DeleteShippingMethodMutation,
             Codegen.DeleteShippingMethodMutationVariables
         >(DELETE_SHIPPING_METHOD, {
-            id: 'T_3',
+            id: 'T_4',
         });
 
         expect(deleteShippingMethod).toEqual({
@@ -330,7 +339,7 @@ describe('ShippingMethod resolver', () => {
         const listResult2 = await adminClient.query<Codegen.GetShippingMethodListQuery>(
             GET_SHIPPING_METHOD_LIST,
         );
-        expect(listResult2.shippingMethods.items.map(i => i.id)).toEqual(['T_1', 'T_2']);
+        expect(listResult2.shippingMethods.items.map(i => i.id)).toEqual(['T_1', 'T_2', 'T_3']);
     });
 
     describe('argument ordering', () => {
@@ -379,7 +388,7 @@ describe('ShippingMethod resolver', () => {
                 Codegen.UpdateShippingMethodMutationVariables
             >(UPDATE_SHIPPING_METHOD, {
                 input: {
-                    id: 'T_4',
+                    id: 'T_5',
                     translations: [],
                     calculator: {
                         code: defaultShippingCalculator.code,
@@ -407,7 +416,7 @@ describe('ShippingMethod resolver', () => {
                 Codegen.GetShippingMethodQuery,
                 Codegen.GetShippingMethodQueryVariables
             >(GET_SHIPPING_METHOD, {
-                id: 'T_4',
+                id: 'T_5',
             });
 
             expect(shippingMethod?.calculator.args).toEqual([

+ 7 - 0
packages/core/e2e/shop-order.e2e-spec.ts

@@ -1623,6 +1623,13 @@ describe('Shop orders', () => {
                         name: 'Express Shipping',
                         description: '',
                     },
+                    {
+                        id: 'T_3',
+                        price: 1000,
+                        code: 'express-shipping-taxed',
+                        name: 'Express Shipping (Taxed)',
+                        description: '',
+                    },
                 ]);
             });
 

+ 5 - 2
packages/core/e2e/utils/test-order-utils.ts

@@ -14,7 +14,10 @@ import {
     TRANSITION_TO_STATE,
 } from '../graphql/shop-definitions';
 
-export async function proceedToArrangingPayment(shopClient: SimpleGraphQLClient): Promise<ID> {
+export async function proceedToArrangingPayment(
+    shopClient: SimpleGraphQLClient,
+    shippingMethodIdx = 1,
+): Promise<ID> {
     await shopClient.query<
         CodegenShop.SetShippingAddressMutation,
         CodegenShop.SetShippingAddressMutationVariables
@@ -36,7 +39,7 @@ export async function proceedToArrangingPayment(shopClient: SimpleGraphQLClient)
         CodegenShop.SetShippingMethodMutation,
         CodegenShop.SetShippingMethodMutationVariables
     >(SET_SHIPPING_METHOD, {
-        id: eligibleShippingMethods[1].id,
+        id: eligibleShippingMethods[shippingMethodIdx].id,
     });
 
     const { transitionOrderToState } = await shopClient.query<

+ 4 - 4
packages/core/src/data-import/providers/importer/fast-importer.service.ts

@@ -85,7 +85,7 @@ export class FastImporterService {
             beforeSave: async p => {
                 p.channels = unique([this.defaultChannel, this.importCtx.channel], 'id');
                 if (input.facetValueIds) {
-                    p.facetValues = input.facetValueIds.map(id => ({ id } as any));
+                    p.facetValues = input.facetValueIds.map(id => ({ id }) as any);
                 }
                 if (input.featuredAssetId) {
                     p.featuredAsset = { id: input.featuredAssetId } as any;
@@ -164,10 +164,10 @@ export class FastImporterService {
                 variant.channels = unique([this.defaultChannel, this.importCtx.channel], 'id');
                 const { optionIds } = input;
                 if (optionIds && optionIds.length) {
-                    variant.options = optionIds.map(id => ({ id } as any));
+                    variant.options = optionIds.map(id => ({ id }) as any);
                 }
                 if (input.facetValueIds) {
-                    variant.facetValues = input.facetValueIds.map(id => ({ id } as any));
+                    variant.facetValues = input.facetValueIds.map(id => ({ id }) as any);
                 }
                 variant.product = { id: input.productId } as any;
                 variant.taxCategory = { id: input.taxCategoryId } as any;
@@ -192,7 +192,7 @@ export class FastImporterService {
         await this.stockMovementService.adjustProductVariantStock(
             this.importCtx,
             createdVariant.id,
-            input.stockOnHand ?? 0,
+            input.stockLevels ?? 0,
         );
         const assignedChannelIds = unique([this.defaultChannel, this.importCtx.channel], 'id').map(c => c.id);
         for (const channelId of assignedChannelIds) {

+ 2 - 2
packages/core/src/data-import/providers/populator/populator.ts

@@ -288,7 +288,7 @@ export class Populator {
 
     private async populateShippingMethods(
         ctx: RequestContext,
-        shippingMethods: Array<{ name: string; price: number }>,
+        shippingMethods: Array<{ name: string; price: number; taxRate?: number }>,
     ) {
         for (const method of shippingMethods) {
             await this.shippingMethodService.create(ctx, {
@@ -301,7 +301,7 @@ export class Populator {
                     code: defaultShippingCalculator.code,
                     arguments: [
                         { name: 'rate', value: method.price.toString() },
-                        { name: 'taxRate', value: '0' },
+                        { name: 'taxRate', value: method.taxRate ? method.taxRate.toString() : '0' },
                         { name: 'includesTax', value: 'auto' },
                     ],
                 },

+ 1 - 1
packages/core/src/data-import/types.ts

@@ -50,7 +50,7 @@ export interface InitialData {
     roles?: RoleDefinition[];
     countries: CountryDefinition[];
     taxRates: Array<{ name: string; percentage: number }>;
-    shippingMethods: Array<{ name: string; price: number }>;
+    shippingMethods: Array<{ name: string; price: number; taxRate?: number }>;
     paymentMethods: Array<{ name: string; handler: ConfigurableOperationInput }>;
     collections: CollectionDefinition[];
 }

+ 24 - 11
packages/core/src/job-queue/subscribable-job.ts

@@ -2,10 +2,11 @@ import { JobState } from '@vendure/common/lib/generated-types';
 import { pick } from '@vendure/common/lib/pick';
 import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
 import ms from 'ms';
-import { interval, Observable } from 'rxjs';
+import { interval, Observable, race, timer } from 'rxjs';
 import { distinctUntilChanged, filter, map, switchMap, takeWhile, tap } from 'rxjs/operators';
 
 import { InternalServerError } from '../common/error/errors';
+import { Logger } from '../config/index';
 import { isInspectableJobQueueStrategy } from '../config/job-queue/inspectable-job-queue-strategy';
 import { JobQueueStrategy } from '../config/job-queue/job-queue-strategy';
 
@@ -87,16 +88,7 @@ export class SubscribableJob<T extends JobData<T> = any> extends Job<T> {
             );
         } else {
             // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            return interval(pollInterval).pipe(
-                tap(i => {
-                    if (timeoutMs < i * pollInterval) {
-                        throw new Error(
-                            `Job ${
-                                this.id ?? ''
-                            } SubscribableJob update polling timed out after ${timeoutMs}ms. The job may still be running.`,
-                        );
-                    }
-                }),
+            const updates$ = interval(pollInterval).pipe(
                 switchMap(() => {
                     const id = this.id;
                     if (!id) {
@@ -120,6 +112,27 @@ export class SubscribableJob<T extends JobData<T> = any> extends Job<T> {
                 }),
                 map(job => pick(job, ['id', 'state', 'progress', 'result', 'error', 'data'])),
             );
+            const timeout$ = timer(timeoutMs).pipe(
+                tap(i => {
+                    Logger.error(
+                        `Job ${
+                            this.id ?? ''
+                        } SubscribableJob update polling timed out after ${timeoutMs}ms. The job may still be running.`,
+                    );
+                }),
+                map(
+                    () =>
+                        ({
+                            id: this.id,
+                            state: JobState.RUNNING,
+                            data: this.data,
+                            error: this.error,
+                            progress: this.progress,
+                            result: 'Job subscription timed out. The job may still be running',
+                        }) satisfies JobUpdate<any>,
+                ),
+            );
+            return race(updates$, timeout$);
         }
     }
 }

+ 1 - 1
packages/core/src/service/helpers/order-modifier/order-modifier.ts

@@ -346,7 +346,7 @@ export class OrderModifier {
                     adjustmentSource: 'CANCEL_ORDER',
                     type: AdjustmentType.OTHER,
                     description: 'shipping cancellation',
-                    amount: -shippingLine.discountedPriceWithTax,
+                    amount: -shippingLine.discountedPrice,
                     data: {},
                 });
                 await this.connection.getRepository(ctx, ShippingLine).save(shippingLine, { reload: false });