Jelajahi Sumber

Merge branch 'master' into minor

Michael Bromley 3 tahun lalu
induk
melakukan
f4f977bb2a

+ 16 - 0
CHANGELOG.md

@@ -1,3 +1,19 @@
+## <small>1.8.5 (2022-11-28)</small>
+
+
+#### Fixes
+
+* **admin-ui** Do not display form errors for pristine fields ([f15028a](https://github.com/vendure-ecommerce/vendure/commit/f15028a)), closes [#1901](https://github.com/vendure-ecommerce/vendure/issues/1901)
+* **core** Fix error when populating initial roles ([41cfaf8](https://github.com/vendure-ecommerce/vendure/commit/41cfaf8)), closes [#1905](https://github.com/vendure-ecommerce/vendure/issues/1905)
+* **core** Fix loading eager custom fields for `order` query ([93b8601](https://github.com/vendure-ecommerce/vendure/commit/93b8601)), closes [#1664](https://github.com/vendure-ecommerce/vendure/issues/1664)
+* **core** ListQueryBuilder handles empty in operator ([229afff](https://github.com/vendure-ecommerce/vendure/commit/229afff))
+* **core** Make order of OrderLine from OrderService.findOne deterministic (#1904) ([2d06390](https://github.com/vendure-ecommerce/vendure/commit/2d06390)), closes [#1904](https://github.com/vendure-ecommerce/vendure/issues/1904)
+
+#### Perf
+
+* **core** Improve speed & memory usage when running collection filters ([464dcea](https://github.com/vendure-ecommerce/vendure/commit/464dcea)), closes [#1893](https://github.com/vendure-ecommerce/vendure/issues/1893)
+* **core** Use TypeORM relation query builder when filtering collection ([8aa7201](https://github.com/vendure-ecommerce/vendure/commit/8aa7201)), closes [#1893](https://github.com/vendure-ecommerce/vendure/issues/1893)
+
 ## <small>1.8.4 (2022-11-18)</small>
 
 

+ 1 - 1
lerna.json

@@ -2,7 +2,7 @@
   "packages": [
     "packages/*"
   ],
-  "version": "1.8.4",
+  "version": "1.8.5",
   "npmClient": "yarn",
   "useWorkspaces": true,
   "command": {

+ 3 - 3
packages/admin-ui-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/admin-ui-plugin",
-  "version": "1.8.4",
+  "version": "1.8.5",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
   "files": [
@@ -21,8 +21,8 @@
   "devDependencies": {
     "@types/express": "^4.17.8",
     "@types/fs-extra": "^9.0.1",
-    "@vendure/common": "^1.8.4",
-    "@vendure/core": "^1.8.4",
+    "@vendure/common": "^1.8.5",
+    "@vendure/core": "^1.8.5",
     "express": "^4.17.1",
     "rimraf": "^3.0.2",
     "typescript": "4.3.5"

+ 2 - 2
packages/admin-ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/admin-ui",
-  "version": "1.8.4",
+  "version": "1.8.5",
   "license": "MIT",
   "scripts": {
     "ng": "ng",
@@ -39,7 +39,7 @@
     "@ng-select/ng-select": "^7.2.0",
     "@ngx-translate/core": "^13.0.0",
     "@ngx-translate/http-loader": "^6.0.0",
-    "@vendure/common": "^1.8.4",
+    "@vendure/common": "^1.8.5",
     "@webcomponents/custom-elements": "^1.4.3",
     "apollo-angular": "^2.6.0",
     "apollo-upload-client": "^16.0.0",

+ 1 - 1
packages/admin-ui/src/lib/core/src/common/version.ts

@@ -1,2 +1,2 @@
 // Auto-generated by the set-version.js script.
-export const ADMIN_UI_VERSION = '1.8.4';
+export const ADMIN_UI_VERSION = '1.8.5';

+ 2 - 1
packages/admin-ui/src/lib/core/src/shared/components/form-field/form-field.component.ts

@@ -52,7 +52,8 @@ export class FormFieldComponent implements OnInit {
         if (!this.formFieldControl || !this.formFieldControl.formControlName) {
             return;
         }
-        const errors = this.formFieldControl.formControlName.errors;
+        const errors =
+            this.formFieldControl.formControlName.dirty && this.formFieldControl.formControlName.errors;
         if (errors) {
             for (const errorKey of Object.keys(errors)) {
                 if (this.errors[errorKey]) {

+ 3 - 3
packages/asset-server-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/asset-server-plugin",
-  "version": "1.8.4",
+  "version": "1.8.5",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
   "files": [
@@ -24,8 +24,8 @@
     "@types/fs-extra": "^9.0.8",
     "@types/node-fetch": "^2.5.8",
     "@types/sharp": "^0.30.4",
-    "@vendure/common": "^1.8.4",
-    "@vendure/core": "^1.8.4",
+    "@vendure/common": "^1.8.5",
+    "@vendure/core": "^1.8.5",
     "aws-sdk": "^2.856.0",
     "express": "^4.17.1",
     "node-fetch": "^2.6.1",

+ 1 - 1
packages/common/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/common",
-  "version": "1.8.4",
+  "version": "1.8.5",
   "main": "index.js",
   "license": "MIT",
   "scripts": {

+ 28 - 0
packages/core/e2e/list-query-builder.e2e-spec.ts

@@ -392,6 +392,20 @@ describe('ListQueryBuilder', () => {
             expect(getItemLabels(testEntities.items)).toEqual(['A', 'F']);
         });
 
+        it('in with empty set', async () => {
+            const { testEntities } = await adminClient.query(GET_LIST, {
+                options: {
+                    filter: {
+                        ownerId: {
+                            in: [],
+                        },
+                    },
+                },
+            });
+
+            expect(getItemLabels(testEntities.items)).toEqual([]);
+        });
+
         it('notIn', async () => {
             const { testEntities } = await adminClient.query(GET_LIST, {
                 options: {
@@ -406,6 +420,20 @@ describe('ListQueryBuilder', () => {
             expect(getItemLabels(testEntities.items)).toEqual(['B', 'C', 'D', 'E']);
         });
 
+        it('notIn with empty set', async () => {
+            const { testEntities } = await adminClient.query(GET_LIST, {
+                options: {
+                    filter: {
+                        ownerId: {
+                            notIn: [],
+                        },
+                    },
+                },
+            });
+
+            expect(getItemLabels(testEntities.items)).toEqual(['A', 'B', 'C', 'D', 'E', 'F']);
+        });
+
         it('isNull true', async () => {
             const { testEntities } = await adminClient.query(GET_LIST, {
                 options: {

+ 2 - 2
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/core",
-  "version": "1.8.4",
+  "version": "1.8.5",
   "description": "A modern, headless ecommerce framework",
   "repository": {
     "type": "git",
@@ -49,7 +49,7 @@
     "@nestjs/testing": "7.6.17",
     "@nestjs/typeorm": "7.1.5",
     "@types/fs-extra": "^9.0.1",
-    "@vendure/common": "^1.8.4",
+    "@vendure/common": "^1.8.5",
     "apollo-server-express": "2.24.1",
     "bcrypt": "^5.1.0",
     "body-parser": "^1.19.0",

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

@@ -1,16 +1,23 @@
 import { Injectable } from '@nestjs/common';
-import { ConfigurableOperationInput, LanguageCode } from '@vendure/common/lib/generated-types';
+import { ConfigurableOperationInput } from '@vendure/common/lib/generated-types';
 import { normalizeString } from '@vendure/common/lib/normalize-string';
 import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
 
 import { RequestContext } from '../../../api/common/request-context';
-import { defaultShippingCalculator, defaultShippingEligibilityChecker, Logger } from '../../../config';
+import {
+    ConfigService,
+    defaultShippingCalculator,
+    defaultShippingEligibilityChecker,
+    Logger,
+} from '../../../config';
 import { manualFulfillmentHandler } from '../../../config/fulfillment/manual-fulfillment-handler';
-import { Channel, Collection, FacetValue, TaxCategory } from '../../../entity';
+import { TransactionalConnection } from '../../../connection/index';
+import { Channel, Collection, FacetValue, TaxCategory, User } from '../../../entity';
 import {
     CollectionService,
     FacetValueService,
     PaymentMethodService,
+    RequestContextService,
     RoleService,
     ShippingMethodService,
 } from '../../../service';
@@ -52,6 +59,9 @@ export class Populator {
         private searchService: SearchService,
         private assetImporter: AssetImporter,
         private roleService: RoleService,
+        private configService: ConfigService,
+        private connection: TransactionalConnection,
+        private requestContextService: RequestContextService,
     ) {}
 
     /**
@@ -190,12 +200,17 @@ export class Populator {
     }
 
     private async createRequestContext(data: InitialData, channel?: Channel) {
-        const ctx = new RequestContext({
+        const { superadminCredentials } = this.configService.authOptions;
+        const superAdminUser = await this.connection.rawConnection.getRepository(User).findOne({
+            where: {
+                identifier: superadminCredentials.identifier,
+            },
+        });
+        const ctx = await this.requestContextService.create({
+            user: superAdminUser,
             apiType: 'admin',
-            isAuthorized: true,
-            authorizedAsOwnerOnly: false,
-            channel: channel ?? (await this.channelService.getDefaultChannel()),
             languageCode: data.defaultLanguage,
+            channelOrToken: channel ?? (await this.channelService.getDefaultChannel()),
         });
         return ctx;
     }

+ 10 - 3
packages/core/src/service/helpers/list-query-builder/parse-filter-params.ts

@@ -20,7 +20,7 @@ import { getCalculatedColumns } from './get-calculated-columns';
 
 export interface WhereCondition {
     clause: string;
-    parameters: { [param: string]: string | number };
+    parameters: { [param: string]: string | number | string[] };
 }
 
 type AllOperators = StringOperators & BooleanOperators & NumberOperators & DateOperators & ListOperators;
@@ -87,6 +87,7 @@ function buildWhereCondition(
     argIndex: number,
     dbType: ConnectionOptions['type'],
 ): WhereCondition {
+    const emptySetPlaceholder = '___empty_set_placeholder___';
     switch (operator) {
         case 'eq':
             return {
@@ -118,12 +119,18 @@ function buildWhereCondition(
         case 'in':
             return {
                 clause: `${fieldName} IN (:...arg${argIndex})`,
-                parameters: { [`arg${argIndex}`]: operand },
+                parameters: {
+                    [`arg${argIndex}`]:
+                        Array.isArray(operand) && operand.length ? operand : [emptySetPlaceholder],
+                },
             };
         case 'notIn':
             return {
                 clause: `${fieldName} NOT IN (:...arg${argIndex})`,
-                parameters: { [`arg${argIndex}`]: operand },
+                parameters: {
+                    [`arg${argIndex}`]:
+                        Array.isArray(operand) && operand.length ? operand : [emptySetPlaceholder],
+                },
             };
         case 'regex':
             return {

+ 42 - 16
packages/core/src/service/services/collection.service.ts

@@ -559,6 +559,15 @@ export class CollectionService implements OnModuleInit {
         return filters;
     }
 
+    private chunkArray = <T>(array: T[], chunkSize: number): T[][] => {
+        const results = [];
+        for (let i = 0; i < array.length; i += chunkSize) {
+            results.push(array.slice(i, i + chunkSize));
+        }
+
+        return results;
+    };
+
     /**
      * Applies the CollectionFilters
      *
@@ -578,27 +587,42 @@ export class CollectionService implements OnModuleInit {
             ),
         );
         const preIds = await this.getCollectionProductVariantIds(collection);
-        collection.productVariants = await this.getFilteredProductVariants([
+        const filteredVariantIds = await this.getFilteredProductVariantIds([
             ...ancestorFilters,
             ...(collection.filters || []),
         ]);
-        const postIds = collection.productVariants.map(v => v.id);
+        const postIds = filteredVariantIds.map(v => v.id);
+        const preIdsSet = new Set(preIds);
+        const postIdsSet = new Set(postIds);
+
+        const toDeleteIds = preIds.filter(id => !postIdsSet.has(id));
+        const toAddIds = postIds.filter(id => !preIdsSet.has(id));
+
         try {
-            await this.connection.rawConnection
-                .getRepository(Collection)
-                // Only update the exact changed properties, to avoid VERY hard-to-debug
-                // non-deterministic race conditions e.g. when the "position" is changed
-                // by moving a Collection and then this save operation clobbers it back
-                // to the old value.
-                .save(pick(collection, ['id', 'productVariants']), {
-                    chunk: Math.ceil(collection.productVariants.length / 500),
-                    reload: false,
-                });
+            // First we remove variants that are no longer in the collection
+            const chunkedDeleteIds = this.chunkArray(toDeleteIds, 500);
+
+            for (const chunkedDeleteId of chunkedDeleteIds) {
+                await this.connection.rawConnection
+                    .createQueryBuilder()
+                    .relation(Collection, 'productVariants')
+                    .of(collection)
+                    .remove(chunkedDeleteId);
+            }
+
+            // Then we add variants have been added
+            const chunkedAddIds = this.chunkArray(toAddIds, 500);
+
+            for (const chunkedAddId of chunkedAddIds) {
+                await this.connection.rawConnection
+                    .createQueryBuilder()
+                    .relation(Collection, 'productVariants')
+                    .of(collection)
+                    .add(chunkedAddId);
+            }
         } catch (e) {
             Logger.error(e);
         }
-        const preIdsSet = new Set(preIds);
-        const postIdsSet = new Set(postIds);
 
         if (applyToChangedVariantsOnly) {
             return [...preIds.filter(id => !postIdsSet.has(id)), ...postIds.filter(id => !preIdsSet.has(id))];
@@ -610,7 +634,7 @@ export class CollectionService implements OnModuleInit {
     /**
      * Applies the CollectionFilters and returns an array of ProductVariant entities which match.
      */
-    private async getFilteredProductVariants(filters: ConfigurableOperation[]): Promise<ProductVariant[]> {
+    private async getFilteredProductVariantIds(filters: ConfigurableOperation[]): Promise<Array<{ id: ID }>> {
         if (filters.length === 0) {
             return [];
         }
@@ -628,7 +652,9 @@ export class CollectionService implements OnModuleInit {
             }
         }
 
-        return qb.getMany();
+        // This is the most performant (time & memory) way to get
+        // just the variant IDs, which is all we need.
+        return qb.select('productVariant.id', 'id').getRawMany();
     }
 
     /**

+ 3 - 1
packages/core/src/service/services/order.service.ts

@@ -251,7 +251,9 @@ export class OrderService {
             .where('order.id = :orderId', { orderId })
             .andWhere('channel.id = :channelId', { channelId: ctx.channelId });
         if (effectiveRelations.includes('lines') && effectiveRelations.includes('lines.items')) {
-            qb.addOrderBy('order__lines.createdAt', 'ASC').addOrderBy('order__lines__items.createdAt', 'ASC');
+            qb.addOrderBy('order__lines.createdAt', 'ASC')
+                .addOrderBy('order__lines__items.createdAt', 'ASC')
+                .addOrderBy('order__lines.productVariantId', 'ASC');
         }
 
         // tslint:disable-next-line:no-non-null-assertion

+ 3 - 3
packages/create/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/create",
-  "version": "1.8.4",
+  "version": "1.8.5",
   "license": "MIT",
   "bin": {
     "create": "./index.js"
@@ -28,13 +28,13 @@
     "@types/handlebars": "^4.1.0",
     "@types/listr": "^0.14.2",
     "@types/semver": "^6.2.2",
-    "@vendure/core": "^1.8.4",
+    "@vendure/core": "^1.8.5",
     "rimraf": "^3.0.2",
     "ts-node": "^10.2.1",
     "typescript": "4.3.5"
   },
   "dependencies": {
-    "@vendure/common": "^1.8.4",
+    "@vendure/common": "^1.8.5",
     "chalk": "^4.1.0",
     "commander": "^7.1.0",
     "cross-spawn": "^7.0.3",

+ 25 - 0
packages/dev-server/memory-profiler.ts

@@ -0,0 +1,25 @@
+/* tslint:disable */
+/**
+ * Used to profile peak memory usage for perf optimization purposes
+ *
+ * Add it to the index.ts or index-worker.ts:
+ * ```ts
+ * import { profileMemory } from './memory-profiler';
+ *
+ * profileMemory();
+ * ```
+ */
+export function profileMemory() {
+    let max = 0;
+    setInterval(() => {
+        const rss = process.memoryUsage().rss;
+        if (max < rss) {
+            max = rss;
+            console.log(`Peak: ${inMb(max)}`);
+        }
+    }, 500);
+}
+
+function inMb(bytes: number) {
+    return `${Math.round((bytes / 1024 / 1024) * 100) / 100}MB;`;
+}

+ 9 - 9
packages/dev-server/package.json

@@ -1,6 +1,6 @@
 {
   "name": "dev-server",
-  "version": "1.8.4",
+  "version": "1.8.5",
   "main": "index.js",
   "license": "MIT",
   "private": true,
@@ -14,18 +14,18 @@
     "load-test:100k": "node -r ts-node/register load-testing/run-load-test.ts 100000"
   },
   "dependencies": {
-    "@vendure/admin-ui-plugin": "^1.8.4",
-    "@vendure/asset-server-plugin": "^1.8.4",
-    "@vendure/common": "^1.8.4",
-    "@vendure/core": "^1.8.4",
-    "@vendure/elasticsearch-plugin": "^1.8.4",
-    "@vendure/email-plugin": "^1.8.4",
+    "@vendure/admin-ui-plugin": "^1.8.5",
+    "@vendure/asset-server-plugin": "^1.8.5",
+    "@vendure/common": "^1.8.5",
+    "@vendure/core": "^1.8.5",
+    "@vendure/elasticsearch-plugin": "^1.8.5",
+    "@vendure/email-plugin": "^1.8.5",
     "typescript": "4.3.5"
   },
   "devDependencies": {
     "@types/csv-stringify": "^3.1.0",
-    "@vendure/testing": "^1.8.4",
-    "@vendure/ui-devkit": "^1.8.4",
+    "@vendure/testing": "^1.8.5",
+    "@vendure/ui-devkit": "^1.8.5",
     "commander": "^7.1.0",
     "concurrently": "^5.0.0",
     "csv-stringify": "^5.3.3",

+ 3 - 3
packages/elasticsearch-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/elasticsearch-plugin",
-  "version": "1.8.4",
+  "version": "1.8.5",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -25,8 +25,8 @@
     "fast-deep-equal": "^3.1.3"
   },
   "devDependencies": {
-    "@vendure/common": "^1.8.4",
-    "@vendure/core": "^1.8.4",
+    "@vendure/common": "^1.8.5",
+    "@vendure/core": "^1.8.5",
     "rimraf": "^3.0.2",
     "typescript": "4.3.5"
   }

+ 3 - 3
packages/email-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/email-plugin",
-  "version": "1.8.4",
+  "version": "1.8.5",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -35,8 +35,8 @@
     "@types/fs-extra": "^9.0.1",
     "@types/handlebars": "^4.1.0",
     "@types/mjml": "^4.0.4",
-    "@vendure/common": "^1.8.4",
-    "@vendure/core": "^1.8.4",
+    "@vendure/common": "^1.8.5",
+    "@vendure/core": "^1.8.5",
     "rimraf": "^3.0.2",
     "typescript": "4.3.5"
   }

+ 3 - 3
packages/job-queue-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/job-queue-plugin",
-  "version": "1.8.4",
+  "version": "1.8.5",
   "license": "MIT",
   "main": "package/index.js",
   "types": "package/index.d.ts",
@@ -24,8 +24,8 @@
   "devDependencies": {
     "@google-cloud/pubsub": "^2.8.0",
     "@types/ioredis": "^4.28.10",
-    "@vendure/common": "^1.8.4",
-    "@vendure/core": "^1.8.4",
+    "@vendure/common": "^1.8.5",
+    "@vendure/core": "^1.8.5",
     "bullmq": "^1.86.7",
     "rimraf": "^3.0.2",
     "typescript": "4.3.5"

+ 4 - 4
packages/payments-plugin/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/payments-plugin",
-    "version": "1.8.4",
+    "version": "1.8.5",
     "license": "MIT",
     "main": "package/index.js",
     "types": "package/index.d.ts",
@@ -29,9 +29,9 @@
     "devDependencies": {
         "@mollie/api-client": "^3.6.0",
         "@types/braintree": "^2.22.15",
-        "@vendure/common": "^1.8.4",
-        "@vendure/core": "^1.8.4",
-        "@vendure/testing": "^1.8.4",
+        "@vendure/common": "^1.8.5",
+        "@vendure/core": "^1.8.5",
+        "@vendure/testing": "^1.8.5",
         "braintree": "^3.0.0",
         "nock": "^13.1.4",
         "rimraf": "^3.0.2",

+ 3 - 3
packages/testing/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/testing",
-  "version": "1.8.4",
+  "version": "1.8.5",
   "description": "End-to-end testing tools for Vendure projects",
   "keywords": [
     "vendure",
@@ -34,7 +34,7 @@
   },
   "dependencies": {
     "@types/node-fetch": "^2.5.4",
-    "@vendure/common": "^1.8.4",
+    "@vendure/common": "^1.8.5",
     "faker": "^4.1.0",
     "form-data": "^3.0.0",
     "graphql": "15.5.1",
@@ -45,7 +45,7 @@
   "devDependencies": {
     "@types/mysql": "^2.15.15",
     "@types/pg": "^7.14.5",
-    "@vendure/core": "^1.8.4",
+    "@vendure/core": "^1.8.5",
     "mysql": "^2.18.1",
     "pg": "^8.4.0",
     "rimraf": "^3.0.0",

+ 4 - 4
packages/ui-devkit/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/ui-devkit",
-  "version": "1.8.4",
+  "version": "1.8.5",
   "description": "A library for authoring Vendure Admin UI extensions",
   "keywords": [
     "vendure",
@@ -40,8 +40,8 @@
     "@angular/cli": "12.2.16",
     "@angular/compiler": "12.2.16",
     "@angular/compiler-cli": "12.2.16",
-    "@vendure/admin-ui": "^1.8.4",
-    "@vendure/common": "^1.8.4",
+    "@vendure/admin-ui": "^1.8.5",
+    "@vendure/common": "^1.8.5",
     "chalk": "^4.1.0",
     "chokidar": "^3.5.1",
     "fs-extra": "^10.0.0",
@@ -52,7 +52,7 @@
     "@rollup/plugin-node-resolve": "^11.2.0",
     "@types/fs-extra": "^9.0.8",
     "@types/glob": "^7.1.3",
-    "@vendure/core": "^1.8.4",
+    "@vendure/core": "^1.8.5",
     "rimraf": "^3.0.2",
     "rollup": "^2.40.0",
     "rollup-plugin-terser": "^7.0.2",