Skip to content

Commit d8559b9

Browse files
authored
feat(import): include the error count in the import toast COMPASS-7826 (#5659)
* include the error count in the import toast * minus the console.log * add comments * only appears once now
1 parent b8fe4f8 commit d8559b9

File tree

10 files changed

+91
-67
lines changed

10 files changed

+91
-67
lines changed

packages/compass-e2e-tests/tests/collection-import.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,12 +1158,12 @@ describe('Collection import', function () {
11581158
.$(Selectors.closeToastButton(Selectors.ImportToast))
11591159
.waitForDisplayed();
11601160

1161-
// Displays first two errors in the toast and view log.
1161+
// Displays first error in the toast and view log.
11621162
const toastText = await toastElement.getText();
11631163
expect(toastText).to.include('Import completed 0/3 with errors:');
11641164
expect(
11651165
(toastText.match(/E11000 duplicate key error collection/g) || []).length
1166-
).to.equal(2);
1166+
).to.equal(1);
11671167
expect(toastText).to.include('VIEW LOG');
11681168

11691169
const logFilePath = path.resolve(

packages/compass-import-export/src/components/import-toast.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,32 @@ export function showInProgressToast({
1515
fileName,
1616
cancelImport,
1717
docsWritten,
18+
numErrors,
1819
bytesProcessed,
1920
bytesTotal,
2021
}: {
2122
fileName: string;
2223
cancelImport: () => void;
2324
docsWritten: number;
25+
numErrors: number;
2426
bytesProcessed: number;
2527
bytesTotal: number;
2628
}) {
2729
// Update the toast with the new progress.
2830
const progress = bytesTotal ? bytesProcessed / bytesTotal : undefined;
31+
32+
let statusMessage = `${docsWritten} document${
33+
docsWritten !== 1 ? 's' : ''
34+
} written.`;
35+
if (numErrors) {
36+
statusMessage += ` ${numErrors} error${numErrors !== 1 ? 's' : ''}.`;
37+
}
38+
2939
openToast(importToastId, {
3040
title: `Importing ${path.basename(fileName)}…`,
3141
description: (
3242
<ToastBody
33-
statusMessage={`${docsWritten} document${
34-
docsWritten !== 1 ? 's' : ''
35-
} written.`}
43+
statusMessage={statusMessage}
3644
actionHandler={cancelImport}
3745
actionText="stop"
3846
/>

packages/compass-import-export/src/import/import-csv.spec.ts

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -781,48 +781,39 @@ describe('importCSV', function () {
781781
expect(result.dbStats.insertedCount).to.equal(0);
782782

783783
expect(progressCallback.callCount).to.equal(3);
784-
expect(errorCallback.callCount).to.equal(4); // yes one more MongoBulkWriteError than items in the batch
784+
expect(errorCallback.callCount).to.equal(1); // once for the batch
785785

786-
const expectedErrors = [
786+
const expectedErrors: ErrorJSON[] = [
787787
{
788-
// first one speems to relate to the batch as there's no index
789788
name: 'MongoBulkWriteError',
790789
message: 'Document failed validation',
791790
code: 121,
792-
},
793-
{
794-
name: 'WriteConcernError',
795-
message: 'Document failed validation',
796-
index: 0,
797-
code: 121,
798-
},
799-
{
800-
name: 'WriteConcernError',
801-
message: 'Document failed validation',
802-
index: 1,
803-
code: 121,
804-
},
805-
{
806-
name: 'WriteConcernError',
807-
message: 'Document failed validation',
808-
index: 2,
809-
code: 121,
791+
numErrors: 3,
810792
},
811793
];
812794

813-
const errorLog = await fs.promises.readFile(output.path, 'utf8');
814-
expect(errorLog).to.equal(
815-
expectedErrors.map((e) => JSON.stringify(e)).join(os.EOL) + os.EOL
816-
);
817-
818795
const errors = errorCallback.args.map((args) => args[0]);
819-
820-
try {
821-
expect(errors).to.deep.equal(expectedErrors);
822-
} catch (err: any) {
823-
console.log(JSON.stringify({ errors, expectedErrors }, null, 2));
824-
throw err;
825-
}
796+
expect(errors).to.deep.equal(expectedErrors);
797+
798+
// the log file has one for each error in the bulk write too
799+
expectedErrors.push({
800+
name: 'WriteConcernError',
801+
message: 'Document failed validation',
802+
index: 0,
803+
code: 121,
804+
});
805+
expectedErrors.push({
806+
name: 'WriteConcernError',
807+
message: 'Document failed validation',
808+
index: 1,
809+
code: 121,
810+
});
811+
expectedErrors.push({
812+
name: 'WriteConcernError',
813+
message: 'Document failed validation',
814+
index: 2,
815+
code: 121,
816+
});
826817

827818
const errorsText = await fs.promises.readFile(output.path, 'utf8');
828819
expect(errorsText).to.equal(formatErrorLines(expectedErrors));

packages/compass-import-export/src/import/import-csv.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ export async function importCSV({
131131
const collectionStream = createCollectionWriteStream(
132132
dataService,
133133
ns,
134-
stopOnErrors ?? false
134+
stopOnErrors ?? false,
135+
errorCallback
135136
);
136137

137138
const parseStream = Papa.parse(Papa.NODE_STREAM_INPUT, {
@@ -166,7 +167,6 @@ export async function importCSV({
166167
await processWriteStreamErrors({
167168
collectionStream,
168169
output,
169-
errorCallback,
170170
});
171171

172172
const result = makeImportResult(
@@ -194,7 +194,6 @@ export async function importCSV({
194194
await processWriteStreamErrors({
195195
collectionStream,
196196
output,
197-
errorCallback,
198197
});
199198

200199
const result = makeImportResult(

packages/compass-import-export/src/import/import-json.spec.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -555,33 +555,34 @@ describe('importJSON', function () {
555555
expect(result.dbStats.insertedCount).to.equal(0);
556556

557557
expect(progressCallback.callCount).to.equal(2);
558-
expect(errorCallback.callCount).to.equal(3); // yes one more MongoBulkWriteError than items in the batch
558+
expect(errorCallback.callCount).to.equal(1); // once for the batch
559559

560-
const expectedErrors = [
560+
const expectedErrors: ErrorJSON[] = [
561561
{
562-
// first one speems to relate to the batch as there's no index
563562
name: 'MongoBulkWriteError',
564563
message: 'Document failed validation',
565564
code: 121,
566-
},
567-
{
568-
name: 'WriteConcernError',
569-
message: 'Document failed validation',
570-
index: 0,
571-
code: 121,
572-
},
573-
{
574-
name: 'WriteConcernError',
575-
message: 'Document failed validation',
576-
index: 1,
577-
code: 121,
565+
numErrors: 2,
578566
},
579567
];
580568

581569
const errors = errorCallback.args.map((args) => args[0]);
582-
583570
expect(errors).to.deep.equal(expectedErrors);
584571

572+
// the log file has one for each error in the bulk write too
573+
expectedErrors.push({
574+
name: 'WriteConcernError',
575+
message: 'Document failed validation',
576+
index: 0,
577+
code: 121,
578+
});
579+
expectedErrors.push({
580+
name: 'WriteConcernError',
581+
message: 'Document failed validation',
582+
index: 1,
583+
code: 121,
584+
});
585+
585586
const errorsText = await fs.promises.readFile(output.path, 'utf8');
586587
expect(errorsText).to.equal(formatErrorLines(expectedErrors));
587588
});

packages/compass-import-export/src/import/import-json.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ export async function importJSON({
100100
const collectionStream = createCollectionWriteStream(
101101
dataService,
102102
ns,
103-
stopOnErrors ?? false
103+
stopOnErrors ?? false,
104+
errorCallback
104105
);
105106

106107
const parserStreams = [];
@@ -132,7 +133,6 @@ export async function importJSON({
132133
await processWriteStreamErrors({
133134
collectionStream,
134135
output,
135-
errorCallback,
136136
});
137137

138138
return makeImportResult(
@@ -156,7 +156,6 @@ export async function importJSON({
156156
await processWriteStreamErrors({
157157
collectionStream,
158158
output,
159-
errorCallback,
160159
});
161160

162161
return makeImportResult(collectionStream, numProcessed, docStatsStream);

packages/compass-import-export/src/import/import-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export type ErrorJSON = {
2222
code?: string | number;
2323
op?: any;
2424
errorInfo?: Document;
25+
numErrors?: number;
2526
};
2627

2728
export type ImportProgress = {

packages/compass-import-export/src/import/import-utils.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,18 @@ export function errorToJSON(error: any): ErrorJSON {
4646
}
4747
}
4848

49+
// For bulk write errors we include the number of errors that were in the
50+
// batch. So one error actually maps to (potentially) many failed documents.
51+
if (error.writeErrors && Array.isArray(error.writeErrors)) {
52+
obj.numErrors = error.writeErrors.length;
53+
}
54+
4955
return obj;
5056
}
5157

5258
export async function processWriteStreamErrors({
5359
collectionStream,
5460
output,
55-
errorCallback,
5661
}: {
5762
collectionStream: WritableCollectionStream;
5863
output?: Writable;
@@ -68,9 +73,9 @@ export async function processWriteStreamErrors({
6873
.concat(stats.writeConcernErrors);
6974

7075
for (const error of allErrors) {
76+
debug('write error', error);
77+
7178
const transformedError = errorToJSON(error);
72-
debug('write error', transformedError);
73-
errorCallback?.(transformedError);
7479

7580
if (!output) {
7681
continue;

packages/compass-import-export/src/modules/import.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,12 @@ export const startImport = (): ImportThunkAction<Promise<void>> => {
275275

276276
let promise: Promise<ImportResult>;
277277

278+
let numErrors = 0;
278279
const errorCallback = (err: ErrorJSON) => {
280+
// For bulk write errors we'll get one callback for the whole batch and
281+
// then numErrors is the number of documents that failed for that batch.
282+
// Usually but not necessarily the entire batch.
283+
numErrors += err.numErrors ?? 1;
279284
if (errors.length < 5) {
280285
// Only store the first few errors in memory.
281286
// The log file tracks all of them.
@@ -295,6 +300,7 @@ export const startImport = (): ImportThunkAction<Promise<void>> => {
295300
showInProgressToast({
296301
cancelImport: () => dispatch(cancelImport()),
297302
docsWritten,
303+
numErrors,
298304
fileName,
299305
bytesProcessed,
300306
bytesTotal: fileSize,

packages/compass-import-export/src/utils/collection-stream.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import type {
1010
BulkWriteResult,
1111
} from 'mongodb';
1212
import type { DataService } from 'mongodb-data-service';
13+
import type { ErrorJSON } from '../import/import-types';
14+
import { errorToJSON } from '../import/import-utils';
1315

1416
import { createDebug } from './logger';
1517

@@ -88,11 +90,13 @@ export class WritableCollectionStream extends Writable {
8890
_batchCounter: number;
8991
_stats: CollectionStreamStats;
9092
_errors: CollectionStreamProgressError[];
93+
errorCallback?: (error: ErrorJSON) => void;
9194

9295
constructor(
9396
dataService: Pick<DataService, 'bulkWrite' | 'insertOne'>,
9497
ns: string,
95-
stopOnErrors: boolean
98+
stopOnErrors: boolean,
99+
errorCallback?: (error: ErrorJSON) => void
96100
) {
97101
super({ objectMode: true });
98102
this.dataService = dataService;
@@ -117,6 +121,7 @@ export class WritableCollectionStream extends Writable {
117121
};
118122

119123
this._errors = [];
124+
this.errorCallback = errorCallback;
120125
}
121126

122127
_write(
@@ -181,7 +186,8 @@ export class WritableCollectionStream extends Writable {
181186
await this.dataService.insertOne(this.ns, doc);
182187
insertedCount += 1;
183188
} catch (insertOneByOneError: any) {
184-
this._errors.push(insertOneByOneError as Error);
189+
this._errors.push(insertOneByOneError);
190+
this.errorCallback?.(errorToJSON(insertOneByOneError));
185191

186192
if (this.stopOnErrors) {
187193
break;
@@ -195,7 +201,9 @@ export class WritableCollectionStream extends Writable {
195201
// will not return any result, but server might write some docs and bulk
196202
// result can still be accessed on the error instance
197203
result = (bulkWriteError as MongoBulkWriteError).result;
204+
198205
this._errors.push(bulkWriteError);
206+
this.errorCallback?.(errorToJSON(bulkWriteError));
199207
}
200208
}
201209

@@ -281,7 +289,13 @@ export class WritableCollectionStream extends Writable {
281289
export const createCollectionWriteStream = function (
282290
dataService: Pick<DataService, 'bulkWrite' | 'insertOne'>,
283291
ns: string,
284-
stopOnErrors: boolean
292+
stopOnErrors: boolean,
293+
errorCallback?: (error: ErrorJSON) => void
285294
) {
286-
return new WritableCollectionStream(dataService, ns, stopOnErrors);
295+
return new WritableCollectionStream(
296+
dataService,
297+
ns,
298+
stopOnErrors,
299+
errorCallback
300+
);
287301
};

0 commit comments

Comments
 (0)