mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-19 09:35:36 -05:00
Migrate cert validation error handling to mssql extension (#21829)
This commit is contained in:
@@ -27,8 +27,7 @@ import * as types from 'vs/base/common/types';
|
||||
import { trim } from 'vs/base/common/strings';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
import { TelemetryView } from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
import { CmsConnectionController } from 'sql/workbench/services/connection/browser/cmsConnectionController';
|
||||
import { entries } from 'sql/base/common/collections';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
@@ -91,7 +90,6 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
@IClipboardService private _clipboardService: IClipboardService,
|
||||
@ICommandService private _commandService: ICommandService,
|
||||
@ILogService private _logService: ILogService,
|
||||
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService
|
||||
) {
|
||||
this.initializeConnectionProviders();
|
||||
}
|
||||
@@ -285,7 +283,7 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
this._logService.debug(`ConnectionDialogService: Error handled and connection reset - Error: ${connectionResult.errorMessage}`);
|
||||
} else {
|
||||
this._connectionDialog.resetConnection();
|
||||
this.showErrorDialog(Severity.Error, this._connectionErrorTitle, connectionResult.errorMessage, connectionResult.callStack, connectionResult.errorCode);
|
||||
this.showErrorDialog(Severity.Error, this._connectionErrorTitle, connectionResult.errorMessage, connectionResult.messageDetails);
|
||||
this._logService.debug(`ConnectionDialogService: Connection error: ${connectionResult.errorMessage}`);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -471,7 +469,7 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
await this.showDialogWithModel();
|
||||
|
||||
if (connectionResult && connectionResult.errorMessage) {
|
||||
this.showErrorDialog(Severity.Error, this._connectionErrorTitle, connectionResult.errorMessage, connectionResult.callStack, connectionResult.errorCode);
|
||||
this.showErrorDialog(Severity.Error, this._connectionErrorTitle, connectionResult.errorMessage, connectionResult.messageDetails);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,8 +501,7 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
recentConnections.forEach(conn => conn.dispose());
|
||||
}
|
||||
|
||||
|
||||
private showErrorDialog(severity: Severity, headerTitle: string, message: string, messageDetails?: string, errorCode?: number): void {
|
||||
private showErrorDialog(severity: Severity, headerTitle: string, message: string, messageDetails?: string): void {
|
||||
// Kerberos errors are currently very hard to understand, so adding handling of these to solve the common scenario
|
||||
// note that ideally we would have an extensible service to handle errors by error code and provider, but for now
|
||||
// this solves the most common "hard error" that we've noticed
|
||||
@@ -532,22 +529,6 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
|
||||
this._logService.error(message);
|
||||
|
||||
// Set instructionText for MSSQL Provider Encryption error code -2146893019 thrown by SqlClient when certificate validation fails.
|
||||
if (errorCode === -2146893019) {
|
||||
let enableTrustServerCert = localize('enableTrustServerCertificate', "Enable Trust server certificate");
|
||||
let instructionText = localize('trustServerCertInstructionText', `Encryption was enabled on this connection, review your SSL and certificate configuration for the target SQL Server, or enable 'Trust server certificate' in the connection dialog.
|
||||
|
||||
Note: A self-signed certificate offers only limited protection and is not a recommended practice for production environments. Do you want to enable 'Trust server certificate' on this connection and retry? `);
|
||||
let readMoreLink = "https://learn.microsoft.com/sql/database-engine/configure-windows/enable-encrypted-connections-to-the-database-engine"
|
||||
actions.push(new Action('trustServerCert', enableTrustServerCert, undefined, true, async () => {
|
||||
this._telemetryService.sendActionEvent(TelemetryKeys.TelemetryView.ConnectionDialog, TelemetryKeys.TelemetryAction.EnableTrustServerCertificate);
|
||||
this._model.options[Constants.trustServerCertificate] = true;
|
||||
await this.handleOnConnect(this._connectionDialog.newConnectionParams, this._model as IConnectionProfile);
|
||||
return;
|
||||
}));
|
||||
this._errorMessageService.showDialog(severity, headerTitle, message, messageDetails, actions, instructionText, readMoreLink);
|
||||
} else {
|
||||
this._errorMessageService.showDialog(severity, headerTitle, message, messageDetails, actions);
|
||||
}
|
||||
this._errorMessageService.showDialog(severity, headerTitle, message, messageDetails, TelemetryView.ConnectionErrorDialog, actions, undefined, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ export class ConnectionDialogWidget extends Modal {
|
||||
private connect(element?: IConnectionProfile): void {
|
||||
this.logService.debug('ConnectionDialogWidget: Connect button is clicked');
|
||||
if (this._connectButton.enabled) {
|
||||
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.ConnectionDialog, TelemetryKeys.TelemetryAction.ConnectToServer).withAdditionalProperties(
|
||||
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.ConnectionErrorDialog, TelemetryKeys.TelemetryAction.ConnectToServer).withAdditionalProperties(
|
||||
{ [TelemetryKeys.TelemetryPropertyName.ConnectionSource]: this._connectionSource }
|
||||
).send();
|
||||
this._connecting = true;
|
||||
|
||||
@@ -329,7 +329,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
|
||||
// If the password is required and still not loaded show the dialog
|
||||
if ((!foundPassword && this._connectionStore.isPasswordRequired(newConnection) && !newConnection.password) || !tokenFillSuccess) {
|
||||
return this.showConnectionDialogOnError(connection, owner, { connected: false, errorMessage: undefined, callStack: undefined, errorCode: undefined }, options);
|
||||
return this.showConnectionDialogOnError(connection, owner, { connected: false, errorMessage: undefined, messageDetails: undefined, errorCode: undefined }, options);
|
||||
} else {
|
||||
// Try to connect
|
||||
return this.connectWithOptions(newConnection, owner.uri, options, owner).then(connectionResult => {
|
||||
@@ -564,14 +564,22 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
return this.connectWithOptions(connection, uri, options, callbacks);
|
||||
}
|
||||
else {
|
||||
let connectionErrorHandled = await this._errorDiagnosticsService.tryHandleConnectionError(connectionResult.errorCode, connectionResult.errorMessage, connection.providerName, connection);
|
||||
if (connectionErrorHandled.handled) {
|
||||
let connectionErrorHandleResult = await this._errorDiagnosticsService.tryHandleConnectionError(connectionResult, connection.providerName, connection);
|
||||
if (connectionErrorHandleResult.handled) {
|
||||
connectionResult.errorHandled = true;
|
||||
if (connectionErrorHandled.options) {
|
||||
if (connectionErrorHandleResult.options) {
|
||||
//copy over altered connection options from the result if provided.
|
||||
connection.options = connectionErrorHandled.options;
|
||||
connection.options = connectionErrorHandleResult.options;
|
||||
}
|
||||
if (connectionErrorHandleResult.reconnect) {
|
||||
// Attempt reconnect if requested by provider
|
||||
return this.connectWithOptions(connection, uri, options, callbacks);
|
||||
} else {
|
||||
if (callbacks.onConnectCanceled) {
|
||||
callbacks.onConnectCanceled();
|
||||
}
|
||||
return connectionResult;
|
||||
}
|
||||
return this.connectWithOptions(connection, uri, options, callbacks);
|
||||
}
|
||||
else {
|
||||
// Error not handled by any registered providers so fail the connection
|
||||
@@ -1299,18 +1307,18 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
if (connectionMngInfo && connectionMngInfo.deleted) {
|
||||
this._logService.info(`Found deleted connection management info for ${uri} - removing`);
|
||||
this._connectionStatusManager.deleteConnection(uri);
|
||||
resolve({ connected: connectResult, errorMessage: undefined, errorCode: undefined, callStack: undefined, errorHandled: true, connectionProfile: connection });
|
||||
resolve({ connected: connectResult, errorMessage: undefined, errorCode: undefined, messageDetails: undefined, errorHandled: true, connectionProfile: connection });
|
||||
} else {
|
||||
if (errorMessage) {
|
||||
// Connection to the server failed
|
||||
this._logService.info(`Error occurred while connecting, removing connection management info for ${uri}`);
|
||||
this._connectionStatusManager.deleteConnection(uri);
|
||||
resolve({ connected: connectResult, errorMessage: errorMessage, errorCode: errorCode, callStack: callStack, connectionProfile: connection });
|
||||
resolve({ connected: connectResult, errorMessage: errorMessage, errorCode: errorCode, messageDetails: callStack, connectionProfile: connection });
|
||||
} else {
|
||||
if (connectionMngInfo.serverInfo) {
|
||||
connection.options.isCloud = connectionMngInfo.serverInfo.isCloud;
|
||||
}
|
||||
resolve({ connected: connectResult, errorMessage: errorMessage, errorCode: errorCode, callStack: callStack, connectionProfile: connection });
|
||||
resolve({ connected: connectResult, errorMessage: errorMessage, errorCode: errorCode, messageDetails: callStack, connectionProfile: connection });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -108,10 +108,9 @@ suite('ConnectionDialogService tests', () => {
|
||||
testInstantiationService.stub(ICapabilitiesService, new TestCapabilitiesService());
|
||||
|
||||
let logService: ILogService = new NullLogService();
|
||||
let telemetryService: IAdsTelemetryService = new NullAdsTelemetryService();
|
||||
|
||||
connectionDialogService = new ConnectionDialogService(testInstantiationService, capabilitiesService, errorMessageService.object,
|
||||
new TestConfigurationService(), new BrowserClipboardService(layoutService, logService), NullCommandService, logService, telemetryService);
|
||||
new TestConfigurationService(), new BrowserClipboardService(layoutService, logService), NullCommandService, logService);
|
||||
(connectionDialogService as any)._connectionManagementService = mockConnectionManagementService.object;
|
||||
let providerDisplayNames = ['Mock SQL Server'];
|
||||
let providerNameToDisplayMap = { 'MSSQL': 'Mock SQL Server' };
|
||||
@@ -290,7 +289,7 @@ suite('ConnectionDialogService tests', () => {
|
||||
connected: false,
|
||||
errorMessage: 'test_error',
|
||||
errorCode: -1,
|
||||
callStack: 'testCallStack'
|
||||
messageDetails: 'testCallStack'
|
||||
};
|
||||
// promise only resolves upon handleDefaultOnConnect, must return it at the end
|
||||
let connectionPromise = connectionDialogService.openDialogAndWait(mockConnectionManagementService.object, testConnectionParams, connectionProfile, connectionResult, false);
|
||||
|
||||
@@ -203,7 +203,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
TypeMoq.It.isAny(),
|
||||
TypeMoq.It.is<INewConnectionParams>(p => p.connectionType === connectionType && (uri === undefined || p.input.uri === uri)),
|
||||
TypeMoq.It.is<IConnectionProfile>(c => c !== undefined && c.serverName === connectionProfile.serverName),
|
||||
connectionResult ? TypeMoq.It.is<IConnectionResult>(r => r.errorMessage === connectionResult.errorMessage && r.callStack === connectionResult.callStack) : undefined,
|
||||
connectionResult ? TypeMoq.It.is<IConnectionResult>(r => r.errorMessage === connectionResult.errorMessage && r.messageDetails === connectionResult.messageDetails) : undefined,
|
||||
options ? TypeMoq.It.isAny() : undefined),
|
||||
didShow ? TypeMoq.Times.once() : TypeMoq.Times.never());
|
||||
|
||||
@@ -212,7 +212,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
TypeMoq.It.isAny(),
|
||||
TypeMoq.It.is<INewConnectionParams>(p => p.connectionType === connectionType && ((uri === undefined && p.input === undefined) || p.input.uri === uri)),
|
||||
undefined,
|
||||
connectionResult ? TypeMoq.It.is<IConnectionResult>(r => r.errorMessage === connectionResult.errorMessage && r.callStack === connectionResult.callStack) : undefined,
|
||||
connectionResult ? TypeMoq.It.is<IConnectionResult>(r => r.errorMessage === connectionResult.errorMessage && r.messageDetails === connectionResult.messageDetails) : undefined,
|
||||
options ? TypeMoq.It.isAny() : undefined),
|
||||
didShow ? TypeMoq.Times.once() : TypeMoq.Times.never());
|
||||
}
|
||||
@@ -422,7 +422,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
connected: expectedConnection,
|
||||
errorMessage: error,
|
||||
errorCode: errorCode,
|
||||
callStack: errorCallStack
|
||||
messageDetails: errorCallStack
|
||||
};
|
||||
|
||||
let result = await connect(uri, options, false, connectionProfile, error, errorCode, errorCallStack);
|
||||
@@ -450,7 +450,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
connected: expectedConnection,
|
||||
errorMessage: error,
|
||||
errorCode: errorCode,
|
||||
callStack: errorCallStack
|
||||
messageDetails: errorCallStack
|
||||
};
|
||||
|
||||
let result = await connect(uri, options, false, connectionProfile, error, errorCode, errorCallStack);
|
||||
@@ -1097,7 +1097,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
connected: expectedConnection,
|
||||
errorMessage: error,
|
||||
errorCode: errorCode,
|
||||
callStack: errorCallStack
|
||||
messageDetails: errorCallStack
|
||||
};
|
||||
|
||||
let result = await connect(uri, options, false, connectionProfile, error, errorCode, errorCallStack);
|
||||
@@ -1177,7 +1177,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
connected: expectedConnection,
|
||||
errorMessage: error,
|
||||
errorCode: errorCode,
|
||||
callStack: errorCallStack
|
||||
messageDetails: errorCallStack
|
||||
};
|
||||
|
||||
let result = await connect(uri, options, false, connectionProfile, error, errorCode, errorCallStack);
|
||||
@@ -1209,7 +1209,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
connected: expectedConnection,
|
||||
errorMessage: error,
|
||||
errorCode: errorCode,
|
||||
callStack: errorCallStack
|
||||
messageDetails: errorCallStack
|
||||
};
|
||||
|
||||
let result = await connect(uri, options, false, connectionProfile, error, errorCode, errorCallStack);
|
||||
@@ -1233,7 +1233,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
connected: expectedConnection,
|
||||
errorMessage: undefined,
|
||||
errorCode: undefined,
|
||||
callStack: undefined
|
||||
messageDetails: undefined
|
||||
};
|
||||
|
||||
let result = await connect(uri, options, false, connectionProfileWithEmptyUnsavedPassword);
|
||||
@@ -1258,7 +1258,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
connected: expectedConnection,
|
||||
errorMessage: undefined,
|
||||
errorCode: undefined,
|
||||
callStack: undefined
|
||||
messageDetails: undefined
|
||||
};
|
||||
|
||||
let result = await connect(uri, options, false, connectionProfileWithEmptySavedPassword);
|
||||
@@ -1294,7 +1294,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
connected: expectedConnection,
|
||||
errorMessage: undefined,
|
||||
errorCode: undefined,
|
||||
callStack: undefined
|
||||
messageDetails: undefined
|
||||
};
|
||||
|
||||
let result = await connect(uri, options, false, connectionProfileWithEmptySavedPassword);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { INewConnectionParams, IConnectionResult, IConnectionManagementService, IConnectionCompletionOptions } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { IErrorDialogOptions } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService';
|
||||
|
||||
export class TestConnectionDialogService implements IConnectionDialogService {
|
||||
@@ -29,4 +30,8 @@ export class TestConnectionDialogService implements IConnectionDialogService {
|
||||
public callDefaultOnConnect(connection: IConnectionProfile, params: INewConnectionParams): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
public async showErrorDialogAsync(options: IErrorDialogOptions): Promise<string | undefined> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { IConnectionResult } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { IErrorDiagnosticsService } from 'sql/workbench/services/diagnostics/common/errorDiagnosticsService';
|
||||
|
||||
export class TestErrorDiagnosticsService implements IErrorDiagnosticsService {
|
||||
@@ -15,7 +16,7 @@ export class TestErrorDiagnosticsService implements IErrorDiagnosticsService {
|
||||
unregisterDiagnosticsProvider(ProviderId: string): void {
|
||||
}
|
||||
|
||||
tryHandleConnectionError(errorCode: number, errorMessage: string, providerId: string, connection: azdata.IConnectionProfile): Promise<azdata.diagnostics.ConnectionDiagnosticsResult> {
|
||||
tryHandleConnectionError(connectionResult: IConnectionResult, providerId: string, connection: azdata.IConnectionProfile): Promise<azdata.diagnostics.ConnectionDiagnosticsResult> {
|
||||
return Promise.resolve({ handled: false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import * as azdata from 'azdata';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import * as Utils from 'sql/platform/connection/common/utils';
|
||||
import * as interfaces from 'sql/platform/connection/common/interfaces';
|
||||
import { IConnectionResult } from 'sql/platform/connection/common/connectionManagement';
|
||||
|
||||
export class ErrorDiagnosticsService implements IErrorDiagnosticsService {
|
||||
|
||||
@@ -18,11 +19,16 @@ export class ErrorDiagnosticsService implements IErrorDiagnosticsService {
|
||||
@ILogService private readonly _logService: ILogService
|
||||
) { }
|
||||
|
||||
public async tryHandleConnectionError(errorCode: number, errorMessage: string, providerId: string, connection: interfaces.IConnectionProfile): Promise<azdata.diagnostics.ConnectionDiagnosticsResult> {
|
||||
public async tryHandleConnectionError(connectionResult: IConnectionResult, providerId: string, connection: interfaces.IConnectionProfile): Promise<azdata.diagnostics.ConnectionDiagnosticsResult> {
|
||||
let result = { handled: false };
|
||||
let provider = this._providers[providerId]
|
||||
if (provider) {
|
||||
result = await provider.handleConnectionError(errorCode, errorMessage, Utils.convertToRpcConnectionProfile(connection));
|
||||
let errorInfo: azdata.diagnostics.IErrorInformation = {
|
||||
errorCode: connectionResult.errorCode ?? 0,
|
||||
errorMessage: connectionResult.errorMessage ?? '',
|
||||
messageDetails: connectionResult.messageDetails ?? ''
|
||||
}
|
||||
result = await provider.handleConnectionError(errorInfo, Utils.convertToRpcConnectionProfile(connection));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { IConnectionResult } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const SERVICE_ID = 'errorDiagnosticsService';
|
||||
@@ -34,5 +35,5 @@ export interface IErrorDiagnosticsService {
|
||||
* @param connection Connection profile that is utilized for connection
|
||||
* @returns a Promise containing a ConnectionDiagnosticsResult object (with handling status and altered options)
|
||||
*/
|
||||
tryHandleConnectionError(errorCode: number, errorMessage: string, providerId: string, connection: azdata.IConnectionProfile): Promise<azdata.diagnostics.ConnectionDiagnosticsResult>;
|
||||
tryHandleConnectionError(connectionResult: IConnectionResult, providerId: string, connection: azdata.IConnectionProfile): Promise<azdata.diagnostics.ConnectionDiagnosticsResult>;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import { WizardModal } from 'sql/workbench/services/dialog/browser/wizardModal';
|
||||
import { Dialog, Wizard } from 'sql/workbench/services/dialog/common/dialogTypes';
|
||||
import { IModalOptions } from 'sql/workbench/browser/modal/modal';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ErrorMessageDialog } from 'sql/workbench/services/errorMessage/browser/errorMessageDialog';
|
||||
import { IErrorDialogOptions } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
export const DefaultDialogOptions: IModalOptions = { hasBackButton: false, width: 'narrow', hasErrors: true, hasSpinner: true };
|
||||
export const DefaultWizardOptions: IModalOptions = { hasBackButton: false, width: 'wide', hasErrors: true, hasSpinner: true };
|
||||
@@ -51,4 +53,15 @@ export class CustomDialogService {
|
||||
public getWizardModal(wizard: Wizard): WizardModal | undefined {
|
||||
return this._wizardModals.get(wizard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows error dialog customized with given options
|
||||
* @param options Error Dialog options to customize error message dialog.
|
||||
*/
|
||||
public async openCustomErrorDialog(options: IErrorDialogOptions): Promise<string | undefined> {
|
||||
let dialog = this._instantiationService.createInstance(ErrorMessageDialog);
|
||||
dialog.render();
|
||||
let result = await dialog.openCustomAsync(options);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
@@ -26,6 +26,8 @@ import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration';
|
||||
import { Link } from 'vs/platform/opener/browser/link';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
import { IErrorDialogOptions, MessageLevel } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
const maxActions = 1;
|
||||
|
||||
@@ -44,6 +46,7 @@ export class ErrorMessageDialog extends Modal {
|
||||
private _okLabel: string;
|
||||
private _closeLabel: string;
|
||||
private _readMoreLabel: string;
|
||||
private _promiseResolver: (value: string) => void;
|
||||
|
||||
private _onOk = new Emitter<void>();
|
||||
public onOk: Event<void> = this._onOk.event;
|
||||
@@ -56,7 +59,8 @@ export class ErrorMessageDialog extends Modal {
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@ILogService logService: ILogService,
|
||||
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService,
|
||||
@IOpenerService private readonly _openerService: IOpenerService
|
||||
@IOpenerService private readonly _openerService: IOpenerService,
|
||||
protected _telemetryView: TelemetryKeys.TelemetryView | string = TelemetryKeys.TelemetryView.ErrorMessageDialog,
|
||||
) {
|
||||
super('', TelemetryKeys.ModalDialogName.ErrorMessage, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, { dialogStyle: 'normal', hasTitleIcon: true });
|
||||
this._okLabel = localize('errorMessageDialog.ok', "OK");
|
||||
@@ -101,11 +105,15 @@ export class ErrorMessageDialog extends Modal {
|
||||
}
|
||||
|
||||
private onActionSelected(index: number): void {
|
||||
// Call OK so it always closes
|
||||
this.ok();
|
||||
// Run the action if possible
|
||||
if (this._actions && index < this._actions.length) {
|
||||
const actionId = this._actions[index].id;
|
||||
this._telemetryService.sendActionEvent(this._telemetryView, actionId);
|
||||
// Call OK to close dialog.
|
||||
this.ok(false);
|
||||
// Run the action if possible
|
||||
this._actions[index].run();
|
||||
// Resolve promise after running action.
|
||||
this._promiseResolver(actionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,16 +165,23 @@ export class ErrorMessageDialog extends Modal {
|
||||
this.ok();
|
||||
}
|
||||
|
||||
public ok(): void {
|
||||
public ok(resolvePromise: boolean = true): void {
|
||||
this._telemetryService.sendActionEvent(this._telemetryView, 'ok');
|
||||
this._onOk.fire();
|
||||
this.close('ok');
|
||||
this.close('ok', resolvePromise);
|
||||
}
|
||||
|
||||
public close(hideReason: HideReason = 'close') {
|
||||
public close(hideReason: HideReason = 'close', resolvePromise: boolean) {
|
||||
this._telemetryService.sendActionEvent(this._telemetryView, hideReason.toString());
|
||||
this.hide(hideReason);
|
||||
if (resolvePromise) {
|
||||
this._promiseResolver(hideReason.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public open(severity: Severity, headerTitle: string, message: string, messageDetails?: string, actions?: IAction[], instructionText?: string, readMoreLink?: string): void {
|
||||
public open(telemetryView: TelemetryKeys.TelemetryView | string, severity: Severity, headerTitle: string, message: string, messageDetails?: string,
|
||||
actions?: IAction[], instructionText?: string, readMoreLink?: string, resetActions: boolean = true): void {
|
||||
this._telemetryView = telemetryView;
|
||||
this._severity = severity;
|
||||
this._message = message;
|
||||
this._instructionText = instructionText;
|
||||
@@ -181,7 +196,9 @@ export class ErrorMessageDialog extends Modal {
|
||||
if (this._message) {
|
||||
this._bodyContainer.setAttribute('aria-description', this._message);
|
||||
}
|
||||
this.resetActions();
|
||||
if (resetActions) {
|
||||
this.resetActions();
|
||||
}
|
||||
if (actions?.length > 0) {
|
||||
for (let i = 0; i < maxActions && i < actions.length; i++) {
|
||||
this._actions.push(actions[i]);
|
||||
@@ -209,6 +226,42 @@ export class ErrorMessageDialog extends Modal {
|
||||
}
|
||||
}
|
||||
|
||||
public openCustomAsync(options: IErrorDialogOptions): Promise<string | undefined> {
|
||||
if (!options) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let actions: IAction[] = [];
|
||||
this.resetActions();
|
||||
options.actions?.forEach(action => {
|
||||
actions.push(new Action(action.id, action.label, '', true, () => { }));
|
||||
});
|
||||
|
||||
this.open(options.telemetryView, this.convertToSeverity(options.severity),
|
||||
options.headerTitle, options.message, options.messageDetails, actions,
|
||||
options.instructionText, options.readMoreLink, false);
|
||||
|
||||
const deferred = new Deferred<string | undefined>();
|
||||
this._promiseResolver = deferred.resolve;
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
private convertToSeverity(messageLevel: MessageLevel): Severity {
|
||||
let severity: Severity = Severity.Error;
|
||||
switch (messageLevel) {
|
||||
case MessageLevel.Error:
|
||||
severity = Severity.Error;
|
||||
break;
|
||||
case MessageLevel.Information:
|
||||
severity = Severity.Info;
|
||||
break;
|
||||
case MessageLevel.Warning:
|
||||
severity = Severity.Warning;
|
||||
break;
|
||||
}
|
||||
return severity;
|
||||
}
|
||||
|
||||
private resetActions(): void {
|
||||
this._actions = [];
|
||||
for (let actionButton of this._actionButtons) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { IAction } from 'vs/base/common/actions';
|
||||
|
||||
import { ErrorMessageDialog } from 'sql/workbench/services/errorMessage/browser/errorMessageDialog';
|
||||
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||
import { TelemetryView } from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
|
||||
export class ErrorMessageService implements IErrorMessageService {
|
||||
|
||||
@@ -24,11 +25,11 @@ export class ErrorMessageService implements IErrorMessageService {
|
||||
@IInstantiationService private _instantiationService: IInstantiationService
|
||||
) { }
|
||||
|
||||
public showDialog(severity: Severity, headerTitle: string, message: string, messageDetails?: string, actions?: IAction[], instructionText?: string, readMoreLink?: string): void {
|
||||
this.doShowDialog(severity, headerTitle, message, messageDetails, actions, instructionText, readMoreLink);
|
||||
public showDialog(severity: Severity, headerTitle: string, message: string, messageDetails?: string, telemetryView: TelemetryView = TelemetryView.ErrorMessageDialog, actions?: IAction[], instructionText?: string, readMoreLink?: string): void {
|
||||
this.doShowDialog(telemetryView, severity, headerTitle, message, messageDetails, actions, instructionText, readMoreLink);
|
||||
}
|
||||
|
||||
private doShowDialog(severity: Severity, headerTitle: string, message: string, messageDetails?: string, actions?: IAction[], instructionText?: string, readMoreLink?: string): void {
|
||||
private doShowDialog(telemetryView: TelemetryView, severity: Severity, headerTitle: string, message: string, messageDetails?: string, actions?: IAction[], instructionText?: string, readMoreLink?: string): void {
|
||||
if (!this._errorDialog) {
|
||||
this._errorDialog = this._instantiationService.createInstance(ErrorMessageDialog);
|
||||
this._errorDialog.onOk(() => this.handleOnOk());
|
||||
@@ -36,7 +37,7 @@ export class ErrorMessageService implements IErrorMessageService {
|
||||
}
|
||||
|
||||
let title = headerTitle ? headerTitle : this.getDefaultTitle(severity);
|
||||
return this._errorDialog.open(severity, title, message, messageDetails, actions, instructionText, readMoreLink);
|
||||
return this._errorDialog.open(telemetryView, severity, title, message, messageDetails, actions, instructionText, readMoreLink);
|
||||
}
|
||||
|
||||
private getDefaultTitle(severity: Severity) {
|
||||
|
||||
Reference in New Issue
Block a user