Added error handling service for providers (#21627)

* added prototype errorHandlerService

* added initial contracts

* Added WIP client sent request function

* added WIP signature for handleOtherError to resourceProviderService

* made some small fixes

* removed unnecessary resourceProviderId

* added updates to contracts and resourceProvider

* moved error codes to azdata proposed

* added connection type instead of profile

* added WIP handleOtherError code

* added fix for send

* added WIP change password function in resource provider

* added work in progress error handling thread

* added errorHandler interface

* added result error check

* renamed errorHandling namespace to diagnostics

* WIP rename of errorhandler

* light cleanup

* Bump json5 from 2.1.3 to 2.2.3 in /extensions/machine-learning (#21514)

Bumps [json5](https://github.com/json5/json5) from 2.1.3 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.1.3...v2.2.3)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Stops second invocation of createNewSession while expanding OE tree items on disconnected servers (#21437)

* Debounces second invocation while expanding OE tree items

* Minor clean up

* Adjusts debounce time

* Adding temp trace comments

* Adds missing semicolon

* Removes debouncer to stop 2nd newSession calls

* Removes temp trace comments

* Updates comment

Co-authored-by: Alan Ren <alanren@microsoft.com>

* Bump json5 from 2.1.3 to 2.2.3 in /extensions/admin-tool-ext-win (#21546)

Bumps [json5](https://github.com/json5/json5) from 2.1.3 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.1.3...v2.2.3)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Fix resource endpoints to end with slash (#21540)

* Bump json5 from 2.1.3 to 2.2.3 in /extensions/azcli (#21543)

Bumps [json5](https://github.com/json5/json5) from 2.1.3 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.1.3...v2.2.3)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump json5 from 2.1.3 to 2.2.3 in /extensions/arc (#21544)

Bumps [json5](https://github.com/json5/json5) from 2.1.3 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.1.3...v2.2.3)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* renamed diagnostics service slightly

* registered service

* added work in progress diagnostics implementation

* small changes

* added new diagnostics exe

* Changes for Diagnostics Service (#21583)

Co-authored-by: Cheena Malhotra <cmalhotra@microsoft.com>

* WIP provider changes

* added changes for errorDiagnosticsService

* removed stuff from mssql.

* made fix to connectionManagementService

* added error catch

* added small changes

* more small changes made

* added small changes to handleOtherError

* moved changePassword to CMS

* added testErrorDiagnosticsService

* Added provider-based handling for change password/generic error handling. (#21662)

* WIP rework for error connection change

* added connectionProfileDuringError

* added working password reset

* added comments

* consolidated connection profile conversion

* added additionalObjects parameter.

* removed unnecessary error profile grab

* added comments

* added changes to parameters and comments

* added changes and params

* added handleConnectionErrorParam

* added more changes

* added async

* added params and more

* added many fixes

* added updated documentation

* added WIP password change dialog with await

* added error handling

* added comment

* added options as parameters

* cleaned up parameters

* added async

* added check fixes

* Added username to title

* added server name to dialog

* Added dialog changes

* Revert "Added dialog changes"

This reverts commit c2bdcd16f4a0dffdc643ef9cae1c1a20642ac512.

* Revert "added server name to dialog"

This reverts commit dbd22e80461b5a068643f0c2d6728adce4010978.

* Revert "Added username to title"

This reverts commit 6d936b4d5f97f9345f8ec2fdbbcf6b52df18820a.

* Revert "added check fixes"

This reverts commit f58081a5af3276766e2042b4d671455b18add9a7.

* Revert "added async"

This reverts commit dd1198e26ec7542ec51add0628f588361d674299.

* Revert "cleaned up parameters"

This reverts commit 51135c9f9db452104697483779d8df15b6430717.

* Revert "added options as parameters"

This reverts commit b167804a2410558bbe60042e017ae2c77af7697f.

* Revert "added comment"

This reverts commit 0ad37326a3e025e88f715e3f2547be6825597a8d.

* Revert "added error handling"

This reverts commit 69340980d2c84056a2bcf126ea77f4b5ed4cddf3.

* Revert "added WIP password change dialog with await"

This reverts commit 9e43113e07b10421b39575f6c7dd14287662b90d.

* added a fix to check

* added fixes

* added back in change password changes

* added in comment

* added suggested changes

* removed param colons

* Update extensions/mssql/src/errorDiagnostics/errorDiagnosticsProvider.ts

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>

* Update src/sql/azdata.proposed.d.ts

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>

* fixed conversion and provider dialog

* altered comments

* Update src/sql/platform/connection/common/utils.ts

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>

* renamed Id

* Update src/sql/platform/connection/common/utils.ts

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>

* Improvements for Change password dialog + logs added (#21794)

* Improvements for Change password dialog + logs added

* Include server

* fixed tab space

* added comment

---------

Co-authored-by: Alex Ma <alma1@microsoft.com>

* Update src/sql/azdata.proposed.d.ts

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>

* added fix to id to extHostErrorDiagnostics

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Lewis Sanchez <87730006+lewis-sanchez@users.noreply.github.com>
Co-authored-by: Alan Ren <alanren@microsoft.com>
Co-authored-by: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com>
Co-authored-by: Cheena Malhotra <cmalhotra@microsoft.com>
Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>
This commit is contained in:
Alex Ma
2023-01-31 14:31:12 -08:00
committed by GitHub
parent e1bbcb2ff1
commit 298402647d
25 changed files with 601 additions and 106 deletions

View File

@@ -20,5 +20,6 @@ import './mainThreadNotebookDocumentsAndEditors';
import './mainThreadObjectExplorer';
import './mainThreadQueryEditor';
import './mainThreadResourceProvider';
import './mainThreadErrorDiagnostics';
import './mainThreadTasks';
import './mainThreadWorkspace';

View File

@@ -10,6 +10,7 @@ import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/br
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import * as TaskUtilities from 'sql/workbench/browser/taskUtilities';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { convertToRpcConnectionProfile } from 'sql/platform/connection/common/utils';
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { generateUuid } from 'vs/base/common/uuid';
@@ -92,7 +93,7 @@ export class MainThreadConnectionManagement extends Disposable implements MainTh
}
public $getConnections(activeConnectionsOnly?: boolean): Thenable<azdata.connection.ConnectionProfile[]> {
return Promise.resolve(this._connectionManagementService.getConnections(activeConnectionsOnly).map(profile => this.convertToConnectionProfile(profile)));
return Promise.resolve(this._connectionManagementService.getConnections(activeConnectionsOnly).map(profile => convertToRpcConnectionProfile(profile, true, this._connectionManagementService.removeConnectionProfileCredentials)));
}
public $getConnection(uri: string): Thenable<azdata.connection.ConnectionProfile> {
@@ -101,22 +102,7 @@ export class MainThreadConnectionManagement extends Disposable implements MainTh
return Promise.resolve(undefined);
}
let connection: azdata.connection.ConnectionProfile = {
providerId: profile.providerName,
connectionId: profile.id,
connectionName: profile.connectionName,
serverName: profile.serverName,
databaseName: profile.databaseName,
userName: profile.userName,
password: profile.password,
authenticationType: profile.authenticationType,
savePassword: profile.savePassword,
groupFullName: profile.groupFullName,
groupId: profile.groupId,
saveProfile: profile.savePassword,
azureTenantId: profile.azureTenantId,
options: profile.options
};
let connection = convertToRpcConnectionProfile(profile);
return Promise.resolve(connection);
}
@@ -129,7 +115,7 @@ export class MainThreadConnectionManagement extends Disposable implements MainTh
}
public $getCurrentConnectionProfile(): Thenable<azdata.connection.ConnectionProfile> {
return Promise.resolve(this.convertToConnectionProfile(TaskUtilities.getCurrentGlobalConnection(this._objectExplorerService, this._connectionManagementService, this._workbenchEditorService, true)));
return Promise.resolve(convertToRpcConnectionProfile(TaskUtilities.getCurrentGlobalConnection(this._objectExplorerService, this._connectionManagementService, this._workbenchEditorService, true,), true, this._connectionManagementService.removeConnectionProfileCredentials));
}
public $getCredentials(connectionId: string): Thenable<{ [name: string]: string }> {
@@ -182,6 +168,12 @@ export class MainThreadConnectionManagement extends Disposable implements MainTh
return connection;
}
public $openChangePasswordDialog(profile: IConnectionProfile): Thenable<string | undefined> {
// Need to have access to getOptionsKey, so recreate profile from details.
let convertedProfile = new ConnectionProfile(this._capabilitiesService, profile);
return this._connectionManagementService.openChangePasswordDialog(convertedProfile);
}
public async $listDatabases(connectionId: string): Promise<string[]> {
let connectionUri = await this.$getUriForConnection(connectionId);
let result = await this._connectionManagementService.listDatabases(connectionUri);
@@ -209,30 +201,6 @@ export class MainThreadConnectionManagement extends Disposable implements MainTh
return connection;
}
private convertToConnectionProfile(profile: IConnectionProfile): azdata.connection.ConnectionProfile {
if (!profile) {
return undefined;
}
profile = this._connectionManagementService.removeConnectionProfileCredentials(profile);
let connection: azdata.connection.ConnectionProfile = {
providerId: profile.providerName,
connectionId: profile.id,
options: deepClone(profile.options),
connectionName: profile.connectionName,
serverName: profile.serverName,
databaseName: profile.databaseName,
userName: profile.userName,
password: profile.password,
authenticationType: profile.authenticationType,
savePassword: profile.savePassword,
groupFullName: profile.groupFullName,
groupId: profile.groupId,
saveProfile: profile.saveProfile
};
return connection;
}
public $connect(connectionProfile: IConnectionProfile, saveConnection: boolean = true, showDashboard: boolean = true): Thenable<azdata.ConnectionResult> {
let profile = new ConnectionProfile(this._capabilitiesService, connectionProfile);
profile.id = generateUuid();

View File

@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { IErrorDiagnosticsService } from 'sql/workbench/services/diagnostics/common/errorDiagnosticsService';
import { Disposable } from 'vs/base/common/lifecycle';
import {
ExtHostErrorDiagnosticsShape,
MainThreadErrorDiagnosticsShape
} from 'sql/workbench/api/common/sqlExtHost.protocol';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { SqlExtHostContext, SqlMainContext } from 'vs/workbench/api/common/extHost.protocol';
@extHostNamedCustomer(SqlMainContext.MainThreadErrorDiagnostics)
export class MainThreadErrorDiagnostics extends Disposable implements MainThreadErrorDiagnosticsShape {
private _providerMetadata: { [handle: number]: azdata.diagnostics.ErrorDiagnosticsProviderMetadata };
private _proxy: ExtHostErrorDiagnosticsShape;
constructor(
extHostContext: IExtHostContext,
@IErrorDiagnosticsService private _errorDiagnosticsService: IErrorDiagnosticsService
) {
super();
this._providerMetadata = {};
if (extHostContext) {
this._proxy = extHostContext.getProxy(SqlExtHostContext.ExtHostErrorDiagnostics);
}
}
public $registerDiagnosticsProvider(providerMetadata: azdata.diagnostics.ErrorDiagnosticsProviderMetadata, handle: number): Thenable<void> {
let self = this;
//Create the error handler that interfaces with the extension via the proxy and register it
let errorDiagnostics: azdata.diagnostics.ErrorDiagnosticsProvider = {
handleConnectionError(errorCode: number, errorMessage: string, connection: azdata.connection.ConnectionProfile): Thenable<azdata.diagnostics.ConnectionDiagnosticsResult> {
return self._proxy.$handleConnectionError(handle, errorCode, errorMessage, connection);
}
};
this._errorDiagnosticsService.registerDiagnosticsProvider(providerMetadata.targetProviderId, errorDiagnostics);
this._providerMetadata[handle] = providerMetadata;
return undefined;
}
public $unregisterDiagnosticsProvider(handle: number): Thenable<void> {
this._errorDiagnosticsService.unregisterDiagnosticsProvider(this._providerMetadata[handle].targetProviderId);
return undefined;
}
}

View File

@@ -74,6 +74,10 @@ export class ExtHostConnectionManagement extends ExtHostConnectionManagementShap
return this._proxy.$openConnectionDialog(providers, initialConnectionProfile, connectionCompletionOptions);
}
public $openChangePasswordDialog(profile: azdata.IConnectionProfile): Thenable<string | undefined> {
return this._proxy.$openChangePasswordDialog(profile);
}
public $listDatabases(connectionId: string): Thenable<string[]> {
return this._proxy.$listDatabases(connectionId);
}

View File

@@ -0,0 +1,84 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { IMainContext } from 'vs/workbench/api/common/extHost.protocol';
import { Disposable } from 'vs/workbench/api/common/extHostTypes';
import {
ExtHostErrorDiagnosticsShape,
MainThreadErrorDiagnosticsShape,
} from 'sql/workbench/api/common/sqlExtHost.protocol';
import { values } from 'vs/base/common/collections';
import { SqlMainContext } from 'vs/workbench/api/common/extHost.protocol';
export class ExtHostErrorDiagnostics extends ExtHostErrorDiagnosticsShape {
private _handlePool: number = 0;
private _proxy: MainThreadErrorDiagnosticsShape;
private _providers: { [handle: number]: DiagnosticsWithMetadata } = {};
constructor(mainContext: IMainContext) {
super();
this._proxy = mainContext.getProxy(SqlMainContext.MainThreadErrorDiagnostics);
}
// PUBLIC METHODS //////////////////////////////////////////////////////
// - MAIN THREAD AVAILABLE METHODS /////////////////////////////////////
public override $handleConnectionError(handle: number, errorCode: number, errorMessage: string, connection: azdata.connection.ConnectionProfile): Thenable<azdata.diagnostics.ConnectionDiagnosticsResult> {
let provider = this._providers[handle];
if (provider === undefined) {
return Promise.resolve({ handled: false });
}
else {
return provider.provider.handleConnectionError(errorCode, errorMessage, connection);
}
}
// - EXTENSION HOST AVAILABLE METHODS //////////////////////////////////
public $registerDiagnosticsProvider(providerMetadata: azdata.diagnostics.ErrorDiagnosticsProviderMetadata, errorDiagnostics: azdata.diagnostics.ErrorDiagnosticsProvider): Disposable {
let self = this;
// Look for any diagnostic providers that have the same provider ID
let matchingProviderIndex = values(this._providers).findIndex((provider: DiagnosticsWithMetadata) => {
return provider.metadata.targetProviderId === providerMetadata.targetProviderId;
});
if (matchingProviderIndex >= 0) {
throw new Error(`Diagnostics Provider with ID '${providerMetadata.targetProviderId}' has already been registered`);
}
// Create the handle for the provider
let handle: number = this._nextHandle();
this._providers[handle] = {
metadata: providerMetadata,
provider: errorDiagnostics
};
// Register the provider in the main thread via the proxy
this._proxy.$registerDiagnosticsProvider(providerMetadata, handle);
// Return a disposable to cleanup the provider
return new Disposable(() => {
delete self._providers[handle];
self._proxy.$unregisterDiagnosticsProvider(handle);
});
}
/**
* This method is for testing only, it is not exposed via the shape.
* @return Number of providers that are currently registered
*/
public getProviderCount(): number {
return Object.keys(this._providers).length;
}
// PRIVATE METHODS /////////////////////////////////////////////////////
private _nextHandle(): number {
return this._handlePool++;
}
}
interface DiagnosticsWithMetadata {
metadata: azdata.diagnostics.ErrorDiagnosticsProviderMetadata;
provider: azdata.diagnostics.ErrorDiagnosticsProvider;
}

View File

@@ -10,6 +10,7 @@ import { ExtHostAccountManagement } from 'sql/workbench/api/common/extHostAccoun
import { ExtHostCredentialManagement } from 'sql/workbench/api/common/extHostCredentialManagement';
import { ExtHostDataProtocol } from 'sql/workbench/api/common/extHostDataProtocol';
import { ExtHostResourceProvider } from 'sql/workbench/api/common/extHostResourceProvider';
import { ExtHostErrorDiagnostics } from 'sql/workbench/api/common/extHostErrorDiagnostics';
import * as sqlExtHostTypes from 'sql/workbench/api/common/sqlExtHostTypes';
import { ExtHostModalDialogs } from 'sql/workbench/api/common/extHostModalDialog';
import { ExtHostTasks } from 'sql/workbench/api/common/extHostTasks';
@@ -87,6 +88,7 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
const extHostDataProvider = rpcProtocol.set(SqlExtHostContext.ExtHostDataProtocol, new ExtHostDataProtocol(rpcProtocol, uriTransformer));
const extHostObjectExplorer = rpcProtocol.set(SqlExtHostContext.ExtHostObjectExplorer, new ExtHostObjectExplorer(rpcProtocol, commands));
const extHostResourceProvider = rpcProtocol.set(SqlExtHostContext.ExtHostResourceProvider, new ExtHostResourceProvider(rpcProtocol));
const extHostErrorDiagnostics = rpcProtocol.set(SqlExtHostContext.ExtHostErrorDiagnostics, new ExtHostErrorDiagnostics(rpcProtocol));
const extHostModalDialogs = rpcProtocol.set(SqlExtHostContext.ExtHostModalDialogs, new ExtHostModalDialogs(rpcProtocol));
const extHostTasks = rpcProtocol.set(SqlExtHostContext.ExtHostTasks, new ExtHostTasks(rpcProtocol, extHostLogService));
const extHostBackgroundTaskManagement = rpcProtocol.set(SqlExtHostContext.ExtHostBackgroundTaskManagement, new ExtHostBackgroundTaskManagement(rpcProtocol));
@@ -136,6 +138,9 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
openConnectionDialog(providers?: string[], initialConnectionProfile?: azdata.IConnectionProfile, connectionCompletionOptions?: azdata.IConnectionCompletionOptions): Thenable<azdata.connection.Connection> {
return extHostConnectionManagement.$openConnectionDialog(providers, initialConnectionProfile, connectionCompletionOptions);
},
openChangePasswordDialog(profile: azdata.IConnectionProfile): Thenable<string | undefined> {
return extHostConnectionManagement.$openChangePasswordDialog(profile);
},
listDatabases(connectionId: string): Thenable<string[]> {
return extHostConnectionManagement.$listDatabases(connectionId);
},
@@ -215,6 +220,13 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
}
};
// namespace: diagnostics
const diagnostics: typeof azdata.diagnostics = {
registerDiagnosticsProvider: (providerMetadata: azdata.diagnostics.ErrorDiagnosticsProviderMetadata, errorDiagnostics: azdata.diagnostics.ErrorDiagnosticsProvider): vscode.Disposable => {
return extHostErrorDiagnostics.$registerDiagnosticsProvider(providerMetadata, errorDiagnostics);
}
}
let registerConnectionProvider = (provider: azdata.ConnectionProvider): vscode.Disposable => {
// Connection callbacks
provider.registerOnConnectionComplete((connSummary: azdata.ConnectionInfoSummary) => {
@@ -666,6 +678,7 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
TextType: sqlExtHostTypes.TextType,
designers: designers,
executionPlan: executionPlan,
diagnostics: diagnostics,
env
};
}

View File

@@ -604,7 +604,16 @@ export abstract class ExtHostResourceProviderShape {
* Handle firewall rule
*/
$handleFirewallRule(handle: number, errorCode: number, errorMessage: string, connectionTypeId: string): Thenable<azdata.HandleFirewallRuleResponse> { throw ni(); }
}
/**
* ResourceProvider extension host class.
*/
export abstract class ExtHostErrorDiagnosticsShape {
/**
* Handle other connection error types
*/
$handleConnectionError(handle: number, errorCode: number, errorMessage: string, connection: azdata.connection.ConnectionProfile): Thenable<azdata.diagnostics.ConnectionDiagnosticsResult> { throw ni(); }
}
/**
@@ -646,6 +655,11 @@ export interface MainThreadResourceProviderShape extends IDisposable {
$unregisterResourceProvider(handle: number): Thenable<any>;
}
export interface MainThreadErrorDiagnosticsShape extends IDisposable {
$registerDiagnosticsProvider(providerMetadata: azdata.diagnostics.ErrorDiagnosticsProviderMetadata, handle: number): Thenable<any>;
$unregisterDiagnosticsProvider(handle: number): Thenable<any>;
}
export interface MainThreadDataProtocolShape extends IDisposable {
$registerConnectionProvider(providerId: string, handle: number): Promise<any>;
$registerBackupProvider(providerId: string, handle: number): Promise<any>;
@@ -708,6 +722,7 @@ export interface MainThreadConnectionManagementShape extends IDisposable {
$getCredentials(connectionId: string): Thenable<{ [name: string]: string }>;
$getServerInfo(connectedId: string): Thenable<azdata.ServerInfo>;
$openConnectionDialog(providers: string[], initialConnectionProfile?: azdata.IConnectionProfile, connectionCompletionOptions?: azdata.IConnectionCompletionOptions): Thenable<azdata.connection.Connection>;
$openChangePasswordDialog(profile: azdata.IConnectionProfile): Thenable<string | undefined>;
$listDatabases(connectionId: string): Thenable<string[]>;
$getConnectionString(connectionId: string, includePassword: boolean): Thenable<string>;
$getUriForConnection(connectionId: string): Thenable<string>;

View File

@@ -30,7 +30,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { CmsConnectionController } from 'sql/workbench/services/connection/browser/cmsConnectionController';
import { PasswordChangeDialog } from 'sql/workbench/services/connection/browser/passwordChangeDialog';
import { entries } from 'sql/base/common/collections';
import { onUnexpectedError } from 'vs/base/common/errors';
import { ILogService } from 'vs/platform/log/common/log';
@@ -284,9 +283,6 @@ export class ConnectionDialogService implements IConnectionDialogService {
} else if (connectionResult && connectionResult.errorHandled) {
this._connectionDialog.resetConnection();
this._logService.debug(`ConnectionDialogService: Error handled and connection reset - Error: ${connectionResult.errorMessage}`);
} else if (connection.providerName === Constants.mssqlProviderName && connectionResult.errorCode === Constants.sqlPasswordErrorCode) {
this._connectionDialog.resetConnection();
this.launchChangePasswordDialog(connection, params);
} else {
this._connectionDialog.resetConnection();
this.showErrorDialog(Severity.Error, this._connectionErrorTitle, connectionResult.errorMessage, connectionResult.callStack, connectionResult.errorCode);
@@ -507,11 +503,6 @@ export class ConnectionDialogService implements IConnectionDialogService {
recentConnections.forEach(conn => conn.dispose());
}
public launchChangePasswordDialog(profile: IConnectionProfile, params: INewConnectionParams): void {
let dialog = this._instantiationService.createInstance(PasswordChangeDialog);
dialog.open(profile, params);
}
private showErrorDialog(severity: Severity, headerTitle: string, message: string, messageDetails?: string, errorCode?: number): void {
// Kerberos errors are currently very hard to understand, so adding handling of these to solve the common scenario

View File

@@ -55,6 +55,8 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b
import { ViewContainerLocation } from 'vs/workbench/common/views';
import { VIEWLET_ID as ExtensionsViewletID } from 'vs/workbench/contrib/extensions/common/extensions';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IErrorDiagnosticsService } from 'sql/workbench/services/diagnostics/common/errorDiagnosticsService';
import { PasswordChangeDialog } from 'sql/workbench/services/connection/browser/passwordChangeDialog';
export class ConnectionManagementService extends Disposable implements IConnectionManagementService {
@@ -94,6 +96,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
@IQuickInputService private _quickInputService: IQuickInputService,
@INotificationService private _notificationService: INotificationService,
@IResourceProviderService private _resourceProviderService: IResourceProviderService,
@IErrorDiagnosticsService private _errorDiagnosticsService: IErrorDiagnosticsService,
@IAngularEventingService private _angularEventing: IAngularEventingService,
@IAccountManagementService private _accountManagementService: IAccountManagementService,
@ILogService private _logService: ILogService,
@@ -430,7 +433,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
/**
* Changes password of the connection profile's user.
*/
public changePassword(connection: interfaces.IConnectionProfile, uri: string, newPassword: string):
public async changePassword(connection: interfaces.IConnectionProfile, uri: string, newPassword: string):
Promise<azdata.PasswordChangeResult> {
return this.sendChangePasswordRequest(connection, uri, newPassword);
}
@@ -552,20 +555,32 @@ export class ConnectionManagementService extends Disposable implements IConnecti
});
}
private handleConnectionError(connection: interfaces.IConnectionProfile, uri: string, options: IConnectionCompletionOptions, callbacks: IConnectionCallbacks, connectionResult: IConnectionResult) {
private async handleConnectionError(connection: interfaces.IConnectionProfile, uri: string, options: IConnectionCompletionOptions, callbacks: IConnectionCallbacks, connectionResult: IConnectionResult) {
let connectionNotAcceptedError = nls.localize('connectionNotAcceptedError', "Connection Not Accepted");
if (options.showFirewallRuleOnError && connectionResult.errorCode) {
return this.handleFirewallRuleError(connection, connectionResult).then(success => {
if (success) {
options.showFirewallRuleOnError = false;
let firewallRuleErrorHandled = await this.handleFirewallRuleError(connection, connectionResult);
if (firewallRuleErrorHandled) {
options.showFirewallRuleOnError = false;
return this.connectWithOptions(connection, uri, options, callbacks);
}
else {
let connectionErrorHandled = await this._errorDiagnosticsService.tryHandleConnectionError(connectionResult.errorCode, connectionResult.errorMessage, connection.providerName, connection);
if (connectionErrorHandled.handled) {
connectionResult.errorHandled = true;
if (connectionErrorHandled.options) {
//copy over altered connection options from the result if provided.
connection.options = connectionErrorHandled.options;
}
return this.connectWithOptions(connection, uri, options, callbacks);
} else {
}
else {
// Error not handled by any registered providers so fail the connection
if (callbacks.onConnectReject) {
callbacks.onConnectReject(connectionNotAcceptedError);
}
return connectionResult;
}
});
}
} else {
if (callbacks.onConnectReject) {
callbacks.onConnectReject(connectionNotAcceptedError);
@@ -585,6 +600,12 @@ export class ConnectionManagementService extends Disposable implements IConnecti
});
}
public async openChangePasswordDialog(profile: interfaces.IConnectionProfile): Promise<string | undefined> {
let dialog = this._instantiationService.createInstance(PasswordChangeDialog);
let result = await dialog.open(profile);
return result;
}
private doActionsAfterConnectionComplete(uri: string, options: IConnectionCompletionOptions): void {
let connectionManagementInfo = this._connectionStatusManager.findConnection(uri);
if (!connectionManagementInfo) {

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.change-password-dialog {
width: 100%;
padding: 30px;
height: 100%;
overflow: hidden;
display: flex;
@@ -15,8 +15,9 @@
overflow-y: auto;
}
.change-password-dialog .component-label {
vertical-align: middle;
.change-password-dialog .component-label-bold {
font-weight: 600;
padding-bottom: 30px;
}
.change-password-dialog .components-grid {
@@ -25,7 +26,7 @@
grid-template-columns: max-content 1fr;
grid-template-rows: max-content;
grid-gap: 10px;
padding: 5px;
padding: 30px 0px 10px;
align-content: start;
box-sizing: border-box;
}

View File

@@ -8,7 +8,6 @@ import { Button } from 'sql/base/browser/ui/button/button';
import { Modal } from 'sql/workbench/browser/modal/modal';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { attachInputBoxStyler } from 'sql/platform/theme/common/styler';
import { INewConnectionParams } from 'sql/platform/connection/common/connectionManagement';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
@@ -19,7 +18,6 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView
import * as DOM from 'vs/base/browser/dom';
import { ILogService } from 'vs/platform/log/common/log';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import Severity from 'vs/base/common/severity';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
@@ -27,12 +25,11 @@ import { attachModalDialogStyler } from 'sql/workbench/common/styler';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration';
const dialogWidth: string = '300px'; // Width is set manually here as there is no default width for normal dialogs.
const dialogWidth: string = '500px'; // Width is set manually here as there is no default width for normal dialogs.
const okText: string = localize('passwordChangeDialog.ok', "OK");
const cancelText: string = localize('passwordChangeDialog.cancel', "Cancel");
const dialogTitle: string = localize('passwordChangeDialog.title', "Change Password");
const newPasswordText: string = localize('passwordChangeDialog.newPassword', 'New password:');
const confirmPasswordText: string = localize('passwordChangeDialog.confirmPassword', 'Confirm password:');
const newPasswordText: string = localize('passwordChangeDialog.newPassword', "New password:");
const confirmPasswordText: string = localize('passwordChangeDialog.confirmPassword', "Confirm password:");
const passwordChangeLoadText: string = localize('passwordChangeDialog.connecting', "Connecting");
const errorHeader: string = localize('passwordChangeDialog.errorHeader', "Failure when attempting to change password");
const errorPasswordMismatchErrorMessage = localize('passwordChangeDialog.errorPasswordMismatchErrorMessage', "Passwords entered do not match");
@@ -42,8 +39,8 @@ export class PasswordChangeDialog extends Modal {
private _okButton?: Button;
private _cancelButton?: Button;
private _promiseResolver: (value: string) => void;
private _profile: IConnectionProfile;
private _params: INewConnectionParams;
private _uri: string;
private _passwordValueText: InputBox;
private _confirmValueText: InputBox;
@@ -53,7 +50,6 @@ export class PasswordChangeDialog extends Modal {
@IClipboardService clipboardService: IClipboardService,
@IConnectionManagementService private readonly connectionManagementService: IConnectionManagementService,
@IErrorMessageService private readonly errorMessageService: IErrorMessageService,
@IConnectionDialogService private readonly connectionDialogService: IConnectionDialogService,
@ILayoutService layoutService: ILayoutService,
@IAdsTelemetryService telemetryService: IAdsTelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@@ -64,31 +60,43 @@ export class PasswordChangeDialog extends Modal {
super('', '', telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, { hasSpinner: true, spinnerTitle: passwordChangeLoadText, dialogStyle: 'normal', width: dialogWidth, dialogPosition: 'left' });
}
public open(profile: IConnectionProfile, params: INewConnectionParams) {
public open(profile: IConnectionProfile): Promise<string> {
if (this._profile) {
// If already in the middle of a password change, reject an incoming open.
let message = localize('passwordChangeDialog.passwordChangeInProgress', "Password change already in progress")
this.errorMessageService.showDialog(Severity.Error, errorHeader, message);
return Promise.reject(new Error(message));
}
this._profile = profile;
this._params = params;
this._uri = this.connectionManagementService.getConnectionUri(profile);
this.render();
this.show();
this._okButton!.focus();
const promise = new Promise<string | undefined>((resolve) => {
this._promiseResolver = resolve;
});
return promise;
}
public override dispose(): void {
}
public override dispose(): void { }
public override render() {
super.render();
this.title = dialogTitle;
this.title = localize('passwordChangeDialog.title', 'Change Password');
this._register(attachModalDialogStyler(this, this._themeService));
this._okButton = this.addFooterButton(okText, () => this.handleOkButtonClick());
this._cancelButton = this.addFooterButton(cancelText, () => this.hide('cancel'), 'right', true);
this._okButton = this.addFooterButton(okText, async () => { await this.handleOkButtonClick(); });
this._cancelButton = this.addFooterButton(cancelText, () => { this.handleCancelButtonClick(); }, 'right', true);
this._register(attachButtonStyler(this._okButton, this._themeService));
this._register(attachButtonStyler(this._cancelButton, this._themeService));
}
protected renderBody(container: HTMLElement) {
const body = container.appendChild(DOM.$('.change-password-dialog'));
body.appendChild(DOM.$('span.component-label-bold')).innerText = localize('passwordChangeDialog.Message1',
`Password must be changed for '{0}' to continue logging into '{1}'.`, this._profile?.userName, this._profile?.serverName);
body.appendChild(DOM.$('span.component-label')).innerText = localize('passwordChangeDialog.Message2',
`Please enter a new password below:`);
const contentElement = body.appendChild(DOM.$('.properties-content.components-grid'));
contentElement.appendChild(DOM.$('')).appendChild(DOM.$('span.component-label')).innerText = newPasswordText;
const passwordInputContainer = contentElement.appendChild(DOM.$(''));
@@ -107,42 +115,50 @@ export class PasswordChangeDialog extends Modal {
/* espace key */
protected override onClose() {
this.hide('close');
this.handleCancelButtonClick();
}
/* enter key */
protected override onAccept() {
this.handleOkButtonClick();
protected override async onAccept() {
await this.handleOkButtonClick();
}
private handleOkButtonClick(): void {
private async handleOkButtonClick(): Promise<void> {
this._okButton.enabled = false;
this._cancelButton.enabled = false;
this.spinner = true;
this.changePasswordFunction(this._profile, this._params, this._uri, this._passwordValueText.value, this._confirmValueText.value).then(
() => {
this.hide('ok'); /* password changed successfully */
},
() => {
this._okButton.enabled = true; /* ignore, user must try again */
this._cancelButton.enabled = true;
this.spinner = false;
}
);
try {
let result = await this.changePasswordFunction(this._profile, this._uri, this._passwordValueText.value, this._confirmValueText.value);
this.hide('ok'); /* password changed successfully */
this._promiseResolver(result);
}
catch {
// Error encountered, keep the dialog open and reset dialog back to previous state.
this._okButton.enabled = true; /* ignore, user must try again */
this._cancelButton.enabled = true;
this.spinner = false;
}
}
private async changePasswordFunction(connection: IConnectionProfile, params: INewConnectionParams, uri: string, oldPassword: string, newPassword: string): Promise<void> {
private handleCancelButtonClick(): void {
this.hide('cancel');
this._promiseResolver(undefined);
}
private async changePasswordFunction(connection: IConnectionProfile, uri: string, oldPassword: string, newPassword: string): Promise<string> {
// Verify passwords match before changing the password.
if (oldPassword !== newPassword) {
this.errorMessageService.showDialog(Severity.Error, errorHeader, errorPasswordMismatchErrorMessage + '\n\n' + errorPasswordMismatchRecoveryInstructions);
return Promise.reject(new Error(errorPasswordMismatchErrorMessage));
}
let passwordChangeResult = await this.connectionManagementService.changePassword(connection, uri, newPassword);
if (!passwordChangeResult.result) {
this.errorMessageService.showDialog(Severity.Error, errorHeader, passwordChangeResult.errorMessage);
return Promise.reject(new Error(passwordChangeResult.errorMessage));
}
connection.options['password'] = newPassword;
await this.connectionDialogService.callDefaultOnConnect(connection, params);
return newPassword;
}
}

View File

@@ -20,6 +20,7 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService';
import { TestConnectionProvider } from 'sql/platform/connection/test/common/testConnectionProvider';
import { TestResourceProvider } from 'sql/workbench/services/resourceProvider/test/common/testResourceProviderService';
import { TestErrorDiagnosticsService } from 'sql/workbench/services/connection/test/common/testErrorDiagnosticsService';
import * as azdata from 'azdata';
@@ -52,6 +53,7 @@ suite('SQL ConnectionManagementService tests', () => {
let workspaceConfigurationServiceMock: TypeMoq.Mock<TestConfigurationService>;
let resourceProviderStubMock: TypeMoq.Mock<TestResourceProvider>;
let accountManagementService: TypeMoq.Mock<TestAccountManagementService>;
let errorDiagnosticsService: TestErrorDiagnosticsService;
let none: void;
@@ -99,6 +101,7 @@ suite('SQL ConnectionManagementService tests', () => {
let resourceProviderStub = new TestResourceProvider();
resourceProviderStubMock = TypeMoq.Mock.ofInstance(resourceProviderStub);
accountManagementService = TypeMoq.Mock.ofType(TestAccountManagementService);
errorDiagnosticsService = new TestErrorDiagnosticsService();
let root = new ConnectionProfileGroup(ConnectionProfileGroup.RootGroupName, undefined, ConnectionProfileGroup.RootGroupName, undefined, undefined);
root.connections = [ConnectionProfile.fromIConnectionProfile(capabilitiesService, connectionProfile)];
@@ -181,6 +184,7 @@ suite('SQL ConnectionManagementService tests', () => {
undefined, // IQuickInputService
new TestNotificationService(),
resourceProviderStubMock.object,
errorDiagnosticsService,
undefined, // IAngularEventingService
accountManagementService.object,
testLogService, // ILogService
@@ -1574,7 +1578,7 @@ suite('SQL ConnectionManagementService tests', () => {
const testInstantiationService = new TestInstantiationService();
testInstantiationService.stub(IStorageService, new TestStorageService());
testInstantiationService.stubCreateInstance(ConnectionStore, connectionStoreMock.object);
const connectionManagementService = new ConnectionManagementService(undefined, testInstantiationService, undefined, undefined, undefined, new TestCapabilitiesService(), undefined, undefined, undefined, undefined, undefined, undefined, undefined, getBasicExtensionService(), undefined, undefined, undefined);
const connectionManagementService = new ConnectionManagementService(undefined, testInstantiationService, undefined, undefined, undefined, new TestCapabilitiesService(), undefined, undefined, undefined, errorDiagnosticsService, undefined, undefined, undefined, undefined, getBasicExtensionService(), undefined, undefined, undefined);
assert.strictEqual(profile.password, '', 'Profile should not have password initially');
assert.strictEqual(profile.options['password'], '', 'Profile options should not have password initially');
// Check for invalid profile id
@@ -1604,7 +1608,7 @@ suite('SQL ConnectionManagementService tests', () => {
testInstantiationService.stub(IStorageService, new TestStorageService());
testInstantiationService.stubCreateInstance(ConnectionStore, connectionStoreMock.object);
const connectionManagementService = new ConnectionManagementService(undefined, testInstantiationService, undefined, undefined, undefined, new TestCapabilitiesService(), undefined, undefined, undefined, undefined, undefined, undefined, undefined, getBasicExtensionService(), undefined, undefined, undefined);
const connectionManagementService = new ConnectionManagementService(undefined, testInstantiationService, undefined, undefined, undefined, new TestCapabilitiesService(), undefined, undefined, undefined, errorDiagnosticsService, undefined, undefined, undefined, undefined, getBasicExtensionService(), undefined, undefined, undefined);
assert.strictEqual(profile.password, '', 'Profile should not have password initially');
assert.strictEqual(profile.options['password'], '', 'Profile options should not have password initially');
let credentials = await connectionManagementService.getConnectionCredentials(profile.id);
@@ -1924,7 +1928,7 @@ suite('SQL ConnectionManagementService tests', () => {
createInstanceStub.withArgs(ConnectionStore).returns(connectionStoreMock.object);
createInstanceStub.withArgs(ConnectionStatusManager).returns(connectionStatusManagerMock.object);
const connectionManagementService = new ConnectionManagementService(undefined, testInstantiationService, undefined, undefined, undefined, new TestCapabilitiesService(), undefined, undefined, undefined, undefined, undefined, undefined, undefined, getBasicExtensionService(), undefined, undefined, undefined);
const connectionManagementService = new ConnectionManagementService(undefined, testInstantiationService, undefined, undefined, undefined, new TestCapabilitiesService(), undefined, undefined, undefined, errorDiagnosticsService, undefined, undefined, undefined, undefined, getBasicExtensionService(), undefined, undefined, undefined);
// dupe connections have been seeded the numbers below already reflected the de-duped results
@@ -1957,7 +1961,7 @@ test('isRecent should evaluate whether a profile was recently connected or not',
});
let profile1 = createConnectionProfile('1');
let profile2 = createConnectionProfile('2');
const connectionManagementService = new ConnectionManagementService(undefined, testInstantiationService, undefined, undefined, undefined, new TestCapabilitiesService(), undefined, undefined, undefined, undefined, undefined, undefined, undefined, getBasicExtensionService(), undefined, undefined, undefined);
const connectionManagementService = new ConnectionManagementService(undefined, testInstantiationService, undefined, undefined, undefined, new TestCapabilitiesService(), undefined, undefined, undefined, new TestErrorDiagnosticsService(), undefined, undefined, undefined, undefined, getBasicExtensionService(), undefined, undefined, undefined);
assert(connectionManagementService.isRecent(profile1));
assert(!connectionManagementService.isRecent(profile2));
});
@@ -1975,7 +1979,7 @@ test('clearRecentConnection and ConnectionsList should call connectionStore func
testInstantiationService.stub(IStorageService, new TestStorageService());
sinon.stub(testInstantiationService, 'createInstance').withArgs(ConnectionStore).returns(connectionStoreMock.object);
let profile1 = createConnectionProfile('1');
const connectionManagementService = new ConnectionManagementService(undefined, testInstantiationService, undefined, undefined, undefined, new TestCapabilitiesService(), undefined, undefined, undefined, undefined, undefined, undefined, undefined, getBasicExtensionService(), undefined, undefined, undefined);
const connectionManagementService = new ConnectionManagementService(undefined, testInstantiationService, undefined, undefined, undefined, new TestCapabilitiesService(), undefined, undefined, undefined, new TestErrorDiagnosticsService(), undefined, undefined, undefined, undefined, getBasicExtensionService(), undefined, undefined, undefined);
connectionManagementService.clearRecentConnection(profile1);
assert(called);
called = false;

View File

@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { IErrorDiagnosticsService } from 'sql/workbench/services/diagnostics/common/errorDiagnosticsService';
export class TestErrorDiagnosticsService implements IErrorDiagnosticsService {
_serviceBrand: undefined;
registerDiagnosticsProvider(providerId: string, errorDiagnostics: azdata.diagnostics.ErrorDiagnosticsProvider): void {
}
unregisterDiagnosticsProvider(ProviderId: string): void {
}
tryHandleConnectionError(errorCode: number, errorMessage: string, providerId: string, connection: azdata.IConnectionProfile): Promise<azdata.diagnostics.ConnectionDiagnosticsResult> {
return Promise.resolve({ handled: false });
}
}

View File

@@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IErrorDiagnosticsService } from 'sql/workbench/services/diagnostics/common/errorDiagnosticsService';
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';
export class ErrorDiagnosticsService implements IErrorDiagnosticsService {
_serviceBrand: undefined;
private _providers: { [handle: string]: azdata.diagnostics.ErrorDiagnosticsProvider; } = Object.create(null);
constructor(
@ILogService private readonly _logService: ILogService
) { }
public async tryHandleConnectionError(errorCode: number, errorMessage: string, 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));
}
return result;
}
/**
* Register a diagnostic provider object for a provider
* Note: only ONE diagnostic provider object can be assigned to a specific provider at a time.
* @param providerId the id of the provider to register.
* @param errorDiagnostics the actual diagnostics provider object to register under the id.
*/
public registerDiagnosticsProvider(providerId: string, errorDiagnostics: azdata.diagnostics.ErrorDiagnosticsProvider): void {
if (this._providers[providerId]) {
this._logService.error('Provider ' + providerId + ' was already registered, cannot register again.')
}
else {
this._providers[providerId] = errorDiagnostics;
}
}
/**
* Unregister a diagnostics provider object for a provider
* @param providerId the id of the provider to unregister.
*/
public unregisterDiagnosticsProvider(providerId: string): void {
delete this._providers[providerId];
}
}

View File

@@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const SERVICE_ID = 'errorDiagnosticsService';
export const IErrorDiagnosticsService = createDecorator<IErrorDiagnosticsService>(SERVICE_ID);
export interface IErrorDiagnosticsService {
_serviceBrand: undefined;
/**
* Register a diagnostics provider object for a provider
* Note: only ONE diagnostic provider object can be assigned to a specific provider at a time.
* @param providerId the id of the provider to be registered.
* @param errorDiagnostics the actual diagnostics provider object to be registered under the id.
*/
registerDiagnosticsProvider(providerId: string, errorDiagnostics: azdata.diagnostics.ErrorDiagnosticsProvider): void;
/**
* Unregister a diagnostics provider object for a provider
* @param providerId the id of the provider to be unregistered.
*/
unregisterDiagnosticsProvider(ProviderId: string): void;
/**
* Checks connection error with given parameters
* @param errorCode Error code indicating the error problem.
* @param errorMessage Error message that describes the problem in detail.
* @param providerId Identifies what provider the error comes from.
* @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>;
}