From b597b3c3cbd9641e13f27d18dbf2b5d7a3a46068 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 15 Apr 2026 17:15:20 +0000 Subject: [PATCH 1/2] fix(import): fix file picker not opening on repeated import attempts COMPASS-10565 Two issues were preventing the import file picker from working reliably: 1. In createElectronFileInputBackend, when showOpenDialog rejected (e.g. on macOS ARM), the catch handler silently swallowed the error without notifying listeners. This left the import modal in a stuck invisible state. Fix: notify listeners with an empty file list on error so the modal can clean up via the onCancel handler. 2. When the import was re-triggered while the modal was stuck, openImport reset the state to the same shape (isOpen: true, fileName: '', errors: []). React reused the existing ImportFileInput without remounting, so the mount-only autoOpen effect never re-fired. Fix: add an openId counter to the import state that increments on each OPEN action and use it as a React key to force remounting ImportFileInput. Also fixed openImportFileDialog in crud-store to accept and forward the origin parameter for correct telemetry tracking. Co-authored-by: Jack Weir --- .../src/components/file-picker-dialog.tsx | 2 +- packages/compass-crud/src/stores/crud-store.ts | 6 ++++-- .../compass-import-export/src/components/import-modal.tsx | 8 ++++++-- packages/compass-import-export/src/modules/import.ts | 3 +++ 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/compass-components/src/components/file-picker-dialog.tsx b/packages/compass-components/src/components/file-picker-dialog.tsx index 1cefeeb7272..1753353acba 100644 --- a/packages/compass-components/src/components/file-picker-dialog.tsx +++ b/packages/compass-components/src/components/file-picker-dialog.tsx @@ -300,7 +300,7 @@ export function createElectronFileInputBackend( for (const listener of listeners) listener(files); }) .catch(() => { - /* ignore */ + for (const listener of listeners) listener([]); }); }, onFilesChosen(listener) { diff --git a/packages/compass-crud/src/stores/crud-store.ts b/packages/compass-crud/src/stores/crud-store.ts index 895f0e95350..ed67c138779 100644 --- a/packages/compass-crud/src/stores/crud-store.ts +++ b/packages/compass-crud/src/stores/crud-store.ts @@ -1266,10 +1266,12 @@ class CrudStoreImpl * Open an import file dialog from compass-import-export-plugin. * Emits a global app registry event the plugin listens to. */ - openImportFileDialog() { + openImportFileDialog( + origin: 'empty-state' | 'crud-toolbar' = 'empty-state' + ) { this.connectionScopedAppRegistry.emit('open-import', { namespace: this.state.ns, - origin: 'empty-state', + origin, }); } diff --git a/packages/compass-import-export/src/components/import-modal.tsx b/packages/compass-import-export/src/components/import-modal.tsx index 5321b28cc0d..e9f4d4db814 100644 --- a/packages/compass-import-export/src/components/import-modal.tsx +++ b/packages/compass-import-export/src/components/import-modal.tsx @@ -75,6 +75,7 @@ const dataTypesLinkStyles = css({ type ImportModalProps = { isOpen: boolean; + openId: number; ns: string; startImport: () => void; cancelImport: () => void; @@ -115,6 +116,7 @@ type ImportModalProps = { function ImportModal({ isOpen, + openId, ns, startImport, cancelImport, @@ -171,11 +173,12 @@ function ImportModal({ if (isOpen && !fileName && errors.length === 0) { // Show the file input when we don't have a file to import yet. + // Use openId as key to force remounting when import is re-opened, + // ensuring autoOpen triggers the file dialog each time. return ( - // Don't actually show it on the screen, just render it to trigger - // autoOpen
({ ns: state.import.namespace, isOpen: state.import.isOpen, + openId: state.import.openId, errors: state.import.firstErrors, fileType: state.import.fileType, fileName: state.import.fileName, diff --git a/packages/compass-import-export/src/modules/import.ts b/packages/compass-import-export/src/modules/import.ts index cc4d2788af0..e3b5ed8910e 100644 --- a/packages/compass-import-export/src/modules/import.ts +++ b/packages/compass-import-export/src/modules/import.ts @@ -86,6 +86,7 @@ type FieldType = FieldFromJSON | FieldFromCSV; type ImportState = { isOpen: boolean; isInProgressMessageOpen: boolean; + openId: number; firstErrors: Error[]; fileType: AcceptedFileType | ''; fileName: string; @@ -122,6 +123,7 @@ type ImportState = { export const INITIAL_STATE: ImportState = { isOpen: false, isInProgressMessageOpen: false, + openId: 0, firstErrors: [], fileName: '', errorLogFilePath: '', @@ -1117,6 +1119,7 @@ export const importReducer: Reducer = ( if (action.type === OPEN) { return { ...INITIAL_STATE, + openId: state.openId + 1, namespace: action.namespace, connectionId: action.connectionId, isOpen: true, From d0febac350c3bf661993cc3120c3daeadc83a0ad Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 15 Apr 2026 17:20:20 +0000 Subject: [PATCH 2/2] test(import): add tests for openId incrementing and dialog error handling COMPASS-10565 --- .../components/file-picker-dialog.spec.tsx | 20 ++++++++ .../src/modules/import.spec.ts | 51 ++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/packages/compass-components/src/components/file-picker-dialog.spec.tsx b/packages/compass-components/src/components/file-picker-dialog.spec.tsx index 8a957895d89..02942842d19 100644 --- a/packages/compass-components/src/components/file-picker-dialog.spec.tsx +++ b/packages/compass-components/src/components/file-picker-dialog.spec.tsx @@ -565,6 +565,26 @@ describe('FileInput', function () { expect(listener).to.been.calledOnceWith([]); }); + it('calls the listener with an empty array if the dialog rejects', async function () { + const { fakeElectron, fakeWebUtils } = createFakeElectron(); + + const backend = createElectronFileInputBackend( + fakeElectron, + fakeWebUtils + )(); + const listener = sinon.stub(); + backend.onFilesChosen(listener); + + fakeElectron.dialog.showOpenDialog.rejects(new Error('dialog failed')); + backend.openFileChooser({ + mode: 'open', + multi: false, + }); + expect(listener).to.not.have.been.called; + await tick(); + expect(listener).to.been.calledOnceWith([]); + }); + it('handles autoOpen:true', async function () { const { fakeElectron, fakeWebUtils } = createFakeElectron(); const backend = createElectronFileInputBackend( diff --git a/packages/compass-import-export/src/modules/import.spec.ts b/packages/compass-import-export/src/modules/import.spec.ts index aca2d9d7a6f..b09aaadb4d3 100644 --- a/packages/compass-import-export/src/modules/import.spec.ts +++ b/packages/compass-import-export/src/modules/import.spec.ts @@ -1,6 +1,11 @@ import { expect } from 'chai'; import path from 'path'; -import { onStarted, openImport, selectImportFileName } from './import'; +import { + onStarted, + openImport, + selectImportFileName, + closeImport, +} from './import'; import type { ImportStore } from '../stores/import-store'; import { ImportPlugin } from '../index'; import { createPluginTestHelpers } from '@mongodb-js/testing-library-compass'; @@ -80,6 +85,50 @@ describe('import [module]', function () { ); expect(mockStore.getState().import.isOpen).to.equal(true); }); + + it('increments openId each time import is opened', function () { + expect(mockStore.getState().import.openId).to.equal(0); + + mockStore.dispatch( + openImport({ + namespace: 'test.test', + origin: 'menu', + connectionId: 'TEST', + }) as any + ); + expect(mockStore.getState().import.openId).to.equal(1); + + mockStore.dispatch(closeImport()); + + mockStore.dispatch( + openImport({ + namespace: 'test.test', + origin: 'menu', + connectionId: 'TEST', + }) as any + ); + expect(mockStore.getState().import.openId).to.equal(2); + }); + + it('increments openId even when re-opened without closing first', function () { + mockStore.dispatch( + openImport({ + namespace: 'test.test', + origin: 'menu', + connectionId: 'TEST', + }) as any + ); + const firstOpenId = mockStore.getState().import.openId; + + mockStore.dispatch( + openImport({ + namespace: 'test.test', + origin: 'menu', + connectionId: 'TEST', + }) as any + ); + expect(mockStore.getState().import.openId).to.equal(firstOpenId + 1); + }); }); describe('#selectImportFileName', function () {