From 9df8e20c2a2248675c3a95a61970c314cd7869e1 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 19 Jul 2023 16:59:31 +0200 Subject: [PATCH 01/18] chore: add language server logs VSCODE-447 --- src/connectionController.ts | 10 +- src/editors/playgroundController.ts | 47 ++++---- src/explorer/explorerController.ts | 6 -- src/explorer/explorerTreeController.ts | 4 +- src/explorer/helpExplorer.ts | 5 - src/explorer/playgroundsExplorer.ts | 5 - src/explorer/playgroundsTree.ts | 4 +- src/extension.ts | 30 +++++- src/language/languageServerController.ts | 86 +++++++++------ src/language/mongoDBService.ts | 100 ++++++++++++------ src/language/server.ts | 10 +- src/language/serverCommands.ts | 3 +- src/language/visitor.ts | 7 +- src/logging.ts | 2 +- src/mdbExtensionController.ts | 7 -- src/telemetry/telemetryService.ts | 4 +- .../editors/playgroundController.test.ts | 8 +- ...aygroundSelectedCodeActionProvider.test.ts | 2 +- .../language/languageServerController.test.ts | 2 +- .../suite/language/mongoDBService.test.ts | 10 +- src/test/suite/playground.test.ts | 2 +- src/test/suite/stubs.ts | 6 +- 22 files changed, 205 insertions(+), 155 deletions(-) diff --git a/src/connectionController.ts b/src/connectionController.ts index 0da160909..7729cec69 100644 --- a/src/connectionController.ts +++ b/src/connectionController.ts @@ -509,10 +509,18 @@ export default class ConnectionController { this.eventEmitter.emit(DataServiceEventTypes.CONNECTIONS_DID_CHANGE); if (this._activeDataService) { + log.info('Disconnecting from the previous connection...', { + connectionId: this._currentConnectionId, + }); await this.disconnect(); } this._statusView.showMessage('Connecting to MongoDB...'); + log.info('Connecting to MongoDB...', { + connectionInfo: JSON.stringify( + extractSecrets(this._connections[connectionId]).connectionInfo + ), + }); const connectionOptions = this._connections[connectionId].connectionOptions; @@ -551,7 +559,7 @@ export default class ConnectionController { throw connectError; } - log.info('Successfully connected'); + log.info('Successfully connected', { connectionId }); void vscode.window.showInformationMessage('MongoDB connection successful.'); this._activeDataService = dataService; diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index 830f479df..d709f5c25 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -152,7 +152,7 @@ export default class PlaygroundController { this._connectionController.addEventListener( DataServiceEventTypes.ACTIVE_CONNECTION_CHANGED, () => { - void this._connectToServiceProvider(); + void this._activeConnectionChanged(); } ); @@ -163,14 +163,15 @@ export default class PlaygroundController { this._playgroundResultViewColumn = editor.viewColumn; this._playgroundResultTextDocument = editor?.document; } + const isPlaygroundEditor = isPlayground(editor?.document.uri); void vscode.commands.executeCommand( 'setContext', 'mdb.isPlayground', - isPlayground(editor?.document.uri) + isPlaygroundEditor ); - if (editor?.document.languageId !== 'Log') { + if (isPlaygroundEditor) { this._activeTextEditor = editor; this._activeConnectionCodeLensProvider.setActiveTextEditor( this._activeTextEditor @@ -178,7 +179,10 @@ export default class PlaygroundController { this._playgroundSelectedCodeActionProvider.setActiveTextEditor( this._activeTextEditor ); - log.info('Active editor path', editor?.document.uri?.path); + log.info('Active editor', { + documentPath: editor?.document.uri?.path, + documentLanguageId: editor?.document.languageId, + }); } }; @@ -245,35 +249,23 @@ export default class PlaygroundController { ); } - async _connectToServiceProvider(): Promise { - // Disconnect if already connected. - await this._languageServerController.disconnectFromServiceProvider(); - + async _activeConnectionChanged(): Promise { const dataService = this._connectionController.getActiveDataService(); const connectionId = this._connectionController.getActiveConnectionId(); + let mongoClientOption; - if (!dataService || !connectionId) { - this._activeConnectionCodeLensProvider.refresh(); - - return; - } - - const mongoClientOption = - this._connectionController.getMongoClientConnectionOptions(); - - if (!mongoClientOption) { - this._activeConnectionCodeLensProvider.refresh(); + this._activeConnectionCodeLensProvider.refresh(); - return; + if (dataService && connectionId) { + mongoClientOption = + this._connectionController.getMongoClientConnectionOptions(); } - await this._languageServerController.connectToServiceProvider({ + await this._languageServerController.activeConnectionChanged({ connectionId, - connectionString: mongoClientOption.url, - connectionOptions: mongoClientOption.options, + connectionString: mongoClientOption?.url, + connectionOptions: mongoClientOption?.options, }); - - this._activeConnectionCodeLensProvider.refresh(); } async _createPlaygroundFileWithContent( @@ -440,12 +432,15 @@ export default class PlaygroundController { // We re-initialize the language server when we encounter an error. // This happens when the language server worker runs out of memory, can't be revitalized, and restarts. if (err?.code === -32097) { + log.error( + 'The error with -32097 error code occurred. Trying to restart and reconnect the language server...' + ); void vscode.window.showErrorMessage( 'An error occurred when running the playground. This can occur when the playground runner runs out of memory.' ); await this._languageServerController.startLanguageServer(); - void this._connectToServiceProvider(); + void this._activeConnectionChanged(); } throw err; diff --git a/src/explorer/explorerController.ts b/src/explorer/explorerController.ts index e7a810c70..5e56aaa78 100644 --- a/src/explorer/explorerController.ts +++ b/src/explorer/explorerController.ts @@ -5,10 +5,6 @@ import ConnectionController, { } from '../connectionController'; import ExplorerTreeController from './explorerTreeController'; -import { createLogger } from '../logging'; - -const log = createLogger('explorer controller'); - export default class ExplorerController { private _connectionController: ConnectionController; private _treeController: ExplorerTreeController; @@ -38,14 +34,12 @@ export default class ExplorerController { }; activateConnectionsTreeView(): void { - log.info('Activating explorer controller...'); // Listen for a change in connections to occur before we create the tree // so that we show the `viewsWelcome` before any connections are added. this._connectionController.addEventListener( DataServiceEventTypes.CONNECTIONS_DID_CHANGE, this.createTreeView ); - log.info('Explorer controller activated'); } deactivate(): void { diff --git a/src/explorer/explorerTreeController.ts b/src/explorer/explorerTreeController.ts index 153374f4f..b74ab117f 100644 --- a/src/explorer/explorerTreeController.ts +++ b/src/explorer/explorerTreeController.ts @@ -64,7 +64,9 @@ export default class ExplorerTreeController }); treeView.onDidExpandElement(async (event: any): Promise => { - log.info('Tree item was expanded', event.element.label); + log.info('Connection tree item was expanded', { + connectionName: event.element.label, + }); if (!event.element.onDidExpand) { return; diff --git a/src/explorer/helpExplorer.ts b/src/explorer/helpExplorer.ts index f40ae0734..8a0e791f7 100644 --- a/src/explorer/helpExplorer.ts +++ b/src/explorer/helpExplorer.ts @@ -1,10 +1,7 @@ import * as vscode from 'vscode'; import HelpTree from './helpTree'; -import { createLogger } from '../logging'; import { TelemetryService } from '../telemetry'; -const log = createLogger('help and info explorer'); - export default class HelpExplorer { _treeController: HelpTree; _treeView?: vscode.TreeView; @@ -15,7 +12,6 @@ export default class HelpExplorer { activateHelpTreeView(telemetryService: TelemetryService): void { if (!this._treeView) { - log.info('Activating help explorer...'); this._treeView = vscode.window.createTreeView('mongoDBHelpExplorer', { treeDataProvider: this._treeController, }); @@ -23,7 +19,6 @@ export default class HelpExplorer { this._treeView, telemetryService ); - log.info('Help explorer activated'); } } diff --git a/src/explorer/playgroundsExplorer.ts b/src/explorer/playgroundsExplorer.ts index aaa4d3972..99e51ff3c 100644 --- a/src/explorer/playgroundsExplorer.ts +++ b/src/explorer/playgroundsExplorer.ts @@ -1,8 +1,5 @@ import * as vscode from 'vscode'; import PlaygroundsTree from './playgroundsTree'; -import { createLogger } from '../logging'; - -const log = createLogger('playgrounds explorer'); export default class PlaygroundsExplorer { private _treeController: PlaygroundsTree; @@ -25,9 +22,7 @@ export default class PlaygroundsExplorer { }; public activatePlaygroundsTreeView(): void { - log.info('Activating playgrounds explorer...'); this.createPlaygroundsTreeView(); - log.info('Playgrounds explorer activated'); } public deactivate(): void { diff --git a/src/explorer/playgroundsTree.ts b/src/explorer/playgroundsTree.ts index caed6becd..e0695c4ce 100644 --- a/src/explorer/playgroundsTree.ts +++ b/src/explorer/playgroundsTree.ts @@ -50,7 +50,9 @@ export default class PlaygroundsTree }); treeView.onDidExpandElement(async (event: any): Promise => { - log.info('Tree item was expanded', event.element.label); + log.info('Playground tree item was expanded', { + laygroundName: event.element.label, + }); if (!event.element.onDidExpand) { return; diff --git a/src/extension.ts b/src/extension.ts index f0d7b576b..ae57ea4d3 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,6 +5,8 @@ import * as vscode from 'vscode'; import { ext } from './extensionConstants'; import { createKeytar } from './utils/keytar'; import { createLogger } from './logging'; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { version } = require('../package.json'); const log = createLogger('extension'); @@ -27,15 +29,38 @@ let mdbExtension: MDBExtensionController; export async function activate( context: vscode.ExtensionContext ): Promise { - log.info('Activating extension...'); ext.context = context; + let hasKeytar = false; try { ext.keytarModule = createKeytar(); + hasKeytar = true; } catch (err) { // Couldn't load keytar, proceed without storing & loading connections. } + const defaultConnectionSavingLocation = vscode.workspace + .getConfiguration('mdb.connectionSaving') + .get('defaultConnectionSavingLocation'); + + log.info('Activating extension...', { + id: context.extension.id, + version: version, + mode: vscode.ExtensionMode[context.extensionMode], + kind: vscode.ExtensionKind[context.extension.extensionKind], + extensionPath: context.extensionPath, + logPath: context.logUri.path, + workspaceStoragePath: context.storageUri?.path, + globalStoragePath: context.globalStorageUri.path, + defaultConnectionSavingLocation, + hasKeytar, + buildInfo: { + nodeVersion: process.version, + runtimePlatform: process.platform, + runtimeArch: process.arch, + }, + }); + mdbExtension = new MDBExtensionController(context, { shouldTrackTelemetry: true, }); @@ -43,12 +68,11 @@ export async function activate( // Add our extension to a list of disposables for when we are deactivated. context.subscriptions.push(mdbExtension); - - log.info('Extension activated'); } // Called when our extension is deactivated. export async function deactivate(): Promise { + log.info('Deactivating extension...'); if (mdbExtension) { await mdbExtension.deactivate(); } diff --git a/src/language/languageServerController.ts b/src/language/languageServerController.ts index 64a0dd66c..a1f7c23c7 100644 --- a/src/language/languageServerController.ts +++ b/src/language/languageServerController.ts @@ -39,8 +39,8 @@ export default class LanguageServerController { this._context = context; // The server is implemented in node. - const serverModule = path.join( - this._context.extensionPath, + const languageServerPath = path.join( + context.extensionPath, 'dist', 'languageServer.js' ); @@ -53,45 +53,54 @@ export default class LanguageServerController { // If the extension is launched in debug mode then the debug server options are used. // Otherwise the run options are used. const serverOptions: ServerOptions = { - run: { module: serverModule, transport: TransportKind.ipc }, + run: { module: languageServerPath, transport: TransportKind.ipc }, debug: { - module: serverModule, + module: languageServerPath, transport: TransportKind.ipc, options: debugOptions, }, }; + const languageServerId = 'mongodbLanguageServer'; + const languageServerName = 'MongoDB Language Server'; + // Define the document patterns to register the language server for. + const documentSelector = [ + { pattern: '**/*.mongodb.js' }, + { pattern: '**/*.mongodb' }, + ]; // Options to control the language client. const clientOptions: LanguageClientOptions = { - // Register the language server for mongodb documents. - documentSelector: [ - { pattern: '**/*.mongodb.js' }, - { pattern: '**/*.mongodb' }, - ], + documentSelector: documentSelector, synchronize: { // Notify the server about file changes in the workspace. fileEvents: workspace.createFileSystemWatcher('**/*'), }, - outputChannel: vscode.window.createOutputChannel( - 'MongoDB Language Server' - ), + outputChannel: vscode.window.createOutputChannel(languageServerName), }; - log.info('Create MongoDB Language Server', { - serverOptions, - clientOptions, + log.info('Creating MongoDB Language Server...', { + extensionPath: context.extensionPath, + languageServer: { + id: languageServerId, + name: languageServerName, + path: languageServerPath, + documentSelector: JSON.stringify(documentSelector), + }, }); // Create the language server client. this._client = new LanguageClient( - 'mongodbLanguageServer', - 'MongoDB Language Server', + languageServerId, + languageServerName, serverOptions, clientOptions ); } async startLanguageServer(): Promise { + log.info('Starting MongoDB Language Server...', { + extensionPath: this._context.extensionPath, + }); // Start the client. This will also launch the server. await this._client.start(); @@ -110,6 +119,7 @@ export default class LanguageServerController { this._client.onNotification( ServerCommands.SHOW_INFO_MESSAGE, (messsage) => { + log.info('The info message shown to a user', messsage); void vscode.window.showInformationMessage(messsage); } ); @@ -117,13 +127,16 @@ export default class LanguageServerController { this._client.onNotification( ServerCommands.SHOW_ERROR_MESSAGE, (messsage) => { + log.info('The error message shown to a user', messsage); void vscode.window.showErrorMessage(messsage); } ); } deactivate(): Thenable | undefined { + log.info('Deactivating MongoDB Language Server...'); if (!this._client) { + log.info('The MongoDB Language Server client is not found'); return undefined; } @@ -134,6 +147,10 @@ export default class LanguageServerController { async evaluate( playgroundExecuteParameters: PlaygroundEvaluateParams ): Promise { + log.info('Running a playground...', { + connectionId: playgroundExecuteParameters.connectionId, + inputLength: playgroundExecuteParameters.codeToEvaluate.length, + }); this._isExecutingInProgress = true; // Instantiate a new CancellationTokenSource object @@ -143,7 +160,7 @@ export default class LanguageServerController { // Send a request with a cancellation token // to the language server instance to execute scripts from a playground // and return results to the playground controller when ready. - const result: ShellEvaluateResult = await this._client.sendRequest( + const res: ShellEvaluateResult = await this._client.sendRequest( ServerCommands.EXECUTE_CODE_FROM_PLAYGROUND, playgroundExecuteParameters, this._source.token @@ -151,7 +168,14 @@ export default class LanguageServerController { this._isExecutingInProgress = false; - return result; + log.info('Evaluate response', { + namespace: res?.result?.namespace, + type: res?.result?.type, + outputLength: JSON.stringify(res?.result?.content || '').length, + language: res?.result?.language, + }); + + return res; } async getExportToLanguageMode( @@ -172,24 +196,23 @@ export default class LanguageServerController { ); } - async connectToServiceProvider(params: { - connectionId: string; - connectionString: string; - connectionOptions: MongoClientOptions; + async activeConnectionChanged(params: { + connectionId: null | string; + connectionString?: string; + connectionOptions?: MongoClientOptions; }): Promise { - await this._client.sendRequest( - ServerCommands.CONNECT_TO_SERVICE_PROVIDER, + log.info('Re-connecting MongoDBService...', { + connectionId: params.connectionId, + }); + const res = await this._client.sendRequest( + ServerCommands.ACTIVE_CONNECTION_CHANGED, params ); - } - - async disconnectFromServiceProvider(): Promise { - await this._client.sendRequest( - ServerCommands.DISCONNECT_TO_SERVICE_PROVIDER - ); + log.info('MongoDBService connection changed', res); } async resetCache(clear: ClearCompletionsCache): Promise { + log.info('Reseting MongoDBService cache...', clear); await this._client.sendRequest( ServerCommands.CLEAR_CACHED_COMPLETIONS, clear @@ -197,6 +220,7 @@ export default class LanguageServerController { } cancelAll(): void { + log.info('Canceling a playground...'); // Send a request for cancellation. As a result // the associated CancellationToken will be notified of the cancellation, // the onCancellationRequested event will be fired, diff --git a/src/language/mongoDBService.ts b/src/language/mongoDBService.ts index 1f0c9d4bf..be2933b4b 100644 --- a/src/language/mongoDBService.ts +++ b/src/language/mongoDBService.ts @@ -51,15 +51,15 @@ const SET_WINDOW_FIELDS = '$setWindowFields'; export const languageServerWorkerFileName = 'languageServerWorker.js'; interface ServiceProviderParams { - connectionId: string; - connectionString: string; - connectionOptions: MongoClientOptions; + connectionId: string | null; + connectionString?: string; + connectionOptions?: MongoClientOptions; } export default class MongoDBService { _extensionPath?: string; _connection: Connection; - _connectionId?: string; + _currentConnectionId: string | null = null; _connectionString?: string; _connectionOptions?: MongoClientOptions; @@ -73,6 +73,7 @@ export default class MongoDBService { _serviceProvider?: CliServiceProvider; constructor(connection: Connection) { + connection.console.log('MongoDBService initialised'); this._connection = connection; this._visitor = new Visitor(); @@ -98,27 +99,68 @@ export default class MongoDBService { * The absolute file path of the directory containing the extension. */ setExtensionPath(extensionPath: string): void { - this._extensionPath = extensionPath; + this._connection.console.log( + `The extension path is set { extensionPath: ${extensionPath} }` + ); + this._extensionPath = extensionPath ?? this._extensionPath; } /** * Connect to CliServiceProvider. */ - async connectToServiceProvider({ + async activeConnectionChanged({ connectionId, connectionString, connectionOptions, - }: ServiceProviderParams): Promise { + }: ServiceProviderParams): Promise<{ + connectionId: string | null; + successfullyConnected: boolean; + connectionErrorMessage?: string; + }> { + this._connection.console.log( + `Changing CliServiceProvider active connection... ${JSON.stringify({ + currentConnectionId: this._currentConnectionId, + newConnectionId: connectionId, + hasConnectionString: !!connectionString, + hasConnectionOptions: !!connectionOptions, + })}` + ); + // If already connected close the previous connection. - await this.disconnectFromServiceProvider(); + if ( + this._currentConnectionId && + this._currentConnectionId !== connectionId + ) { + this.clearCachedCompletions({ + databases: true, + collections: true, + fields: true, + }); + await this._closeCurrentConnection(); + } - this._connectionId = connectionId; + this._currentConnectionId = connectionId; this._connectionString = connectionString; this._connectionOptions = connectionOptions; - this._serviceProvider = await CliServiceProvider.connect( - connectionString, - connectionOptions - ); + + if (connectionId && (!connectionString || !connectionOptions)) { + this._connection.console.error( + 'Failed to change CliServiceProvider active connection: connectionString and connectionOptions are required' + ); + return { + connectionId, + successfullyConnected: false, + connectionErrorMessage: + 'connectionString and connectionOptions are required', + }; + } + + if (connectionString && connectionOptions) { + this._serviceProvider = await CliServiceProvider.connect( + connectionString, + connectionOptions + ); + } try { // Get database names for the current connection. @@ -130,18 +172,14 @@ export default class MongoDBService { `LS get databases error: ${util.inspect(error)}` ); } - } - /** - * Disconnect from CliServiceProvider. - */ - async disconnectFromServiceProvider(): Promise { - this.clearCachedCompletions({ - databases: true, - collections: true, - fields: true, - }); - await this._clearCurrentConnection(); + this._connection.console.log( + `CliServiceProvider active connection has changed: { connectionId: ${connectionId} }` + ); + return { + successfullyConnected: true, + connectionId, + }; } /** @@ -154,7 +192,7 @@ export default class MongoDBService { this.clearCachedFields(); return new Promise((resolve) => { - if (this._connectionId !== params.connectionId) { + if (this._currentConnectionId !== params.connectionId) { void this._connection.sendNotification( ServerCommands.SHOW_ERROR_MESSAGE, "The playground's active connection does not match the extension's active connection. Please reconnect and try again." @@ -194,9 +232,6 @@ export default class MongoDBService { languageServerWorkerFileName ) ); - this._connection.console.log( - `WORKER thread is created on path: ${this._extensionPath}` - ); worker?.on( 'message', @@ -1060,12 +1095,11 @@ export default class MongoDBService { this._collections = {}; } - async _clearCurrentConnection(): Promise { - this._connectionId = undefined; - this._connectionString = undefined; - this._connectionOptions = undefined; - + async _closeCurrentConnection(): Promise { if (this._serviceProvider) { + this._connection.console.log( + `Disconnecting from a previous connection... { connectionId: ${this._currentConnectionId} }` + ); const serviceProvider = this._serviceProvider; this._serviceProvider = undefined; await serviceProvider.close(true); diff --git a/src/language/server.ts b/src/language/server.ts index 284572cd3..c5484e5b5 100644 --- a/src/language/server.ts +++ b/src/language/server.ts @@ -168,14 +168,8 @@ connection.onRequest(ServerCommands.SET_EXTENSION_PATH, (extensionPath) => { // Connect the MongoDB language service to CliServiceProvider // using the current connection of the client. -connection.onRequest(ServerCommands.CONNECT_TO_SERVICE_PROVIDER, (params) => { - return mongoDBService.connectToServiceProvider(params); -}); - -// Clear connectionString and connectionOptions values -// when there is no active connection. -connection.onRequest(ServerCommands.DISCONNECT_TO_SERVICE_PROVIDER, () => { - return mongoDBService.disconnectFromServiceProvider(); +connection.onRequest(ServerCommands.ACTIVE_CONNECTION_CHANGED, (params) => { + return mongoDBService.activeConnectionChanged(params); }); // Set fields for tests. diff --git a/src/language/serverCommands.ts b/src/language/serverCommands.ts index 418ea914a..be55091b3 100644 --- a/src/language/serverCommands.ts +++ b/src/language/serverCommands.ts @@ -1,6 +1,5 @@ export enum ServerCommands { - CONNECT_TO_SERVICE_PROVIDER = 'CONNECT_TO_SERVICE_PROVIDER', - DISCONNECT_TO_SERVICE_PROVIDER = 'DISCONNECT_TO_SERVICE_PROVIDER', + ACTIVE_CONNECTION_CHANGED = 'ACTIVE_CONNECTION_CHANGED', EXECUTE_CODE_FROM_PLAYGROUND = 'EXECUTE_CODE_FROM_PLAYGROUND', EXECUTE_RANGE_FROM_PLAYGROUND = 'EXECUTE_RANGE_FROM_PLAYGROUND', SET_EXTENSION_PATH = 'SET_EXTENSION_PATH', diff --git a/src/language/visitor.ts b/src/language/visitor.ts index c49d8994d..88b907e1d 100644 --- a/src/language/visitor.ts +++ b/src/language/visitor.ts @@ -1,7 +1,6 @@ import type * as babel from '@babel/core'; import * as parser from '@babel/parser'; import traverse from '@babel/traverse'; -import * as util from 'util'; const PLACEHOLDER = 'TRIGGER_CHARACTER'; @@ -180,10 +179,8 @@ export class Visitor { sourceType: 'module', }); } catch (error) { - console.error(`parseAST error: ${util.inspect((error as any).message)}`); - console.error( - `parseAST error textFromEditor: ${util.inspect(textFromEditor)}` - ); + // Silent errors here, since when a user hasn't finish typing + // it can be invalid JavaScrip what will throw such parsing errors. } traverse(ast, { diff --git a/src/logging.ts b/src/logging.ts index f2204edf2..38b0bc857 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -4,7 +4,7 @@ import util from 'util'; class Logger implements ILogger { static channel: vscode.OutputChannel = - vscode.window.createOutputChannel('mongodb'); + vscode.window.createOutputChannel('MongoDB'); private name: string; diff --git a/src/mdbExtensionController.ts b/src/mdbExtensionController.ts index 246a70add..591397322 100644 --- a/src/mdbExtensionController.ts +++ b/src/mdbExtensionController.ts @@ -10,7 +10,6 @@ import PlaygroundSelectedCodeActionProvider from './editors/playgroundSelectedCo import PlaygroundDiagnosticsCodeActionProvider from './editors/playgroundDiagnosticsCodeActionProvider'; import ConnectionController from './connectionController'; import ConnectionTreeItem from './explorer/connectionTreeItem'; -import { createLogger } from './logging'; import DatabaseTreeItem from './explorer/databaseTreeItem'; import DocumentListTreeItem from './explorer/documentListTreeItem'; import { DocumentSource } from './documentSource'; @@ -40,8 +39,6 @@ import PlaygroundResultProvider from './editors/playgroundResultProvider'; import WebviewController from './views/webviewController'; import { createIdFactory, generateId } from './utils/objectIdHelper'; -const log = createLogger('commands'); - // This class is the top-level controller for our extension. // Commands which the extensions handles are defined in the function `activate`. export default class MDBExtensionController implements vscode.Disposable { @@ -150,8 +147,6 @@ export default class MDBExtensionController implements vscode.Disposable { } registerCommands = (): void => { - log.info('Registering commands...'); - // Register our extension's commands. These are the event handlers and // control the functionality of our extension. // ------ CONNECTION ------ // @@ -252,8 +247,6 @@ export default class MDBExtensionController implements vscode.Disposable { this.registerEditorCommands(); this.registerTreeViewCommands(); - - log.info('Commands registered'); }; registerCommand = ( diff --git a/src/telemetry/telemetryService.ts b/src/telemetry/telemetryService.ts index e774d6308..42c5fc297 100644 --- a/src/telemetry/telemetryService.ts +++ b/src/telemetry/telemetryService.ts @@ -151,8 +151,6 @@ export default class TelemetryService { if (!this._segmentKey) { return; } - - log.info('Activating segment analytics...'); this._segmentAnalytics = new SegmentAnalytics(this._segmentKey, { // Segment batches messages and flushes asynchronously to the server. // The flushAt is a number of messages to enqueue before flushing. @@ -166,7 +164,7 @@ export default class TelemetryService { const segmentProperties = this.getTelemetryUserIdentity(); this._segmentAnalytics.identify(segmentProperties); - log.info('Segment analytics activated with properties', segmentProperties); + log.info('Segment analytics activated', segmentProperties); } deactivate(): void { diff --git a/src/test/suite/editors/playgroundController.test.ts b/src/test/suite/editors/playgroundController.test.ts index 68edf9333..0acb1cb47 100644 --- a/src/test/suite/editors/playgroundController.test.ts +++ b/src/test/suite/editors/playgroundController.test.ts @@ -130,7 +130,7 @@ suite('Playground Controller Test Suite', function () { ); sandbox.replace( testPlaygroundController._languageServerController, - 'connectToServiceProvider', + 'activeConnectionChanged', fakeConnectToServiceProvider ); sandbox.stub(vscode.window, 'showInformationMessage'); @@ -138,7 +138,7 @@ suite('Playground Controller Test Suite', function () { testPlaygroundController._connectionController.setActiveDataService( mockActiveDataService ); - await testPlaygroundController._connectToServiceProvider(); + await testPlaygroundController._activeConnectionChanged(); }); test('it should pass the active connection id to the language server for connecting', () => { @@ -305,7 +305,7 @@ suite('Playground Controller Test Suite', function () { ); showTextDocumentStub = sandbox.stub(vscode.window, 'showTextDocument'); - await testPlaygroundController._connectToServiceProvider(); + await testPlaygroundController._activeConnectionChanged(); }); test('keep a playground in focus after running it', async () => { @@ -347,7 +347,7 @@ suite('Playground Controller Test Suite', function () { .resolves(); const stubConnectToServiceProvider = sinon - .stub(testPlaygroundController, '_connectToServiceProvider') + .stub(testPlaygroundController, '_activeConnectionChanged') .resolves(); try { diff --git a/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts b/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts index e3260b534..1d4297b3e 100644 --- a/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts +++ b/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts @@ -79,7 +79,7 @@ suite('Playground Selected CodeAction Provider Test Suite', function () { .update('confirmRunAll', false); await mdbTestExtension.testExtensionController._languageServerController.startLanguageServer(); - await mdbTestExtension.testExtensionController._playgroundController._connectToServiceProvider(); + await mdbTestExtension.testExtensionController._playgroundController._activeConnectionChanged(); const fakeIsPlayground = sandbox.fake.returns(true); sandbox.replace(testCodeActionProvider, 'isPlayground', fakeIsPlayground); diff --git a/src/test/suite/language/languageServerController.test.ts b/src/test/suite/language/languageServerController.test.ts index 64eb8da23..0e66629c1 100644 --- a/src/test/suite/language/languageServerController.test.ts +++ b/src/test/suite/language/languageServerController.test.ts @@ -77,7 +77,7 @@ suite('Language Server Controller Test Suite', () => { playgroundSelectedCodeActionProvider: testCodeActionProvider, }); await languageServerControllerStub.startLanguageServer(); - await testPlaygroundController._connectToServiceProvider(); + await testPlaygroundController._activeConnectionChanged(); }); beforeEach(() => { diff --git a/src/test/suite/language/mongoDBService.test.ts b/src/test/suite/language/mongoDBService.test.ts index 1b372bc96..dfa1c0bab 100644 --- a/src/test/suite/language/mongoDBService.test.ts +++ b/src/test/suite/language/mongoDBService.test.ts @@ -57,7 +57,7 @@ suite('MongoDBService Test Suite', () => { before(async () => { testMongoDBService._extensionPath = ''; - await testMongoDBService.connectToServiceProvider(params); + await testMongoDBService.activeConnectionChanged(params); }); test('catches error when evaluate is called and extension path is empty string', async () => { @@ -99,13 +99,13 @@ suite('MongoDBService Test Suite', () => { const testMongoDBService = new MongoDBService(connection); test('connect and disconnect from cli service provider', async () => { - await testMongoDBService.connectToServiceProvider(params); + await testMongoDBService.activeConnectionChanged(params); expect(testMongoDBService.connectionString).to.be.equal( 'mongodb://localhost:27018' ); - await testMongoDBService.disconnectFromServiceProvider(); + await testMongoDBService.activeConnectionChanged({ connectionId: null }); expect(testMongoDBService.connectionString).to.be.undefined; expect(testMongoDBService.connectionOptions).to.be.undefined; @@ -129,7 +129,7 @@ suite('MongoDBService Test Suite', () => { testMongoDBService._getSchemaFields = (): Promise => Promise.resolve([]); - await testMongoDBService.connectToServiceProvider(params); + await testMongoDBService.activeConnectionChanged(params); }); test('provide shell collection methods completion if global scope', async () => { @@ -1806,7 +1806,7 @@ suite('MongoDBService Test Suite', () => { before(async () => { testMongoDBService._extensionPath = mdbTestExtension.extensionContextStub.extensionPath; - await testMongoDBService.connectToServiceProvider(params); + await testMongoDBService.activeConnectionChanged(params); }); test('evaluate should sum numbers', async () => { diff --git a/src/test/suite/playground.test.ts b/src/test/suite/playground.test.ts index e8b329e88..9866760b1 100644 --- a/src/test/suite/playground.test.ts +++ b/src/test/suite/playground.test.ts @@ -72,7 +72,7 @@ suite('Playground', function () { fakeGetMongoClientConnectionOptions ); - await mdbTestExtension.testExtensionController._playgroundController._connectToServiceProvider(); + await mdbTestExtension.testExtensionController._playgroundController._activeConnectionChanged(); await mdbTestExtension.testExtensionController._playgroundController._languageServerController.updateCurrentSessionFields( { namespace: 'mongodbVSCodePlaygroundDB.sales', diff --git a/src/test/suite/stubs.ts b/src/test/suite/stubs.ts index b6cf7a890..c95fed7b1 100644 --- a/src/test/suite/stubs.ts +++ b/src/test/suite/stubs.ts @@ -339,7 +339,7 @@ class LanguageServerControllerStub { return Promise.resolve({ databaseName: null, collectionName: null }); } - connectToServiceProvider(/* params: { + activeConnectionChanged(/* params: { connectionString?: string; connectionOptions?: MongoClientOptions; extensionPath: string; @@ -347,10 +347,6 @@ class LanguageServerControllerStub { return Promise.resolve(); } - disconnectFromServiceProvider(): Promise { - return Promise.resolve(); - } - cancelAll(): void { return; } From 655f92ae3850e94225dc63791dcc839891224823 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 19 Jul 2023 17:10:25 +0200 Subject: [PATCH 02/18] chore: update log message --- src/language/languageServerController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/language/languageServerController.ts b/src/language/languageServerController.ts index a1f7c23c7..5e5f792b2 100644 --- a/src/language/languageServerController.ts +++ b/src/language/languageServerController.ts @@ -201,14 +201,14 @@ export default class LanguageServerController { connectionString?: string; connectionOptions?: MongoClientOptions; }): Promise { - log.info('Re-connecting MongoDBService...', { + log.info('Changing MongoDBService active connection...', { connectionId: params.connectionId, }); const res = await this._client.sendRequest( ServerCommands.ACTIVE_CONNECTION_CHANGED, params ); - log.info('MongoDBService connection changed', res); + log.info('MongoDBService active connection has changed', res); } async resetCache(clear: ClearCompletionsCache): Promise { From e14f762b7663903776b5b74c705d0512246c1a3c Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 19 Jul 2023 17:16:17 +0200 Subject: [PATCH 03/18] docs: update comment --- src/language/visitor.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/language/visitor.ts b/src/language/visitor.ts index 88b907e1d..75433e7fa 100644 --- a/src/language/visitor.ts +++ b/src/language/visitor.ts @@ -179,8 +179,7 @@ export class Visitor { sourceType: 'module', }); } catch (error) { - // Silent errors here, since when a user hasn't finish typing - // it can be invalid JavaScrip what will throw such parsing errors. + /* Silent fail. When a user hasn't finished typing it causes parsing JS errors */ } traverse(ast, { From 0d260a0568901919014153bd06ad12712b03b9d8 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 19 Jul 2023 20:27:02 +0200 Subject: [PATCH 04/18] feat: grab the active connection if ls recovers from a failure VSCODE-448 --- src/language/languageServerController.ts | 59 +++++++++++++++++++----- src/language/mongoDBService.ts | 39 ++++++++++------ src/language/server.ts | 9 +++- src/language/serverCommands.ts | 3 +- src/test/suite/stubs.ts | 1 + 5 files changed, 82 insertions(+), 29 deletions(-) diff --git a/src/language/languageServerController.ts b/src/language/languageServerController.ts index 5e5f792b2..ccccdabe2 100644 --- a/src/language/languageServerController.ts +++ b/src/language/languageServerController.ts @@ -34,6 +34,9 @@ export default class LanguageServerController { _source?: CancellationTokenSource; _isExecutingInProgress = false; _client: LanguageClient; + _currentConnectionId: string | null = null; + _currentConnectionString?: string; + _currentConnectionOptions?: MongoClientOptions; constructor(context: ExtensionContext) { this._context = context; @@ -98,9 +101,7 @@ export default class LanguageServerController { } async startLanguageServer(): Promise { - log.info('Starting MongoDB Language Server...', { - extensionPath: this._context.extensionPath, - }); + log.info('Starting MongoDB Language Server...'); // Start the client. This will also launch the server. await this._client.start(); @@ -111,10 +112,33 @@ export default class LanguageServerController { } // Subscribe on notifications from the server when the client is ready. - await this._client.sendRequest( - ServerCommands.SET_EXTENSION_PATH, - this._context.extensionPath - ); + // If the connection to server got closed, server will restart, + // so we need to re-send extensionPath and the active connection. + // https://jira.mongodb.org/browse/VSCODE-448 + this._client.onNotification(ServerCommands.MONGODB_SERVICE_CREATED, () => { + const msg = this._currentConnectionId + ? 'MongoDBService restarted because of some problem' + : 'MongoDBService is ready'; + log.info( + `${msg}. Sending default settings... ${JSON.stringify({ + extensionPath: this._context.extensionPath, + connectionId: this._currentConnectionId, + hasConnectionString: !!this._currentConnectionString, + hasConnectionOptions: !!this._currentConnectionOptions, + })}` + ); + void this._client.sendRequest(ServerCommands.INITIALIZE_MONGODB_SERVICE, { + extensionPath: this._context.extensionPath, + connectionId: this._currentConnectionId, + connectionString: this._currentConnectionString, + connectionOptions: this._currentConnectionOptions, + }); + if (this._currentConnectionId) { + void vscode.window.showErrorMessage( + 'Connection to playground got closed. Server will restart' + ); + } + }); this._client.onNotification( ServerCommands.SHOW_INFO_MESSAGE, @@ -196,17 +220,28 @@ export default class LanguageServerController { ); } - async activeConnectionChanged(params: { + async activeConnectionChanged({ + connectionId, + connectionString, + connectionOptions, + }: { connectionId: null | string; connectionString?: string; connectionOptions?: MongoClientOptions; }): Promise { - log.info('Changing MongoDBService active connection...', { - connectionId: params.connectionId, - }); + log.info('Changing MongoDBService active connection...', { connectionId }); + + this._currentConnectionId = connectionId; + this._currentConnectionString = connectionString; + this._currentConnectionOptions = connectionOptions; + const res = await this._client.sendRequest( ServerCommands.ACTIVE_CONNECTION_CHANGED, - params + { + connectionId, + connectionString, + connectionOptions, + } ); log.info('MongoDBService active connection has changed', res); } diff --git a/src/language/mongoDBService.ts b/src/language/mongoDBService.ts index be2933b4b..218106baf 100644 --- a/src/language/mongoDBService.ts +++ b/src/language/mongoDBService.ts @@ -57,11 +57,11 @@ interface ServiceProviderParams { } export default class MongoDBService { - _extensionPath?: string; + _extensionPath?: string; // The absolute file path of the directory containing the extension. _connection: Connection; _currentConnectionId: string | null = null; - _connectionString?: string; - _connectionOptions?: MongoClientOptions; + _currentConnectionString?: string; + _currentConnectionOptions?: MongoClientOptions; _databaseCompletionItems: CompletionItem[] = []; _shellSymbolCompletionItems: { [symbol: string]: CompletionItem[] } = {}; @@ -73,7 +73,8 @@ export default class MongoDBService { _serviceProvider?: CliServiceProvider; constructor(connection: Connection) { - connection.console.log('MongoDBService initialised'); + connection.console.log('MongoDBService initializing...'); + this._connection = connection; this._visitor = new Visitor(); @@ -85,24 +86,34 @@ export default class MongoDBService { * The connectionString used by LS to connect to MongoDB. */ get connectionString(): string | undefined { - return this._connectionString; + return this._currentConnectionString; } /** * The connectionOptions used by LS to connect to MongoDB. */ get connectionOptions(): MongoClientOptions | undefined { - return this._connectionOptions; + return this._currentConnectionOptions; } - /** - * The absolute file path of the directory containing the extension. - */ - setExtensionPath(extensionPath: string): void { + initialize({ + extensionPath, + connectionId, + connectionString, + connectionOptions, + }): void { + this._extensionPath = extensionPath; + this._currentConnectionId = connectionId; + this._currentConnectionString = connectionString; + this._currentConnectionOptions = connectionOptions; this._connection.console.log( - `The extension path is set { extensionPath: ${extensionPath} }` + `MongoDBService initialized ${JSON.stringify({ + extensionPath, + connectionId, + hasConnectionString: !!connectionString, + hasConnectionOptions: !!connectionOptions, + })}` ); - this._extensionPath = extensionPath ?? this._extensionPath; } /** @@ -140,8 +151,8 @@ export default class MongoDBService { } this._currentConnectionId = connectionId; - this._connectionString = connectionString; - this._connectionOptions = connectionOptions; + this._currentConnectionString = connectionString; + this._currentConnectionOptions = connectionOptions; if (connectionId && (!connectionString || !connectionOptions)) { this._connection.console.error( diff --git a/src/language/server.ts b/src/language/server.ts index c5484e5b5..b38869882 100644 --- a/src/language/server.ts +++ b/src/language/server.ts @@ -79,6 +79,11 @@ connection.onInitialize((params: InitializeParams) => { }); connection.onInitialized(() => { + void connection.sendNotification( + ServerCommands.MONGODB_SERVICE_CREATED, + 'An instance of MongoDBService is created' + ); + if (hasConfigurationCapability) { // Register for all configuration changes. void connection.client.register( @@ -162,8 +167,8 @@ connection.onRequest( ); // Pass the extension path to the MongoDB service. -connection.onRequest(ServerCommands.SET_EXTENSION_PATH, (extensionPath) => { - mongoDBService.setExtensionPath(extensionPath); +connection.onRequest(ServerCommands.INITIALIZE_MONGODB_SERVICE, (settings) => { + mongoDBService.initialize(settings); }); // Connect the MongoDB language service to CliServiceProvider diff --git a/src/language/serverCommands.ts b/src/language/serverCommands.ts index be55091b3..883229dc6 100644 --- a/src/language/serverCommands.ts +++ b/src/language/serverCommands.ts @@ -2,13 +2,14 @@ export enum ServerCommands { ACTIVE_CONNECTION_CHANGED = 'ACTIVE_CONNECTION_CHANGED', EXECUTE_CODE_FROM_PLAYGROUND = 'EXECUTE_CODE_FROM_PLAYGROUND', EXECUTE_RANGE_FROM_PLAYGROUND = 'EXECUTE_RANGE_FROM_PLAYGROUND', - SET_EXTENSION_PATH = 'SET_EXTENSION_PATH', SHOW_ERROR_MESSAGE = 'SHOW_ERROR_MESSAGE', SHOW_INFO_MESSAGE = 'SHOW_INFO_MESSAGE', GET_NAMESPACE_FOR_SELECTION = 'GET_NAMESPACE_FOR_SELECTION', GET_EXPORT_TO_LANGUAGE_MODE = 'GET_EXPORT_TO_LANGUAGE_MODE', UPDATE_CURRENT_SESSION_FIELDS = 'UPDATE_CURRENT_SESSION_FIELDS', CLEAR_CACHED_COMPLETIONS = 'CLEAR_CACHED_COMPLETIONS', + MONGODB_SERVICE_CREATED = 'MONGODB_SERVICE_CREATED', + INITIALIZE_MONGODB_SERVICE = 'INITIALIZE_MONGODB_SERVICE', } export type PlaygroundRunParameters = { diff --git a/src/test/suite/stubs.ts b/src/test/suite/stubs.ts index c95fed7b1..1c31f5d07 100644 --- a/src/test/suite/stubs.ts +++ b/src/test/suite/stubs.ts @@ -247,6 +247,7 @@ class LanguageServerControllerStub { _source?: CancellationTokenSource; _isExecutingInProgress: boolean; _client: LanguageClient; + _currentConnectionId: string | null = null; constructor( context: ExtensionContextStub, From b37b3a9d9cb5a43353dc4426a9ee7091cbd52ae1 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 19 Jul 2023 20:40:20 +0200 Subject: [PATCH 05/18] refactor: update output length --- src/language/languageServerController.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/language/languageServerController.ts b/src/language/languageServerController.ts index ccccdabe2..b73ff1429 100644 --- a/src/language/languageServerController.ts +++ b/src/language/languageServerController.ts @@ -195,7 +195,9 @@ export default class LanguageServerController { log.info('Evaluate response', { namespace: res?.result?.namespace, type: res?.result?.type, - outputLength: JSON.stringify(res?.result?.content || '').length, + outputLength: res?.result?.content + ? JSON.stringify(res.result.content).length + : 0, language: res?.result?.language, }); From 2cee9e965f81860f2411e0ff1f27e0ff71f35799 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 19 Jul 2023 21:53:20 +0200 Subject: [PATCH 06/18] docs: update error message --- src/language/languageServerController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/language/languageServerController.ts b/src/language/languageServerController.ts index b73ff1429..fa76ac425 100644 --- a/src/language/languageServerController.ts +++ b/src/language/languageServerController.ts @@ -135,7 +135,7 @@ export default class LanguageServerController { }); if (this._currentConnectionId) { void vscode.window.showErrorMessage( - 'Connection to playground got closed. Server will restart' + 'An internal error has occurred. The playground services have been restored.' ); } }); From 210c207a64f781767b16e911d509111d4676385f Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 19 Jul 2023 22:18:42 +0200 Subject: [PATCH 07/18] docs: update comments --- src/editors/playgroundController.ts | 1 + src/language/languageServerController.ts | 20 ++++++++++++-------- src/language/mongoDBService.ts | 2 +- src/language/server.ts | 5 ++--- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index d709f5c25..e00ef9514 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -261,6 +261,7 @@ export default class PlaygroundController { this._connectionController.getMongoClientConnectionOptions(); } + // The connectionId is null when disconnecting. await this._languageServerController.activeConnectionChanged({ connectionId, connectionString: mongoClientOption?.url, diff --git a/src/language/languageServerController.ts b/src/language/languageServerController.ts index fa76ac425..420672bc4 100644 --- a/src/language/languageServerController.ts +++ b/src/language/languageServerController.ts @@ -41,7 +41,6 @@ export default class LanguageServerController { constructor(context: ExtensionContext) { this._context = context; - // The server is implemented in node. const languageServerPath = path.join( context.extensionPath, 'dist', @@ -101,7 +100,7 @@ export default class LanguageServerController { } async startLanguageServer(): Promise { - log.info('Starting MongoDB Language Server...'); + log.info('Starting the language server...'); // Start the client. This will also launch the server. await this._client.start(); @@ -111,14 +110,14 @@ export default class LanguageServerController { this._context.subscriptions.push(this._client); } - // Subscribe on notifications from the server when the client is ready. + // Subscribe on notifications from the server when the MongoDBService is ready. // If the connection to server got closed, server will restart, - // so we need to re-send extensionPath and the active connection. + // but we also need to re-send default configurations // https://jira.mongodb.org/browse/VSCODE-448 this._client.onNotification(ServerCommands.MONGODB_SERVICE_CREATED, () => { const msg = this._currentConnectionId - ? 'MongoDBService restarted because of some problem' - : 'MongoDBService is ready'; + ? 'MongoDBService restored from an internal error' + : 'MongoDBService initialized'; log.info( `${msg}. Sending default settings... ${JSON.stringify({ extensionPath: this._context.extensionPath, @@ -133,6 +132,11 @@ export default class LanguageServerController { connectionString: this._currentConnectionString, connectionOptions: this._currentConnectionOptions, }); + // The _currentConnectionId is null when the extension initially activates. + // When an unexpected error occurs and the language server restarts after failure, + // it loses all default configurations. + // But the LanguageServerController still stores the previous _currentConnectionId, + // based on what we can tell that the services have been restored. if (this._currentConnectionId) { void vscode.window.showErrorMessage( 'An internal error has occurred. The playground services have been restored.' @@ -158,9 +162,9 @@ export default class LanguageServerController { } deactivate(): Thenable | undefined { - log.info('Deactivating MongoDB Language Server...'); + log.info('Deactivating the language server...'); if (!this._client) { - log.info('The MongoDB Language Server client is not found'); + log.info('The LanguageServerController client is not found'); return undefined; } diff --git a/src/language/mongoDBService.ts b/src/language/mongoDBService.ts index 218106baf..a12584978 100644 --- a/src/language/mongoDBService.ts +++ b/src/language/mongoDBService.ts @@ -117,7 +117,7 @@ export default class MongoDBService { } /** - * Connect to CliServiceProvider. + * Change CliServiceProvider active connection. */ async activeConnectionChanged({ connectionId, diff --git a/src/language/server.ts b/src/language/server.ts index b38869882..0bef401a4 100644 --- a/src/language/server.ts +++ b/src/language/server.ts @@ -166,13 +166,12 @@ connection.onRequest( } ); -// Pass the extension path to the MongoDB service. +// Send default configurations to mongoDBService. connection.onRequest(ServerCommands.INITIALIZE_MONGODB_SERVICE, (settings) => { mongoDBService.initialize(settings); }); -// Connect the MongoDB language service to CliServiceProvider -// using the current connection of the client. +// Change CliServiceProvider active connection. connection.onRequest(ServerCommands.ACTIVE_CONNECTION_CHANGED, (params) => { return mongoDBService.activeConnectionChanged(params); }); From 70d19fe96c094acc1724629bb298e06c06482b1b Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Thu, 20 Jul 2023 12:58:02 +0200 Subject: [PATCH 08/18] refactor: remove ls extra initialisation from playground controller --- src/editors/playgroundController.ts | 44 ++++++------------- .../editors/playgroundController.test.ts | 38 ---------------- 2 files changed, 13 insertions(+), 69 deletions(-) diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index e00ef9514..78ac2629b 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -413,39 +413,21 @@ export default class PlaygroundController { this._statusView.showMessage('Getting results...'); - try { - // Send a request to the language server to execute scripts from a playground. - const result: ShellEvaluateResult = - await this._languageServerController.evaluate({ - codeToEvaluate, - connectionId, - }); - - this._statusView.hideMessage(); - this._telemetryService.trackPlaygroundCodeExecuted( - result, - this._isPartialRun, - result ? false : true - ); - - return result; - } catch (err: any) { - // We re-initialize the language server when we encounter an error. - // This happens when the language server worker runs out of memory, can't be revitalized, and restarts. - if (err?.code === -32097) { - log.error( - 'The error with -32097 error code occurred. Trying to restart and reconnect the language server...' - ); - void vscode.window.showErrorMessage( - 'An error occurred when running the playground. This can occur when the playground runner runs out of memory.' - ); + // Send a request to the language server to execute scripts from a playground. + const result: ShellEvaluateResult = + await this._languageServerController.evaluate({ + codeToEvaluate, + connectionId, + }); - await this._languageServerController.startLanguageServer(); - void this._activeConnectionChanged(); - } + this._statusView.hideMessage(); + this._telemetryService.trackPlaygroundCodeExecuted( + result, + this._isPartialRun, + result ? false : true + ); - throw err; - } + return result; } _getAllText(): string { diff --git a/src/test/suite/editors/playgroundController.test.ts b/src/test/suite/editors/playgroundController.test.ts index 0acb1cb47..2f268a2b3 100644 --- a/src/test/suite/editors/playgroundController.test.ts +++ b/src/test/suite/editors/playgroundController.test.ts @@ -333,44 +333,6 @@ suite('Playground Controller Test Suite', function () { }); }); - test('it shows an error message and restarts, and connects the language server when an error occurs in evaluate (out of memory can cause this)', async () => { - const mockConnectionDisposedError = new Error( - 'Pending response rejected since connection got disposed' - ); - (mockConnectionDisposedError).code = -32097; - sinon - .stub(languageServerControllerStub, 'evaluate') - .rejects(mockConnectionDisposedError); - - const stubStartLanguageServer = sinon - .stub(languageServerControllerStub, 'startLanguageServer') - .resolves(); - - const stubConnectToServiceProvider = sinon - .stub(testPlaygroundController, '_activeConnectionChanged') - .resolves(); - - try { - await testPlaygroundController._evaluate('console.log("test");'); - - // It should have thrown in the above evaluation. - expect(true).to.equal(false); - } catch (error) { - expect((error).message).to.equal( - 'Pending response rejected since connection got disposed' - ); - expect((error).code).to.equal(-32097); - } - - expect(showErrorMessageStub.calledOnce).to.equal(true); - expect(showErrorMessageStub.firstCall.args[0]).to.equal( - 'An error occurred when running the playground. This can occur when the playground runner runs out of memory.' - ); - - expect(stubStartLanguageServer.calledOnce).to.equal(true); - expect(stubConnectToServiceProvider.calledOnce).to.equal(true); - }); - test('playground controller loads the active editor on start', () => { sandbox.replaceGetter( vscode.window, From ad70d0c6bdcbffe39b4eae295e6d5146deeb4cad Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 26 Jul 2023 16:18:47 +0200 Subject: [PATCH 09/18] refactor: address PR comments --- package-lock.json | 14 ++++----- package.json | 2 +- src/editors/playgroundController.ts | 11 +++++-- src/explorer/playgroundsTree.ts | 2 +- src/language/languageServerController.ts | 2 +- src/test/suite/playground.test.ts | 40 ++++++++++++++++++++++++ src/views/webviewController.ts | 2 +- 7 files changed, 60 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4a09dfda6..f3a0d4a07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "micromatch": "^4.0.5", "mongodb": "^5.6.0", "mongodb-build-info": "^1.5.0", - "mongodb-cloud-info": "^2.0.1", + "mongodb-cloud-info": "^2.1.0", "mongodb-connection-string-url": "^2.6.0", "mongodb-data-service": "^22.8.0", "mongodb-query-parser": "^2.5.0", @@ -14722,9 +14722,9 @@ } }, "node_modules/mongodb-cloud-info": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mongodb-cloud-info/-/mongodb-cloud-info-2.0.1.tgz", - "integrity": "sha512-foNUam/t3r2niuN5l9kF9KzNPyiwO9emp8gPN97kxImA+ZumJ37LYTL3XpdCI79CZNUcGtB1WdD9kXGW4CyxXA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mongodb-cloud-info/-/mongodb-cloud-info-2.1.0.tgz", + "integrity": "sha512-IueWuLvkG1xF9Ooxm3blKHVE8x5UL9BYKeCP+VYXNfEzmPruidW5D/5M35Ql5ZedzQhxbZ/RCA55OsuRC7RISw==", "dependencies": { "cross-fetch": "^3.1.6", "gce-ips": "^1.0.2", @@ -34341,9 +34341,9 @@ } }, "mongodb-cloud-info": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mongodb-cloud-info/-/mongodb-cloud-info-2.0.1.tgz", - "integrity": "sha512-foNUam/t3r2niuN5l9kF9KzNPyiwO9emp8gPN97kxImA+ZumJ37LYTL3XpdCI79CZNUcGtB1WdD9kXGW4CyxXA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mongodb-cloud-info/-/mongodb-cloud-info-2.1.0.tgz", + "integrity": "sha512-IueWuLvkG1xF9Ooxm3blKHVE8x5UL9BYKeCP+VYXNfEzmPruidW5D/5M35Ql5ZedzQhxbZ/RCA55OsuRC7RISw==", "requires": { "cross-fetch": "^3.1.6", "gce-ips": "^1.0.2", diff --git a/package.json b/package.json index e82408462..b996fe032 100644 --- a/package.json +++ b/package.json @@ -986,7 +986,7 @@ "micromatch": "^4.0.5", "mongodb": "^5.6.0", "mongodb-build-info": "^1.5.0", - "mongodb-cloud-info": "^2.0.1", + "mongodb-cloud-info": "^2.1.0", "mongodb-connection-string-url": "^2.6.0", "mongodb-data-service": "^22.8.0", "mongodb-query-parser": "^2.5.0", diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index 78ac2629b..fa1232012 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -414,11 +414,18 @@ export default class PlaygroundController { this._statusView.showMessage('Getting results...'); // Send a request to the language server to execute scripts from a playground. - const result: ShellEvaluateResult = - await this._languageServerController.evaluate({ + let result: ShellEvaluateResult; + try { + result = await this._languageServerController.evaluate({ codeToEvaluate, connectionId, }); + } catch (error) { + log.error( + 'An internal error has occurred. The playground services have been restored. This can occur when the playground runner runs out of memory.', + error + ); + } this._statusView.hideMessage(); this._telemetryService.trackPlaygroundCodeExecuted( diff --git a/src/explorer/playgroundsTree.ts b/src/explorer/playgroundsTree.ts index e0695c4ce..b613afe94 100644 --- a/src/explorer/playgroundsTree.ts +++ b/src/explorer/playgroundsTree.ts @@ -51,7 +51,7 @@ export default class PlaygroundsTree treeView.onDidExpandElement(async (event: any): Promise => { log.info('Playground tree item was expanded', { - laygroundName: event.element.label, + playgroundName: event.element.label, }); if (!event.element.onDidExpand) { diff --git a/src/language/languageServerController.ts b/src/language/languageServerController.ts index 420672bc4..3b4c3c9ee 100644 --- a/src/language/languageServerController.ts +++ b/src/language/languageServerController.ts @@ -139,7 +139,7 @@ export default class LanguageServerController { // based on what we can tell that the services have been restored. if (this._currentConnectionId) { void vscode.window.showErrorMessage( - 'An internal error has occurred. The playground services have been restored.' + 'An internal error has occurred. The playground services have been restored. This can occur when the playground runner runs out of memory.' ); } }); diff --git a/src/test/suite/playground.test.ts b/src/test/suite/playground.test.ts index 9866760b1..301bcaafe 100644 --- a/src/test/suite/playground.test.ts +++ b/src/test/suite/playground.test.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import { afterEach, beforeEach } from 'mocha'; import chai from 'chai'; import sinon from 'sinon'; +import type { SinonStub } from 'sinon'; import chaiAsPromised from 'chai-as-promised'; import { mdbTestExtension } from './stubbableMdbExtension'; @@ -22,6 +23,7 @@ suite('Playground', function () { const _disposables: vscode.Disposable[] = []; const sandbox = sinon.createSandbox(); + let showErrorMessageStub: SinonStub; beforeEach(async () => { sandbox.replace( @@ -71,6 +73,7 @@ suite('Playground', function () { 'getMongoClientConnectionOptions', fakeGetMongoClientConnectionOptions ); + showErrorMessageStub = sandbox.stub(vscode.window, 'showErrorMessage'); await mdbTestExtension.testExtensionController._playgroundController._activeConnectionChanged(); await mdbTestExtension.testExtensionController._playgroundController._languageServerController.updateCurrentSessionFields( @@ -79,6 +82,9 @@ suite('Playground', function () { schemaFields: ['_id', 'name', 'time'], } ); + await vscode.workspace + .getConfiguration('mdb') + .update('confirmRunAll', false); }); afterEach(async () => { @@ -119,4 +125,38 @@ suite('Playground', function () { "use('mongodbVSCodePlaygroundDB'); db.sales.find({ name});" ); }); + + test('restored the language server when out of memory occurred', async function () { + this.timeout(8000); + await vscode.commands.executeCommand('mdb.createPlayground'); + + const editor = vscode.window.activeTextEditor; + if (!editor) { + throw new Error('Window active text editor is undefined'); + } + + const testDocumentUri = editor.document.uri; + + // Modify initial content. + const edit = new vscode.WorkspaceEdit(); + edit.replace( + testDocumentUri, + getFullRange(editor.document), + "use('test'); const mockDataArray = []; for(let i = 0; i < 50000; i++) { mockDataArray.push(Math.random() * 10000); } const docs = []; for(let i = 0; i < 10000000; i++) { docs.push({ mockData: [...mockDataArray], a: 'test 123', b: Math.ceil(Math.random() * 10000) }); }" + ); + await vscode.workspace.applyEdit(edit); + await vscode.commands.executeCommand('mdb.runPlayground'); + + const onDidChangeDiagnostics = () => + new Promise((resolve) => { + // The diagnostics are set again when the server restarts. + vscode.languages.onDidChangeDiagnostics(resolve); + }); + await onDidChangeDiagnostics(); + + expect(showErrorMessageStub.calledOnce).to.equal(true); + expect(showErrorMessageStub.firstCall.args[0]).to.equal( + 'An internal error has occurred. The playground services have been restored. This can occur when the playground runner runs out of memory.' + ); + }); }); diff --git a/src/views/webviewController.ts b/src/views/webviewController.ts index 8af8eeb79..6b611a327 100644 --- a/src/views/webviewController.ts +++ b/src/views/webviewController.ts @@ -234,7 +234,7 @@ export default class WebviewController { // Create and show a new connect dialogue webview. const panel = vscode.window.createWebviewPanel( 'connectDialogueWebview', - 'MongoDB', + 'MongoDB Extension', vscode.ViewColumn.One, // Editor column to show the webview panel in. { enableScripts: true, From 597cdc7542ea6ab78a771d4c3978163bfc8b5fdc Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 26 Jul 2023 16:20:32 +0200 Subject: [PATCH 10/18] refactor: rename --- src/logging.ts | 2 +- src/views/webviewController.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/logging.ts b/src/logging.ts index 38b0bc857..43183c043 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -4,7 +4,7 @@ import util from 'util'; class Logger implements ILogger { static channel: vscode.OutputChannel = - vscode.window.createOutputChannel('MongoDB'); + vscode.window.createOutputChannel('MongoDB Extension'); private name: string; diff --git a/src/views/webviewController.ts b/src/views/webviewController.ts index 6b611a327..8af8eeb79 100644 --- a/src/views/webviewController.ts +++ b/src/views/webviewController.ts @@ -234,7 +234,7 @@ export default class WebviewController { // Create and show a new connect dialogue webview. const panel = vscode.window.createWebviewPanel( 'connectDialogueWebview', - 'MongoDB Extension', + 'MongoDB', vscode.ViewColumn.One, // Editor column to show the webview panel in. { enableScripts: true, From 8d01e0414aa2833576efccee42e5ea25a77cfb00 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 26 Jul 2023 16:27:29 +0200 Subject: [PATCH 11/18] test: try to skip the failing test --- src/test/suite/connectionController.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/suite/connectionController.test.ts b/src/test/suite/connectionController.test.ts index 2ce61cac4..e3c70a50a 100644 --- a/src/test/suite/connectionController.test.ts +++ b/src/test/suite/connectionController.test.ts @@ -201,7 +201,7 @@ suite('Connection Controller Test Suite', function () { assert.strictEqual(wasSetToConnectingWhenDisconnecting, true); }); - test('"connect()" should fire a CONNECTIONS_DID_CHANGE event', async () => { + test.skip('"connect()" should fire a CONNECTIONS_DID_CHANGE event', async () => { let isConnectionChanged = false; testConnectionController.addEventListener( From bbfc9008b7f6aa243480e018b104044f5551d060 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 26 Jul 2023 16:35:09 +0200 Subject: [PATCH 12/18] test: stub trackNewConnection --- src/test/suite/connectionController.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/suite/connectionController.test.ts b/src/test/suite/connectionController.test.ts index e3c70a50a..8f639b3cc 100644 --- a/src/test/suite/connectionController.test.ts +++ b/src/test/suite/connectionController.test.ts @@ -65,6 +65,7 @@ suite('Connection Controller Test Suite', function () { vscode.window, 'showInformationMessage' ); + sandbox.stub(testTelemetryService, 'trackNewConnection'); showErrorMessageStub = sandbox.stub(vscode.window, 'showErrorMessage'); }); @@ -201,7 +202,7 @@ suite('Connection Controller Test Suite', function () { assert.strictEqual(wasSetToConnectingWhenDisconnecting, true); }); - test.skip('"connect()" should fire a CONNECTIONS_DID_CHANGE event', async () => { + test('"connect()" should fire a CONNECTIONS_DID_CHANGE event', async () => { let isConnectionChanged = false; testConnectionController.addEventListener( From 0a1cd0c7baf610061863d4c67dcf52c36e96718a Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 26 Jul 2023 16:39:52 +0200 Subject: [PATCH 13/18] test: stub trackNewConnection in playground tests --- src/test/suite/editors/playgroundController.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/suite/editors/playgroundController.test.ts b/src/test/suite/editors/playgroundController.test.ts index 2f268a2b3..326098298 100644 --- a/src/test/suite/editors/playgroundController.test.ts +++ b/src/test/suite/editors/playgroundController.test.ts @@ -100,6 +100,7 @@ suite('Playground Controller Test Suite', function () { playgroundSelectedCodeActionProvider: testCodeActionProvider, }); showErrorMessageStub = sandbox.stub(vscode.window, 'showErrorMessage'); + sandbox.stub(testTelemetryService, 'trackNewConnection'); }); afterEach(() => { From 9b93e4914b0ca05a5065af813c8d743617734793 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 26 Jul 2023 17:45:28 +0200 Subject: [PATCH 14/18] test: more trackNewConnection stubs --- src/connectionController.ts | 2 +- src/editors/playgroundController.ts | 8 +- src/language/languageServerController.ts | 10 -- ...ollectionDocumentsCodeLensProvider.test.ts | 2 +- .../collectionDocumentsProvider.test.ts | 4 + ...aygroundSelectedCodeActionProvider.test.ts | 8 + .../suite/explorer/explorerController.test.ts | 4 + src/test/suite/mdbExtensionController.test.ts | 8 + .../suite/views/webviewController.test.ts | 142 ++++++++++++------ 9 files changed, 123 insertions(+), 65 deletions(-) diff --git a/src/connectionController.ts b/src/connectionController.ts index 7729cec69..3459617a0 100644 --- a/src/connectionController.ts +++ b/src/connectionController.ts @@ -125,7 +125,7 @@ export default class ConnectionController { private _disconnecting = false; private _statusView: StatusView; - private _telemetryService: TelemetryService; + _telemetryService: TelemetryService; // Used by other parts of the extension that respond to changes in the connections. private eventEmitter: EventEmitter = new EventEmitter(); diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index fa1232012..7fabb0476 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -421,10 +421,10 @@ export default class PlaygroundController { connectionId, }); } catch (error) { - log.error( - 'An internal error has occurred. The playground services have been restored. This can occur when the playground runner runs out of memory.', - error - ); + const msg = + 'An internal error has occurred. The playground services have been restored. This can occur when the playground runner runs out of memory.'; + log.error(msg, error); + void vscode.window.showErrorMessage(msg); } this._statusView.hideMessage(); diff --git a/src/language/languageServerController.ts b/src/language/languageServerController.ts index 3b4c3c9ee..719760e46 100644 --- a/src/language/languageServerController.ts +++ b/src/language/languageServerController.ts @@ -132,16 +132,6 @@ export default class LanguageServerController { connectionString: this._currentConnectionString, connectionOptions: this._currentConnectionOptions, }); - // The _currentConnectionId is null when the extension initially activates. - // When an unexpected error occurs and the language server restarts after failure, - // it loses all default configurations. - // But the LanguageServerController still stores the previous _currentConnectionId, - // based on what we can tell that the services have been restored. - if (this._currentConnectionId) { - void vscode.window.showErrorMessage( - 'An internal error has occurred. The playground services have been restored. This can occur when the playground runner runs out of memory.' - ); - } }); this._client.onNotification( diff --git a/src/test/suite/editors/collectionDocumentsCodeLensProvider.test.ts b/src/test/suite/editors/collectionDocumentsCodeLensProvider.test.ts index 046a07bbf..c464ecb38 100644 --- a/src/test/suite/editors/collectionDocumentsCodeLensProvider.test.ts +++ b/src/test/suite/editors/collectionDocumentsCodeLensProvider.test.ts @@ -5,7 +5,7 @@ import CollectionDocumentsCodeLensProvider from '../../../editors/collectionDocu import CollectionDocumentsOperationsStore from '../../../editors/collectionDocumentsOperationsStore'; import { mockVSCodeTextDocument } from '../stubs'; -suite('Collection Documents Provider Test Suite', () => { +suite('Collection CodeLens Provider Test Suite', () => { test('expected provideCodeLenses to return a code lens with positions at the end of the document', () => { const testQueryStore = new CollectionDocumentsOperationsStore(); const testCodeLensProvider = new CollectionDocumentsCodeLensProvider( diff --git a/src/test/suite/editors/collectionDocumentsProvider.test.ts b/src/test/suite/editors/collectionDocumentsProvider.test.ts index a853526b0..a44bd060b 100644 --- a/src/test/suite/editors/collectionDocumentsProvider.test.ts +++ b/src/test/suite/editors/collectionDocumentsProvider.test.ts @@ -68,6 +68,10 @@ suite('Collection Documents Provider Test Suite', () => { statusView: testStatusView, editDocumentCodeLensProvider: testCodeLensProvider, }); + sandbox.stub( + testConnectionController._telemetryService, + 'trackNewConnection' + ); }); afterEach(() => { diff --git a/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts b/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts index 1d4297b3e..ea8efa192 100644 --- a/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts +++ b/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts @@ -37,6 +37,10 @@ suite('Playground Selected CodeAction Provider Test Suite', function () { new LanguageServerController(extensionContextStub) ); sandbox.stub(vscode.window, 'showInformationMessage'); + sandbox.stub( + mdbTestExtension.testExtensionController._telemetryService, + 'trackNewConnection' + ); await mdbTestExtension.testExtensionController._connectionController.addNewConnectionStringAndConnect( TEST_DATABASE_URI @@ -479,6 +483,10 @@ suite('Playground Selected CodeAction Provider Test Suite', function () { beforeEach(() => { const fakeIsPlayground = sandbox.fake.returns(false); sandbox.replace(testCodeActionProvider, 'isPlayground', fakeIsPlayground); + sandbox.stub( + mdbTestExtension.testExtensionController._telemetryService, + 'trackNewConnection' + ); }); afterEach(() => { diff --git a/src/test/suite/explorer/explorerController.test.ts b/src/test/suite/explorer/explorerController.test.ts index 7fbaa99c8..8b8d936eb 100644 --- a/src/test/suite/explorer/explorerController.test.ts +++ b/src/test/suite/explorer/explorerController.test.ts @@ -30,6 +30,10 @@ suite('Explorer Controller Test Suite', function () { ); sandbox.stub(vscode.window, 'showInformationMessage'); sandbox.stub(vscode.window, 'showErrorMessage'); + sandbox.stub( + mdbTestExtension.testExtensionController._telemetryService, + 'trackNewConnection' + ); }); afterEach(async () => { diff --git a/src/test/suite/mdbExtensionController.test.ts b/src/test/suite/mdbExtensionController.test.ts index 71fc9e14a..1efe137d9 100644 --- a/src/test/suite/mdbExtensionController.test.ts +++ b/src/test/suite/mdbExtensionController.test.ts @@ -126,6 +126,10 @@ suite('MDBExtensionController Test Suite', function () { sandbox.stub(vscode.workspace, 'openTextDocument'); sandbox.stub(vscode.window, 'showTextDocument'); showErrorMessageStub = sandbox.stub(vscode.window, 'showErrorMessage'); + sandbox.stub( + mdbTestExtension.testExtensionController._telemetryService, + 'trackNewConnection' + ); }); afterEach(() => { @@ -181,6 +185,10 @@ suite('MDBExtensionController Test Suite', function () { ); showErrorMessageStub = sandbox.stub(vscode.window, 'showErrorMessage'); sandbox.stub(vscode.window, 'showTextDocument'); + sandbox.stub( + mdbTestExtension.testExtensionController._telemetryService, + 'trackNewConnection' + ); }); afterEach(() => { diff --git a/src/test/suite/views/webviewController.test.ts b/src/test/suite/views/webviewController.test.ts index 88f311239..3e1999a79 100644 --- a/src/test/suite/views/webviewController.test.ts +++ b/src/test/suite/views/webviewController.test.ts @@ -19,22 +19,31 @@ import WebviewController, { import * as linkHelper from '../../../utils/linkHelper'; suite('Webview Test Suite', () => { + const sandbox = sinon.createSandbox(); + + beforeEach(() => { + sandbox.stub( + mdbTestExtension.testExtensionController._telemetryService, + 'trackNewConnection' + ); + }); + afterEach(() => { - sinon.restore(); + sandbox.restore(); }); test('it creates a web view panel and sets the html content', () => { - const stubOnDidRecieveMessage = sinon.stub(); + const stubOnDidRecieveMessage = sandbox.stub(); const fakeWebview = { html: '', onDidReceiveMessage: stubOnDidRecieveMessage, - asWebviewUri: sinon.fake.returns(''), + asWebviewUri: sandbox.fake.returns(''), }; - const fakeVSCodeCreateWebviewPanel = sinon.fake.returns({ + const fakeVSCodeCreateWebviewPanel = sandbox.fake.returns({ webview: fakeWebview, }); - sinon.replace( + sandbox.replace( vscode.window, 'createWebviewPanel', fakeVSCodeCreateWebviewPanel @@ -131,6 +140,9 @@ suite('Webview Test Suite', () => { }); let messageReceivedSet = false; let messageReceived; + + sandbox.stub(testTelemetryService, 'trackNewConnection'); + const fakeWebview = { html: '', postMessage: async (): Promise => { @@ -147,14 +159,14 @@ suite('Webview Test Suite', () => { messageReceived = callback; messageReceivedSet = true; }, - asWebviewUri: sinon.fake.returns(''), + asWebviewUri: sandbox.fake.returns(''), }; - const fakeVSCodeCreateWebviewPanel = sinon.fake.returns({ + const fakeVSCodeCreateWebviewPanel = sandbox.fake.returns({ webview: fakeWebview, }); - sinon.replace( + sandbox.replace( vscode.window, 'createWebviewPanel', fakeVSCodeCreateWebviewPanel @@ -200,6 +212,9 @@ suite('Webview Test Suite', () => { }); let messageReceivedSet = false; let messageReceived; + + sandbox.stub(testTelemetryService, 'trackNewConnection'); + const fakeWebview = { html: '', postMessage: async (message): Promise => { @@ -217,13 +232,13 @@ suite('Webview Test Suite', () => { messageReceived = callback; messageReceivedSet = true; }, - asWebviewUri: sinon.fake.returns(''), + asWebviewUri: sandbox.fake.returns(''), }; - const fakeVSCodeCreateWebviewPanel = sinon.fake.returns({ + const fakeVSCodeCreateWebviewPanel = sandbox.fake.returns({ webview: fakeWebview, }); - sinon.replace( + sandbox.replace( vscode.window, 'createWebviewPanel', fakeVSCodeCreateWebviewPanel @@ -268,6 +283,9 @@ suite('Webview Test Suite', () => { telemetryService: testTelemetryService, }); let messageReceived; + + sandbox.stub(testTelemetryService, 'trackNewConnection'); + const fakeWebview = { html: '', postMessage: async (message): Promise => { @@ -280,13 +298,13 @@ suite('Webview Test Suite', () => { onDidReceiveMessage: (callback): void => { messageReceived = callback; }, - asWebviewUri: sinon.fake.returns(''), + asWebviewUri: sandbox.fake.returns(''), }; - const fakeVSCodeCreateWebviewPanel = sinon.fake.returns({ + const fakeVSCodeCreateWebviewPanel = sandbox.fake.returns({ webview: fakeWebview, }); - sinon.replace( + sandbox.replace( vscode.window, 'createWebviewPanel', fakeVSCodeCreateWebviewPanel @@ -327,6 +345,9 @@ suite('Webview Test Suite', () => { telemetryService: testTelemetryService, }); let messageReceived; + + sandbox.stub(testTelemetryService, 'trackNewConnection'); + const fakeWebview = { html: '', postMessage: (message): void => { @@ -343,12 +364,12 @@ suite('Webview Test Suite', () => { onDidReceiveMessage: (callback): void => { messageReceived = callback; }, - asWebviewUri: sinon.fake.returns(''), + asWebviewUri: sandbox.fake.returns(''), }; - const fakeVSCodeCreateWebviewPanel = sinon.fake.returns({ + const fakeVSCodeCreateWebviewPanel = sandbox.fake.returns({ webview: fakeWebview, }); - sinon.replace( + sandbox.replace( vscode.window, 'createWebviewPanel', fakeVSCodeCreateWebviewPanel @@ -391,11 +412,13 @@ suite('Webview Test Suite', () => { storageController: testStorageController, telemetryService: testTelemetryService, }); - const fakeVSCodeOpenDialog = sinon.fake.resolves({ + const fakeVSCodeOpenDialog = sandbox.fake.resolves({ path: '/somefilepath/test.text', }); - let messageReceived; + + sandbox.stub(testTelemetryService, 'trackNewConnection'); + const fakeWebview = { html: '', postMessage: async (): Promise => { @@ -408,19 +431,19 @@ suite('Webview Test Suite', () => { onDidReceiveMessage: (callback): void => { messageReceived = callback; }, - asWebviewUri: sinon.fake.returns(''), + asWebviewUri: sandbox.fake.returns(''), }; - const fakeVSCodeCreateWebviewPanel = sinon.fake.returns({ + const fakeVSCodeCreateWebviewPanel = sandbox.fake.returns({ webview: fakeWebview, }); - sinon.replace( + sandbox.replace( vscode.window, 'createWebviewPanel', fakeVSCodeCreateWebviewPanel ); - sinon.replace(vscode.window, 'showOpenDialog', fakeVSCodeOpenDialog); + sandbox.replace(vscode.window, 'showOpenDialog', fakeVSCodeOpenDialog); const testWebviewController = new WebviewController({ connectionController: testConnectionController, @@ -452,6 +475,9 @@ suite('Webview Test Suite', () => { telemetryService: testTelemetryService, }); let messageReceived; + + sandbox.stub(testTelemetryService, 'trackNewConnection'); + const fakeWebview = { html: '', postMessage: async (message): Promise => { @@ -469,25 +495,25 @@ suite('Webview Test Suite', () => { onDidReceiveMessage: (callback): void => { messageReceived = callback; }, - asWebviewUri: sinon.fake.returns(''), + asWebviewUri: sandbox.fake.returns(''), }; - const fakeVSCodeCreateWebviewPanel = sinon.fake.returns({ + const fakeVSCodeCreateWebviewPanel = sandbox.fake.returns({ webview: fakeWebview, }); - sinon.replace( + sandbox.replace( vscode.window, 'createWebviewPanel', fakeVSCodeCreateWebviewPanel ); - const fakeVSCodeOpenDialog = sinon.fake.resolves([ + const fakeVSCodeOpenDialog = sandbox.fake.resolves([ { fsPath: '/somefilepath/test.text', }, ]); - sinon.replace(vscode.window, 'showOpenDialog', fakeVSCodeOpenDialog); + sandbox.replace(vscode.window, 'showOpenDialog', fakeVSCodeOpenDialog); const testWebviewController = new WebviewController({ connectionController: testConnectionController, @@ -518,22 +544,29 @@ suite('Webview Test Suite', () => { telemetryService: testTelemetryService, }); let messageReceived; + + sandbox.stub(testTelemetryService, 'trackNewConnection'); + const fakeWebview = { html: '', onDidReceiveMessage: (callback): void => { messageReceived = callback; }, - asWebviewUri: sinon.fake.returns(''), + asWebviewUri: sandbox.fake.returns(''), }; - const fakeVSCodeExecuteCommand = sinon.fake.resolves(false); + const fakeVSCodeExecuteCommand = sandbox.fake.resolves(false); - sinon.replace(vscode.commands, 'executeCommand', fakeVSCodeExecuteCommand); + sandbox.replace( + vscode.commands, + 'executeCommand', + fakeVSCodeExecuteCommand + ); - const fakeVSCodeCreateWebviewPanel = sinon.fake.returns({ + const fakeVSCodeCreateWebviewPanel = sandbox.fake.returns({ webview: fakeWebview, }); - sinon.replace( + sandbox.replace( vscode.window, 'createWebviewPanel', fakeVSCodeCreateWebviewPanel @@ -576,6 +609,9 @@ suite('Webview Test Suite', () => { telemetryService: testTelemetryService, }); let messageReceived; + + sandbox.stub(testTelemetryService, 'trackNewConnection'); + const fakeWebview = { html: '', postMessage: (message): void => { @@ -588,13 +624,13 @@ suite('Webview Test Suite', () => { onDidReceiveMessage: (callback): void => { messageReceived = callback; }, - asWebviewUri: sinon.fake.returns(''), + asWebviewUri: sandbox.fake.returns(''), }; - const fakeVSCodeCreateWebviewPanel = sinon.fake.returns({ + const fakeVSCodeCreateWebviewPanel = sandbox.fake.returns({ webview: fakeWebview, }); - sinon.replace( + sandbox.replace( vscode.window, 'createWebviewPanel', fakeVSCodeCreateWebviewPanel @@ -629,6 +665,9 @@ suite('Webview Test Suite', () => { telemetryService: testTelemetryService, }); let messageReceived; + + sandbox.stub(testTelemetryService, 'trackNewConnection'); + const fakeWebview = { html: '', postMessage: async (message): Promise => { @@ -642,13 +681,13 @@ suite('Webview Test Suite', () => { onDidReceiveMessage: (callback): void => { messageReceived = callback; }, - asWebviewUri: sinon.fake.returns(''), + asWebviewUri: sandbox.fake.returns(''), }; - const fakeVSCodeCreateWebviewPanel = sinon.fake.returns({ + const fakeVSCodeCreateWebviewPanel = sandbox.fake.returns({ webview: fakeWebview, }); - sinon.replace( + sandbox.replace( vscode.window, 'createWebviewPanel', fakeVSCodeCreateWebviewPanel @@ -687,27 +726,31 @@ suite('Webview Test Suite', () => { telemetryService: testTelemetryService, }); let messageReceived; + + sandbox.stub(testTelemetryService, 'trackNewConnection'); + const fakeWebview = { html: '', postMessage: (): void => {}, onDidReceiveMessage: (callback): void => { messageReceived = callback; }, - asWebviewUri: sinon.fake.returns(''), + asWebviewUri: sandbox.fake.returns(''), }; - const fakeVSCodeCreateWebviewPanel = sinon.fake.returns({ + const fakeVSCodeCreateWebviewPanel = sandbox.fake.returns({ webview: fakeWebview, }); - sinon.replace( + sandbox.replace( vscode.window, 'createWebviewPanel', fakeVSCodeCreateWebviewPanel ); - const mockRenameConnectionOnConnectionController = sinon.fake.returns(null); + const mockRenameConnectionOnConnectionController = + sandbox.fake.returns(null); - sinon.replace( + sandbox.replace( testConnectionController, 'renameConnection', mockRenameConnectionOnConnectionController @@ -768,13 +811,13 @@ suite('Webview Test Suite', () => { onDidReceiveMessage: (callback): void => { messageReceived = callback; }, - asWebviewUri: sinon.fake.returns(''), + asWebviewUri: sandbox.fake.returns(''), }; - const fakeVSCodeCreateWebviewPanel = sinon.fake.returns({ + const fakeVSCodeCreateWebviewPanel = sandbox.fake.returns({ webview: fakeWebview, }); - sinon.replace( + sandbox.replace( vscode.window, 'createWebviewPanel', fakeVSCodeCreateWebviewPanel @@ -787,11 +830,12 @@ suite('Webview Test Suite', () => { }); testWebviewController.openWebview(mdbTestExtension.extensionContextStub); + sandbox.stub(testTelemetryService, 'trackNewConnection'); }); test('it should handle opening trusted links', () => { - const stubOpenLink = sinon.fake.resolves(null); - sinon.replace(linkHelper, 'openLink', stubOpenLink); + const stubOpenLink = sandbox.fake.resolves(null); + sandbox.replace(linkHelper, 'openLink', stubOpenLink); messageReceived({ command: MESSAGE_TYPES.OPEN_TRUSTED_LINK, From ec9a236c9a3bb54e73436eff48a85062debc547c Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 26 Jul 2023 17:57:07 +0200 Subject: [PATCH 15/18] feat: add connection id to tree logs --- src/explorer/explorerTreeController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/explorer/explorerTreeController.ts b/src/explorer/explorerTreeController.ts index b74ab117f..7db23f3c9 100644 --- a/src/explorer/explorerTreeController.ts +++ b/src/explorer/explorerTreeController.ts @@ -65,7 +65,9 @@ export default class ExplorerTreeController treeView.onDidExpandElement(async (event: any): Promise => { log.info('Connection tree item was expanded', { + connectionId: event.element.connectionId, connectionName: event.element.label, + isExpanded: event.element.isExpanded, }); if (!event.element.onDidExpand) { From 3309922113797921b2365bc811940f4765f86b9a Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 26 Jul 2023 18:05:36 +0200 Subject: [PATCH 16/18] test: skip telemetry test with live connection --- src/test/suite/telemetry/connectionTelemetry.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/suite/telemetry/connectionTelemetry.test.ts b/src/test/suite/telemetry/connectionTelemetry.test.ts index 27c5af31d..92c6c5c97 100644 --- a/src/test/suite/telemetry/connectionTelemetry.test.ts +++ b/src/test/suite/telemetry/connectionTelemetry.test.ts @@ -109,7 +109,7 @@ suite('ConnectionTelemetry Controller Test Suite', function () { }); }); - suite('with live connection', function () { + suite.skip('with live connection', function () { this.timeout(20000); let dataServ; From c136bcb771734a1ce7c3e875a5352c35f9072bc5 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 26 Jul 2023 18:23:13 +0200 Subject: [PATCH 17/18] docs: link jira ticket --- src/connectionController.ts | 2 +- src/test/suite/telemetry/connectionTelemetry.test.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/connectionController.ts b/src/connectionController.ts index 3459617a0..b3250d269 100644 --- a/src/connectionController.ts +++ b/src/connectionController.ts @@ -110,6 +110,7 @@ export default class ConnectionController { } = Object.create(null); _activeDataService: DataService | null = null; _storageController: StorageController; + _telemetryService: TelemetryService; private readonly _serviceName = 'mdb.vscode.savedConnections'; private _currentConnectionId: null | string = null; @@ -125,7 +126,6 @@ export default class ConnectionController { private _disconnecting = false; private _statusView: StatusView; - _telemetryService: TelemetryService; // Used by other parts of the extension that respond to changes in the connections. private eventEmitter: EventEmitter = new EventEmitter(); diff --git a/src/test/suite/telemetry/connectionTelemetry.test.ts b/src/test/suite/telemetry/connectionTelemetry.test.ts index 92c6c5c97..95de92e2a 100644 --- a/src/test/suite/telemetry/connectionTelemetry.test.ts +++ b/src/test/suite/telemetry/connectionTelemetry.test.ts @@ -109,6 +109,8 @@ suite('ConnectionTelemetry Controller Test Suite', function () { }); }); + // TODO: Enable test back when Insider is fixed https://jira.mongodb.org/browse/VSCODE-452 + // MS GitHub Issue: https://github.com/microsoft/vscode/issues/188676 suite.skip('with live connection', function () { this.timeout(20000); let dataServ; From 541c290c18e518aece4edb87dc8be478ce6210dd Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 26 Jul 2023 18:45:22 +0200 Subject: [PATCH 18/18] test: clean up --- src/editors/playgroundController.ts | 2 +- src/test/suite/playground.test.ts | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index 7fabb0476..2f843d9d0 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -413,9 +413,9 @@ export default class PlaygroundController { this._statusView.showMessage('Getting results...'); - // Send a request to the language server to execute scripts from a playground. let result: ShellEvaluateResult; try { + // Send a request to the language server to execute scripts from a playground. result = await this._languageServerController.evaluate({ codeToEvaluate, connectionId, diff --git a/src/test/suite/playground.test.ts b/src/test/suite/playground.test.ts index 301bcaafe..e4a484a43 100644 --- a/src/test/suite/playground.test.ts +++ b/src/test/suite/playground.test.ts @@ -93,7 +93,7 @@ suite('Playground', function () { sandbox.restore(); }); - test('show mongodb completion items before other js completion', async () => { + test('shows mongodb completion items before other js completion', async () => { await vscode.commands.executeCommand('mdb.createPlayground'); const editor = vscode.window.activeTextEditor; @@ -126,8 +126,8 @@ suite('Playground', function () { ); }); - test('restored the language server when out of memory occurred', async function () { - this.timeout(8000); + test('restores the language server when the out of memory error occurred', async function () { + this.timeout(20000); await vscode.commands.executeCommand('mdb.createPlayground'); const editor = vscode.window.activeTextEditor; @@ -136,8 +136,6 @@ suite('Playground', function () { } const testDocumentUri = editor.document.uri; - - // Modify initial content. const edit = new vscode.WorkspaceEdit(); edit.replace( testDocumentUri,