Skip to content

Commit b19f2e7

Browse files
fix(connect): can not select files on the connection form VSCODE-658 (#898)
1 parent 6af38f8 commit b19f2e7

12 files changed

+397
-193
lines changed

package-lock.json

+117-114
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1209,9 +1209,9 @@
12091209
"@babel/core": "^7.25.8",
12101210
"@babel/parser": "^7.25.8",
12111211
"@babel/traverse": "^7.25.7",
1212-
"@mongodb-js/compass-components": "^1.30.1",
1213-
"@mongodb-js/connection-form": "^1.42.0",
1214-
"@mongodb-js/connection-info": "^0.9.1",
1212+
"@mongodb-js/compass-components": "^1.32.1",
1213+
"@mongodb-js/connection-form": "^1.45.1",
1214+
"@mongodb-js/connection-info": "^0.9.5",
12151215
"@mongodb-js/mongodb-constants": "^0.10.3",
12161216
"@mongosh/browser-runtime-electron": "^2.3.3",
12171217
"@mongosh/i18n": "^2.3.3",

src/test/suite/views/webview-app/overview-page.test.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ describe('OverviewPage test suite', function () {
156156
.getCalls()
157157
.filter(
158158
(call) =>
159-
call.args[0].command === MESSAGE_TYPES.EDIT_AND_CONNECT_CONNECTION
159+
call.args[0].command === MESSAGE_TYPES.EDIT_CONNECTION_AND_CONNECT
160160
);
161161
};
162162
expect(getConnectMessages()).to.have.length(0);
@@ -165,7 +165,7 @@ describe('OverviewPage test suite', function () {
165165
expect(connectMessages).to.have.length(1);
166166

167167
expect(connectMessages[0].args[0]).to.deep.equal({
168-
command: 'EDIT_AND_CONNECT_CONNECTION',
168+
command: 'EDIT_CONNECTION_AND_CONNECT',
169169
connectionInfo: {
170170
id: 'pear',
171171
connectionOptions: {

src/test/suite/views/webviewController.test.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ suite('Webview Test Suite', () => {
124124
const extensionPath = mdbTestExtension.extensionContextStub.extensionPath;
125125
const htmlString = getWebviewContent({
126126
extensionPath,
127-
telemetryUserId: 'MOCK_ANONYMOU_ID',
127+
telemetryUserId: 'MOCK_ANONYMOUS_ID',
128128
webview: {
129129
asWebviewUri: (jsUri) => {
130130
return jsUri;
@@ -133,7 +133,7 @@ suite('Webview Test Suite', () => {
133133
});
134134

135135
expect(htmlString).to.include(
136-
">window['VSCODE_EXTENSION_SEGMENT_ANONYMOUS_ID'] = 'MOCK_ANONYMOU_ID';"
136+
">window['VSCODE_EXTENSION_SEGMENT_ANONYMOUS_ID'] = 'MOCK_ANONYMOUS_ID';"
137137
);
138138
});
139139

@@ -143,7 +143,7 @@ suite('Webview Test Suite', () => {
143143
extensionPath,
144144
telemetryUserId: 'test',
145145
webview: {
146-
asWebviewUri: (jsUri) => {
146+
asWebviewUri: (jsUri: vscode.Uri) => {
147147
return jsUri;
148148
},
149149
} as unknown as vscode.Webview,
@@ -177,7 +177,7 @@ suite('Webview Test Suite', () => {
177177
extensionPath,
178178
telemetryUserId: 'test',
179179
webview: {
180-
asWebviewUri: (jsUri) => {
180+
asWebviewUri: (jsUri: vscode.Uri) => {
181181
return jsUri;
182182
},
183183
} as unknown as vscode.Webview,
@@ -368,7 +368,7 @@ suite('Webview Test Suite', () => {
368368
onDidReceiveMessage: (callback): void => {
369369
messageReceived = callback;
370370
},
371-
asWebviewUri: () => '',
371+
asWebviewUri: (): string => '',
372372
};
373373
const fakeVSCodeExecuteCommand = sandbox
374374
.stub(vscode.commands, 'executeCommand')
@@ -538,7 +538,7 @@ suite('Webview Test Suite', () => {
538538

539539
// Mock a connection status request call.
540540
messageReceived({
541-
command: MESSAGE_TYPES.EDIT_AND_CONNECT_CONNECTION,
541+
command: MESSAGE_TYPES.EDIT_CONNECTION_AND_CONNECT,
542542
connectionInfo: {
543543
id: 'pineapple',
544544
connectionOptions: {

src/views/webview-app/connection-form.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ const ConnectionForm: React.FunctionComponent<
6767
// Warning: This property may be removed in future
6868
// modal releases.
6969
contentClassName={modalContentStyles}
70-
setOpen={() => onClose()}
70+
setOpen={(): void => onClose()}
7171
open={open}
7272
size="large"
7373
>

src/views/webview-app/extension-app-message-constants.ts

+28-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ConnectionOptions } from 'mongodb-data-service';
2+
import type { FileChooserOptions } from './use-connection-form';
23

34
export enum CONNECTION_STATUS {
45
LOADING = 'LOADING', // When the connection status has not yet been shared from the extension.
@@ -19,9 +20,11 @@ export enum MESSAGE_TYPES {
1920
CANCEL_CONNECT = 'CANCEL_CONNECT',
2021
CONNECT_RESULT = 'CONNECT_RESULT',
2122
CONNECTION_FORM_OPENED = 'CONNECTION_FORM_OPENED',
23+
OPEN_FILE_CHOOSER = 'OPEN_FILE_CHOOSER',
24+
OPEN_FILE_CHOOSER_RESULT = 'OPEN_FILE_CHOOSER_RESULT',
2225
CONNECTION_STATUS_MESSAGE = 'CONNECTION_STATUS_MESSAGE',
2326
OPEN_EDIT_CONNECTION = 'OPEN_EDIT_CONNECTION',
24-
EDIT_AND_CONNECT_CONNECTION = 'EDIT_AND_CONNECT_CONNECTION',
27+
EDIT_CONNECTION_AND_CONNECT = 'EDIT_CONNECTION_AND_CONNECT',
2528
EXTENSION_LINK_CLICKED = 'EXTENSION_LINK_CLICKED',
2629
CREATE_NEW_PLAYGROUND = 'CREATE_NEW_PLAYGROUND',
2730
GET_CONNECTION_STATUS = 'GET_CONNECTION_STATUS',
@@ -58,14 +61,20 @@ export interface OpenEditConnectionMessage extends BasicWebviewMessage {
5861
};
5962
}
6063

61-
export interface EditAndConnectConnection extends BasicWebviewMessage {
62-
command: MESSAGE_TYPES.EDIT_AND_CONNECT_CONNECTION;
64+
export interface EditConnectionAndConnectMessage extends BasicWebviewMessage {
65+
command: MESSAGE_TYPES.EDIT_CONNECTION_AND_CONNECT;
6366
connectionInfo: {
6467
id: string;
6568
connectionOptions: ConnectionOptions;
6669
};
6770
}
6871

72+
export interface OpenFileChooserMessage extends BasicWebviewMessage {
73+
command: MESSAGE_TYPES.OPEN_FILE_CHOOSER;
74+
fileChooserOptions: FileChooserOptions;
75+
requestId: string;
76+
}
77+
6978
export interface ConnectMessage extends BasicWebviewMessage {
7079
command: MESSAGE_TYPES.CONNECT;
7180
connectionInfo: {
@@ -85,6 +94,16 @@ export interface ConnectResultsMessage extends BasicWebviewMessage {
8594
connectionId: string;
8695
}
8796

97+
export type FileChooserResult =
98+
| { canceled: false; filePaths: string[] }
99+
| { canceled: false; filePath?: string };
100+
101+
export interface OpenFileChooserResultMessage extends BasicWebviewMessage {
102+
command: MESSAGE_TYPES.OPEN_FILE_CHOOSER_RESULT;
103+
fileChooserResult: FileChooserResult;
104+
requestId: string;
105+
}
106+
88107
export interface GetConnectionStatusMessage extends BasicWebviewMessage {
89108
command: MESSAGE_TYPES.GET_CONNECTION_STATUS;
90109
}
@@ -113,7 +132,7 @@ export interface ThemeChangedMessage extends BasicWebviewMessage {
113132
darkMode: boolean;
114133
}
115134

116-
export type MESSAGE_FROM_WEBVIEW_TO_EXTENSION =
135+
export type MessageFromWebviewToExtension =
117136
| ConnectMessage
118137
| CancelConnectMessage
119138
| ConnectionFormOpenedMessage
@@ -123,10 +142,12 @@ export type MESSAGE_FROM_WEBVIEW_TO_EXTENSION =
123142
| OpenConnectionStringInputMessage
124143
| OpenTrustedLinkMessage
125144
| RenameConnectionMessage
126-
| EditAndConnectConnection;
145+
| EditConnectionAndConnectMessage
146+
| OpenFileChooserMessage;
127147

128-
export type MESSAGE_FROM_EXTENSION_TO_WEBVIEW =
148+
export type MessageFromExtensionToWebview =
129149
| ConnectResultsMessage
130150
| ConnectionStatusMessage
131151
| ThemeChangedMessage
132-
| OpenEditConnectionMessage;
152+
| OpenEditConnectionMessage
153+
| OpenFileChooserResultMessage;

src/views/webview-app/overview-page.tsx

+89-17
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
import React, { useCallback, useLayoutEffect, useState } from 'react';
2+
import type { ElectronShowFileDialogProvider } from '@mongodb-js/compass-components';
23
import {
34
HorizontalRule,
45
css,
56
resetGlobalCSS,
67
spacing,
8+
FileInputBackendProvider,
9+
createElectronFileInputBackend,
10+
type ElectronFileDialogOptions,
711
} from '@mongodb-js/compass-components';
12+
import type { ConnectionOptions } from 'mongodb-data-service';
813

914
import OverviewHeader from './overview-header';
1015
import ConnectionStatus from './connection-status';
1116
import ConnectHelper from './connect-helper';
1217
import AtlasCta from './atlas-cta';
1318
import ResourcesPanel from './resources-panel/panel';
1419
import { ConnectionForm } from './connection-form';
15-
import useConnectionForm from './use-connection-form';
20+
import useConnectionForm, {
21+
FILE_CHOOSER_MODE,
22+
type FileChooserOptions,
23+
} from './use-connection-form';
24+
import type { MessageFromExtensionToWebview } from './extension-app-message-constants';
25+
import { MESSAGE_TYPES } from './extension-app-message-constants';
1626

1727
const pageStyles = css({
1828
width: '90%',
@@ -39,6 +49,7 @@ const OverviewPage: React.FC = () => {
3949
handleCancelConnectClicked,
4050
handleSaveConnectionClicked,
4151
handleConnectClicked,
52+
handleOpenFileChooser,
4253
} = useConnectionForm();
4354
const handleResourcesPanelClose = useCallback(
4455
() => setShowResourcesPanel(false),
@@ -55,30 +66,91 @@ const OverviewPage: React.FC = () => {
5566
resetGlobalCSS();
5667
}, []);
5768

69+
function handleOpenFileChooserResult<T>(
70+
options: FileChooserOptions
71+
): Promise<T> {
72+
const requestId = handleOpenFileChooser(options);
73+
return new Promise((resolve) => {
74+
const messageHandler = (
75+
event: MessageEvent<MessageFromExtensionToWebview>
76+
): void => {
77+
const message = event.data;
78+
if (
79+
message.command === MESSAGE_TYPES.OPEN_FILE_CHOOSER_RESULT &&
80+
message.requestId === requestId
81+
) {
82+
window.removeEventListener('message', messageHandler);
83+
resolve(message.fileChooserResult as T);
84+
}
85+
};
86+
window.addEventListener('message', messageHandler);
87+
});
88+
}
89+
90+
// Electron 32.0 removed support for the `path` property of the Web File object in favor of the webUtils.getPathForFile method.
91+
// https://github.com/electron/electron/blob/83d704009687956fb4b69cb13ab03664d7950118/docs/breaking-changes.md%23removed-filepath
92+
// We can not import `dialog` and `webUtils` from 'electron' in the sandboxed webview.
93+
// To work around this, we use a custom dialog provider that uses webview APIs
94+
// to send a message to the extension process to open the electron file dialog
95+
// and listen for the response to get the file path and send them to the electron file input backend.
96+
const dialogProvider: ElectronShowFileDialogProvider<void> = {
97+
getCurrentWindow(): void {},
98+
dialog: {
99+
async showSaveDialog(
100+
window: void,
101+
electronFileDialogOptions: Partial<ElectronFileDialogOptions>
102+
): Promise<{ canceled: boolean; filePath?: string }> {
103+
return handleOpenFileChooserResult({
104+
electronFileDialogOptions,
105+
mode: FILE_CHOOSER_MODE.SAVE,
106+
});
107+
},
108+
async showOpenDialog(
109+
window: void,
110+
electronFileDialogOptions: Partial<ElectronFileDialogOptions>
111+
): Promise<{ canceled: boolean; filePaths: string[] }> {
112+
return handleOpenFileChooserResult({
113+
electronFileDialogOptions,
114+
mode: FILE_CHOOSER_MODE.OPEN,
115+
});
116+
},
117+
},
118+
};
119+
58120
return (
59121
<div data-testid="overview-page" className={pageStyles}>
60122
{showResourcesPanel && (
61123
<ResourcesPanel onClose={handleResourcesPanelClose} />
62124
)}
63125
{isConnectionFormOpen && (
64-
<ConnectionForm
65-
isConnecting={isConnecting}
66-
initialConnectionInfo={initialConnectionInfo}
67-
onSaveAndConnectClicked={({ id, connectionOptions }) => {
68-
void handleSaveConnectionClicked({
69-
id,
70-
connectionOptions,
71-
});
72-
handleConnectClicked({
126+
<FileInputBackendProvider
127+
createFileInputBackend={createElectronFileInputBackend(
128+
dialogProvider,
129+
null
130+
)}
131+
>
132+
<ConnectionForm
133+
isConnecting={isConnecting}
134+
initialConnectionInfo={initialConnectionInfo}
135+
onSaveAndConnectClicked={({
73136
id,
74137
connectionOptions,
75-
});
76-
}}
77-
onCancelConnectClicked={handleCancelConnectClicked}
78-
onClose={closeConnectionForm}
79-
open={isConnectionFormOpen}
80-
connectionErrorMessage={connectionErrorMessage}
81-
/>
138+
}: {
139+
id: string;
140+
connectionOptions: ConnectionOptions;
141+
}): void => {
142+
void handleSaveConnectionClicked();
143+
handleConnectClicked({
144+
id,
145+
connectionOptions,
146+
});
147+
}}
148+
onCancelConnectClicked={handleCancelConnectClicked}
149+
onClose={closeConnectionForm}
150+
open={isConnectionFormOpen}
151+
connectionErrorMessage={connectionErrorMessage}
152+
/>
153+
</FileInputBackendProvider>
82154
)}
83155
<OverviewHeader onResourcesClick={handleResourcesClick} />
84156
<HorizontalRule />

0 commit comments

Comments
 (0)