Просмотр исходного кода

feat(server): Add progress reporting to cli import-products command

Michael Bromley 7 лет назад
Родитель
Сommit
cfd3e98e78

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
schema.json


+ 29 - 7
server/cli/populate.ts

@@ -1,6 +1,8 @@
 import { INestApplication } from '@nestjs/common';
 import * as fs from 'fs-extra';
 import * as path from 'path';
+import * as ProgressBar from 'progress';
+import { tap } from 'rxjs/operators';
 import { Connection } from 'typeorm';
 
 import { logColored } from './cli-utils';
@@ -22,7 +24,7 @@ export async function populate() {
 }
 
 export async function importProducts(csvPath: string, languageCode: string) {
-    logColored(`\nImporting from "${csvPath}"... (this may take a minute or two)\n`);
+    logColored(`\nImporting from "${csvPath}"...\n`);
     const app = await getApplicationRef();
     if (app) {
         await importProductsFromFile(app, csvPath, languageCode);
@@ -76,6 +78,7 @@ async function getApplicationRef(): Promise<INestApplication | undefined> {
     }
 
     const config = index.config;
+    config.silent = true;
     const { bootstrap } = require('vendure');
     console.log('Bootstrapping Vendure server...');
     const app = await bootstrap(config);
@@ -100,18 +103,37 @@ async function populateProducts(app: INestApplication, initialData: any) {
     // import the csv of same product data
     const sampleProductsFile = path.join(__dirname, 'assets', 'sample-products.csv');
     await importProductsFromFile(app, sampleProductsFile, initialData.defaultLanguage);
-    await fs.emptyDir(destination);
 }
 
 async function importProductsFromFile(app: INestApplication, csvPath: string, languageCode: string) {
     // import the csv of same product data
     const importer = app.get(Importer);
     const productData = await fs.readFile(csvPath, 'utf-8');
-    const importResult = await importer.parseAndImport(productData, languageCode);
+    let bar: ProgressBar | undefined;
+
+    const importResult = await importer
+        .parseAndImport(productData, languageCode)
+        .pipe(
+            tap((progress: any) => {
+                if (!bar) {
+                    bar = new ProgressBar('  importing [:bar] :percent :etas  Importing: :prodName', {
+                        complete: '=',
+                        incomplete: ' ',
+                        total: progress.processed,
+                        width: 40,
+                    });
+                }
+                bar.tick({ prodName: progress.currentProduct });
+            }),
+        )
+        .toPromise();
     if (importResult.errors.length) {
-        console.error(`Error encountered when importing product data:`);
-        console.error(importResult.errors.join('\n'));
-    } else {
-        console.log(`Imported ${importResult.importedCount} products`);
+        const errorFile = path.join(process.cwd(), 'vendure-import-error.log');
+        console.log(
+            `${importResult.errors.length} errors encountered when importing product data. See: ${errorFile}`,
+        );
+        await fs.writeFile(errorFile, importResult.errors.join('\n'));
     }
+
+    logColored(`\nImported ${importResult.imported} products`);
 }

+ 2 - 1
server/e2e/import.e2e-spec.ts

@@ -41,7 +41,8 @@ describe('Import resolver', () => {
         expect(result.importProducts.errors).toEqual([
             'Invalid Record Length: header length is 10, got 1 on line 8',
         ]);
-        expect(result.importProducts.importedCount).toBe(4);
+        expect(result.importProducts.imported).toBe(4);
+        expect(result.importProducts.processed).toBe(4);
 
         const productResult = await client.query(
             gql`

+ 2 - 1
server/mock-data/simple-graphql-client.ts

@@ -73,7 +73,8 @@ export class SimpleGraphQLClient {
             mutation: gql`
                 mutation ImportProducts($csvFile: Upload!) {
                     importProducts(csvFile: $csvFile) {
-                        importedCount
+                        imported
+                        processed
                         errors
                     }
                 }

+ 2 - 0
server/package.json

@@ -27,6 +27,7 @@
     "@nestjs/graphql": "5.4.0",
     "@nestjs/testing": "5.4.0",
     "@nestjs/typeorm": "^5.2.2",
+    "@types/progress": "^2.0.1",
     "apollo-server-express": "^2.2.2",
     "bcrypt": "^3.0.2",
     "body-parser": "^1.18.3",
@@ -53,6 +54,7 @@
     "mysql": "^2.16.0",
     "nanoid": "^2.0.0",
     "nodemailer": "^4.7.0",
+    "progress": "^2.0.3",
     "prompts": "^1.2.1",
     "reflect-metadata": "^0.1.12",
     "rxjs": "^6.3.3",

+ 1 - 1
server/src/api/resolvers/import.resolver.ts

@@ -17,6 +17,6 @@ export class ImportResolver {
         @Args() args: ImportProductsMutationArgs,
     ): Promise<ImportInfo> {
         const { stream, filename, mimetype, encoding } = await args.csvFile;
-        return this.importer.parseAndImport(stream, ctx);
+        return this.importer.parseAndImport(stream, ctx).toPromise();
     }
 }

+ 2 - 1
server/src/api/types/import.api.graphql

@@ -4,5 +4,6 @@ type Mutation {
 
 type ImportInfo {
     errors: [String!]
-    importedCount: Int!
+    processed: Int!
+    imported: Int!
 }

+ 12 - 0
server/yarn.lock

@@ -354,6 +354,13 @@
     "@types/events" "*"
     "@types/node" "*"
 
+"@types/progress@^2.0.1":
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/@types/progress/-/progress-2.0.1.tgz#af866001a836dbd28a6656e4b335d333bad7faf2"
+  integrity sha512-1Smw4LvkCM4zgie7Zo/GtzrMx1Zlq1XVfPoOBXPLIkmtep06wBE7sXupVDq7MXWrHCYtsM0d6i5eF6OM+LJJrQ==
+  dependencies:
+    "@types/node" "*"
+
 "@types/prompts@^1.1.1":
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/@types/prompts/-/prompts-1.1.1.tgz#5688abfa6a95ddfe808f8ae5337e86e5cf8f3147"
@@ -5494,6 +5501,11 @@ process-nextick-args@^2.0.0, process-nextick-args@~2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
 
+progress@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
+  integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
+
 prompts@^0.1.9:
   version "0.1.11"
   resolved "https://registry.yarnpkg.com/prompts/-/prompts-0.1.11.tgz#fdfac72f61d2887f4eaf2e65e748a9d9ef87206f"

+ 7 - 4
shared/generated-types.ts

@@ -681,7 +681,8 @@ export interface LoginResult {
 
 export interface ImportInfo {
     errors?: string[] | null;
-    importedCount: number;
+    processed: number;
+    imported: number;
 }
 
 export interface AdministratorListOptions {
@@ -1383,7 +1384,7 @@ export interface CreateProductVariantInput {
     sku: string;
     price?: number | null;
     taxCategoryId: string;
-    optionCodes?: string[] | null;
+    optionIds?: string[] | null;
     featuredAssetId?: string | null;
     assetIds?: string[] | null;
     customFields?: Json | null;
@@ -4605,7 +4606,8 @@ export namespace LoginResultResolvers {
 export namespace ImportInfoResolvers {
     export interface Resolvers<Context = any> {
         errors?: ErrorsResolver<string[] | null, any, Context>;
-        importedCount?: ImportedCountResolver<number, any, Context>;
+        processed?: ProcessedResolver<number, any, Context>;
+        imported?: ImportedResolver<number, any, Context>;
     }
 
     export type ErrorsResolver<R = string[] | null, Parent = any, Context = any> = Resolver<
@@ -4613,7 +4615,8 @@ export namespace ImportInfoResolvers {
         Parent,
         Context
     >;
-    export type ImportedCountResolver<R = number, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type ProcessedResolver<R = number, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type ImportedResolver<R = number, Parent = any, Context = any> = Resolver<R, Parent, Context>;
 }
 
 export namespace GetAdministrators {

Некоторые файлы не были показаны из-за большого количества измененных файлов