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

@@ -0,0 +1,7 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Error code reference comes from here: https://learn.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors?view=sql-server-ver16
export const MssqlPasswordResetErrorCode: number = 18488;

View File

@@ -0,0 +1,98 @@
/*---------------------------------------------------------------------------------------------
* 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 { ISqlOpsFeature, SqlOpsDataClient, SqlOpsFeature } from 'dataprotocol-client';
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
import { AppContext } from '../appContext';
import { ServerCapabilities, ClientCapabilities, RPCMessageType } from 'vscode-languageclient';
import { Disposable } from 'vscode';
import * as CoreConstants from '../constants';
import * as ErrorDiagnosticsConstants from './errorDiagnosticsConstants';
import { logDebug } from '../utils';
export class ErrorDiagnosticsProvider extends SqlOpsFeature<any> {
private static readonly messagesTypes: RPCMessageType[] = [];
public static asFeature(context: AppContext): ISqlOpsFeature {
return class extends ErrorDiagnosticsProvider {
constructor(client: SqlOpsDataClient) {
super(context, client);
}
override fillClientCapabilities(capabilities: ClientCapabilities): void { }
override initialize(): void {
this.register(this.messages, {
id: UUID.generateUuid(),
registerOptions: undefined
});
}
private convertToIConnectionProfile(profile: azdata.connection.ConnectionProfile): azdata.IConnectionProfile {
return {
providerName: profile.providerId,
id: profile.connectionId,
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
};
}
protected override registerProvider(options: any): Disposable {
let handleConnectionError = async (errorCode: number, errorMessage: string, connection: azdata.connection.ConnectionProfile): Promise<azdata.diagnostics.ConnectionDiagnosticsResult> => {
let restoredProfile = this.convertToIConnectionProfile(connection);
if (errorCode === ErrorDiagnosticsConstants.MssqlPasswordResetErrorCode) {
logDebug(`Error Code ${errorCode} requires user to change their password, launching change password dialog.`)
return await this.handleChangePassword(restoredProfile);
}
logDebug(`No error handler found for errorCode ${errorCode}.`);
return { handled: false };
}
return azdata.diagnostics.registerDiagnosticsProvider({
targetProviderId: CoreConstants.providerId,
}, {
handleConnectionError
});
}
private async handleChangePassword(connection: azdata.IConnectionProfile): Promise<azdata.diagnostics.ConnectionDiagnosticsResult> {
try {
const result = await azdata.connection.openChangePasswordDialog(connection);
// result will be undefined if password change was closed or cancelled.
if (result) {
// MSSQL uses 'password' as the option key for connection profile.
connection.options['password'] = result;
return { handled: true, options: connection.options };
}
}
catch (e) {
console.error(`Change password failed unexpectedly with error: ${e}`);
}
return { handled: false };
}
}
}
fillClientCapabilities(capabilities: ClientCapabilities): void { }
initialize(capabilities: ServerCapabilities): void { }
private constructor(context: AppContext, protected readonly client: SqlOpsDataClient) {
super(client, ErrorDiagnosticsProvider.messagesTypes);
}
protected registerProvider(options: any): Disposable { return undefined; }
}

View File

@@ -27,6 +27,7 @@ import { NotebookConvertService } from './notebookConvert/notebookConvertService
import { SqlMigrationService } from './sqlMigration/sqlMigrationService';
import { SqlCredentialService } from './credentialstore/sqlCredentialService';
import { AzureBlobService } from './azureBlob/azureBlobService';
import { ErrorDiagnosticsProvider } from './errorDiagnostics/errorDiagnosticsProvider';
import { TdeMigrationService } from './tdeMigration/tdeMigrationService';
const localize = nls.loadMessageBundle();
@@ -194,7 +195,8 @@ function getClientOptions(context: AppContext): ClientOptions {
SqlCredentialService.asFeature(context),
TableDesignerFeature,
ExecutionPlanServiceFeature,
TdeMigrationService.asFeature(context),
ErrorDiagnosticsProvider.asFeature(context),
TdeMigrationService.asFeature(context)
],
outputChannel: outputChannel
};

View File

@@ -432,6 +432,54 @@ declare module 'azdata' {
azurePortalEndpoint?: string;
}
export namespace diagnostics {
/**
* Represents a diagnostics provider of accounts.
*/
export interface ErrorDiagnosticsProviderMetadata {
/**
* The id of the provider (ex. a connection provider) that a diagnostics provider will handle errors for.
* Note: only ONE diagnostic provider per id/name at a time.
*/
targetProviderId: string;
}
export interface ConnectionDiagnosticsResult {
/**
* Status indicating if the error was handled or not.
*/
handled: boolean,
/**
* If given, the new set of connection options to assign to the original connection profile, overwriting any previous options.
*/
options?: { [name: string]: any };
}
/**
* Diagnostics object for handling errors for a provider.
*/
export interface ErrorDiagnosticsProvider {
/**
* Called when a connection error occurs, allowing the provider to optionally handle the error and fix any issues before continuing with completing the connection.
* @param errorCode The error code of the connection error.
* @param errorMessage The error message of the connection error.
* @param connection The connection profile that caused the error.
* @returns ConnectionDiagnosticsResult: The result from the provider for whether the error was handled.
*/
handleConnectionError(errorCode: number, errorMessage: string, connection: connection.ConnectionProfile): Thenable<ConnectionDiagnosticsResult>;
}
/**
* Registers provider with instance of Diagnostic Provider implementation.
* Note: only ONE diagnostic provider object can be assigned to a specific provider at a time.
* @param providerMetadata Additional data used to register the provider
* @param errorDiagnostics The provider's diagnostic object that handles errors.
* @returns A disposable that when disposed will unregister the provider
*/
export function registerDiagnosticsProvider(providerMetadata: ErrorDiagnosticsProviderMetadata, errorDiagnostics: ErrorDiagnosticsProvider): vscode.Disposable;
}
export namespace connection {
/**
* Well-known Authentication types commonly supported by connection providers.
@@ -462,6 +510,13 @@ declare module 'azdata' {
*/
None = 'None'
}
/**
* Opens the change password dialog.
* @param profile The connection profile to change the password for.
* @returns The new password that is returned from the operation or undefined if unsuccessful.
*/
export function openChangePasswordDialog(profile: IConnectionProfile): Thenable<string | undefined>;
}
/*

View File

@@ -333,6 +333,13 @@ export interface IConnectionManagementService {
* @returns Promise with a boolean value indicating whether the user has accepted the suggestion.
*/
handleUnsupportedProvider(providerId: string): Promise<boolean>;
/**
* Launches the password change dialog.
* @param profile The connection profile to change the password.
* @returns the new valid password that is entered, or undefined if cancelled or errored.
*/
openChangePasswordDialog(profile: IConnectionProfile): Promise<string | undefined>;
}
export enum RunQueryOnConnectionMode {

View File

@@ -65,6 +65,3 @@ export const UNSAVED_GROUP_ID = 'unsaved';
/* Server Type Constants */
export const sqlDataWarehouse = 'Azure SQL Data Warehouse';
export const gen3Version = 12;
/* SQL Server Password Reset Error Code */
export const sqlPasswordErrorCode = 18488;

View File

@@ -6,6 +6,8 @@
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
import { deepClone } from 'vs/base/common/objects';
import * as sqlExtHostTypes from 'sql/workbench/api/common/sqlExtHostTypes'
// CONSTANTS //////////////////////////////////////////////////////////////////////////////////////
const msInH = 3.6e6;
@@ -137,3 +139,42 @@ export function isServerConnection(profile: IConnectionProfile): boolean {
// If the user did not specify a database in the original connection, then this is considered a server-level connection
return !profile.options.originalDatabase;
}
/**
* Convert a IConnectionProfile with services to an azdata.connection.ConnectionProfile
* shaped object that can be sent via RPC.
* @param profile The profile to be converted.
* @param deepCopyOptions whether to deep copy the options or not.
* @param removeFunction the function that strips the credentials from the connection profile if provided.
* @returns An azdata.connection.ConnectionProfile shaped object that contains only the data and none of the services.
*/
export function convertToRpcConnectionProfile(profile: IConnectionProfile | undefined, deepCopyOptions: boolean = false, removeFunction?: (profile: IConnectionProfile) => IConnectionProfile): sqlExtHostTypes.ConnectionProfile | undefined {
if (!profile) {
return undefined;
}
// If provided, that means the connection profile must be stripped of credentials.
if (removeFunction) {
profile = removeFunction(profile);
}
let connection: sqlExtHostTypes.ConnectionProfile = {
providerId: profile.providerName,
connectionId: profile.id,
options: deepCopyOptions ? deepClone(profile.options) : 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,
azureTenantId: profile.azureTenantId,
azureAccount: profile.azureAccount
}
return connection;
}

View File

@@ -330,4 +330,8 @@ export class TestConnectionManagementService implements IConnectionManagementSer
async handleUnsupportedProvider(providerName: string): Promise<boolean> {
return true;
}
openChangePasswordDialog(profile: IConnectionProfile): Promise<string | undefined> {
return undefined;
}
}

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>;
}

