From 5bf3c469f0e6b273afd0266365bda8d448a42371 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Mon, 7 Jul 2025 17:27:09 +0200 Subject: [PATCH 1/3] fix(compass-crud): show error for bulk operations COMPASS-8529 --- .../components/bulk-actions-toasts.spec.tsx | 41 +++++++++--- .../src/components/bulk-actions-toasts.tsx | 65 +++++++++++-------- .../compass-crud/src/stores/crud-store.ts | 3 +- 3 files changed, 72 insertions(+), 37 deletions(-) diff --git a/packages/compass-crud/src/components/bulk-actions-toasts.spec.tsx b/packages/compass-crud/src/components/bulk-actions-toasts.spec.tsx index 6d9f53d989c..33ebf9d8d78 100644 --- a/packages/compass-crud/src/components/bulk-actions-toasts.spec.tsx +++ b/packages/compass-crud/src/components/bulk-actions-toasts.spec.tsx @@ -67,17 +67,23 @@ describe('Bulk Action Toasts', function () { { modal: openBulkDeleteFailureToast, affected: undefined, - expected: 'The delete operation failed.', + error: new Error('Test error'), + expected: ['The delete operation failed.', 'Test error'], }, { modal: openBulkDeleteFailureToast, affected: 1, - expected: '1 document could not been deleted.', + error: new Error('Another test error'), + expected: [ + '1 document could not been deleted.', + 'Another test error', + ], }, { modal: openBulkDeleteFailureToast, affected: 2, - expected: '2 documents could not been deleted.', + error: new Error('Another failure'), + expected: ['2 documents could not been deleted.', 'Another failure'], }, ]; @@ -85,12 +91,18 @@ describe('Bulk Action Toasts', function () { it(`${useCase.modal.name} shows the text '${useCase.expected}' when affected document/s is/are '${useCase.affected}'`, async function () { useCase.modal({ affectedDocuments: useCase.affected, + error: useCase.error, onRefresh: () => {}, }); await waitFor(async function () { - const node = await screen.findByText(useCase.expected); - expect(node).to.exist; + if (!Array.isArray(useCase.expected)) { + expect(await screen.findByText(useCase.expected)).to.exist; + } else { + for (const expectedText of useCase.expected) { + expect(await screen.findByText(expectedText)).to.exist; + } + } }); }); } @@ -160,17 +172,20 @@ describe('Bulk Action Toasts', function () { { modal: openBulkUpdateFailureToast, affected: undefined, - expected: 'The update operation failed.', + error: new Error('Test error'), + expected: ['The update operation failed.', 'Test error'], }, { modal: openBulkUpdateFailureToast, affected: 1, - expected: '1 document could not been updated.', + error: new Error('Could not update'), + expected: ['1 document could not been updated.', 'Could not update'], }, { modal: openBulkUpdateFailureToast, affected: 2, - expected: '2 documents could not been updated.', + error: new Error('Update failed'), + expected: ['2 documents could not been updated.', 'Update failed'], }, ]; @@ -178,12 +193,18 @@ describe('Bulk Action Toasts', function () { it(`${useCase.modal.name} shows the text '${useCase.expected}' when ${useCase.affected} document/s affected`, async function () { useCase.modal({ affectedDocuments: useCase.affected, + error: useCase.error, onRefresh: () => {}, }); await waitFor(async function () { - const node = await screen.findByText(useCase.expected); - expect(node).to.exist; + if (!Array.isArray(useCase.expected)) { + expect(await screen.findByText(useCase.expected)).to.exist; + } else { + for (const expectedText of useCase.expected) { + expect(await screen.findByText(expectedText)).to.exist; + } + } }); }); } diff --git a/packages/compass-crud/src/components/bulk-actions-toasts.tsx b/packages/compass-crud/src/components/bulk-actions-toasts.tsx index 474470ba8a3..96f7faa2329 100644 --- a/packages/compass-crud/src/components/bulk-actions-toasts.tsx +++ b/packages/compass-crud/src/components/bulk-actions-toasts.tsx @@ -4,6 +4,7 @@ import { closeToast, ToastBody, } from '@mongodb-js/compass-components'; +import { MongoNetworkError } from 'mongodb'; type BulkDeleteSuccessToastProps = { affectedDocuments?: number; @@ -74,28 +75,35 @@ export function openBulkDeleteProgressToast({ type BulkDeleteFailureToastProps = { affectedDocuments?: number; + error: any; }; +const isNetworkError = (error: any) => error instanceof MongoNetworkError; + export function openBulkDeleteFailureToast({ affectedDocuments, + error, }: BulkDeleteFailureToastProps): void { - let text; - switch (affectedDocuments) { - case undefined: - text = 'The delete operation failed.'; - break; - case 1: - text = `${affectedDocuments} document could not been deleted.`; - break; - default: - text = `${affectedDocuments} documents could not been deleted.`; - } + let title: string; + if (isNetworkError(error)) { + title = 'Delete operation - status unknown.'; + } else + switch (affectedDocuments) { + case undefined: + title = 'The delete operation failed.'; + break; + case 1: + title = `${affectedDocuments} document could not been deleted.`; + break; + default: + title = `${affectedDocuments} documents could not been deleted.`; + } openToast('bulk-delete-toast', { - title: '', + title, variant: 'warning', dismissible: true, - description: , + description: , }); } @@ -168,27 +176,32 @@ export function openBulkUpdateProgressToast({ type BulkUpdateFailureToastProps = { affectedDocuments?: number; + error: any; }; export function openBulkUpdateFailureToast({ affectedDocuments, + error, }: BulkUpdateFailureToastProps): void { - let text; - switch (affectedDocuments) { - case undefined: - text = 'The update operation failed.'; - break; - case 1: - text = `${affectedDocuments} document could not been updated.`; - break; - default: - text = `${affectedDocuments} documents could not been updated.`; - } + let title: string; + if (isNetworkError(error)) { + title = 'Update operation - status unknown.'; + } else + switch (affectedDocuments) { + case undefined: + title = 'The update operation failed.'; + break; + case 1: + title = `${affectedDocuments} document could not been updated.`; + break; + default: + title = `${affectedDocuments} documents could not been updated.`; + } openToast('bulk-update-toast', { - title: '', + title, variant: 'warning', dismissible: true, - description: , + description: , }); } diff --git a/packages/compass-crud/src/stores/crud-store.ts b/packages/compass-crud/src/stores/crud-store.ts index 81bc2087c3d..3f0d884a5b8 100644 --- a/packages/compass-crud/src/stores/crud-store.ts +++ b/packages/compass-crud/src/stores/crud-store.ts @@ -2,7 +2,6 @@ import type { Listenable, Store } from 'reflux'; import Reflux from 'reflux'; import toNS from 'mongodb-ns'; import { findIndex, isEmpty, isEqual } from 'lodash'; -import type { MongoServerError } from 'mongodb'; import semver from 'semver'; import StateMixin from '@mongodb-js/reflux-state-mixin'; import type { Element } from 'hadron-document'; @@ -1243,6 +1242,7 @@ class CrudStoreImpl } catch (err: any) { openBulkUpdateFailureToast({ affectedDocuments: this.state.bulkUpdate.affected, + error: err, }); this.logger.log.error( @@ -1899,6 +1899,7 @@ class CrudStoreImpl bulkDeleteFailed(ex: Error) { openBulkDeleteFailureToast({ affectedDocuments: this.state.bulkDelete.affected, + error: ex, }); this.logger.log.error( From 92b596afbd2c61aa032caa7b5b6ef54743ba3236 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Mon, 7 Jul 2025 17:41:15 +0200 Subject: [PATCH 2/3] update message and add tests --- .../components/bulk-actions-toasts.spec.tsx | 20 +++++++++++++++++++ .../src/components/bulk-actions-toasts.tsx | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/compass-crud/src/components/bulk-actions-toasts.spec.tsx b/packages/compass-crud/src/components/bulk-actions-toasts.spec.tsx index 33ebf9d8d78..1102de1038f 100644 --- a/packages/compass-crud/src/components/bulk-actions-toasts.spec.tsx +++ b/packages/compass-crud/src/components/bulk-actions-toasts.spec.tsx @@ -17,6 +17,7 @@ import { } from './bulk-actions-toasts'; import { expect } from 'chai'; import sinon from 'sinon'; +import { MongoNetworkError } from 'mongodb'; function renderToastPortal() { return render(); @@ -85,6 +86,15 @@ describe('Bulk Action Toasts', function () { error: new Error('Another failure'), expected: ['2 documents could not been deleted.', 'Another failure'], }, + { + modal: openBulkDeleteFailureToast, + affected: 2, + error: new MongoNetworkError('Connection lost'), + expected: [ + 'Delete operation - network error occurred.', + 'Connection lost', + ], + }, ]; for (const useCase of USE_CASES) { @@ -187,6 +197,16 @@ describe('Bulk Action Toasts', function () { error: new Error('Update failed'), expected: ['2 documents could not been updated.', 'Update failed'], }, + + { + modal: openBulkUpdateFailureToast, + affected: 2, + error: new MongoNetworkError('Connection lost'), + expected: [ + 'Update operation - network error occurred.', + 'Connection lost', + ], + }, ]; for (const useCase of USE_CASES) { diff --git a/packages/compass-crud/src/components/bulk-actions-toasts.tsx b/packages/compass-crud/src/components/bulk-actions-toasts.tsx index 96f7faa2329..2d420a4f7b8 100644 --- a/packages/compass-crud/src/components/bulk-actions-toasts.tsx +++ b/packages/compass-crud/src/components/bulk-actions-toasts.tsx @@ -86,7 +86,7 @@ export function openBulkDeleteFailureToast({ }: BulkDeleteFailureToastProps): void { let title: string; if (isNetworkError(error)) { - title = 'Delete operation - status unknown.'; + title = 'Delete operation - network error occurred.'; } else switch (affectedDocuments) { case undefined: @@ -185,7 +185,7 @@ export function openBulkUpdateFailureToast({ }: BulkUpdateFailureToastProps): void { let title: string; if (isNetworkError(error)) { - title = 'Update operation - status unknown.'; + title = 'Update operation - network error occurred.'; } else switch (affectedDocuments) { case undefined: From 2a55bd53c6a697c1bfd12611f8623b07be4d45a3 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Tue, 8 Jul 2025 10:37:55 +0200 Subject: [PATCH 3/3] style --- .../components/bulk-actions-toasts.spec.tsx | 13 ++-- .../src/components/bulk-actions-toasts.tsx | 78 +++++++------------ .../compass-crud/src/stores/crud-store.ts | 3 +- 3 files changed, 36 insertions(+), 58 deletions(-) diff --git a/packages/compass-crud/src/components/bulk-actions-toasts.spec.tsx b/packages/compass-crud/src/components/bulk-actions-toasts.spec.tsx index 1102de1038f..9f821da670d 100644 --- a/packages/compass-crud/src/components/bulk-actions-toasts.spec.tsx +++ b/packages/compass-crud/src/components/bulk-actions-toasts.spec.tsx @@ -75,16 +75,13 @@ describe('Bulk Action Toasts', function () { modal: openBulkDeleteFailureToast, affected: 1, error: new Error('Another test error'), - expected: [ - '1 document could not been deleted.', - 'Another test error', - ], + expected: ['1 document could not be deleted.', 'Another test error'], }, { modal: openBulkDeleteFailureToast, affected: 2, error: new Error('Another failure'), - expected: ['2 documents could not been deleted.', 'Another failure'], + expected: ['2 documents could not be deleted.', 'Another failure'], }, { modal: openBulkDeleteFailureToast, @@ -101,7 +98,7 @@ describe('Bulk Action Toasts', function () { it(`${useCase.modal.name} shows the text '${useCase.expected}' when affected document/s is/are '${useCase.affected}'`, async function () { useCase.modal({ affectedDocuments: useCase.affected, - error: useCase.error, + error: useCase.error as Error, onRefresh: () => {}, }); @@ -189,13 +186,13 @@ describe('Bulk Action Toasts', function () { modal: openBulkUpdateFailureToast, affected: 1, error: new Error('Could not update'), - expected: ['1 document could not been updated.', 'Could not update'], + expected: ['1 document could not be updated.', 'Could not update'], }, { modal: openBulkUpdateFailureToast, affected: 2, error: new Error('Update failed'), - expected: ['2 documents could not been updated.', 'Update failed'], + expected: ['2 documents could not be updated.', 'Update failed'], }, { diff --git a/packages/compass-crud/src/components/bulk-actions-toasts.tsx b/packages/compass-crud/src/components/bulk-actions-toasts.tsx index 2d420a4f7b8..be1c308072f 100644 --- a/packages/compass-crud/src/components/bulk-actions-toasts.tsx +++ b/packages/compass-crud/src/components/bulk-actions-toasts.tsx @@ -73,33 +73,37 @@ export function openBulkDeleteProgressToast({ }); } -type BulkDeleteFailureToastProps = { +type BulkOperationFailureToastProps = { affectedDocuments?: number; - error: any; + error: Error; + type: 'delete' | 'update'; }; -const isNetworkError = (error: any) => error instanceof MongoNetworkError; +const isNetworkError = (error: Error) => error instanceof MongoNetworkError; -export function openBulkDeleteFailureToast({ +export function openBulkOperationFailureToast({ affectedDocuments, error, -}: BulkDeleteFailureToastProps): void { + type, +}: BulkOperationFailureToastProps): void { let title: string; if (isNetworkError(error)) { - title = 'Delete operation - network error occurred.'; - } else - switch (affectedDocuments) { - case undefined: - title = 'The delete operation failed.'; - break; - case 1: - title = `${affectedDocuments} document could not been deleted.`; - break; - default: - title = `${affectedDocuments} documents could not been deleted.`; - } + title = `${ + type === 'delete' ? 'Delete' : 'Update' + } operation - network error occurred.`; + } else if (affectedDocuments === undefined) { + title = `The ${type} operation failed.`; + } else if (affectedDocuments === 1) { + title = `${affectedDocuments} document could not be ${ + type === 'delete' ? 'deleted' : 'updated' + }.`; + } else { + title = `${affectedDocuments} documents could not be ${ + type === 'delete' ? 'deleted' : 'updated' + }.`; + } - openToast('bulk-delete-toast', { + openToast(`bulk-${type}-toast`, { title, variant: 'warning', dismissible: true, @@ -107,6 +111,10 @@ export function openBulkDeleteFailureToast({ }); } +export const openBulkDeleteFailureToast = ( + props: Omit +): void => openBulkOperationFailureToast({ ...props, type: 'delete' }); + type BulkUpdateSuccessToastProps = { affectedDocuments?: number; onRefresh: () => void; @@ -174,34 +182,6 @@ export function openBulkUpdateProgressToast({ }); } -type BulkUpdateFailureToastProps = { - affectedDocuments?: number; - error: any; -}; - -export function openBulkUpdateFailureToast({ - affectedDocuments, - error, -}: BulkUpdateFailureToastProps): void { - let title: string; - if (isNetworkError(error)) { - title = 'Update operation - network error occurred.'; - } else - switch (affectedDocuments) { - case undefined: - title = 'The update operation failed.'; - break; - case 1: - title = `${affectedDocuments} document could not been updated.`; - break; - default: - title = `${affectedDocuments} documents could not been updated.`; - } - - openToast('bulk-update-toast', { - title, - variant: 'warning', - dismissible: true, - description: , - }); -} +export const openBulkUpdateFailureToast = ( + props: Omit +): void => openBulkOperationFailureToast({ ...props, type: 'update' }); diff --git a/packages/compass-crud/src/stores/crud-store.ts b/packages/compass-crud/src/stores/crud-store.ts index 3f0d884a5b8..94266d6a916 100644 --- a/packages/compass-crud/src/stores/crud-store.ts +++ b/packages/compass-crud/src/stores/crud-store.ts @@ -68,6 +68,7 @@ import type { } from '@mongodb-js/compass-connections/provider'; import type { Query, QueryBarService } from '@mongodb-js/compass-query-bar'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; +import type { MongoServerError } from 'mongodb'; export type BSONObject = TypeCastMap['Object']; export type BSONArray = TypeCastMap['Array']; @@ -1242,7 +1243,7 @@ class CrudStoreImpl } catch (err: any) { openBulkUpdateFailureToast({ affectedDocuments: this.state.bulkUpdate.affected, - error: err, + error: err as Error, }); this.logger.log.error(