diff --git a/src/sql/platform/telemetry/common/telemetryKeys.ts b/src/sql/platform/telemetry/common/telemetryKeys.ts index 46e62eb57d..63206bf73f 100644 --- a/src/sql/platform/telemetry/common/telemetryKeys.ts +++ b/src/sql/platform/telemetry/common/telemetryKeys.ts @@ -46,6 +46,7 @@ export const enum TelemetryView { LinkedAccounts = 'LinkedAccounts', Notebook = 'Notebook', NotifyEncryptionDialog = 'NotifyEncryptionDialog', + NotifyHiddenTenantDialog = 'NotifyHiddenTenantDialog', ResultsPanel = 'ResultsPanel', Shell = 'Shell', SqlAssessment = 'SqlAssessment', diff --git a/src/sql/workbench/contrib/welcome/constants.ts b/src/sql/workbench/contrib/welcome/constants.ts new file mode 100644 index 0000000000..a077d70bff --- /dev/null +++ b/src/sql/workbench/contrib/welcome/constants.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export const NOTIFY_ENCRYPT_SHOWN = 'workbench.notifyEncryptionShown'; +export const NOTIFY_HIDETENANT_SHOWN = 'workbench.notifyHiddenTenantShown'; + +// Link is regularly updated with new information from release. +export const NOTIFY_READMORE_LINK = 'https://aka.ms/azuredatastudio-connection'; diff --git a/src/sql/workbench/contrib/welcome/notifyEncryption/notifyEncryptionDialog.ts b/src/sql/workbench/contrib/welcome/notifyEncryption/notifyEncryptionDialog.ts index fde9d9e687..8f57c88cee 100644 --- a/src/sql/workbench/contrib/welcome/notifyEncryption/notifyEncryptionDialog.ts +++ b/src/sql/workbench/contrib/welcome/notifyEncryption/notifyEncryptionDialog.ts @@ -21,10 +21,9 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { mssqlProviderName } from 'sql/platform/connection/common/constants'; import { TelemetryView } from 'sql/platform/telemetry/common/telemetryKeys'; +import { NOTIFY_ENCRYPT_SHOWN, NOTIFY_READMORE_LINK } from 'sql/workbench/contrib/welcome/constants'; export class NotifyEncryptionDialog extends ErrorMessageDialog { - private static NOTIFY_ENCRYPT_SHOWN = 'workbench.notifyEncryptionShown'; - private static NOTIFY_ENCRYPT_LINK = 'https://aka.ms/azuredatastudio-connection'; constructor( @IThemeService themeService: IThemeService, @@ -43,11 +42,11 @@ export class NotifyEncryptionDialog extends ErrorMessageDialog { } public override open(): void { - if (this._storageService.get(NotifyEncryptionDialog.NOTIFY_ENCRYPT_SHOWN, StorageScope.APPLICATION)) { + if (this._storageService.get(NOTIFY_ENCRYPT_SHOWN, StorageScope.APPLICATION)) { return; } - this._storageService.store(NotifyEncryptionDialog.NOTIFY_ENCRYPT_SHOWN, true, StorageScope.APPLICATION, StorageTarget.MACHINE); + this._storageService.store(NOTIFY_ENCRYPT_SHOWN, true, StorageScope.APPLICATION, StorageTarget.MACHINE); if (!this._connectionManagementService.getConnections()?.some(conn => conn.providerName === mssqlProviderName)) { return; @@ -64,7 +63,7 @@ export class NotifyEncryptionDialog extends ErrorMessageDialog { this._instantiationService.createInstance(Link, moreInfoLink, { label: localize('notifyEncryption.moreInfoLink', 'More information'), - href: NotifyEncryptionDialog.NOTIFY_ENCRYPT_LINK + href: NOTIFY_READMORE_LINK }, undefined); } } diff --git a/src/sql/workbench/contrib/welcome/notifyHiddenTenant/notifyHiddenTenant.contribution.ts b/src/sql/workbench/contrib/welcome/notifyHiddenTenant/notifyHiddenTenant.contribution.ts new file mode 100644 index 0000000000..b3b1ea509f --- /dev/null +++ b/src/sql/workbench/contrib/welcome/notifyHiddenTenant/notifyHiddenTenant.contribution.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { NotifyHiddenTenantDialog } from 'sql/workbench/contrib/welcome/notifyHiddenTenant/notifyHiddenTenantDialog'; + +export class NotifyHiddenTenant extends Disposable implements IWorkbenchContribution { + + constructor( + @IInstantiationService private readonly _instantiationService: IInstantiationService, + ) { + super(); + const dialog = this._instantiationService.createInstance(NotifyHiddenTenantDialog); + dialog.render(); + dialog.open(); + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NotifyHiddenTenant, LifecyclePhase.Starting); diff --git a/src/sql/workbench/contrib/welcome/notifyHiddenTenant/notifyHiddenTenantDialog.ts b/src/sql/workbench/contrib/welcome/notifyHiddenTenant/notifyHiddenTenantDialog.ts new file mode 100644 index 0000000000..4d1f8ef032 --- /dev/null +++ b/src/sql/workbench/contrib/welcome/notifyHiddenTenant/notifyHiddenTenantDialog.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import Severity from 'vs/base/common/severity'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { localize } from 'vs/nls'; +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 { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ErrorMessageDialog } from 'sql/workbench/services/errorMessage/browser/errorMessageDialog'; +import { Link } from 'vs/platform/opener/browser/link'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; +import { mssqlProviderName } from 'sql/platform/connection/common/constants'; +import { TelemetryView } from 'sql/platform/telemetry/common/telemetryKeys'; +import { NOTIFY_HIDETENANT_SHOWN, NOTIFY_READMORE_LINK } from 'sql/workbench/contrib/welcome/constants'; +import { IAccountManagementService } from 'sql/platform/accounts/common/interfaces'; + +export class NotifyHiddenTenantDialog extends ErrorMessageDialog { + + constructor( + @IThemeService themeService: IThemeService, + @IClipboardService clipboardService: IClipboardService, + @ILayoutService layoutService: ILayoutService, + @IAdsTelemetryService telemetryService: IAdsTelemetryService, + @IContextKeyService contextKeyService: IContextKeyService, + @ILogService logService: ILogService, + @ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService, + @IOpenerService openerService: IOpenerService, + @IInstantiationService private _instantiationService: IInstantiationService, + @IStorageService private _storageService: IStorageService, + @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, + @IAccountManagementService private _accountManagementService: IAccountManagementService + ) { + super(themeService, clipboardService, layoutService, telemetryService, contextKeyService, logService, textResourcePropertiesService, openerService); + } + + public override open(): void { + if (this._storageService.get(NOTIFY_HIDETENANT_SHOWN, StorageScope.APPLICATION)) { + return; + } + + this._storageService.store(NOTIFY_HIDETENANT_SHOWN, true, StorageScope.APPLICATION, StorageTarget.MACHINE); + this._accountManagementService.getAccounts().then(accounts => { + // Do not notify users who don't have any Azure connection in their list of connections and no Azure accounts registered. + if (!this._connectionManagementService.getConnections()?.some(conn => conn.providerName === mssqlProviderName + && conn.authenticationType === 'AzureMFA') && (!accounts || accounts.length === 0)) { + return; + } + + super.open(TelemetryView.NotifyHiddenTenantDialog, Severity.Info, + localize('notifyHiddenTenant.title', 'Important Update'), + localize('notifyHiddenTenant.message', `Connections using Azure Active Directory authentication will now retrieve tenant information from the server during login. The 'Azure AD Tenant' entry no longer needs to be provided when connecting to Azure SQL instances.`, '\n\n')); + }); + } + + protected override updateDialogBody(): void { + super.updateDialogBody(); + let moreInfoLink = DOM.append(this.getBody()!, DOM.$('.more-info')); + this._instantiationService.createInstance(Link, moreInfoLink, + { + label: localize('notifyHiddenTenant.moreInfoLink', 'More information'), + href: NOTIFY_READMORE_LINK + }, undefined); + } +} diff --git a/src/sql/workbench/services/connection/browser/connectionManagementService.ts b/src/sql/workbench/services/connection/browser/connectionManagementService.ts index 70d1c6c5b4..9e36ad6af7 100644 --- a/src/sql/workbench/services/connection/browser/connectionManagementService.ts +++ b/src/sql/workbench/services/connection/browser/connectionManagementService.ts @@ -57,7 +57,7 @@ import { VIEWLET_ID as ExtensionsViewletID } from 'vs/workbench/contrib/extensio 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'; -import { enableSqlAuthenticationProviderConfig, mssqlProviderName } from 'sql/platform/connection/common/constants'; +import { isMssqlAuthProviderEnabled } from 'sql/workbench/services/connection/browser/utils'; export class ConnectionManagementService extends Disposable implements IConnectionManagementService { @@ -1141,7 +1141,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti if (connectionProfile && connectionProfile.authenticationType === Constants.AuthenticationType.AzureMFA) { // We do not need to reconnect for MSSQL Provider, if 'SQL Authentication Provider' setting is enabled. // Update the token in case it needs refreshing/reauthentication. - if (connectionProfile.providerName === mssqlProviderName && this.getEnableSqlAuthenticationProviderConfig()) { + if (isMssqlAuthProviderEnabled(connectionProfile.providerName, this._configurationService)) { await this.fillInOrClearToken(connectionProfile); return true; } @@ -1181,10 +1181,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti } } - private getEnableSqlAuthenticationProviderConfig(): boolean { - return this._configurationService.getValue(enableSqlAuthenticationProviderConfig) ?? true; - } - // Request Senders private async sendConnectRequest(connection: interfaces.IConnectionProfile, uri: string): Promise { let connectionInfo = Object.assign({}, { diff --git a/src/sql/workbench/services/connection/browser/connectionWidget.ts b/src/sql/workbench/services/connection/browser/connectionWidget.ts index dde54cf0df..1a1a46a20b 100644 --- a/src/sql/workbench/services/connection/browser/connectionWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionWidget.ts @@ -43,6 +43,7 @@ import { AdsWidget } from 'sql/base/browser/ui/adsWidget'; import { createCSSRule } from 'vs/base/browser/dom'; import { AuthLibrary, getAuthLibrary } from 'sql/workbench/services/accountManagement/utils'; import { adjustForMssqlAppName } from 'sql/platform/connection/common/utils'; +import { isMssqlAuthProviderEnabled } from 'sql/workbench/services/connection/browser/utils'; import { RequiredIndicatorClassName } from 'sql/base/browser/ui/label/label'; const ConnectionStringText = localize('connectionWidget.connectionString', "Connection string"); @@ -75,6 +76,7 @@ export class ConnectionWidget extends lifecycle.Disposable { private _trueInputValue: string = localize('boolean.true', 'True'); private _falseInputValue: string = localize('boolean.false', 'False'); private _token: string; + private _mssqlAuthProviderEnabled: boolean; private _connectionStringOptions: ConnectionStringOptions; protected _container: HTMLElement; protected _serverGroupSelectBox: SelectBox; @@ -144,6 +146,7 @@ export class ConnectionWidget extends lifecycle.Disposable { this._register(this._authTypeSelectBox); } this._providerName = providerName; + this._mssqlAuthProviderEnabled = isMssqlAuthProviderEnabled(this._providerName, this._configurationService) this._connectionStringOptions = this._connectionManagementService.getProviderProperties(this._providerName).connectionStringOptions; } @@ -648,6 +651,9 @@ export class ConnectionWidget extends lifecycle.Disposable { this._passwordInputBox.hideMessage(); this._azureAccountDropdown.hideMessage(); this._azureTenantDropdown.hideMessage(); + if (this._mssqlAuthProviderEnabled) { + this._tableContainer.classList.add('hide-azure-tenants'); + } this._tableContainer.classList.add('hide-username'); this._tableContainer.classList.add('hide-password'); this._tableContainer.classList.add('hide-azure-accounts'); @@ -795,7 +801,9 @@ export class ConnectionWidget extends lifecycle.Disposable { // There are multiple tenants available so let the user select one let options = selectedAccount.properties.tenants.map(tenant => tenant.displayName); this._azureTenantDropdown.setOptions(options); - this._tableContainer.classList.remove(hideTenantsClassName); + if (!this._mssqlAuthProviderEnabled) { + this._tableContainer.classList.remove(hideTenantsClassName); + } // If we have a tenant ID available, select that instead of the first one if (this._azureTenantId) { @@ -820,7 +828,9 @@ export class ConnectionWidget extends lifecycle.Disposable { this._azureTenantId = selectedAccount.properties.tenants[0].id; this.onAzureTenantSelected(0); } - this._tableContainer.classList.add(hideTenantsClassName); + if (!this._mssqlAuthProviderEnabled) { + this._tableContainer.classList.add(hideTenantsClassName); + } } } @@ -972,6 +982,9 @@ export class ConnectionWidget extends lifecycle.Disposable { account = this._azureAccountList?.find(account => account.key.accountId === this.getModelValue(accountName) || account.key.accountId.split('.')[0] === this.getModelValue(accountName)); if (account) { + if (!account.properties.tenants?.find(tenant => tenant.id === this._azureTenantId)) { + this._azureTenantId = account.properties.tenants[0].id; + } this._azureAccountDropdown.selectWithOptionName(account.key.accountId); } } diff --git a/src/sql/workbench/services/connection/browser/utils.ts b/src/sql/workbench/services/connection/browser/utils.ts new file mode 100644 index 0000000000..d86350c67b --- /dev/null +++ b/src/sql/workbench/services/connection/browser/utils.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { enableSqlAuthenticationProviderConfig, mssqlProviderName } from 'sql/platform/connection/common/constants'; + +/** + * Reads setting 'mssql.enableSqlAuthenticationProvider' returns true if it's enabled. + * Returns false for other providers. + * @param provider Connection provider name + * @param configService Configuration service instance + * @returns True if provider is MSSQL and Sql Auth provider is enabled. + */ +export function isMssqlAuthProviderEnabled(provider: string, configService: IConfigurationService): boolean { + return provider === mssqlProviderName && (configService?.getValue(enableSqlAuthenticationProviderConfig) ?? true); +} diff --git a/src/sql/workbench/services/objectExplorer/browser/treeUpdateUtils.ts b/src/sql/workbench/services/objectExplorer/browser/treeUpdateUtils.ts index 55eb922196..9468fc5779 100644 --- a/src/sql/workbench/services/objectExplorer/browser/treeUpdateUtils.ts +++ b/src/sql/workbench/services/objectExplorer/browser/treeUpdateUtils.ts @@ -16,6 +16,7 @@ import { getErrorMessage, onUnexpectedError } from 'vs/base/common/errors'; import { AsyncServerTree, ConnectionError as AsyncTreeConnectionError, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree'; import { ObjectExplorerRequestStatus } from 'sql/workbench/services/objectExplorer/browser/treeSelectionHandler'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { localize } from 'vs/nls'; export interface IExpandableTree extends ITree { /** @@ -217,11 +218,11 @@ export class TreeUpdateUtils { } const result = await connectionManagementService.connect(connection, undefined, options, callbacks); - if (result.connected) { + if (result?.connected) { let existingConnection = connectionManagementService.findExistingConnection(connection); return existingConnection; } else { - throw new Error(result.errorMessage); + throw new Error(result ? result.errorMessage : localize('connectionFailedError', 'Failed to connect, please try again.')); } } } else { diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index fa95c3f002..4e3996129c 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -416,6 +416,7 @@ import 'vs/workbench/contrib/surveys/browser/languageSurveys.contribution'; import 'vs/workbench/contrib/welcomeOverlay/browser/welcomeOverlay'; import 'sql/workbench/contrib/welcome/page/browser/welcomePage.contribution'; // {{SQL CARBON EDIT}} - add welcome page contribution import 'sql/workbench/contrib/welcome/notifyEncryption/notifyEncryption.contribution'; // {{SQL CARBON EDIT}} - add notifying Encrypt change contribution +import 'sql/workbench/contrib/welcome/notifyHiddenTenant/notifyHiddenTenant.contribution'; // {{SQL CARBON EDIT}} - add notifying Hidden Tenant change contribution // import 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution'; // {{SQL CARBON EDIT}} - remove vscode getting started import 'vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution'; import 'vs/workbench/contrib/welcomeViews/common/viewsWelcome.contribution';