View File

@@ -78,10 +78,10 @@ import {
MainThreadBackgroundTaskManagementShape, MainThreadConnectionManagementShape, MainThreadCredentialManagementShape,
MainThreadDashboardShape, MainThreadDashboardWebviewShape, MainThreadDataProtocolShape, MainThreadExtensionManagementShape,
MainThreadModalDialogShape, MainThreadModelViewDialogShape, MainThreadModelViewShape, MainThreadNotebookDocumentsAndEditorsShape,
MainThreadObjectExplorerShape, MainThreadQueryEditorShape, MainThreadResourceProviderShape, MainThreadTasksShape,
MainThreadObjectExplorerShape, MainThreadQueryEditorShape, MainThreadResourceProviderShape, MainThreadErrorDiagnosticsShape, MainThreadTasksShape,
MainThreadNotebookShape as SqlMainThreadNotebookShape, MainThreadWorkspaceShape as SqlMainThreadWorkspaceShape,
ExtHostAccountManagementShape, ExtHostAzureAccountShape, ExtHostConnectionManagementShape, ExtHostCredentialManagementShape,
ExtHostDataProtocolShape, ExtHostObjectExplorerShape, ExtHostResourceProviderShape, ExtHostModalDialogsShape, ExtHostTasksShape,
ExtHostDataProtocolShape, ExtHostObjectExplorerShape, ExtHostResourceProviderShape, ExtHostErrorDiagnosticsShape, ExtHostModalDialogsShape, ExtHostTasksShape,
ExtHostBackgroundTaskManagementShape, ExtHostDashboardWebviewsShape, ExtHostModelViewShape, ExtHostModelViewTreeViewsShape,
ExtHostDashboardShape, ExtHostModelViewDialogShape, ExtHostQueryEditorShape, ExtHostExtensionManagementShape, ExtHostAzureBlobShape,
ExtHostNotebookShape as SqlExtHostNotebookShape, ExtHostWorkspaceShape as SqlExtHostWorkspaceShape,
@@ -2393,6 +2393,7 @@ export const SqlMainContext = {
MainThreadObjectExplorer: createProxyIdentifier<MainThreadObjectExplorerShape>('MainThreadObjectExplorer'),
MainThreadBackgroundTaskManagement: createProxyIdentifier<MainThreadBackgroundTaskManagementShape>('MainThreadBackgroundTaskManagement'),
MainThreadResourceProvider: createProxyIdentifier<MainThreadResourceProviderShape>('MainThreadResourceProvider'),
MainThreadErrorDiagnostics: createProxyIdentifier<MainThreadErrorDiagnosticsShape>('MainThreadErrorDiagnostics'),
MainThreadModalDialog: createProxyIdentifier<MainThreadModalDialogShape>('MainThreadModalDialog'),
MainThreadTasks: createProxyIdentifier<MainThreadTasksShape>('MainThreadTasks'),
MainThreadDashboardWebview: createProxyIdentifier<MainThreadDashboardWebviewShape>('MainThreadDashboardWebview'),
@@ -2415,6 +2416,7 @@ export const SqlExtHostContext = {
ExtHostDataProtocol: createProxyIdentifier<ExtHostDataProtocolShape>('ExtHostDataProtocol'),
ExtHostObjectExplorer: createProxyIdentifier<ExtHostObjectExplorerShape>('ExtHostObjectExplorer'),
ExtHostResourceProvider: createProxyIdentifier<ExtHostResourceProviderShape>('ExtHostResourceProvider'),
ExtHostErrorDiagnostics: createProxyIdentifier<ExtHostErrorDiagnosticsShape>('ExtHostErrorDiagnostics'),
ExtHostModalDialogs: createProxyIdentifier<ExtHostModalDialogsShape>('ExtHostModalDialogs'),
ExtHostTasks: createProxyIdentifier<ExtHostTasksShape>('ExtHostTasks'),
ExtHostBackgroundTaskManagement: createProxyIdentifier<ExtHostBackgroundTaskManagementShape>('ExtHostBackgroundTaskManagement'),

View File

@@ -220,6 +220,8 @@ import { ITableDesignerService } from 'sql/workbench/services/tableDesigner/comm
import { TableDesignerService } from 'sql/workbench/services/tableDesigner/browser/tableDesignerService';
import { IExecutionPlanService } from 'sql/workbench/services/executionPlan/common/interfaces';
import { ExecutionPlanService } from 'sql/workbench/services/executionPlan/common/executionPlanService';
import { IErrorDiagnosticsService } from 'sql/workbench/services/diagnostics/common/errorDiagnosticsService';
import { ErrorDiagnosticsService } from 'sql/workbench/services/diagnostics/browser/errorDiagnosticsService';
registerSingleton(IDashboardService, DashboardService);
registerSingleton(IDashboardViewService, DashboardViewService);
@@ -263,6 +265,7 @@ registerSingleton(IAssessmentService, AssessmentService);
registerSingleton(IDataGridProviderService, DataGridProviderService);
registerSingleton(ITableDesignerService, TableDesignerService);
registerSingleton(IExecutionPlanService, ExecutionPlanService);
registerSingleton(IErrorDiagnosticsService, ErrorDiagnosticsService);
//#endregion