Move code around for more linting (#8190)

* testing

* moving around all the code

* fix strict nulls
This commit is contained in:
Anthony Dresser
2019-11-04 10:41:28 -08:00
committed by GitHub
parent 3c702c15e2
commit ade68b184d
145 changed files with 248 additions and 231 deletions

View File

@@ -1,357 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/accountDialog';
import 'vs/css!./media/accountActions';
import * as DOM from 'vs/base/browser/dom';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { Event, Emitter } from 'vs/base/common/event';
import { localize } from 'vs/nls';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { IAction } from 'vs/base/common/actions';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { values } from 'vs/base/common/map';
import * as azdata from 'azdata';
import { Button } from 'sql/base/browser/ui/button/button';
import { Modal } from 'sql/workbench/browser/modal/modal';
import { attachModalDialogStyler, attachButtonStyler, attachPanelStyler } from 'sql/platform/theme/common/styler';
import { AccountViewModel } from 'sql/platform/accounts/common/accountViewModel';
import { AddAccountAction } from 'sql/platform/accounts/common/accountActions';
import { AccountListRenderer, AccountListDelegate } from 'sql/platform/accounts/browser/accountListRenderer';
import { AccountProviderAddedEventParams, UpdateAccountListEventParams } from 'sql/platform/accounts/common/eventTypes';
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
class AccountPanel extends ViewletPanel {
public index: number;
private accountList: List<azdata.Account>;
constructor(
private options: IViewletPanelOptions,
@IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService,
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService private themeService: IThemeService,
@IContextKeyService contextKeyService: IContextKeyService
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService);
}
protected renderBody(container: HTMLElement): void {
this.accountList = new List<azdata.Account>('AccountList', container, new AccountListDelegate(AccountDialog.ACCOUNTLIST_HEIGHT), [this.instantiationService.createInstance(AccountListRenderer)]);
this._register(attachListStyler(this.accountList, this.themeService));
}
protected layoutBody(size: number): void {
if (this.accountList) {
this.accountList.layout(size);
}
}
public get length(): number {
return this.accountList.length;
}
public focus() {
this.accountList.domFocus();
}
public updateAccounts(accounts: azdata.Account[]) {
this.accountList.splice(0, this.accountList.length, accounts);
}
public setSelection(indexes: number[]) {
this.accountList.setSelection(indexes);
}
public getActions(): IAction[] {
return [this.instantiationService.createInstance(
AddAccountAction,
this.options.id
)];
}
}
export interface IProviderViewUiComponent {
view: AccountPanel;
addAccountAction: AddAccountAction;
}
export class AccountDialog extends Modal {
public static ACCOUNTLIST_HEIGHT = 77;
public viewModel: AccountViewModel;
// MEMBER VARIABLES ////////////////////////////////////////////////////
private _providerViewsMap = new Map<string, IProviderViewUiComponent>();
private _closeButton: Button;
private _addAccountButton: Button;
private _splitView: SplitView;
private _container: HTMLElement;
private _splitViewContainer: HTMLElement;
private _noaccountViewContainer: HTMLElement;
// EVENTING ////////////////////////////////////////////////////////////
private _onAddAccountErrorEmitter: Emitter<string>;
public get onAddAccountErrorEvent(): Event<string> { return this._onAddAccountErrorEmitter.event; }
private _onCloseEmitter: Emitter<void>;
public get onCloseEvent(): Event<void> { return this._onCloseEmitter.event; }
constructor(
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IThemeService themeService: IThemeService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IContextMenuService private _contextMenuService: IContextMenuService,
@IKeybindingService private _keybindingService: IKeybindingService,
@IConfigurationService private _configurationService: IConfigurationService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IClipboardService clipboardService: IClipboardService,
@ILogService logService: ILogService,
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
) {
super(
localize('linkedAccounts', "Linked accounts"),
TelemetryKeys.Accounts,
telemetryService,
layoutService,
clipboardService,
themeService,
logService,
textResourcePropertiesService,
contextKeyService,
{ hasSpinner: true }
);
// Setup the event emitters
this._onAddAccountErrorEmitter = new Emitter<string>();
this._onCloseEmitter = new Emitter<void>();
// Create the view model and wire up the events
this.viewModel = this._instantiationService.createInstance(AccountViewModel);
this.viewModel.addProviderEvent(arg => { this.addProvider(arg); });
this.viewModel.removeProviderEvent(arg => { this.removeProvider(arg); });
this.viewModel.updateAccountListEvent(arg => { this.updateProviderAccounts(arg); });
// Load the initial contents of the view model
this.viewModel.initialize()
.then(addedProviders => {
for (const addedProvider of addedProviders) {
this.addProvider(addedProvider);
}
});
}
// MODAL OVERRIDE METHODS //////////////////////////////////////////////
protected layout(height?: number): void {
this._splitView.layout(DOM.getContentHeight(this._container));
}
public render() {
super.render();
attachModalDialogStyler(this, this._themeService);
this._closeButton = this.addFooterButton(localize('accountDialog.close', "Close"), () => this.close());
this.registerListeners();
}
protected renderBody(container: HTMLElement) {
this._container = container;
this._splitViewContainer = DOM.$('div.account-view.monaco-panel-view');
DOM.append(container, this._splitViewContainer);
this._splitView = new SplitView(this._splitViewContainer);
this._noaccountViewContainer = DOM.$('div.no-account-view');
const noAccountTitle = DOM.append(this._noaccountViewContainer, DOM.$('.no-account-view-label'));
const noAccountLabel = localize('accountDialog.noAccountLabel', "There is no linked account. Please add an account.");
noAccountTitle.innerText = noAccountLabel;
// Show the add account button for the first provider
// Todo: If we have more than 1 provider, need to show all add account buttons for all providers
const buttonSection = DOM.append(this._noaccountViewContainer, DOM.$('div.button-section'));
this._addAccountButton = new Button(buttonSection);
this._addAccountButton.label = localize('accountDialog.addConnection', "Add an account");
this._register(this._addAccountButton.onDidClick(() => {
(<IProviderViewUiComponent>values(this._providerViewsMap)[0]).addAccountAction.run();
}));
DOM.append(container, this._noaccountViewContainer);
}
private registerListeners(): void {
// Theme styler
this._register(attachButtonStyler(this._closeButton, this._themeService));
this._register(attachButtonStyler(this._addAccountButton, this._themeService));
}
/* Overwrite escape key behavior */
protected onClose() {
this.close();
}
/* Overwrite enter key behavior */
protected onAccept() {
this.close();
}
public close() {
this._onCloseEmitter.fire();
this.hide();
}
public open() {
this.show();
if (!this.isEmptyLinkedAccount()) {
this.showSplitView();
} else {
this.showNoAccountContainer();
}
}
private showNoAccountContainer() {
this._splitViewContainer.hidden = true;
this._noaccountViewContainer.hidden = false;
this._addAccountButton.focus();
}
private showSplitView() {
this._splitViewContainer.hidden = false;
this._noaccountViewContainer.hidden = true;
if (values(this._providerViewsMap).length > 0) {
const firstView = values(this._providerViewsMap)[0];
if (firstView instanceof AccountPanel) {
firstView.setSelection([0]);
firstView.focus();
}
}
}
private isEmptyLinkedAccount(): boolean {
for (const provider of values(this._providerViewsMap)) {
const listView = provider.view;
if (listView && listView.length > 0) {
return false;
}
}
return true;
}
public dispose(): void {
super.dispose();
for (const provider of values(this._providerViewsMap)) {
if (provider.addAccountAction) {
provider.addAccountAction.dispose();
}
if (provider.view) {
provider.view.dispose();
}
}
}
// PRIVATE HELPERS /////////////////////////////////////////////////////
private addProvider(newProvider: AccountProviderAddedEventParams) {
// Skip adding the provider if it already exists
if (this._providerViewsMap.get(newProvider.addedProvider.id)) {
return;
}
// Account provider doesn't exist, so add it
// Create a scoped add account action
let addAccountAction = this._instantiationService.createInstance(
AddAccountAction,
newProvider.addedProvider.id
);
addAccountAction.addAccountCompleteEvent(() => this.spinner = false);
addAccountAction.addAccountErrorEvent(msg => this._onAddAccountErrorEmitter.fire(msg));
addAccountAction.addAccountStartEvent(() => this.spinner = true);
let providerView = new AccountPanel(
{
id: newProvider.addedProvider.id,
title: newProvider.addedProvider.displayName,
ariaHeaderLabel: newProvider.addedProvider.displayName
},
this._keybindingService,
this._contextMenuService,
this._configurationService,
this._instantiationService,
this._themeService,
this.contextKeyService
);
attachPanelStyler(providerView, this._themeService);
const insertIndex = this._splitView.length;
// Append the list view to the split view
this._splitView.addView(providerView, Sizing.Distribute, insertIndex);
providerView.render();
providerView.index = insertIndex;
this._splitView.layout(DOM.getContentHeight(this._container));
// Set the initial items of the list
providerView.updateAccounts(newProvider.initialAccounts);
if (newProvider.initialAccounts.length > 0 && this._splitViewContainer.hidden) {
this.showSplitView();
}
this.layout();
// Store the view for the provider and action
this._providerViewsMap.set(newProvider.addedProvider.id, { view: providerView, addAccountAction: addAccountAction });
}
private removeProvider(removedProvider: azdata.AccountProviderMetadata) {
// Skip removing the provider if it doesn't exist
const providerView = this._providerViewsMap.get(removedProvider.id);
if (!providerView || !providerView.view) {
return;
}
// Remove the list view from the split view
this._splitView.removeView(providerView.view.index);
this._splitView.layout(DOM.getContentHeight(this._container));
// Remove the list view from our internal map
this._providerViewsMap.delete(removedProvider.id);
this.layout();
}
private updateProviderAccounts(args: UpdateAccountListEventParams) {
const providerMapping = this._providerViewsMap.get(args.providerId);
if (!providerMapping || !providerMapping.view) {
return;
}
providerMapping.view.updateAccounts(args.accountList);
if (args.accountList.length > 0 && this._splitViewContainer.hidden) {
this.showSplitView();
}
if (this.isEmptyLinkedAccount() && this._noaccountViewContainer.hidden) {
this.showNoAccountContainer();
}
this.layout();
}
}

View File

@@ -1,47 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 { AccountDialog } from 'sql/platform/accounts/browser/accountDialog';
import { localize } from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
export class AccountDialogController {
// MEMBER VARIABLES ////////////////////////////////////////////////////
private _addAccountErrorTitle = localize('accountDialog.addAccountErrorTitle', "Error adding account");
private _accountDialog: AccountDialog;
public get accountDialog(): AccountDialog { return this._accountDialog; }
constructor(
@IInstantiationService private _instantiationService: IInstantiationService,
@IErrorMessageService private _errorMessageService: IErrorMessageService
) { }
/**
* Open account dialog
*/
public openAccountDialog(): void {
// Create a new dialog if one doesn't exist
if (!this._accountDialog) {
this._accountDialog = this._instantiationService.createInstance(AccountDialog);
this._accountDialog.onAddAccountErrorEvent(msg => this.handleOnAddAccountError(msg));
this._accountDialog.onCloseEvent(() => this.handleOnClose());
this._accountDialog.render();
}
// Open the dialog
this._accountDialog.open();
}
// PRIVATE HELPERS /////////////////////////////////////////////////////
private handleOnClose(): void { }
private handleOnAddAccountError(msg: string): void {
this._errorMessageService.showDialog(Severity.Error, this._addAccountErrorTitle, msg);
}
}

View File

@@ -1,140 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/accountListRenderer';
import 'vs/css!./media/accountActions';
import 'vs/css!sql/media/icons/common-icons';
import * as DOM from 'vs/base/browser/dom';
import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ActionBar, IActionOptions } from 'vs/base/browser/ui/actionbar/actionbar';
import { localize } from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { RemoveAccountAction, RefreshAccountAction } from 'sql/platform/accounts/common/accountActions';
import * as azdata from 'azdata';
export class AccountListDelegate implements IListVirtualDelegate<azdata.Account> {
constructor(
private _height: number
) {
}
public getHeight(element: azdata.Account): number {
return this._height;
}
public getTemplateId(element: azdata.Account): string {
return 'accountListRenderer';
}
}
export interface AccountPickerListTemplate {
root: HTMLElement;
icon: HTMLElement;
badgeContent: HTMLElement;
contextualDisplayName: HTMLElement;
label: HTMLElement;
displayName: HTMLElement;
}
export interface AccountListTemplate extends AccountPickerListTemplate {
content: HTMLElement;
actions: ActionBar;
}
export class AccountPickerListRenderer implements IListRenderer<azdata.Account, AccountPickerListTemplate> {
public static TEMPLATE_ID = 'accountListRenderer';
public get templateId(): string {
return AccountPickerListRenderer.TEMPLATE_ID;
}
public renderTemplate(container: HTMLElement): AccountPickerListTemplate {
const tableTemplate: AccountPickerListTemplate = Object.create(null);
const badge = DOM.$('div.badge');
tableTemplate.root = DOM.append(container, DOM.$('div.list-row.account-picker-list'));
tableTemplate.icon = DOM.append(tableTemplate.root, DOM.$('div.codicon'));
DOM.append(tableTemplate.icon, badge);
tableTemplate.badgeContent = DOM.append(badge, DOM.$('div.badge-content'));
tableTemplate.label = DOM.append(tableTemplate.root, DOM.$('div.label'));
tableTemplate.contextualDisplayName = DOM.append(tableTemplate.label, DOM.$('div.contextual-display-name'));
tableTemplate.displayName = DOM.append(tableTemplate.label, DOM.$('div.display-name'));
return tableTemplate;
}
public renderElement(account: azdata.Account, index: number, templateData: AccountPickerListTemplate): void {
// Set the account icon
templateData.icon.classList.add('account-logo', account.displayInfo.accountType);
templateData.contextualDisplayName.innerText = account.displayInfo.contextualDisplayName;
// show the account display name text, something like "User Name (user@email.com)"
if (account.displayInfo.userId && account.displayInfo.displayName) {
templateData.displayName.innerText = account.displayInfo.displayName + ' (' + account.displayInfo.userId + ')';
} else {
templateData.displayName.innerText = account.displayInfo.displayName;
}
if (account.isStale) {
templateData.badgeContent.className = 'badge-content codicon warning-badge';
} else {
templateData.badgeContent.className = 'badge-content';
}
}
public disposeTemplate(template: AccountPickerListTemplate): void {
// noop
}
public disposeElement(element: azdata.Account, index: number, templateData: AccountPickerListTemplate): void {
// noop
}
}
export class AccountListRenderer extends AccountPickerListRenderer {
constructor(
@IInstantiationService private _instantiationService: IInstantiationService
) {
super();
}
public get templateId(): string {
return AccountListRenderer.TEMPLATE_ID;
}
public renderTemplate(container: HTMLElement): AccountListTemplate {
const tableTemplate = super.renderTemplate(container) as AccountListTemplate;
tableTemplate.content = DOM.append(tableTemplate.label, DOM.$('div.content'));
tableTemplate.actions = new ActionBar(tableTemplate.root, { animated: false });
return tableTemplate;
}
public renderElement(account: azdata.Account, index: number, templateData: AccountListTemplate): void {
super.renderElement(account, index, templateData);
if (account.isStale) {
templateData.content.innerText = localize('refreshCredentials', "You need to refresh the credentials for this account.");
} else {
templateData.content.innerText = '';
}
templateData.actions.clear();
const actionOptions: IActionOptions = { icon: true, label: false };
if (account.isStale) {
const refreshAction = this._instantiationService.createInstance(RefreshAccountAction);
refreshAction.account = account;
templateData.actions.push(refreshAction, actionOptions);
} else {
// Todo: Will show filter action when API/GUI for filtering is implemented (#3022, #3024)
// templateData.actions.push(new ApplyFilterAction(ApplyFilterAction.ID, ApplyFilterAction.LABEL), actionOptions);
}
const removeAction = this._instantiationService.createInstance(RemoveAccountAction, account);
templateData.actions.push(removeAction, actionOptions);
}
}

View File

@@ -1,88 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IExtensionPointUser, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { localize } from 'vs/nls';
import { createCSSRule, asCSSUrl } from 'vs/base/browser/dom';
import * as resources from 'vs/base/common/resources';
export interface IAccountContrib {
id: string;
icon?: IUserFriendlyIcon;
}
export type IUserFriendlyIcon = string | { light: string; dark: string; };
const account: IJSONSchema = {
type: 'object',
properties: {
id: {
description: localize('carbon.extension.contributes.account.id', "Identifier of the account type"),
type: 'string'
},
icon: {
description: localize('carbon.extension.contributes.account.icon', "(Optional) Icon which is used to represent the accpunt in the UI. Either a file path or a themable configuration"),
anyOf: [{
type: 'string'
},
{
type: 'object',
properties: {
light: {
description: localize('carbon.extension.contributes.account.icon.light', "Icon path when a light theme is used"),
type: 'string'
},
dark: {
description: localize('carbon.extension.contributes.account.icon.dark', "Icon path when a dark theme is used"),
type: 'string'
}
}
}]
}
}
};
export const accountsContribution: IJSONSchema = {
description: localize('carbon.extension.contributes.account', "Contributes icons to account provider."),
oneOf: [
account,
{
type: 'array',
items: account
}
]
};
ExtensionsRegistry.registerExtensionPoint<IAccountContrib | IAccountContrib[]>({ extensionPoint: 'account-type', jsonSchema: accountsContribution }).setHandler(extensions => {
function handleCommand(account: IAccountContrib, extension: IExtensionPointUser<any>) {
const { icon, id } = account;
if (icon) {
const iconClass = id;
if (typeof icon === 'string') {
const path = resources.joinPath(extension.description.extensionLocation, icon);
createCSSRule(`.codicon.${iconClass}`, `background-image: ${asCSSUrl(path)}`);
} else {
const light = resources.joinPath(extension.description.extensionLocation, icon.light);
const dark = resources.joinPath(extension.description.extensionLocation, icon.dark);
createCSSRule(`.codicon.${iconClass}`, `background-image: ${asCSSUrl(light)}`);
createCSSRule(`.vs-dark .codicon.${iconClass}, .hc-black .codicon.${iconClass}`, `background-image: ${asCSSUrl(dark)}`);
}
}
}
for (let extension of extensions) {
const { value } = extension;
if (Array.isArray<IAccountContrib>(value)) {
for (let command of value) {
handleCommand(command, extension);
}
} else {
handleCommand(value, extension);
}
}
});

View File

@@ -1,20 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
import * as azdata from 'azdata';
export const IAccountPickerService = createDecorator<IAccountPickerService>('AccountPickerService');
export interface IAccountPickerService {
_serviceBrand: undefined;
renderAccountPicker(container: HTMLElement): void;
addAccountCompleteEvent: Event<void>;
addAccountErrorEvent: Event<string>;
addAccountStartEvent: Event<void>;
onAccountSelectionChangeEvent: Event<azdata.Account | undefined>;
selectedAccount: azdata.Account | undefined;
}

View File

@@ -1,236 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/accountPicker';
import * as DOM from 'vs/base/browser/dom';
import { Event, Emitter } from 'vs/base/common/event';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { IDropdownOptions } from 'vs/base/browser/ui/dropdown/dropdown';
import { IListEvent } from 'vs/base/browser/ui/list/list';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { buttonBackground } from 'vs/platform/theme/common/colorRegistry';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import * as azdata from 'azdata';
import { DropdownList } from 'sql/base/browser/ui/dropdownList/dropdownList';
import { attachDropdownStyler } from 'sql/platform/theme/common/styler';
import { AddAccountAction, RefreshAccountAction } from 'sql/platform/accounts/common/accountActions';
import { AccountPickerListRenderer, AccountListDelegate } from 'sql/platform/accounts/browser/accountListRenderer';
import { AccountPickerViewModel } from 'sql/platform/accounts/common/accountPickerViewModel';
export class AccountPicker extends Disposable {
public static ACCOUNTPICKERLIST_HEIGHT = 47;
public viewModel: AccountPickerViewModel;
private _accountList: List<azdata.Account>;
private _rootElement: HTMLElement;
private _refreshContainer: HTMLElement;
private _listContainer: HTMLElement;
private _dropdown: DropdownList;
private _refreshAccountAction: RefreshAccountAction;
// EVENTING ////////////////////////////////////////////////////////////
private _addAccountCompleteEmitter: Emitter<void>;
public get addAccountCompleteEvent(): Event<void> { return this._addAccountCompleteEmitter.event; }
private _addAccountErrorEmitter: Emitter<string>;
public get addAccountErrorEvent(): Event<string> { return this._addAccountErrorEmitter.event; }
private _addAccountStartEmitter: Emitter<void>;
public get addAccountStartEvent(): Event<void> { return this._addAccountStartEmitter.event; }
private _onAccountSelectionChangeEvent: Emitter<azdata.Account | undefined>;
public get onAccountSelectionChangeEvent(): Event<azdata.Account | undefined> { return this._onAccountSelectionChangeEvent.event; }
constructor(
private _providerId: string,
@IThemeService private _themeService: IThemeService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IContextViewService private _contextViewService: IContextViewService
) {
super();
// Create event emitters
this._addAccountCompleteEmitter = new Emitter<void>();
this._addAccountErrorEmitter = new Emitter<string>();
this._addAccountStartEmitter = new Emitter<void>();
this._onAccountSelectionChangeEvent = new Emitter<azdata.Account>();
// Create the view model, wire up the events, and initialize with baseline data
this.viewModel = this._instantiationService.createInstance(AccountPickerViewModel, this._providerId);
this.viewModel.updateAccountListEvent(arg => {
if (arg.providerId === this._providerId) {
this.updateAccountList(arg.accountList);
}
});
}
// PUBLIC METHODS //////////////////////////////////////////////////////
/**
* Render account picker
*/
public render(container: HTMLElement): void {
DOM.append(container, this._rootElement);
}
// PUBLIC METHODS //////////////////////////////////////////////////////
/**
* Create account picker component
*/
public createAccountPickerComponent() {
// Create an account list
const delegate = new AccountListDelegate(AccountPicker.ACCOUNTPICKERLIST_HEIGHT);
const accountRenderer = new AccountPickerListRenderer();
this._listContainer = DOM.$('div.account-list-container');
this._accountList = new List<azdata.Account>('AccountPicker', this._listContainer, delegate, [accountRenderer]);
this._register(attachListStyler(this._accountList, this._themeService));
this._rootElement = DOM.$('div.account-picker-container');
// Create a dropdown for account picker
const option: IDropdownOptions = {
contextViewProvider: this._contextViewService,
labelRenderer: (container) => this.renderLabel(container)
};
// Create the add account action
const addAccountAction = this._instantiationService.createInstance(AddAccountAction, this._providerId);
addAccountAction.addAccountCompleteEvent(() => this._addAccountCompleteEmitter.fire());
addAccountAction.addAccountErrorEvent((msg) => this._addAccountErrorEmitter.fire(msg));
addAccountAction.addAccountStartEvent(() => this._addAccountStartEmitter.fire());
this._dropdown = this._register(new DropdownList(this._rootElement, option, this._listContainer, this._accountList, addAccountAction));
this._register(attachDropdownStyler(this._dropdown, this._themeService));
this._register(this._accountList.onSelectionChange((e: IListEvent<azdata.Account>) => {
if (e.elements.length === 1) {
this._dropdown.renderLabel();
this.onAccountSelectionChange(e.elements[0]);
}
}));
// Create refresh account action
this._refreshContainer = DOM.append(this._rootElement, DOM.$('div.refresh-container'));
DOM.append(this._refreshContainer, DOM.$('div.sql codicon warning'));
const actionBar = new ActionBar(this._refreshContainer, { animated: false });
this._refreshAccountAction = this._instantiationService.createInstance(RefreshAccountAction);
actionBar.push(this._refreshAccountAction, { icon: false, label: true });
if (this._accountList.length > 0) {
this._accountList.setSelection([0]);
this.onAccountSelectionChange(this._accountList.getSelectedElements()[0]);
} else {
DOM.hide(this._refreshContainer);
}
this._register(this._themeService.onThemeChange(e => this.updateTheme(e)));
this.updateTheme(this._themeService.getTheme());
// Load the initial contents of the view model
this.viewModel.initialize()
.then((accounts: azdata.Account[]) => {
this.updateAccountList(accounts);
});
}
public dispose() {
super.dispose();
if (this._accountList) {
this._accountList.dispose();
}
}
// PRIVATE HELPERS /////////////////////////////////////////////////////
private onAccountSelectionChange(account: azdata.Account | undefined) {
this.viewModel.selectedAccount = account;
if (account && account.isStale) {
this._refreshAccountAction.account = account;
DOM.show(this._refreshContainer);
} else {
DOM.hide(this._refreshContainer);
}
this._onAccountSelectionChangeEvent.fire(account);
}
private renderLabel(container: HTMLElement): IDisposable | null {
if (container.hasChildNodes()) {
for (let i = 0; i < container.childNodes.length; i++) {
container.removeChild(container.childNodes.item(i));
}
}
const selectedAccounts = this._accountList.getSelectedElements();
const account = selectedAccounts ? selectedAccounts[0] : undefined;
if (account) {
const badge = DOM.$('div.badge');
const row = DOM.append(container, DOM.$('div.selected-account-container'));
const icon = DOM.append(row, DOM.$('div.codicon'));
DOM.append(icon, badge);
const badgeContent = DOM.append(badge, DOM.$('div.badge-content'));
const label = DOM.append(row, DOM.$('div.label'));
// Set the account icon
icon.classList.add('icon', account.displayInfo.accountType);
// TODO: Pick between the light and dark logo
label.innerText = account.displayInfo.displayName + ' (' + account.displayInfo.contextualDisplayName + ')';
if (account.isStale) {
badgeContent.className = 'badge-content codicon warning-badge';
} else {
badgeContent.className = 'badge-content';
}
} else {
const row = DOM.append(container, DOM.$('div.no-account-container'));
row.innerText = AddAccountAction.LABEL + '...';
}
return null;
}
private updateAccountList(accounts: azdata.Account[]): void {
// keep the selection to the current one
const selectedElements = this._accountList.getSelectedElements();
// find selected index
let selectedIndex: number | undefined;
if (selectedElements.length > 0 && accounts.length > 0) {
selectedIndex = accounts.findIndex((account) => {
return (account.key.accountId === selectedElements[0].key.accountId);
});
}
// Replace the existing list with the new one
this._accountList.splice(0, this._accountList.length, accounts);
if (this._accountList.length > 0) {
if (selectedIndex && selectedIndex !== -1) {
this._accountList.setSelection([selectedIndex]);
} else {
this._accountList.setSelection([0]);
}
} else {
// if the account is empty, re-render dropdown label
this.onAccountSelectionChange(undefined);
this._dropdown.renderLabel();
}
this._accountList.layout(this._accountList.contentHeight);
}
/**
* Update theming that is specific to account picker
*/
private updateTheme(theme: ITheme): void {
const linkColor = theme.getColor(buttonBackground);
const link = linkColor ? linkColor.toString() : null;
this._refreshContainer.style.color = link;
if (this._refreshContainer) {
this._refreshContainer.style.color = link;
}
}
}

View File

@@ -1,65 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Event, Emitter } from 'vs/base/common/event';
import * as azdata from 'azdata';
import { IAccountPickerService } from 'sql/platform/accounts/browser/accountPicker';
import { AccountPicker } from 'sql/platform/accounts/browser/accountPickerImpl';
export class AccountPickerService implements IAccountPickerService {
_serviceBrand: undefined;
private _accountPicker: AccountPicker;
// EVENTING ////////////////////////////////////////////////////////////
private _addAccountCompleteEmitter: Emitter<void>;
public get addAccountCompleteEvent(): Event<void> { return this._addAccountCompleteEmitter.event; }
private _addAccountErrorEmitter: Emitter<string>;
public get addAccountErrorEvent(): Event<string> { return this._addAccountErrorEmitter.event; }
private _addAccountStartEmitter: Emitter<void>;
public get addAccountStartEvent(): Event<void> { return this._addAccountStartEmitter.event; }
private _onAccountSelectionChangeEvent: Emitter<azdata.Account | undefined>;
public get onAccountSelectionChangeEvent(): Event<azdata.Account | undefined> { return this._onAccountSelectionChangeEvent.event; }
constructor(
@IInstantiationService private _instantiationService: IInstantiationService
) {
// Create event emitters
this._addAccountCompleteEmitter = new Emitter<void>();
this._addAccountErrorEmitter = new Emitter<string>();
this._addAccountStartEmitter = new Emitter<void>();
this._onAccountSelectionChangeEvent = new Emitter<azdata.Account>();
}
/**
* Get selected account
*/
public get selectedAccount(): azdata.Account | undefined {
return this._accountPicker.viewModel.selectedAccount;
}
/**
* Render account picker
*/
public renderAccountPicker(container: HTMLElement): void {
if (!this._accountPicker) {
// TODO: expand support to multiple providers
const providerId: string = 'azurePublicCloud';
this._accountPicker = this._instantiationService.createInstance(AccountPicker, providerId);
this._accountPicker.createAccountPickerComponent();
}
this._accountPicker.addAccountCompleteEvent(() => this._addAccountCompleteEmitter.fire());
this._accountPicker.addAccountErrorEvent((msg) => this._addAccountErrorEmitter.fire(msg));
this._accountPicker.addAccountStartEvent(() => this._addAccountStartEmitter.fire());
this._accountPicker.onAccountSelectionChangeEvent((account) => this._onAccountSelectionChangeEvent.fire(account));
this._accountPicker.render(container);
}
}

View File

@@ -1,157 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/autoOAuthDialog';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { Event, Emitter } from 'vs/base/common/event';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { localize } from 'vs/nls';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { $, append } from 'vs/base/browser/dom';
import { Button } from 'sql/base/browser/ui/button/button';
import { Modal } from 'sql/workbench/browser/modal/modal';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import { attachModalDialogStyler, attachButtonStyler } from 'sql/platform/theme/common/styler';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
export class AutoOAuthDialog extends Modal {
private _copyAndOpenButton: Button;
private _closeButton: Button;
private _userCodeInputBox: InputBox;
private _websiteInputBox: InputBox;
private _descriptionElement: HTMLElement;
// EVENTING ////////////////////////////////////////////////////////////
private _onHandleAddAccount = new Emitter<void>();
public get onHandleAddAccount(): Event<void> { return this._onHandleAddAccount.event; }
private _onCancel = new Emitter<void>();
public get onCancel(): Event<void> { return this._onCancel.event; }
private _onCloseEvent = new Emitter<void>();
public get onCloseEvent(): Event<void> { return this._onCloseEvent.event; }
constructor(
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IThemeService themeService: IThemeService,
@IContextViewService private _contextViewService: IContextViewService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IClipboardService clipboardService: IClipboardService,
@ILogService logService: ILogService,
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
) {
super(
'',
TelemetryKeys.AutoOAuth,
telemetryService,
layoutService,
clipboardService,
themeService,
logService,
textResourcePropertiesService,
contextKeyService,
{
isFlyout: true,
hasBackButton: true,
hasSpinner: true
}
);
}
public render() {
super.render();
attachModalDialogStyler(this, this._themeService);
this.backButton.onDidClick(() => this.cancel());
this._register(attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND }));
this._copyAndOpenButton = this.addFooterButton(localize('copyAndOpen', "Copy & Open"), () => this.addAccount());
this._closeButton = this.addFooterButton(localize('oauthDialog.cancel', "Cancel"), () => this.cancel());
this.registerListeners();
this._userCodeInputBox.disable();
this._websiteInputBox.disable();
}
protected layout(height?: number): void {
// NO OP
}
protected renderBody(container: HTMLElement) {
const body = append(container, $('.auto-oauth-dialog'));
this._descriptionElement = append(body, $('.auto-oauth-description-section.new-section'));
const addAccountSection = append(body, $('.auto-oauth-info-section.new-section'));
this._userCodeInputBox = this.createInputBoxHelper(addAccountSection, localize('userCode', "User code"));
this._websiteInputBox = this.createInputBoxHelper(addAccountSection, localize('website', "Website"));
}
private createInputBoxHelper(container: HTMLElement, label: string): InputBox {
const inputContainer = append(container, $('.dialog-input-section'));
append(inputContainer, $('.dialog-label')).innerText = label;
const inputCellContainer = append(inputContainer, $('.dialog-input'));
return new InputBox(inputCellContainer, this._contextViewService, {
ariaLabel: label
});
}
private registerListeners(): void {
// Theme styler
this._register(attachButtonStyler(this._copyAndOpenButton, this._themeService));
this._register(attachButtonStyler(this._closeButton, this._themeService));
this._register(attachInputBoxStyler(this._userCodeInputBox, this._themeService));
this._register(attachInputBoxStyler(this._websiteInputBox, this._themeService));
}
/* Overwrite escape key behavior */
protected onClose() {
this.cancel();
}
/* Overwrite enter key behavior */
protected onAccept() {
this.addAccount();
}
private addAccount() {
if (this._copyAndOpenButton.enabled) {
this._copyAndOpenButton.enabled = false;
this.spinner = true;
this._onHandleAddAccount.fire();
}
}
public cancel() {
this._onCancel.fire();
}
public close() {
this._copyAndOpenButton.enabled = true;
this._onCloseEvent.fire();
this.spinner = false;
this.hide();
}
public open(title: string, message: string, userCode: string, uri: string) {
// Update dialog
this.title = title;
this._descriptionElement.innerText = message;
this._userCodeInputBox.value = userCode;
this._websiteInputBox.value = uri;
this.show();
this._copyAndOpenButton.focus();
}
}

View File

@@ -1,76 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import Severity from 'vs/base/common/severity';
import { localize } from 'vs/nls';
import { AutoOAuthDialog } from 'sql/platform/accounts/browser/autoOAuthDialog';
import { IAccountManagementService } from 'sql/platform/accounts/common/interfaces';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
export class AutoOAuthDialogController {
// MEMBER VARIABLES ////////////////////////////////////////////////////
private _autoOAuthDialog: AutoOAuthDialog;
private _providerId?: string;
private _userCode: string;
private _uri: string;
constructor(
@IInstantiationService private _instantiationService: IInstantiationService,
@IAccountManagementService private _accountManagementService: IAccountManagementService,
@IErrorMessageService private _errorMessageService: IErrorMessageService
) { }
/**
* Open auto OAuth dialog
*/
public openAutoOAuthDialog(providerId: string, title: string, message: string, userCode: string, uri: string): Thenable<void> {
if (this._providerId !== undefined) {
// If a oauth flyout is already open, return an error
const errorMessage = localize('oauthFlyoutIsAlreadyOpen', "Cannot start auto OAuth. An auto OAuth is already in progress.");
this._errorMessageService.showDialog(Severity.Error, '', errorMessage);
return Promise.reject(new Error('Auto OAuth dialog already open'));
}
// Create a new dialog if one doesn't exist
if (!this._autoOAuthDialog) {
this._autoOAuthDialog = this._instantiationService.createInstance(AutoOAuthDialog);
this._autoOAuthDialog.onHandleAddAccount(this.handleOnAddAccount, this);
this._autoOAuthDialog.onCancel(this.handleOnCancel, this);
this._autoOAuthDialog.onCloseEvent(this.handleOnClose, this);
this._autoOAuthDialog.render();
}
this._userCode = userCode;
this._uri = uri;
// Open the dialog
this._autoOAuthDialog.open(title, message, userCode, uri);
this._providerId = providerId;
return Promise.resolve();
}
/**
* Close auto OAuth dialog
*/
public closeAutoOAuthDialog(): void {
this._autoOAuthDialog.close();
this._providerId = undefined;
}
// PRIVATE HELPERS /////////////////////////////////////////////////////
private handleOnCancel(): void {
this._accountManagementService.cancelAutoOAuthDeviceCode(this._providerId!); // this should be always true
}
private handleOnClose(): void {
this._providerId = undefined;
}
private handleOnAddAccount(): void {
this._accountManagementService.copyUserCodeAndOpenBrowser(this._userCode, this._uri);
}
}

View File

@@ -1,305 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/firewallRuleDialog';
import * as DOM from 'vs/base/browser/dom';
import { Event, Emitter } from 'vs/base/common/event';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { localize } from 'vs/nls';
import { buttonBackground } from 'vs/platform/theme/common/colorRegistry';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { URI } from 'vs/base/common/uri';
import * as azdata from 'azdata';
import { Button } from 'sql/base/browser/ui/button/button';
import { Modal } from 'sql/workbench/browser/modal/modal';
import { FirewallRuleViewModel } from 'sql/platform/accounts/common/firewallRuleViewModel';
import { attachModalDialogStyler, attachButtonStyler } from 'sql/platform/theme/common/styler';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import { IAccountPickerService } from 'sql/platform/accounts/browser/accountPicker';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { IOpenerService } from 'vs/platform/opener/common/opener';
// TODO: Make the help link 1) extensible (01/08/2018, https://github.com/Microsoft/azuredatastudio/issues/450)
// in case that other non-Azure sign in is to be used
const firewallHelpUri = 'https://aka.ms/sqlopsfirewallhelp';
const LocalizedStrings = {
FROM: localize('from', "From"),
TO: localize('to', "To")
};
export class FirewallRuleDialog extends Modal {
public viewModel: FirewallRuleViewModel;
private _createButton: Button;
private _closeButton: Button;
private _fromRangeinputBox: InputBox;
private _toRangeinputBox: InputBox;
private _helpLink: HTMLElement;
private _IPAddressInput: HTMLElement;
private _subnetIPRangeInput: HTMLElement;
private _IPAddressElement: HTMLElement;
// EVENTING ////////////////////////////////////////////////////////////
private _onAddAccountErrorEmitter: Emitter<string>;
public get onAddAccountErrorEvent(): Event<string> { return this._onAddAccountErrorEmitter.event; }
private _onCancel: Emitter<void>;
public get onCancel(): Event<void> { return this._onCancel.event; }
private _onCreateFirewallRule: Emitter<void>;
public get onCreateFirewallRule(): Event<void> { return this._onCreateFirewallRule.event; }
constructor(
@IAccountPickerService private _accountPickerService: IAccountPickerService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IThemeService themeService: IThemeService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IContextViewService private _contextViewService: IContextViewService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IClipboardService clipboardService: IClipboardService,
@ILogService logService: ILogService,
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService,
@IOpenerService private readonly openerService: IOpenerService
) {
super(
localize('createNewFirewallRule', "Create new firewall rule"),
TelemetryKeys.FireWallRule,
telemetryService,
layoutService,
clipboardService,
themeService,
logService,
textResourcePropertiesService,
contextKeyService,
{
isFlyout: true,
hasBackButton: true,
hasSpinner: true
}
);
// Setup event emitters
this._onAddAccountErrorEmitter = new Emitter<string>();
this._onCancel = new Emitter<void>();
this._onCreateFirewallRule = new Emitter<void>();
this.viewModel = this._instantiationService.createInstance(FirewallRuleViewModel);
}
public render() {
super.render();
attachModalDialogStyler(this, this._themeService);
this.backButton.onDidClick(() => this.cancel());
this._register(attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND }));
this._createButton = this.addFooterButton(localize('firewall.ok', "OK"), () => this.createFirewallRule());
this._closeButton = this.addFooterButton(localize('firewall.cancel', "Cancel"), () => this.cancel());
this.registerListeners();
}
protected renderBody(container: HTMLElement) {
const body = DOM.append(container, DOM.$('.firewall-rule-dialog'));
const descriptionSection = DOM.append(body, DOM.$('.firewall-rule-description-section.new-section'));
DOM.append(descriptionSection, DOM.$('div.firewall-rule-icon'));
const textDescriptionContainer = DOM.append(descriptionSection, DOM.$('div.firewall-rule-description'));
const dialogDescription = localize('firewallRuleDialogDescription',
"Your client IP address does not have access to the server. Sign in to an Azure account and create a new firewall rule to enable access.");
this.createLabelElement(textDescriptionContainer, dialogDescription, false);
this._helpLink = DOM.append(textDescriptionContainer, DOM.$('a.help-link'));
this._helpLink.setAttribute('href', firewallHelpUri);
this._helpLink.innerHTML += localize('firewallRuleHelpDescription', "Learn more about firewall settings");
this._helpLink.onclick = () => {
this.openerService.open(URI.parse(firewallHelpUri));
};
// Create account picker with event handling
this._accountPickerService.addAccountCompleteEvent(() => this.spinner = false);
this._accountPickerService.addAccountErrorEvent((msg) => {
this.spinner = false;
this._onAddAccountErrorEmitter.fire(msg);
});
this._accountPickerService.addAccountStartEvent(() => this.spinner = true);
this._accountPickerService.onAccountSelectionChangeEvent((account) => this.onAccountSelectionChange(account));
const azureAccountSection = DOM.append(body, DOM.$('.azure-account-section.new-section'));
const azureAccountLabel = localize('azureAccount', "Azure account");
this.createLabelElement(azureAccountSection, azureAccountLabel, true);
this._accountPickerService.renderAccountPicker(DOM.append(azureAccountSection, DOM.$('.dialog-input')));
const firewallRuleSection = DOM.append(body, DOM.$('.firewall-rule-section.new-section'));
const firewallRuleLabel = localize('filewallRule', "Firewall rule");
this.createLabelElement(firewallRuleSection, firewallRuleLabel, true);
const radioContainer = DOM.append(firewallRuleSection, DOM.$('.radio-section'));
const form = DOM.append(radioContainer, DOM.$('form.firewall-rule'));
const IPAddressDiv = DOM.append(form, DOM.$('div.firewall-ip-address dialog-input'));
const subnetIPRangeDiv = DOM.append(form, DOM.$('div.firewall-subnet-ip-range dialog-input'));
const IPAddressContainer = DOM.append(IPAddressDiv, DOM.$('div.option-container'));
this._IPAddressInput = DOM.append(IPAddressContainer, DOM.$('input.option-input'));
this._IPAddressInput.setAttribute('type', 'radio');
this._IPAddressInput.setAttribute('name', 'firewallRuleChoice');
this._IPAddressInput.setAttribute('value', 'ipAddress');
const IPAddressDescription = DOM.append(IPAddressContainer, DOM.$('div.option-description'));
IPAddressDescription.innerText = localize('addIPAddressLabel', "Add my client IP ");
this._IPAddressElement = DOM.append(IPAddressContainer, DOM.$('div.option-ip-address'));
const subnetIpRangeContainer = DOM.append(subnetIPRangeDiv, DOM.$('div.option-container'));
this._subnetIPRangeInput = DOM.append(subnetIpRangeContainer, DOM.$('input.option-input'));
this._subnetIPRangeInput.setAttribute('type', 'radio');
this._subnetIPRangeInput.setAttribute('name', 'firewallRuleChoice');
this._subnetIPRangeInput.setAttribute('value', 'ipRange');
const subnetIPRangeDescription = DOM.append(subnetIpRangeContainer, DOM.$('div.option-description'));
subnetIPRangeDescription.innerText = localize('addIpRangeLabel', "Add my subnet IP range");
const subnetIPRangeSection = DOM.append(subnetIPRangeDiv, DOM.$('.subnet-ip-range-input'));
const inputContainer = DOM.append(subnetIPRangeSection, DOM.$('.dialog-input-section'));
DOM.append(inputContainer, DOM.$('.dialog-label')).innerText = LocalizedStrings.FROM;
this._fromRangeinputBox = new InputBox(DOM.append(inputContainer, DOM.$('.dialog-input')), this._contextViewService, {
ariaLabel: LocalizedStrings.FROM
});
DOM.append(inputContainer, DOM.$('.dialog-label')).innerText = LocalizedStrings.TO;
this._toRangeinputBox = new InputBox(DOM.append(inputContainer, DOM.$('.dialog-input')), this._contextViewService, {
ariaLabel: LocalizedStrings.TO
});
this._register(this._themeService.onThemeChange(e => this.updateTheme(e)));
this.updateTheme(this._themeService.getTheme());
this._register(DOM.addDisposableListener(this._IPAddressElement, DOM.EventType.CLICK, () => {
this.onFirewallRuleOptionSelected(true);
}));
this._register(DOM.addDisposableListener(this._subnetIPRangeInput, DOM.EventType.CLICK, () => {
this.onFirewallRuleOptionSelected(false);
}));
}
private onFirewallRuleOptionSelected(isIPAddress: boolean) {
this.viewModel.isIPAddressSelected = isIPAddress;
if (this._fromRangeinputBox) {
isIPAddress ? this._fromRangeinputBox.disable() : this._fromRangeinputBox.enable();
}
if (this._toRangeinputBox) {
isIPAddress ? this._toRangeinputBox.disable() : this._toRangeinputBox.enable();
}
}
protected layout(height?: number): void {
// Nothing currently laid out statically in this class
}
private createLabelElement(container: HTMLElement, content: string, isHeader?: boolean) {
let className = 'dialog-label';
if (isHeader) {
className += ' header';
}
DOM.append(container, DOM.$(`.${className}`)).innerText = content;
}
// Update theming that is specific to firewall rule flyout body
private updateTheme(theme: ITheme): void {
const linkColor = theme.getColor(buttonBackground);
const link = linkColor ? linkColor.toString() : null;
if (this._helpLink) {
this._helpLink.style.color = link;
}
}
private registerListeners(): void {
// Theme styler
this._register(attachButtonStyler(this._createButton, this._themeService));
this._register(attachButtonStyler(this._closeButton, this._themeService));
this._register(attachInputBoxStyler(this._fromRangeinputBox, this._themeService));
this._register(attachInputBoxStyler(this._toRangeinputBox, this._themeService));
// handler for from subnet ip range change events
this._register(this._fromRangeinputBox.onDidChange(IPAddress => {
this.fromRangeInputChanged(IPAddress);
}));
// handler for to subnet ip range change events
this._register(this._toRangeinputBox.onDidChange(IPAddress => {
this.toRangeInputChanged(IPAddress);
}));
}
private fromRangeInputChanged(IPAddress: string) {
this.viewModel.fromSubnetIPRange = IPAddress;
}
private toRangeInputChanged(IPAddress: string) {
this.viewModel.toSubnetIPRange = IPAddress;
}
/* Overwrite esapce key behavior */
protected onClose() {
this.cancel();
}
/* Overwrite enter key behavior */
protected onAccept() {
this.createFirewallRule();
}
public cancel() {
this._onCancel.fire();
this.close();
}
public close() {
this.hide();
}
public createFirewallRule() {
if (this._createButton.enabled) {
this._createButton.enabled = false;
this.spinner = true;
this._onCreateFirewallRule.fire();
}
}
public onAccountSelectionChange(account: azdata.Account | undefined): void {
this.viewModel.selectedAccount = account;
if (account && !account.isStale) {
this._createButton.enabled = true;
} else {
this._createButton.enabled = false;
}
}
public onServiceComplete() {
this._createButton.enabled = true;
this.spinner = false;
}
public open() {
this._IPAddressInput.click();
this.onAccountSelectionChange(this._accountPickerService.selectedAccount);
this._fromRangeinputBox.setPlaceHolder(this.viewModel.defaultFromSubnetIPRange);
this._toRangeinputBox.setPlaceHolder(this.viewModel.defaultToSubnetIPRange);
this._IPAddressElement.innerText = '(' + this.viewModel.defaultIPAddress + ')';
this.show();
}
}

View File

@@ -1,94 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import Severity from 'vs/base/common/severity';
import { localize } from 'vs/nls';
import * as azdata from 'azdata';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { FirewallRuleDialog } from 'sql/platform/accounts/browser/firewallRuleDialog';
import { IAccountManagementService, AzureResource } from 'sql/platform/accounts/common/interfaces';
import { IResourceProviderService } from 'sql/workbench/services/resourceProvider/common/resourceProviderService';
import { Deferred } from 'sql/base/common/promise';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
export class FirewallRuleDialogController {
private _firewallRuleDialog: FirewallRuleDialog;
private _connection: IConnectionProfile;
private _resourceProviderId: string;
private _addAccountErrorTitle = localize('firewallDialog.addAccountErrorTitle', "Error adding account");
private _firewallRuleErrorTitle = localize('firewallRuleError', "Firewall rule error");
private _deferredPromise: Deferred<boolean>;
constructor(
@IInstantiationService private _instantiationService: IInstantiationService,
@IResourceProviderService private _resourceProviderService: IResourceProviderService,
@IAccountManagementService private _accountManagementService: IAccountManagementService,
@IErrorMessageService private _errorMessageService: IErrorMessageService
) {
}
/**
* Open firewall rule dialog
*/
public openFirewallRuleDialog(connection: IConnectionProfile, ipAddress: string, resourceProviderId: string): Promise<boolean> {
if (!this._firewallRuleDialog) {
this._firewallRuleDialog = this._instantiationService.createInstance(FirewallRuleDialog);
this._firewallRuleDialog.onCancel(this.handleOnCancel, this);
this._firewallRuleDialog.onCreateFirewallRule(this.handleOnCreateFirewallRule, this);
this._firewallRuleDialog.onAddAccountErrorEvent(this.handleOnAddAccountError, this);
this._firewallRuleDialog.render();
}
this._connection = connection;
this._resourceProviderId = resourceProviderId;
this._firewallRuleDialog.viewModel.updateDefaultValues(ipAddress);
this._firewallRuleDialog.open();
this._deferredPromise = new Deferred();
return this._deferredPromise.promise;
}
// PRIVATE HELPERS /////////////////////////////////////////////////////
private handleOnAddAccountError(message: string): void {
this._errorMessageService.showDialog(Severity.Error, this._addAccountErrorTitle, message);
}
private async handleOnCreateFirewallRule(): Promise<void> {
const resourceProviderId = this._resourceProviderId;
try {
const securityTokenMappings = await this._accountManagementService.getSecurityToken(this._firewallRuleDialog.viewModel.selectedAccount!, AzureResource.ResourceManagement);
const firewallRuleInfo: azdata.FirewallRuleInfo = {
startIpAddress: this._firewallRuleDialog.viewModel.isIPAddressSelected ? this._firewallRuleDialog.viewModel.defaultIPAddress : this._firewallRuleDialog.viewModel.fromSubnetIPRange,
endIpAddress: this._firewallRuleDialog.viewModel.isIPAddressSelected ? this._firewallRuleDialog.viewModel.defaultIPAddress : this._firewallRuleDialog.viewModel.toSubnetIPRange,
serverName: this._connection.serverName,
securityTokenMappings
};
const response = await this._resourceProviderService.createFirewallRule(this._firewallRuleDialog.viewModel.selectedAccount!, firewallRuleInfo, resourceProviderId);
if (response.result) {
this._firewallRuleDialog.close();
this._deferredPromise.resolve(true);
} else {
this._errorMessageService.showDialog(Severity.Error, this._firewallRuleErrorTitle, response.errorMessage);
}
this._firewallRuleDialog.onServiceComplete();
} catch (e) {
this.showError(e);
}
}
private showError(error: any): void {
this._errorMessageService.showDialog(Severity.Error, this._firewallRuleErrorTitle, error);
this._firewallRuleDialog.onServiceComplete();
// Note: intentionally not rejecting the promise as we want users to be able to choose a different account
}
private handleOnCancel(): void {
this._deferredPromise.resolve(false);
}
}

View File

@@ -1,14 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Icons for various account actions */
.vs .action-item .codicon.add-linked-account-action {
background-image: url('new_account.svg');
}
.vs-dark .action-item .codicon.add-linked-account-action,
.hc-black .action-item .codicon.add-linked-account-action {
background-image: url('new_account_inverse.svg');
}

View File

@@ -1,105 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.no-account-view {
font-size: 12px;
padding: 15px;
}
.no-account-view .no-account-view-label {
padding-bottom: 15px;
}
.account-view .header {
position: relative;
line-height: 22px;
font-size: 11px;
font-weight: bold;
text-transform: uppercase;
padding-left: 20px;
padding-right: 12px;
overflow: hidden;
display: flex;
}
.account-view .header .title {
display: flex;
justify-content: flex-start;
flex: 1 1 auto;
}
.account-view .header .actions {
display: flex;
justify-content: flex-end;
}
.account-view .header .actions .action-item .action-label {
width: 30px;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
margin-right: 0;
height: 22px;
}
.account-view .header .count-badge-wrapper {
justify-content: flex-end;
/* hide the count badge as it is not providing much value and meanwhile not keyboard accessible*/
display: none;
}
.account-view .list-row {
padding: 12px;
line-height: 18px !important;
}
.account-view .list-row .codicon {
flex: 0 0 50px;
height: 50px;
width: 50px;
background-size: 50px;
}
.account-view .list-row .codicon .badge {
position: absolute;
top: 43px;
left: 43px;
overflow: hidden;
width: 22px;
height: 22px;
}
.account-view .list-row .codicon .badge .badge-content {
width: 22px;
height: 22px;
background-size: 22px;
}
.account-view .list-row .actions-container {
flex: 0 0 50px;
}
.account-view .list-row .actions-container .action-item .action-label {
width: 16px;
margin-left: 20px;
margin-right: 10px;
background-size: 16px;
background-position: center;
background-repeat: no-repeat;
}
.account-view .list-row .actions-container .action-item .action-label.codicon.remove {
background-size: 14px !important;
}
.account-view .list-row .actions-container {
display: none;
}
.account-view .monaco-list .monaco-list-row:hover .list-row .actions-container,
.account-view .monaco-list .monaco-list-row.selected .list-row .actions-container,
.account-view .monaco-list .monaco-list-row.focused .list-row .actions-container{
display: block;
}

View File

@@ -1,32 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.list-row.account-picker-list {
display: flex;
align-items: flex-start;
}
.list-row.account-picker-list .label {
flex: 1 1 auto;
margin-left: 15px;
}
.list-row.account-picker-list .label .contextual-display-name {
font-size: 15px;
line-height: 15px;
}
.list-row.account-picker-list .label .display-name {
font-size: 13px;
line-height: 13px;
}
.list-row.account-picker-list .label .content {
opacity: 0.7;
}
.account-logo {
background: no-repeat center center;
}

View File

@@ -1,15 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.statusbar-item .linked-account-staus a.linked-account-status-selection .linked-account-icon {
-webkit-mask: url('accounts_statusbar_inverse.svg') no-repeat 50% 50%;
-webkit-mask-size: 12px;
width: 12px;
height: 22px;
}
.statusbar-item .linked-account-staus a.linked-account-status-selection {
padding: 0 5px 0 5px;
}

View File

@@ -1,99 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Selected account */
.selected-account-container {
padding: 6px;
display: flex;
align-items: flex-start;
}
.selected-account-container .codicon {
flex: 0 0 25px;
height: 25px;
width: 25px;
}
.selected-account-container .label {
flex: 1 1 auto;
padding-left: 10px;
align-self: center;
}
.selected-account-container .codicon {
background-size: 25px;
}
.selected-account-container .codicon .badge {
position: relative;
top: 15px;
left: 15px;
overflow: hidden;
width: 12px;
height: 12px;
}
.selected-account-container .codicon .badge .badge-content {
width: 12px;
height: 12px;
background-size: 12px;
}
/* A container when the account list is empty */
.no-account-container {
padding: 6px;
opacity: 0.7;
font-style: italic;
}
/* Account list */
.account-list-container .list-row {
padding: 6px;
}
.account-list-container .list-row .codicon {
flex: 0 0 35px;
height: 35px;
width: 35px;
background-size: 35px;
}
.account-list-container .list-row .codicon .badge {
position: relative;
top: 22px;
left: 22px;
overflow: hidden;
width: 15px;
height: 15px;
}
.account-list-container .list-row .codicon .badge .badge-content {
width: 15px;
height: 15px;
background-size: 15px;
}
/* Refresh link */
.refresh-container {
padding-left: 15px;
padding-top: 6px;
display: flex;
align-items: flex-start;
}
.refresh-container .monaco-action-bar .actions-container {
justify-content: flex-start;
}
.refresh-container .codicon {
flex: 0 0 16px;
height: 16px;
width: 16px;
}
.refresh-container .monaco-action-bar {
flex: 1 1 auto;
margin-left: 10px;
}

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 11 10.89"><defs><style>.cls-1{fill:#fff;}</style></defs><title>accounts_statusbar_inverse_11x11</title><path class="cls-1" d="M10.08,5.66a3.44,3.44,0,0,0-.34-.75,3.47,3.47,0,0,0-.52-.64A3.23,3.23,0,0,0,8.8,4a2.28,2.28,0,0,0,.7-1.65,2.27,2.27,0,0,0-.18-.89A2.31,2.31,0,0,0,8.09.19,2.19,2.19,0,0,0,7.19,0,2.16,2.16,0,0,0,6.3.19a2.41,2.41,0,0,0-.73.49,2.41,2.41,0,0,0-.49.73,2.16,2.16,0,0,0-.19.89,2.22,2.22,0,0,0,.25,1A2.31,2.31,0,0,0,5.58,4a3.62,3.62,0,0,0-.52.41,3.62,3.62,0,0,0-.41.52A2.31,2.31,0,0,0,4,4.44a2.26,2.26,0,0,0-1-.25,2.16,2.16,0,0,0-.89.19,2.41,2.41,0,0,0-.73.49,2.41,2.41,0,0,0-.49.73,2.16,2.16,0,0,0-.19.89,2.22,2.22,0,0,0,.25,1,2.31,2.31,0,0,0,.45.61A3.7,3.7,0,0,0,1,8.47a3.47,3.47,0,0,0-.52.64,2.88,2.88,0,0,0-.34.75,3.06,3.06,0,0,0-.12.83v.21H1.12v-.21A1.83,1.83,0,0,1,1.27,10a1.9,1.9,0,0,1,1-1A1.83,1.83,0,0,1,3,8.8a1.86,1.86,0,0,1,.73.15,2,2,0,0,1,.6.4,1.9,1.9,0,0,1,.4.6,1.85,1.85,0,0,1,.15.73v.21H6v-.21a3.05,3.05,0,0,0-.11-.83,3.44,3.44,0,0,0-.34-.75A3.25,3.25,0,0,0,5,8.47a3.23,3.23,0,0,0-.43-.32,2.28,2.28,0,0,0,.7-1.65,1.83,1.83,0,0,1,.15-.73,1.9,1.9,0,0,1,1-1,1.83,1.83,0,0,1,.73-.15,1.86,1.86,0,0,1,.73.15,1.9,1.9,0,0,1,1,1,1.82,1.82,0,0,1,.15.73V6.7h1.12V6.49A3.05,3.05,0,0,0,10.08,5.66ZM4.1,7a1.24,1.24,0,0,1-.64.64A1.13,1.13,0,0,1,3,7.68a1.1,1.1,0,0,1-.45-.09,1.21,1.21,0,0,1-.38-.26A1.1,1.1,0,0,1,1.91,7a1.11,1.11,0,0,1-.1-.46A1.13,1.13,0,0,1,1.91,6a1.1,1.1,0,0,1,.26-.38,1.1,1.1,0,0,1,.38-.26A1.1,1.1,0,0,1,3,5.31a1.16,1.16,0,0,1,.46.1A1.24,1.24,0,0,1,4.1,6a1.15,1.15,0,0,1,.09.45A1.13,1.13,0,0,1,4.1,7ZM8.29,2.76a1.24,1.24,0,0,1-.64.64,1.13,1.13,0,0,1-.46.09,1.1,1.1,0,0,1-.45-.09,1.21,1.21,0,0,1-.38-.26,1.1,1.1,0,0,1-.26-.38A1.14,1.14,0,0,1,6,2.3a1.15,1.15,0,0,1,.1-.45,1.1,1.1,0,0,1,.26-.38,1.19,1.19,0,0,1,.83-.36,1.16,1.16,0,0,1,.46.1A1.1,1.1,0,0,1,8,1.47a1.21,1.21,0,0,1,.26.38,1.15,1.15,0,0,1,.09.45A1.13,1.13,0,0,1,8.29,2.76Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1,29 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.auto-oauth-dialog {
padding: 15px
}
.modal .auto-oauth-dialog .new-section {
padding-bottom: 30px;
}
.modal .auto-oauth-dialog .dialog-input-section {
display: flex;
padding-left: 15px;
padding-right: 15px;
padding-top: 10px;
padding-bottom: 10px;
}
.modal .auto-oauth-dialog .dialog-input-section .dialog-label {
flex: 0 0 100px;
align-self: center;
}
.modal .auto-oauth-dialog .dialog-input-section .dialog-input {
flex: 1 1 auto;
}

View File

@@ -1,86 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.firewall-rule-dialog {
padding: 15px
}
.modal .firewall-rule-dialog .dialog-label.header {
font-size: 15px;
}
.modal .firewall-rule-dialog .new-section {
padding-bottom: 30px;
}
.modal .firewall-rule-dialog .dialog-input {
padding-left: 15px;
padding-right: 15px;
padding-top: 10px;
}
.modal .firewall-rule-dialog .dialog-input-section {
display: flex;
padding-left: 30px;
padding-right: 20px;
padding-top: 10px;
padding-bottom: 10px;
}
.modal .firewall-rule-dialog .dialog-input-section .dialog-label {
flex: 0 0 15px;
align-self: center;
}
.modal .firewall-rule-dialog .dialog-input-section .dialog-input {
flex: 0 0 100px;
padding-left: 10px;
padding-right: 10px;
padding-top: 0px;
padding-bottom: 0px;
}
.modal .firewall-rule-dialog .azure-account-section {
height: 92px;
}
/* Firewall rule description section */
.modal .firewall-rule-dialog a:link {
text-decoration: underline;
}
.modal .firewall-rule-dialog .firewall-rule-description-section {
display: flex;
}
.modal .firewall-rule-dialog .firewall-rule-description-section .firewall-rule-icon {
background: url('secure.svg') center center no-repeat;
width: 50px;
height: 50px;
flex: 0 0 50px;
}
.modal .firewall-rule-dialog .firewall-rule-description-section .firewall-rule-description {
padding-left: 15px;
}
/* Radio button section*/
.modal .firewall-rule-dialog .firewall-rule-section .radio-section .option-container {
display: flex;
align-items: flex-start;
}
.modal .firewall-rule-dialog .firewall-rule-section .radio-section .option-container .option-input {
margin-top: 0px;
align-self: center;
}
.modal .firewall-rule-dialog .firewall-rule-section .radio-section .option-container .option-description {
padding-left: 10px;
}
.modal .firewall-rule-dialog .firewall-rule-section .radio-section .option-container .option-ip-address {
padding-left: 3px;
}

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>new_account_16x16</title><path d="M8.09,7.39a5.91,5.91,0,0,1,1.38.74,6.06,6.06,0,0,1,1.13,1.06A6,6,0,0,1,11.88,12h-1a4.94,4.94,0,0,0-.63-1.61A5,5,0,0,0,7.66,8.3a5,5,0,0,0-3-.12,5.09,5.09,0,0,0-1.2.5,5,5,0,0,0-1.79,1.79,5.07,5.07,0,0,0-.5,1.2A4.88,4.88,0,0,0,1,13H0a5.88,5.88,0,0,1,.28-1.8A6,6,0,0,1,1,9.59a6.1,6.1,0,0,1,1.22-1.3,5.8,5.8,0,0,1,1.59-.9A4.2,4.2,0,0,1,2.46,5.94,3.86,3.86,0,0,1,2,4a3.92,3.92,0,0,1,.31-1.56A4,4,0,0,1,4.4.31a4,4,0,0,1,3.12,0A4,4,0,0,1,9.65,2.44,4,4,0,0,1,9.83,5a4,4,0,0,1-1,1.74A3.94,3.94,0,0,1,8.09,7.39ZM3,4A2.92,2.92,0,0,0,3.2,5.17a3,3,0,0,0,1.6,1.6,3,3,0,0,0,2.33,0,3,3,0,0,0,1.6-1.6,3,3,0,0,0,0-2.33,3,3,0,0,0-1.6-1.6,3,3,0,0,0-2.33,0,3,3,0,0,0-1.6,1.6A2.92,2.92,0,0,0,3,4Zm13,9v1H14v2H13V14H11V13h2V11h1v2Z"/></svg>

Before

Width:  |  Height:  |  Size: 850 B

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>new_account_inverse_16x16</title><path class="cls-1" d="M8.13,7.39a5.91,5.91,0,0,1,1.38.74,6.06,6.06,0,0,1,1.13,1.06A6,6,0,0,1,11.91,12h-1a4.94,4.94,0,0,0-.63-1.61A5,5,0,0,0,7.7,8.3a5,5,0,0,0-3-.12,5.09,5.09,0,0,0-1.2.5,5,5,0,0,0-1.79,1.79,5.07,5.07,0,0,0-.5,1.2A4.88,4.88,0,0,0,1,13H0a5.88,5.88,0,0,1,.28-1.8,6,6,0,0,1,.79-1.6,6.1,6.1,0,0,1,1.22-1.3,5.8,5.8,0,0,1,1.59-.9A4.2,4.2,0,0,1,2.5,5.94,3.86,3.86,0,0,1,2,4a3.92,3.92,0,0,1,.31-1.56A4,4,0,0,1,4.44.31a4,4,0,0,1,3.12,0A4,4,0,0,1,9.69,2.44,4,4,0,0,1,9.87,5a4,4,0,0,1-1,1.74A3.94,3.94,0,0,1,8.13,7.39ZM3,4a2.92,2.92,0,0,0,.23,1.17,3,3,0,0,0,1.6,1.6,3,3,0,0,0,2.33,0,3,3,0,0,0,1.6-1.6,3,3,0,0,0,0-2.33,3,3,0,0,0-1.6-1.6,3,3,0,0,0-2.33,0,3,3,0,0,0-1.6,1.6A2.92,2.92,0,0,0,3,4Zm13,9v1H14v2H13V14H11V13h2V11h1v2Z"/></svg>

Before

Width:  |  Height:  |  Size: 918 B

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36.65 50"><defs><style>.cls-1{fill:#7fba00;}.cls-2{fill:#b8d432;opacity:0.4;isolation:isolate;}.cls-3{fill:#fff;}</style></defs><title>secure</title><path class="cls-1" d="M31.19,6h0c-11-1.22-12.86-6-12.86-6S15.85,6.24,0,6.24V31.85c0,3.1,1.72,6,4.1,8.53h0C9.51,46.13,18.33,50,18.33,50s18.33-8,18.33-18.15V6.24A49.77,49.77,0,0,1,31.19,6Z"/><path class="cls-2" d="M22.86,16.54,31.19,6c-11-1.22-12.86-6-12.86-6S15.85,6.24,0,6.24V31.85c0,3.1,1.72,6,4.1,8.53l6.15-7.82Z"/><path class="cls-3" d="M25.6,24.46H24.53V20.91a6.7,6.7,0,0,0-1.67-4.45h0l-.11-.13a6,6,0,0,0-8.84,0,6.7,6.7,0,0,0-1.78,4.58v3.55H11.06a.8.8,0,0,0-.8.8v7.22h0v2.17a.8.8,0,0,0,.8.8H25.6a.8.8,0,0,0,.8-.8V25.26A.8.8,0,0,0,25.6,24.46Zm-4,0H15.07V20.91a3.75,3.75,0,0,1,1-2.57,3,3,0,0,1,4.54,0,3.67,3.67,0,0,1,.39.5h0a3.8,3.8,0,0,1,.6,2.06Z"/></svg>

Before

Width:  |  Height:  |  Size: 894 B

View File

@@ -1,103 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as TypeMoq from 'typemoq';
import { Emitter } from 'vs/base/common/event';
import { AccountDialog } from 'sql/platform/accounts/browser/accountDialog';
import { AccountDialogController } from 'sql/platform/accounts/browser/accountDialogController';
import { AccountViewModel } from 'sql/platform/accounts/common/accountViewModel';
import { TestAccountManagementService } from 'sql/platform/accounts/test/common/testAccountManagementService';
import { TestErrorMessageService } from 'sql/platform/errorMessage/test/common/testErrorMessageService';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { AccountListRenderer } from 'sql/platform/accounts/browser/accountListRenderer';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
// TESTS ///////////////////////////////////////////////////////////////////
suite('Account Management Dialog Controller Tests', () => {
test('Open Account Dialog - Dialog Doesn\'t Exist', () => {
// Setup: Create instance of the controller
let instantiationService = createInstantiationService();
let controller = new AccountDialogController(instantiationService, undefined!);
assert.strictEqual(controller.accountDialog, undefined);
// If: I open the account dialog when one hasn't been opened
controller.openAccountDialog();
// Then:
// ... The account dialog should be defined
assert.notStrictEqual(controller.accountDialog, undefined);
});
test('Open Account Dialog - Dialog Exists', () => {
// Setup: Create instance of the controller with an account dialog already loaded
let instantiationService = createInstantiationService();
let controller = new AccountDialogController(instantiationService, undefined!);
controller.openAccountDialog();
let accountDialog = controller.accountDialog;
// If: I open the account dialog when one has already been opened
controller.openAccountDialog();
// Then: It should be the same dialog that already existed
assert.equal(controller.accountDialog, accountDialog);
});
test('Add Account Failure - Error Message Shown', () => {
// Setup:
// ... Create instantiation service that returns mock emitter for account dialog
let mockEventEmitter = new Emitter<string>();
let instantiationService = createInstantiationService(mockEventEmitter);
// ... Create a mock instance of the error message service
let errorMessageServiceStub = new TestErrorMessageService();
let mockErrorMessageService = TypeMoq.Mock.ofInstance(errorMessageServiceStub);
mockErrorMessageService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()));
// ... Create instance of the controller with an opened dialog
let controller = new AccountDialogController(instantiationService, mockErrorMessageService.object);
controller.openAccountDialog();
// If: The account dialog reports a failure adding an account
mockEventEmitter.fire('Error message');
// Then: An error dialog should have been opened
mockErrorMessageService.verify(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
});
});
function createInstantiationService(addAccountFailureEmitter?: Emitter<string>): InstantiationService {
// Create a mock account dialog view model
let accountViewModel = new AccountViewModel(new TestAccountManagementService());
let mockAccountViewModel = TypeMoq.Mock.ofInstance(accountViewModel);
let mockEvent = new Emitter<any>();
mockAccountViewModel.setup(x => x.addProviderEvent).returns(() => mockEvent.event);
mockAccountViewModel.setup(x => x.removeProviderEvent).returns(() => mockEvent.event);
mockAccountViewModel.setup(x => x.updateAccountListEvent).returns(() => mockEvent.event);
mockAccountViewModel.setup(x => x.initialize()).returns(() => Promise.resolve([]));
// Create a mocked out instantiation service
let instantiationService = TypeMoq.Mock.ofType(InstantiationService, TypeMoq.MockBehavior.Strict);
instantiationService.setup(x => x.createInstance(TypeMoq.It.isValue(AccountViewModel)))
.returns(() => mockAccountViewModel.object);
instantiationService.setup(x => x.createInstance(TypeMoq.It.isValue(AccountListRenderer)))
.returns(() => undefined);
// Create a mock account dialog
let accountDialog = new AccountDialog(undefined!, undefined!, instantiationService.object, undefined!, undefined!, undefined!, undefined!, new MockContextKeyService(), undefined!, undefined!, undefined!);
let mockAccountDialog = TypeMoq.Mock.ofInstance(accountDialog);
mockAccountDialog.setup(x => x.onAddAccountErrorEvent)
.returns(() => { return addAccountFailureEmitter ? addAccountFailureEmitter.event : mockEvent.event; });
mockAccountDialog.setup(x => x.onCloseEvent)
.returns(() => mockEvent.event);
mockAccountDialog.setup(x => x.render())
.returns(() => undefined);
mockAccountDialog.setup(x => x.open())
.returns(() => undefined);
instantiationService.setup(x => x.createInstance(TypeMoq.It.isValue(AccountDialog)))
.returns(() => mockAccountDialog.object);
return instantiationService.object;
}

View File

@@ -1,122 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 * as assert from 'assert';
import * as TypeMoq from 'typemoq';
import { EventVerifierSingle } from 'sqltest/utils/eventVerifier';
import { Emitter } from 'vs/base/common/event';
import { AccountPicker } from 'sql/platform/accounts/browser/accountPickerImpl';
import { AccountPickerService } from 'sql/platform/accounts/browser/accountPickerService';
import { AccountPickerViewModel } from 'sql/platform/accounts/common/accountPickerViewModel';
import { TestAccountManagementService } from 'sql/platform/accounts/test/common/testAccountManagementService';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
// SUITE STATE /////////////////////////////////////////////////////////////
let mockAddAccountCompleteEmitter: Emitter<void>;
let mockAddAccountErrorEmitter: Emitter<string>;
let mockAddAccountStartEmitter: Emitter<void>;
let mockOnAccountSelectionChangeEvent: Emitter<azdata.Account>;
// TESTS ///////////////////////////////////////////////////////////////////
suite('Account picker service tests', () => {
setup(() => {
// Setup event mocks for the account picker service
mockAddAccountCompleteEmitter = new Emitter<void>();
mockAddAccountErrorEmitter = new Emitter<string>();
mockAddAccountStartEmitter = new Emitter<void>();
mockOnAccountSelectionChangeEvent = new Emitter<azdata.Account>();
});
test('Construction - Events are properly defined', () => {
// Setup:
// ... Create instantiation service
let instantiationService = createInstantiationService();
// ... Create instance of the service and reder account picker
let service = new AccountPickerService(instantiationService);
service.renderAccountPicker(TypeMoq.It.isAny());
// Then:
// ... All the events for the view models should be properly initialized
assert.notEqual(service.addAccountCompleteEvent, undefined);
assert.notEqual(service.addAccountErrorEvent, undefined);
assert.notEqual(service.addAccountStartEvent, undefined);
assert.notEqual(service.onAccountSelectionChangeEvent, undefined);
// ... All the events should properly fire
let evAddAccountCompleteEvent = new EventVerifierSingle<void>();
service.addAccountCompleteEvent(evAddAccountCompleteEvent.eventHandler);
mockAddAccountCompleteEmitter.fire();
evAddAccountCompleteEvent.assertFired();
let errorMsg = 'Error';
let evAddAccountErrorEvent = new EventVerifierSingle<string>();
service.addAccountErrorEvent(evAddAccountErrorEvent.eventHandler);
mockAddAccountErrorEmitter.fire(errorMsg);
evAddAccountErrorEvent.assertFired(errorMsg);
let evAddAccountStartEvent = new EventVerifierSingle<void>();
service.addAccountStartEvent(evAddAccountStartEvent.eventHandler);
mockAddAccountStartEmitter.fire();
evAddAccountStartEvent.assertFired();
let account = {
key: { providerId: 'azure', accountId: 'account1' },
name: 'Account 1',
displayInfo: {
contextualDisplayName: 'Microsoft Account',
accountType: 'microsoft',
displayName: 'Account 1',
userId: 'user@email.com'
},
properties: [],
isStale: false
};
let evOnAccountSelectionChangeEvent = new EventVerifierSingle<azdata.Account>();
service.onAccountSelectionChangeEvent(evOnAccountSelectionChangeEvent.eventHandler);
mockOnAccountSelectionChangeEvent.fire(account);
evOnAccountSelectionChangeEvent.assertFired(account);
});
});
function createInstantiationService(): InstantiationService {
// Create a mock account picker view model
let providerId = 'azure';
let accountPickerViewModel = new AccountPickerViewModel(providerId, new TestAccountManagementService());
let mockAccountViewModel = TypeMoq.Mock.ofInstance(accountPickerViewModel);
let mockEvent = new Emitter<any>();
mockAccountViewModel.setup(x => x.updateAccountListEvent).returns(() => mockEvent.event);
// Create a mocked out instantiation service
let instantiationService = TypeMoq.Mock.ofType(InstantiationService, TypeMoq.MockBehavior.Strict);
instantiationService.setup(x => x.createInstance(TypeMoq.It.isValue(AccountPickerViewModel), TypeMoq.It.isAny()))
.returns(() => mockAccountViewModel.object);
// Create a mock account picker
let accountPicker = new AccountPicker('provider', new TestThemeService(), instantiationService.object, undefined!);
let mockAccountDialog = TypeMoq.Mock.ofInstance(accountPicker);
mockAccountDialog.setup(x => x.addAccountCompleteEvent)
.returns(() => mockAddAccountCompleteEmitter.event);
mockAccountDialog.setup(x => x.addAccountErrorEvent)
.returns((msg) => mockAddAccountErrorEmitter.event);
mockAccountDialog.setup(x => x.addAccountStartEvent)
.returns(() => mockAddAccountStartEmitter.event);
mockAccountDialog.setup(x => x.onAccountSelectionChangeEvent)
.returns((account) => mockOnAccountSelectionChangeEvent.event);
mockAccountDialog.setup(x => x.render(TypeMoq.It.isAny()))
.returns((container) => undefined);
mockAccountDialog.setup(x => x.createAccountPickerComponent());
instantiationService.setup(x => x.createInstance(TypeMoq.It.isValue(AccountPicker), TypeMoq.It.isAny()))
.returns(() => mockAccountDialog.object);
return instantiationService.object;
}

View File

@@ -1,139 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as TypeMoq from 'typemoq';
import { Emitter } from 'vs/base/common/event';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { AutoOAuthDialog } from 'sql/platform/accounts/browser/autoOAuthDialog';
import { AutoOAuthDialogController } from 'sql/platform/accounts/browser/autoOAuthDialogController';
import { TestAccountManagementService } from 'sql/platform/accounts/test/common/testAccountManagementService';
import { TestErrorMessageService } from 'sql/platform/errorMessage/test/common/testErrorMessageService';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
// TESTS ///////////////////////////////////////////////////////////////////
suite('auto OAuth dialog controller tests', () => {
let instantiationService: TypeMoq.Mock<InstantiationService>;
let mockAutoOAuthDialog: TypeMoq.Mock<AutoOAuthDialog>;
let mockAccountManagementService: TypeMoq.Mock<TestAccountManagementService>;
let mockErrorMessageService: TypeMoq.Mock<TestErrorMessageService>;
let autoOAuthDialogController: AutoOAuthDialogController;
let mockOnCancelEvent: Emitter<void>;
let mockOnAddAccountEvent: Emitter<void>;
let mockOnCloseEvent: Emitter<void>;
let providerId = 'azure';
let title = 'Add Account';
let message = 'This is the dialog description';
let userCode = 'abcde';
let uri = 'uri';
setup(() => {
mockOnCancelEvent = new Emitter<void>();
mockOnAddAccountEvent = new Emitter<void>();
mockOnCloseEvent = new Emitter<void>();
// Create a mock auto OAuth dialog
let autoOAuthDialog = new AutoOAuthDialog(undefined!, undefined!, undefined!, undefined!, new MockContextKeyService(), undefined!, undefined!, undefined!);
mockAutoOAuthDialog = TypeMoq.Mock.ofInstance(autoOAuthDialog);
mockAutoOAuthDialog.setup(x => x.onCancel).returns(() => mockOnCancelEvent.event);
mockAutoOAuthDialog.setup(x => x.onHandleAddAccount).returns(() => mockOnAddAccountEvent.event);
mockAutoOAuthDialog.setup(x => x.onCloseEvent).returns(() => mockOnCloseEvent.event);
mockAutoOAuthDialog.setup(x => x.render());
mockAutoOAuthDialog.setup(x => x.open(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()));
mockAutoOAuthDialog.setup(x => x.close()).callback(() => {
mockOnCloseEvent.fire();
});
// Create a mocked out instantiation service
instantiationService = TypeMoq.Mock.ofType(InstantiationService, TypeMoq.MockBehavior.Strict);
instantiationService.setup(x => x.createInstance(TypeMoq.It.isValue(AutoOAuthDialog)))
.returns(() => mockAutoOAuthDialog.object);
// Create a mocked account management service
let accountManagementTestService = new TestAccountManagementService();
mockAccountManagementService = TypeMoq.Mock.ofInstance(accountManagementTestService);
mockAccountManagementService.setup(x => x.copyUserCodeAndOpenBrowser(TypeMoq.It.isAny(), TypeMoq.It.isAny()));
// Create a mocked error message service
let errorMessageServiceStub = new TestErrorMessageService();
mockErrorMessageService = TypeMoq.Mock.ofInstance(errorMessageServiceStub);
mockErrorMessageService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()));
// Create a mocked auto OAuth dialog controller
autoOAuthDialogController = new AutoOAuthDialogController(instantiationService.object, mockAccountManagementService.object, mockErrorMessageService.object);
});
test('Open auto OAuth when the flyout is already open, return an error', (done) => {
// If: Open auto OAuth dialog first time
autoOAuthDialogController.openAutoOAuthDialog(providerId, title, message, userCode, uri);
// Then: It should open the flyout successfully
mockAutoOAuthDialog.verify(x => x.open(title, message, userCode, uri), TypeMoq.Times.once());
mockErrorMessageService.verify(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never());
// If: a oauth flyout is already open
autoOAuthDialogController.openAutoOAuthDialog(providerId, title, message, userCode, uri)
.then(success => done('Failure: Expected error on 2nd dialog open'), error => done());
// Then: An error dialog should have been opened
mockErrorMessageService.verify(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
});
test('Close auto OAuth dialog successfully', () => {
let title = 'Add Account';
let message = 'This is the dialog description';
let userCode = 'abcde';
let uri = 'uri';
autoOAuthDialogController.openAutoOAuthDialog(providerId, title, message, userCode, uri);
// If: closeAutoOAuthDialog is called
autoOAuthDialogController.closeAutoOAuthDialog();
// Then: it should close the dialog
mockAutoOAuthDialog.verify(x => x.close(), TypeMoq.Times.once());
});
test('Open and close auto OAuth dialog multiple times should work properly', () => {
let title = 'Add Account';
let message = 'This is the dialog description';
let userCode = 'abcde';
let uri = 'uri';
autoOAuthDialogController.openAutoOAuthDialog(providerId, title, message, userCode, uri);
autoOAuthDialogController.closeAutoOAuthDialog();
// If: Open the flyout second time
autoOAuthDialogController.openAutoOAuthDialog(providerId, title, message, userCode, uri);
// Then: It should open the flyout twice successfully
mockAutoOAuthDialog.verify(x => x.open(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(2));
mockErrorMessageService.verify(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never());
});
test('Copy and open button in auto OAuth dialog should work properly', () => {
let title = 'Add Account';
let message = 'This is the dialog description';
let userCode = 'abcde';
let uri = 'uri';
autoOAuthDialogController.openAutoOAuthDialog(providerId, title, message, userCode, uri);
// If: the 'copy & open' button in auto Oauth dialog is selected
mockOnAddAccountEvent.fire();
// Then: copyUserCodeAndOpenBrowser should get called
mockAccountManagementService.verify(x => x.copyUserCodeAndOpenBrowser(userCode, uri), TypeMoq.Times.once());
});
// TODO: Test for cancel button
});

View File

@@ -1,249 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 * as TypeMoq from 'typemoq';
import { Emitter, Event } from 'vs/base/common/event';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { FirewallRuleDialog } from 'sql/platform/accounts/browser/firewallRuleDialog';
import { FirewallRuleViewModel } from 'sql/platform/accounts/common/firewallRuleViewModel';
import { FirewallRuleDialogController } from 'sql/platform/accounts/browser/firewallRuleDialogController';
import { TestAccountManagementService } from 'sql/platform/accounts/test/common/testAccountManagementService';
import { TestResourceProvider } from 'sql/workbench/services/resourceProvider/test/common/testResourceProviderService';
import { TestErrorMessageService } from 'sql/platform/errorMessage/test/common/testErrorMessageService';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { Deferred } from 'sql/base/common/promise';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
// TESTS ///////////////////////////////////////////////////////////////////
suite('Firewall rule dialog controller tests', () => {
let connectionProfile: IConnectionProfile;
let account: azdata.Account;
let IPAddress = '250.222.155.198';
let mockOnAddAccountErrorEvent: Emitter<string>;
let mockOnCreateFirewallRule: Emitter<void>;
let instantiationService: TypeMoq.Mock<InstantiationService>;
let mockFirewallRuleViewModel: TypeMoq.Mock<FirewallRuleViewModel>;
let mockFirewallRuleDialog: TypeMoq.Mock<FirewallRuleDialog>;
setup(() => {
account = {
key: { providerId: 'azure', accountId: 'account1' },
displayInfo: {
contextualDisplayName: 'Microsoft Account',
accountType: 'microsoft',
displayName: 'Account 1',
userId: 'user@email.com'
},
properties: [],
isStale: false
};
mockOnAddAccountErrorEvent = new Emitter<string>();
mockOnCreateFirewallRule = new Emitter<void>();
// Create a mock firewall rule view model
let firewallRuleViewModel = new FirewallRuleViewModel();
mockFirewallRuleViewModel = TypeMoq.Mock.ofInstance(firewallRuleViewModel);
mockFirewallRuleViewModel.setup(x => x.updateDefaultValues(TypeMoq.It.isAny()))
.returns((ipAddress) => undefined);
mockFirewallRuleViewModel.object.selectedAccount = account;
mockFirewallRuleViewModel.object.isIPAddressSelected = true;
// Create a mocked out instantiation service
instantiationService = TypeMoq.Mock.ofType(InstantiationService, TypeMoq.MockBehavior.Strict);
instantiationService.setup(x => x.createInstance(TypeMoq.It.isValue(FirewallRuleViewModel)))
.returns(() => mockFirewallRuleViewModel.object);
// Create a mock account picker
let firewallRuleDialog = new FirewallRuleDialog(undefined!, undefined!, undefined!, instantiationService.object, undefined!, undefined!, new MockContextKeyService(), undefined!, undefined!, undefined!, undefined!);
mockFirewallRuleDialog = TypeMoq.Mock.ofInstance(firewallRuleDialog);
let mockEvent = new Emitter<any>();
mockFirewallRuleDialog.setup(x => x.onCancel)
.returns(() => mockEvent.event);
mockFirewallRuleDialog.setup(x => x.onCreateFirewallRule)
.returns(() => mockOnCreateFirewallRule.event);
mockFirewallRuleDialog.setup(x => x.onAddAccountErrorEvent)
.returns((msg) => mockOnAddAccountErrorEvent.event);
mockFirewallRuleDialog.setup(x => x.render());
mockFirewallRuleDialog.setup(x => x.open());
mockFirewallRuleDialog.setup(x => x.close());
instantiationService.setup(x => x.createInstance(TypeMoq.It.isValue(FirewallRuleDialog)))
.returns(() => mockFirewallRuleDialog.object);
connectionProfile = {
connectionName: 'new name',
serverName: 'new server',
databaseName: 'database',
userName: 'user',
password: 'password',
authenticationType: '',
savePassword: true,
groupFullName: 'g2/g2-2',
groupId: 'group id',
getOptionsKey: () => '',
matches: () => false,
providerName: mssqlProviderName,
options: {},
saveProfile: true,
id: ''
};
});
test('Add Account Failure - Error Message Shown', () => {
// ... Create a mock instance of the error message service
let errorMessageServiceStub = new TestErrorMessageService();
let mockErrorMessageService = TypeMoq.Mock.ofInstance(errorMessageServiceStub);
mockErrorMessageService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()));
// ... Create instance of the controller with an opened dialog
let controller = new FirewallRuleDialogController(instantiationService.object, undefined!, undefined!, mockErrorMessageService.object);
controller.openFirewallRuleDialog(connectionProfile, IPAddress, 'resourceID');
// If: The firewall rule dialog reports a failure
mockOnAddAccountErrorEvent.fire('Error message');
// Then: An error dialog should have been opened
mockErrorMessageService.verify(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
});
test('create firewall rule success', async () => {
let deferredPromise = new Deferred();
mockFirewallRuleDialog.setup(x => x.onServiceComplete())
.callback(() => {
deferredPromise.resolve(true);
});
// ... Create a mock instance of the account management test service
let mockAccountManagementService = getMockAccountManagementService(true);
// ... Create a mock instance of the resource provider
let mockResourceProvider = getMockResourceProvider(true, { result: true, errorMessage: '' });
// ... Create instance of the controller with an opened dialog
let controller = new FirewallRuleDialogController(instantiationService.object, mockResourceProvider.object, mockAccountManagementService.object, undefined!);
controller.openFirewallRuleDialog(connectionProfile, IPAddress, 'resourceID');
// If: The firewall rule dialog's create firewall rule get fired
mockOnCreateFirewallRule.fire();
// Then: it should get security token from account management service and call create firewall rule in resource provider
await deferredPromise;
mockAccountManagementService.verify(x => x.getSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
mockResourceProvider.verify(x => x.createFirewallRule(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
mockFirewallRuleDialog.verify(x => x.close(), TypeMoq.Times.once());
mockFirewallRuleDialog.verify(x => x.onServiceComplete(), TypeMoq.Times.once());
});
test('create firewall rule fails during getSecurity', async () => {
let deferredPromise = new Deferred<{}>();
// ... Create a mock instance of the error message service
let mockErrorMessageService = getMockErrorMessageService(deferredPromise);
// ... Create a mock instance of the account management test service
let mockAccountManagementService = getMockAccountManagementService(false);
// ... Create a mock instance of the resource provider
let mockResourceProvider = getMockResourceProvider(true, { result: true, errorMessage: '' });
// ... Create instance of the controller with an opened dialog
let controller = new FirewallRuleDialogController(instantiationService.object, mockResourceProvider.object, mockAccountManagementService.object, mockErrorMessageService.object);
controller.openFirewallRuleDialog(connectionProfile, IPAddress, 'resourceID');
// If: The firewall rule dialog's create firewall rule get fired
mockOnCreateFirewallRule.fire();
// Then: it should get security token from account management service and an error dialog should have been opened
await deferredPromise;
mockAccountManagementService.verify(x => x.getSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
mockErrorMessageService.verify(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
mockResourceProvider.verify(x => x.createFirewallRule(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never());
});
test('create firewall rule fails during createFirewallRule in ResourceProvider - result is false', async () => {
let deferredPromise = new Deferred<{}>();
// ... Create a mock instance of the error message service
let mockErrorMessageService = getMockErrorMessageService(deferredPromise);
// ... Create a mock instance of the account management test service
let mockAccountManagementService = getMockAccountManagementService(true);
// ... Create a mock instance of the resource provider
let mockResourceProvider = getMockResourceProvider(true, { result: false, errorMessage: '' });
// ... Create instance of the controller with an opened dialog
let controller = new FirewallRuleDialogController(instantiationService.object, mockResourceProvider.object, mockAccountManagementService.object, mockErrorMessageService.object);
controller.openFirewallRuleDialog(connectionProfile, IPAddress, 'resourceID');
// If: The firewall rule dialog's create firewall rule get fired
mockOnCreateFirewallRule.fire();
// Then: it should get security token from account management service and an error dialog should have been opened
await deferredPromise;
mockAccountManagementService.verify(x => x.getSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
mockResourceProvider.verify(x => x.createFirewallRule(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
mockErrorMessageService.verify(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
});
test('create firewall rule fails during createFirewallRule in ResourceProvider - reject promise', async () => {
let deferredPromise = new Deferred<{}>();
// ... Create a mock instance of the error message service
let mockErrorMessageService = getMockErrorMessageService(deferredPromise);
// ... Create a mock instance of the account management test service
let mockAccountManagementService = getMockAccountManagementService(true);
// ... Create a mock instance of the resource provider
let mockResourceProvider = getMockResourceProvider(false);
// ... Create instance of the controller with an opened dialog
let controller = new FirewallRuleDialogController(instantiationService.object, mockResourceProvider.object, mockAccountManagementService.object, mockErrorMessageService.object);
controller.openFirewallRuleDialog(connectionProfile, IPAddress, 'resourceID');
// If: The firewall rule dialog's create firewall rule get fired
mockOnCreateFirewallRule.fire();
// Then: it should get security token from account management service and an error dialog should have been opened
await deferredPromise;
mockAccountManagementService.verify(x => x.getSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
mockResourceProvider.verify(x => x.createFirewallRule(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
mockErrorMessageService.verify(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
});
});
function getMockAccountManagementService(resolveSecurityToken: boolean): TypeMoq.Mock<TestAccountManagementService> {
let accountManagementTestService = new TestAccountManagementService();
let mockAccountManagementService = TypeMoq.Mock.ofInstance(accountManagementTestService);
mockAccountManagementService.setup(x => x.getSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.returns(() => resolveSecurityToken ? Promise.resolve({}) : Promise.reject(null));
return mockAccountManagementService;
}
function getMockResourceProvider(resolveCreateFirewallRule: boolean, response?: azdata.CreateFirewallRuleResponse): TypeMoq.Mock<TestResourceProvider> {
let resourceProviderStub = new TestResourceProvider();
let mockResourceProvider = TypeMoq.Mock.ofInstance(resourceProviderStub);
mockResourceProvider.setup(x => x.createFirewallRule(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.returns(() => resolveCreateFirewallRule ? Promise.resolve(response) : Promise.reject(null));
return mockResourceProvider;
}
function getMockErrorMessageService(deferredPromise: Deferred<{}>): TypeMoq.Mock<TestErrorMessageService> {
let errorMessageServiceStub = new TestErrorMessageService();
let mockErrorMessageService = TypeMoq.Mock.ofInstance(errorMessageServiceStub);
mockErrorMessageService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).callback(() => {
deferredPromise.resolve(true);
});
return mockErrorMessageService;
}

View File

@@ -6,11 +6,11 @@
import * as azdata from 'azdata';
import * as assert from 'assert';
import * as TypeMoq from 'typemoq';
import { EventVerifierSingle } from 'sqltest/utils/eventVerifier';
import { Emitter } from 'vs/base/common/event';
import { AccountPickerViewModel } from 'sql/platform/accounts/common/accountPickerViewModel';
import { UpdateAccountListEventParams } from 'sql/platform/accounts/common/eventTypes';
import { TestAccountManagementService } from 'sql/platform/accounts/test/common/testAccountManagementService';
import { EventVerifierSingle } from 'sql/base/test/common/event';
// SUITE STATE /////////////////////////////////////////////////////////////
let mockUpdateAccountEmitter: Emitter<UpdateAccountListEventParams>;

View File

@@ -6,7 +6,7 @@
import * as assert from 'assert';
import * as azdata from 'azdata';
import AccountStore from 'sql/platform/accounts/common/accountStore';
import { EventVerifierSingle } from 'sqltest/utils/eventVerifier';
import { EventVerifierSingle } from 'sql/base/test/common/event';
suite('Account Store Tests', () => {
test('AddOrUpdate - Uninitialized memento', done => {

View File

@@ -6,11 +6,11 @@
import * as assert from 'assert';
import * as azdata from 'azdata';
import * as TypeMoq from 'typemoq';
import { EventVerifierSingle } from 'sqltest/utils/eventVerifier';
import { Emitter } from 'vs/base/common/event';
import { AccountViewModel } from 'sql/platform/accounts/common/accountViewModel';
import { AccountProviderAddedEventParams, UpdateAccountListEventParams } from 'sql/platform/accounts/common/eventTypes';
import { TestAccountManagementService } from 'sql/platform/accounts/test/common/testAccountManagementService';
import { EventVerifierSingle } from 'sql/base/test/common/event';
// SUITE STATE /////////////////////////////////////////////////////////////
let mockAddProviderEmitter: Emitter<AccountProviderAddedEventParams>;

View File

@@ -6,7 +6,7 @@
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import { IAngularEventingService, IAngularEvent, AngularEventType } from 'sql/platform/angularEventing/common/angularEventingService';
import { IAngularEventingService, IAngularEvent, AngularEventType } from 'sql/platform/angularEventing/browser/angularEventingService';
import { ILogService } from 'vs/platform/log/common/log';
export class AngularEventingService implements IAngularEventingService {

View File

@@ -1,70 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NgModuleRef, PlatformRef, Provider, enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { IInstantiationService, _util } from 'vs/platform/instantiation/common/instantiation';
import { IEditorInput } from 'vs/workbench/common/editor';
import { Trace } from 'vs/platform/instantiation/common/instantiationService';
import { values } from 'vs/base/common/map';
import { IModuleFactory, IBootstrapParams } from 'sql/platform/bootstrap/common/bootstrapParams';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
const selectorCounter = new Map<string, number>();
export function providerIterator(service: IInstantiationService): Provider[] {
return Array.from(values(_util.serviceIds)).map(v => {
let factory = () => {
return (<any>service)._getOrCreateServiceInstance(v, Trace.traceCreation(v));
};
factory.prototype = factory;
return {
provide: v, useFactory: factory
};
});
}
function createUniqueSelector(selector: string): string {
let num: number;
if (selectorCounter.has(selector)) {
num = selectorCounter.get(selector);
} else {
num = 0;
}
selectorCounter.set(selector, num + 1);
return `${selector}_${num}`;
}
let platform: PlatformRef;
export function bootstrapAngular<T>(service: IInstantiationService, moduleType: IModuleFactory<T>, container: HTMLElement, selectorString: string, params: IBootstrapParams, input?: IEditorInput, callbackSetModule?: (value: NgModuleRef<T>) => void): string {
// Create the uniqueSelectorString
let uniqueSelectorString = createUniqueSelector(selectorString);
let selector = document.createElement(uniqueSelectorString);
container.appendChild(selector);
if (!platform) {
service.invokeFunction((accessor) => {
const environmentService = accessor.get(IEnvironmentService);
if (environmentService.isBuilt) {
enableProdMode();
}
});
platform = platformBrowserDynamic();
}
platform.bootstrapModule(moduleType(params, uniqueSelectorString, service)).then(moduleRef => {
if (input) {
input.onDispose(() => {
moduleRef.destroy();
});
}
if (callbackSetModule) {
callbackSetModule(moduleRef);
}
});
return uniqueSelectorString;
}

View File

@@ -1,171 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Node Modules */
import { Injectable, Inject } from '@angular/core';
import { Observable } from 'rxjs/Observable';
/* SQL imports */
import { IDefaultComponentParams, IBootstrapParams } from 'sql/platform/bootstrap/common/bootstrapParams';
import { IMetadataService } from 'sql/platform/metadata/common/metadataService';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo';
import { IAdminService } from 'sql/workbench/services/admin/common/adminService';
import { IQueryManagementService } from 'sql/platform/query/common/queryManagement';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { AngularDisposable } from 'sql/base/browser/lifecycle';
import { ConnectionContextKey } from 'sql/workbench/parts/connection/common/connectionContextKey';
import { ProviderMetadata, DatabaseInfo, SimpleExecuteResult } from 'azdata';
/* VS imports */
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
/* Wrapper for a metadata service that contains the uri string to use on each request */
export class SingleConnectionMetadataService {
constructor(
private _metadataService: IMetadataService,
private _uri: string
) { }
get metadata(): Observable<ProviderMetadata | undefined> {
return Observable.fromPromise(this._metadataService.getMetadata(this._uri));
}
get databaseNames(): Observable<string[]> {
return Observable.fromPromise(this._metadataService.getDatabaseNames(this._uri));
}
}
/* Wrapper for a connection service that contains the uri string to use on each request */
export class SingleConnectionManagementService {
constructor(
private _connectionService: IConnectionManagementService,
private _uri: string,
private _contextKey: ConnectionContextKey
) { }
public changeDatabase(name: string): Promise<boolean> {
return Promise.resolve(this._connectionService.changeDatabase(this._uri, name).then(e => {
// we need to update our context
this._contextKey.set(this.connectionInfo.connectionProfile);
return e;
}));
}
public get connectionInfo(): ConnectionManagementInfo {
return this._connectionService.getConnectionInfo(this._uri);
}
}
export class SingleAdminService {
constructor(
private _adminService: IAdminService,
private _uri: string
) { }
public get databaseInfo(): Observable<DatabaseInfo> {
return Observable.fromPromise(this._adminService.getDatabaseInfo(this._uri));
}
}
export class SingleQueryManagementService {
constructor(
private _queryManagementService: IQueryManagementService,
private _uri: string
) { }
public runQueryAndReturn(queryString: string): Thenable<SimpleExecuteResult> {
return this._queryManagementService.runQueryAndReturn(this._uri, queryString);
}
}
/*
Providers a interface between a UI interface and the rest of carbon.
Stores the uri and unique selector of a UI instance and uses that
whenever a call to a carbon service needs this information, so that the widgets
don't need to be aware of the uri or selector. Simplifies the initialization and
usage of a widget.
*/
@Injectable()
export class CommonServiceInterface extends AngularDisposable {
protected _uri: string;
/* Special Services */
protected _singleMetadataService: SingleConnectionMetadataService;
protected _singleConnectionManagementService: SingleConnectionManagementService;
protected _singleAdminService: SingleAdminService;
protected _singleQueryManagementService: SingleQueryManagementService;
public scopedContextKeyService: IContextKeyService;
protected _connectionContextKey: ConnectionContextKey;
constructor(
@Inject(IBootstrapParams) protected _params: IDefaultComponentParams,
@Inject(IMetadataService) protected _metadataService: IMetadataService,
@Inject(IConnectionManagementService) protected _connectionManagementService: IConnectionManagementService,
@Inject(IAdminService) protected _adminService: IAdminService,
@Inject(IQueryManagementService) protected _queryManagementService: IQueryManagementService
) {
super();
// during testing there may not be params
if (this._params) {
this.scopedContextKeyService = this._params.scopedContextService;
this._connectionContextKey = this._params.connectionContextKey;
this.uri = this._params.ownerUri;
}
}
public get metadataService(): SingleConnectionMetadataService {
return this._singleMetadataService;
}
public get connectionManagementService(): SingleConnectionManagementService {
return this._singleConnectionManagementService;
}
public get adminService(): SingleAdminService {
return this._singleAdminService;
}
public get queryManagementService(): SingleQueryManagementService {
return this._singleQueryManagementService;
}
protected setUri(uri: string) {
this._uri = uri;
this._singleMetadataService = new SingleConnectionMetadataService(this._metadataService, this._uri);
this._singleConnectionManagementService = new SingleConnectionManagementService(this._connectionManagementService, this._uri, this._connectionContextKey);
this._singleAdminService = new SingleAdminService(this._adminService, this._uri);
this._singleQueryManagementService = new SingleQueryManagementService(this._queryManagementService, this._uri);
}
/**
* Set the uri for this instance, should only be set once
* Inits all the services that depend on knowing a uri
*/
protected set uri(uri: string) {
this.setUri(uri);
}
protected get uri(): string {
return this._uri;
}
/**
* Gets the underlying Uri
* In general don't use this, use specific services instances exposed publicly
*/
public getUnderlyingUri(): string {
return this._uri;
}
public getOriginalConnectionProfile(): IConnectionProfile {
return this._params.connection;
}
}

View File

@@ -1,53 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DataService } from 'sql/workbench/parts/grid/common/dataService';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ConnectionContextKey } from 'sql/workbench/parts/connection/common/connectionContextKey';
import { Event } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export interface IQueryComponentParams extends IBootstrapParams {
dataService: DataService;
onSaveViewState: Event<void>;
onRestoreViewState: Event<void>;
}
export interface IEditDataComponentParams extends IBootstrapParams {
dataService: DataService;
onSaveViewState: Event<void>;
onRestoreViewState: Event<void>;
}
export interface IDefaultComponentParams extends IBootstrapParams {
connection: IConnectionProfile;
ownerUri: string;
scopedContextService: IContextKeyService;
connectionContextKey: ConnectionContextKey;
}
export interface IDashboardComponentParams extends IDefaultComponentParams {
}
export interface ITaskDialogComponentParams extends IBootstrapParams {
ownerUri: string;
}
export interface IQueryPlanParams extends IBootstrapParams {
planXml: string;
}
export const ISelector = 'selector';
export const IBootstrapParams = 'bootstrap_params';
export interface IBootstrapParams {
}
export interface Type<T> {
new(...args: any[]): T;
}
export type IModuleFactory<T> = (params: IBootstrapParams, selector: string, service: IInstantiationService) => Type<T>;

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
import * as azdata from 'azdata';
import { Event } from 'vs/base/common/event';
@@ -19,6 +17,12 @@ export const clientCapabilities = {
hostVersion: HOST_VERSION
};
export interface ConnectionProviderProperties {
providerId: string;
displayName: string;
connectionOptions: azdata.ConnectionOption[];
}
export interface ProviderFeatures {
connection: ConnectionProviderProperties;
}

View File

@@ -1,154 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { Memento } from 'vs/workbench/common/memento';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { Registry } from 'vs/platform/registry/common/platform';
import * as azdata from 'azdata';
import { entries } from 'sql/base/common/objects';
import { toObject } from 'sql/base/common/map';
import { ConnectionProviderProperties, IConnectionProviderRegistry, Extensions as ConnectionExtensions } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
import { ICapabilitiesService, ProviderFeatures, clientCapabilities } from 'sql/platform/capabilities/common/capabilitiesService';
const connectionRegistry = Registry.as<IConnectionProviderRegistry>(ConnectionExtensions.ConnectionProviderContributions);
interface ConnectionCache {
[id: string]: ConnectionProviderProperties;
}
interface CapabilitiesMomento {
connectionProviderCache: ConnectionCache;
}
/**
* Capabilities service implementation class. This class provides the ability
* to discover the DMP capabilities that a DMP provider offers.
*/
export class CapabilitiesService extends Disposable implements ICapabilitiesService {
_serviceBrand: undefined;
private _momento: Memento;
private _providers = new Map<string, ProviderFeatures>();
private _featureUpdateEvents = new Map<string, Emitter<ProviderFeatures>>();
private _legacyProviders = new Map<string, azdata.DataProtocolServerCapabilities>();
private _onCapabilitiesRegistered = this._register(new Emitter<ProviderFeatures>());
public readonly onCapabilitiesRegistered = this._onCapabilitiesRegistered.event;
constructor(
@IStorageService private _storageService: IStorageService,
@IExtensionService extensionService: IExtensionService,
@IExtensionManagementService extensionManagementService: IExtensionManagementService
) {
super();
this._momento = new Memento('capabilities', this._storageService);
if (!this.capabilities.connectionProviderCache) {
this.capabilities.connectionProviderCache = {};
}
// handle in case some extensions have already registered (unlikley)
entries(connectionRegistry.providers).map(v => {
this.handleConnectionProvider({ id: v[0], properties: v[1] });
});
// register for when new extensions are added
this._register(connectionRegistry.onNewProvider(this.handleConnectionProvider, this));
// handle adding already known capabilities (could have caching problems)
entries(this.capabilities.connectionProviderCache).map(v => {
this.handleConnectionProvider({ id: v[0], properties: v[1] }, false);
});
extensionService.whenInstalledExtensionsRegistered().then(() => {
this.cleanupProviders();
});
_storageService.onWillSaveState(() => this.shutdown());
this._register(extensionManagementService.onDidUninstallExtension(({ identifier }) => {
const connectionProvider = 'connectionProvider';
extensionService.getExtensions().then(i => {
let extension = i.find(c => c.identifier.value.toLowerCase() === identifier.id.toLowerCase());
if (extension && extension.contributes
&& extension.contributes[connectionProvider]
&& extension.contributes[connectionProvider].providerId) {
let id = extension.contributes[connectionProvider].providerId;
delete this.capabilities.connectionProviderCache[id];
}
});
}));
}
private cleanupProviders(): void {
let knownProviders = Object.keys(connectionRegistry.providers);
for (let key in this.capabilities.connectionProviderCache) {
if (!knownProviders.includes(key)) {
this._providers.delete(key);
delete this.capabilities.connectionProviderCache[key];
}
}
}
private handleConnectionProvider(e: { id: string, properties: ConnectionProviderProperties }, isNew = true): void {
let provider = this._providers.get(e.id);
if (provider) {
provider.connection = e.properties;
} else {
provider = {
connection: e.properties
};
this._providers.set(e.id, provider);
}
if (!this._featureUpdateEvents.has(e.id)) {
this._featureUpdateEvents.set(e.id, new Emitter<ProviderFeatures>());
}
if (isNew) {
this.capabilities.connectionProviderCache[e.id] = e.properties;
this._onCapabilitiesRegistered.fire(provider);
}
}
/**
* Retrieve a list of registered server capabilities
*/
public getCapabilities(provider: string): ProviderFeatures | undefined {
return this._providers.get(provider);
}
public getLegacyCapabilities(provider: string): azdata.DataProtocolServerCapabilities | undefined {
return this._legacyProviders.get(provider);
}
public get providers(): { [id: string]: ProviderFeatures } {
return toObject(this._providers);
}
private get capabilities(): CapabilitiesMomento {
return this._momento.getMemento(StorageScope.GLOBAL) as CapabilitiesMomento;
}
/**
* Register the capabilities provider and query the provider for its capabilities
*/
public registerProvider(provider: azdata.CapabilitiesProvider): void {
// request the capabilities from server
provider.getServerCapabilities(clientCapabilities).then(serverCapabilities => {
this._legacyProviders.set(serverCapabilities.providerName, serverCapabilities);
});
}
private shutdown(): void {
this._momento.saveMemento();
}
}

View File

@@ -10,8 +10,7 @@ import { IConnectionProfileGroup, ConnectionProfileGroup } from 'sql/platform/co
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo';
import { IServerGroupDialogCallbacks } from 'sql/platform/serverGroup/common/serverGroupController';
import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
import { ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService';
/**
* Options for the actions that could happen after connecting is complete

View File

@@ -9,8 +9,7 @@ import { isString } from 'vs/base/common/types';
import * as azdata from 'azdata';
import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
import * as Constants from 'sql/platform/connection/common/constants';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
import { ICapabilitiesService, ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService';
type SettableProperty = 'serverName' | 'authenticationType' | 'databaseName' | 'password' | 'connectionName' | 'userName';

View File

@@ -1,992 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TestConnectionDialogService } from 'sql/workbench/services/connection/test/common/testConnectionDialogService';
import { ConnectionManagementService } from 'sql/platform/connection/browser/connectionManagementService';
import { ConnectionStatusManager } from 'sql/platform/connection/common/connectionStatusManager';
import { ConnectionStore } from 'sql/platform/connection/common/connectionStore';
import {
INewConnectionParams, ConnectionType,
IConnectionCompletionOptions, IConnectionResult,
RunQueryOnConnectionMode
} from 'sql/platform/connection/common/connectionManagement';
import * as Constants from 'sql/platform/connection/common/constants';
import * as Utils from 'sql/platform/connection/common/utils';
import { IHandleFirewallRuleResult } from 'sql/workbench/services/resourceProvider/common/resourceProviderService';
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 * as azdata from 'azdata';
import * as assert from 'assert';
import * as TypeMoq from 'typemoq';
import { IConnectionProfileGroup, ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { TestAccountManagementService } from 'sql/platform/accounts/test/common/testAccountManagementService';
import { TestStorageService, TestEnvironmentService, TestEditorService } from 'vs/workbench/test/workbenchTestServices';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { NullLogService } from 'vs/platform/log/common/log';
suite('SQL ConnectionManagementService tests', () => {
let capabilitiesService: TestCapabilitiesService;
let connectionDialogService: TypeMoq.Mock<TestConnectionDialogService>;
let connectionStore: TypeMoq.Mock<ConnectionStore>;
let workbenchEditorService: TypeMoq.Mock<TestEditorService>;
let connectionStatusManager: ConnectionStatusManager;
let mssqlConnectionProvider: TypeMoq.Mock<TestConnectionProvider>;
let workspaceConfigurationServiceMock: TypeMoq.Mock<TestConfigurationService>;
let resourceProviderStubMock: TypeMoq.Mock<TestResourceProvider>;
let accountManagementService: TypeMoq.Mock<TestAccountManagementService>;
let none: void;
let connectionProfile: IConnectionProfile = {
connectionName: 'new name',
serverName: 'new server',
databaseName: 'database',
userName: 'user',
password: 'password',
authenticationType: 'integrated',
savePassword: true,
groupFullName: 'g2/g2-2',
groupId: 'group id',
getOptionsKey: () => { return 'connectionId'; },
matches: undefined,
providerName: 'MSSQL',
options: {},
saveProfile: true,
id: undefined
};
let connectionProfileWithEmptySavedPassword: IConnectionProfile =
Object.assign({}, connectionProfile, { password: '', serverName: connectionProfile.serverName + 1 });
let connectionProfileWithEmptyUnsavedPassword: IConnectionProfile =
Object.assign({}, connectionProfile, { password: '', serverName: connectionProfile.serverName + 2, savePassword: false });
let connectionManagementService: ConnectionManagementService;
let configResult: { [key: string]: any } = {};
configResult['defaultEngine'] = 'MSSQL';
let handleFirewallRuleResult: IHandleFirewallRuleResult;
let resolveHandleFirewallRuleDialog: boolean;
let isFirewallRuleAdded: boolean;
setup(() => {
capabilitiesService = new TestCapabilitiesService();
connectionDialogService = TypeMoq.Mock.ofType(TestConnectionDialogService);
connectionStore = TypeMoq.Mock.ofType(ConnectionStore, TypeMoq.MockBehavior.Loose, new TestStorageService());
workbenchEditorService = TypeMoq.Mock.ofType(TestEditorService);
connectionStatusManager = new ConnectionStatusManager(capabilitiesService, new NullLogService(), TestEnvironmentService, new TestNotificationService());
mssqlConnectionProvider = TypeMoq.Mock.ofType(TestConnectionProvider);
let resourceProviderStub = new TestResourceProvider();
resourceProviderStubMock = TypeMoq.Mock.ofInstance(resourceProviderStub);
accountManagementService = TypeMoq.Mock.ofType(TestAccountManagementService);
let root = new ConnectionProfileGroup(ConnectionProfileGroup.RootGroupName, undefined, ConnectionProfileGroup.RootGroupName, undefined, undefined);
root.connections = [ConnectionProfile.fromIConnectionProfile(capabilitiesService, connectionProfile)];
connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), undefined, undefined)).returns(() => Promise.resolve(none));
connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), undefined, TypeMoq.It.isAny())).returns(() => Promise.resolve(none));
connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), undefined, undefined, TypeMoq.It.isAny())).returns(() => Promise.resolve(none));
connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), undefined, undefined, undefined)).returns(() => Promise.resolve(none));
connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), undefined)).returns(() => Promise.resolve(none));
connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(none));
connectionStore.setup(x => x.addRecentConnection(TypeMoq.It.isAny())).returns(() => Promise.resolve());
connectionStore.setup(x => x.saveProfile(TypeMoq.It.isAny())).returns(() => Promise.resolve(connectionProfile));
workbenchEditorService.setup(x => x.openEditor(undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined));
connectionStore.setup(x => x.addSavedPassword(TypeMoq.It.is<IConnectionProfile>(
c => c.serverName === connectionProfile.serverName))).returns(() => Promise.resolve({ profile: connectionProfile, savedCred: true }));
connectionStore.setup(x => x.addSavedPassword(TypeMoq.It.is<IConnectionProfile>(
c => c.serverName === connectionProfileWithEmptySavedPassword.serverName))).returns(
() => Promise.resolve({ profile: connectionProfileWithEmptySavedPassword, savedCred: true }));
connectionStore.setup(x => x.addSavedPassword(TypeMoq.It.is<IConnectionProfile>(
c => c.serverName === connectionProfileWithEmptyUnsavedPassword.serverName))).returns(
() => Promise.resolve({ profile: connectionProfileWithEmptyUnsavedPassword, savedCred: false }));
connectionStore.setup(x => x.isPasswordRequired(TypeMoq.It.isAny())).returns(() => true);
connectionStore.setup(x => x.getConnectionProfileGroups(false, undefined)).returns(() => [root]);
connectionStore.setup(x => x.savePassword(TypeMoq.It.isAny())).returns(() => Promise.resolve(true));
mssqlConnectionProvider.setup(x => x.connect(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => undefined);
// Setup resource provider
handleFirewallRuleResult = {
canHandleFirewallRule: false,
ipAddress: '123.123.123.123',
resourceProviderId: 'Azure'
};
resourceProviderStubMock.setup(x => x.handleFirewallRule(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.returns(() => Promise.resolve(handleFirewallRuleResult));
resolveHandleFirewallRuleDialog = true;
isFirewallRuleAdded = true;
resourceProviderStubMock.setup(x => x.showFirewallRuleDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.returns(() => {
if (resolveHandleFirewallRuleDialog) {
return isFirewallRuleAdded ? Promise.resolve(true) : Promise.resolve(false);
} else {
return Promise.reject(null).then();
}
});
// Setup configuration to return a config that can be modified later.
workspaceConfigurationServiceMock = TypeMoq.Mock.ofType(TestConfigurationService);
workspaceConfigurationServiceMock.setup(x => x.getValue(Constants.sqlConfigSectionName))
.returns(() => configResult);
connectionManagementService = createConnectionManagementService();
connectionManagementService.registerProvider('MSSQL', mssqlConnectionProvider.object);
});
function createConnectionManagementService(): ConnectionManagementService {
let connectionManagementService = new ConnectionManagementService(
connectionStore.object,
undefined,
connectionDialogService.object,
undefined, // IInstantiationService
workbenchEditorService.object,
undefined, // ITelemetryService
workspaceConfigurationServiceMock.object,
capabilitiesService,
undefined, // IQuickInputService
new TestNotificationService(),
resourceProviderStubMock.object,
undefined, // IAngularEventingService
accountManagementService.object,
new NullLogService(), // ILogService
undefined, // IStorageService
TestEnvironmentService
);
return connectionManagementService;
}
function verifyShowConnectionDialog(connectionProfile: IConnectionProfile, connectionType: ConnectionType, uri: string, options: boolean, connectionResult?: IConnectionResult, didShow: boolean = true): void {
if (connectionProfile) {
connectionDialogService.verify(x => x.showDialog(
TypeMoq.It.isAny(),
TypeMoq.It.is<INewConnectionParams>(p => p.connectionType === connectionType && (uri === undefined || p.input.uri === uri)),
TypeMoq.It.is<IConnectionProfile>(c => c !== undefined && c.serverName === connectionProfile.serverName),
connectionResult ? TypeMoq.It.is<IConnectionResult>(r => r.errorMessage === connectionResult.errorMessage && r.callStack === connectionResult.callStack) : undefined,
options ? TypeMoq.It.isAny() : undefined),
didShow ? TypeMoq.Times.once() : TypeMoq.Times.never());
} else {
connectionDialogService.verify(x => x.showDialog(
TypeMoq.It.isAny(),
TypeMoq.It.is<INewConnectionParams>(p => p.connectionType === connectionType && ((uri === undefined && p.input === undefined) || p.input.uri === uri)),
undefined,
connectionResult ? TypeMoq.It.is<IConnectionResult>(r => r.errorMessage === connectionResult.errorMessage && r.callStack === connectionResult.callStack) : undefined,
options ? TypeMoq.It.isAny() : undefined),
didShow ? TypeMoq.Times.once() : TypeMoq.Times.never());
}
}
function verifyShowFirewallRuleDialog(connectionProfile: IConnectionProfile, didShow: boolean = true): void {
resourceProviderStubMock.verify(x => x.showFirewallRuleDialog(
TypeMoq.It.is<IConnectionProfile>(c => c.serverName === connectionProfile.serverName),
TypeMoq.It.isAny(),
TypeMoq.It.isAny()),
didShow ? TypeMoq.Times.once() : TypeMoq.Times.never());
}
function verifyOptions(options?: IConnectionCompletionOptions, fromDialog?: boolean): void {
if (options) {
if (options.saveTheConnection) {
connectionStore.verify(x => x.saveProfile(TypeMoq.It.isAny()), TypeMoq.Times.once());
}
if (options.showDashboard) {
workbenchEditorService.verify(x => x.openEditor(undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
}
}
if (fromDialog !== undefined && !fromDialog) {
connectionStore.verify(x => x.addSavedPassword(TypeMoq.It.isAny()), TypeMoq.Times.once());
}
}
function connect(uri: string, options?: IConnectionCompletionOptions, fromDialog?: boolean, connection?: IConnectionProfile, error?: string, errorCode?: number, errorCallStack?: string): Promise<IConnectionResult> {
let connectionToUse = connection ? connection : connectionProfile;
return new Promise<IConnectionResult>((resolve, reject) => {
let id = connectionToUse.getOptionsKey();
let defaultUri = 'connection:' + (id ? id : connectionToUse.serverName + ':' + connectionToUse.databaseName);
connectionManagementService.onConnectionRequestSent(() => {
let info: azdata.ConnectionInfoSummary = {
connectionId: error ? undefined : 'id',
connectionSummary: {
databaseName: connectionToUse.databaseName,
serverName: connectionToUse.serverName,
userName: connectionToUse.userName
},
errorMessage: error,
errorNumber: errorCode,
messages: errorCallStack,
ownerUri: uri ? uri : defaultUri,
serverInfo: undefined
};
connectionManagementService.onConnectionComplete(0, info);
});
connectionManagementService.cancelConnectionForUri(uri).then(() => {
if (fromDialog) {
resolve(connectionManagementService.connectAndSaveProfile(connectionToUse, uri, options));
} else {
resolve(connectionManagementService.connect(connectionToUse, uri, options));
}
});
});
}
test('showConnectionDialog should open the dialog with default type given no parameters', done => {
connectionManagementService.showConnectionDialog().then(() => {
verifyShowConnectionDialog(undefined, ConnectionType.default, undefined, false);
done();
}).catch(err => {
done(err);
});
});
test('showConnectionDialog should open the dialog with given type given valid input', done => {
let params: INewConnectionParams = {
connectionType: ConnectionType.editor,
input: {
onConnectReject: undefined,
onConnectStart: undefined,
onDisconnect: undefined,
onConnectSuccess: undefined,
onConnectCanceled: undefined,
uri: 'Editor Uri'
},
runQueryOnCompletion: RunQueryOnConnectionMode.executeQuery
};
connectionManagementService.showConnectionDialog(params).then(() => {
verifyShowConnectionDialog(undefined, params.connectionType, params.input.uri, false);
done();
}).catch(err => {
done(err);
});
});
test('showConnectionDialog should pass the model to the dialog if there is a model assigned to the uri', done => {
let params: INewConnectionParams = {
connectionType: ConnectionType.editor,
input: {
onConnectReject: undefined,
onConnectStart: undefined,
onDisconnect: undefined,
onConnectSuccess: undefined,
onConnectCanceled: undefined,
uri: 'Editor Uri'
},
runQueryOnCompletion: RunQueryOnConnectionMode.executeQuery
};
connect(params.input.uri).then(() => {
let saveConnection = connectionManagementService.getConnectionProfile(params.input.uri);
assert.notEqual(saveConnection, undefined, `profile was not added to the connections`);
assert.equal(saveConnection.serverName, connectionProfile.serverName, `Server names are different`);
connectionManagementService.showConnectionDialog(params).then(() => {
verifyShowConnectionDialog(connectionProfile, params.connectionType, params.input.uri, false);
done();
}).catch(err => {
done(err);
});
}, err => done(err));
});
test('connect should save profile given options with saveProfile set to true', done => {
let uri: string = 'Editor Uri';
let options: IConnectionCompletionOptions = {
params: undefined,
saveTheConnection: true,
showDashboard: false,
showConnectionDialogOnError: false,
showFirewallRuleOnError: true
};
connect(uri, options).then(() => {
verifyOptions(options);
done();
}).catch(err => {
done(err);
});
});
test('getDefaultProviderId is MSSQL', done => {
let defaultProvider = connectionManagementService.getDefaultProviderId();
assert.equal(defaultProvider, 'MSSQL', `Default provider is not equal to MSSQL`);
done();
});
/* Andresse 10/5/17 commented this test out since it was only working before my changes by the chance of how Promises work
If we want to continue to test this, the connection logic needs to be rewritten to actually wait for everything to be done before it resolves */
// test('connect should show dashboard given options with showDashboard set to true', done => {
// let uri: string = 'Editor Uri';
// let options: IConnectionCompletionOptions = {
// params: undefined,
// saveTheConnection: false,
// showDashboard: true,
// showConnectionDialogOnError: false
// };
// connect(uri, options).then(() => {
// verifyOptions(options);
// done();
// }).catch(err => {
// done(err);
// });
// });
test('connect should pass the params in options to onConnectSuccess callback', done => {
let uri: string = 'Editor Uri';
let paramsInOnConnectSuccess: INewConnectionParams;
let options: IConnectionCompletionOptions = {
params: {
connectionType: ConnectionType.editor,
input: {
onConnectSuccess: (params?: INewConnectionParams) => {
paramsInOnConnectSuccess = params;
},
onConnectReject: undefined,
onConnectStart: undefined,
onDisconnect: undefined,
onConnectCanceled: undefined,
uri: uri
},
querySelection: undefined,
runQueryOnCompletion: RunQueryOnConnectionMode.none
},
saveTheConnection: true,
showDashboard: false,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true
};
connect(uri, options).then(() => {
verifyOptions(options);
assert.notEqual(paramsInOnConnectSuccess, undefined);
assert.equal(paramsInOnConnectSuccess.connectionType, options.params.connectionType);
done();
}).catch(err => {
done(err);
});
});
test('connectAndSaveProfile should show not load the password', done => {
let uri: string = 'Editor Uri';
let options: IConnectionCompletionOptions = undefined;
connect(uri, options, true).then(() => {
verifyOptions(options, true);
done();
}).catch(err => {
done(err);
});
});
test('connect with undefined uri and options should connect using the default uri', done => {
let uri = undefined;
let options: IConnectionCompletionOptions = undefined;
connect(uri, options).then(() => {
assert.equal(connectionManagementService.isProfileConnected(connectionProfile), true);
done();
}).catch(err => {
done(err);
});
});
test('failed connection should open the dialog if connection fails', done => {
let uri = undefined;
let error: string = 'error';
let errorCode: number = 111;
let errorCallStack: string = 'error call stack';
let expectedConnection: boolean = false;
let options: IConnectionCompletionOptions = {
params: undefined,
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true
};
let connectionResult: IConnectionResult = {
connected: expectedConnection,
errorMessage: error,
errorCode: errorCode,
callStack: errorCallStack
};
connect(uri, options, false, connectionProfile, error, errorCode, errorCallStack).then(result => {
assert.equal(result.connected, expectedConnection);
assert.equal(result.errorMessage, connectionResult.errorMessage);
verifyShowFirewallRuleDialog(connectionProfile, false);
verifyShowConnectionDialog(connectionProfile, ConnectionType.default, uri, true, connectionResult);
done();
}).catch(err => {
done(err);
});
});
test('failed connection should not open the dialog if the option is set to false even if connection fails', done => {
let uri = undefined;
let error: string = 'error when options set to false';
let errorCode: number = 111;
let errorCallStack: string = 'error call stack';
let expectedConnection: boolean = false;
let options: IConnectionCompletionOptions = {
params: undefined,
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: false,
showFirewallRuleOnError: true
};
let connectionResult: IConnectionResult = {
connected: expectedConnection,
errorMessage: error,
errorCode: errorCode,
callStack: errorCallStack
};
connect(uri, options, false, connectionProfile, error, errorCode, errorCallStack).then(result => {
assert.equal(result.connected, expectedConnection);
assert.equal(result.errorMessage, connectionResult.errorMessage);
verifyShowFirewallRuleDialog(connectionProfile, false);
verifyShowConnectionDialog(connectionProfile, ConnectionType.default, uri, true, connectionResult, false);
done();
}).catch(err => {
done(err);
});
});
test('failed firewall rule should open the firewall rule dialog', done => {
handleFirewallRuleResult.canHandleFirewallRule = true;
resolveHandleFirewallRuleDialog = true;
isFirewallRuleAdded = true;
let uri = undefined;
let error: string = 'error';
let errorCode: number = 111;
let expectedConnection: boolean = false;
let expectedError: string = error;
let options: IConnectionCompletionOptions = {
params: undefined,
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true
};
connect(uri, options, false, connectionProfile, error, errorCode).then(result => {
assert.equal(result.connected, expectedConnection);
assert.equal(result.errorMessage, expectedError);
verifyShowFirewallRuleDialog(connectionProfile, true);
done();
}).catch(err => {
done(err);
});
});
test('failed firewall rule connection should not open the firewall rule dialog if the option is set to false even if connection fails', done => {
handleFirewallRuleResult.canHandleFirewallRule = true;
resolveHandleFirewallRuleDialog = true;
isFirewallRuleAdded = true;
let uri = undefined;
let error: string = 'error when options set to false';
let errorCallStack: string = 'error call stack';
let errorCode: number = 111;
let expectedConnection: boolean = false;
let options: IConnectionCompletionOptions = {
params: undefined,
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: false,
showFirewallRuleOnError: false
};
let connectionResult: IConnectionResult = {
connected: expectedConnection,
errorMessage: error,
errorCode: errorCode,
callStack: errorCallStack
};
connect(uri, options, false, connectionProfile, error, errorCode, errorCallStack).then(result => {
assert.equal(result.connected, expectedConnection);
assert.equal(result.errorMessage, connectionResult.errorMessage);
verifyShowFirewallRuleDialog(connectionProfile, false);
verifyShowConnectionDialog(connectionProfile, ConnectionType.default, uri, true, connectionResult, false);
done();
}).catch(err => {
done(err);
});
});
test('failed firewall rule connection and failed during open firewall rule should open the firewall rule dialog and connection dialog with error', done => {
handleFirewallRuleResult.canHandleFirewallRule = true;
resolveHandleFirewallRuleDialog = false;
isFirewallRuleAdded = true;
let uri = undefined;
let error: string = 'error when options set to false';
let errorCode: number = 111;
let errorCallStack: string = 'error call stack';
let expectedConnection: boolean = false;
let options: IConnectionCompletionOptions = {
params: undefined,
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true
};
let connectionResult: IConnectionResult = {
connected: expectedConnection,
errorMessage: error,
errorCode: errorCode,
callStack: errorCallStack
};
connect(uri, options, false, connectionProfile, error, errorCode, errorCallStack).then(result => {
assert.equal(result.connected, expectedConnection);
assert.equal(result.errorMessage, connectionResult.errorMessage);
verifyShowFirewallRuleDialog(connectionProfile, true);
verifyShowConnectionDialog(connectionProfile, ConnectionType.default, uri, true, connectionResult, true);
done();
}).catch(err => {
done(err);
});
});
test('failed firewall rule connection should open the firewall rule dialog. Then canceled firewall rule dialog should not open connection dialog', done => {
handleFirewallRuleResult.canHandleFirewallRule = true;
resolveHandleFirewallRuleDialog = true;
isFirewallRuleAdded = false;
let uri = undefined;
let error: string = 'error when options set to false';
let errorCallStack: string = 'error call stack';
let errorCode: number = 111;
let expectedConnection: boolean = false;
let options: IConnectionCompletionOptions = {
params: undefined,
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true
};
let connectionResult: IConnectionResult = {
connected: expectedConnection,
errorMessage: error,
errorCode: errorCode,
callStack: errorCallStack
};
connect(uri, options, false, connectionProfile, error, errorCode, errorCallStack).then(result => {
assert.equal(result, undefined);
verifyShowFirewallRuleDialog(connectionProfile, true);
verifyShowConnectionDialog(connectionProfile, ConnectionType.default, uri, true, connectionResult, false);
done();
}).catch(err => {
done(err);
});
});
test('connect when password is empty and unsaved should open the dialog', done => {
let uri = undefined;
let expectedConnection: boolean = false;
let options: IConnectionCompletionOptions = {
params: undefined,
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true
};
let connectionResult: IConnectionResult = {
connected: expectedConnection,
errorMessage: undefined,
errorCode: undefined,
callStack: undefined
};
connect(uri, options, false, connectionProfileWithEmptyUnsavedPassword).then(result => {
assert.equal(result.connected, expectedConnection);
assert.equal(result.errorMessage, connectionResult.errorMessage);
verifyShowConnectionDialog(connectionProfileWithEmptyUnsavedPassword, ConnectionType.default, uri, true, connectionResult);
verifyShowFirewallRuleDialog(connectionProfile, false);
done();
}).catch(err => {
done(err);
});
});
test('connect when password is empty and saved should not open the dialog', done => {
let uri = undefined;
let expectedConnection: boolean = true;
let options: IConnectionCompletionOptions = {
params: undefined,
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true
};
let connectionResult: IConnectionResult = {
connected: expectedConnection,
errorMessage: undefined,
errorCode: undefined,
callStack: undefined
};
connect(uri, options, false, connectionProfileWithEmptySavedPassword).then(result => {
assert.equal(result.connected, expectedConnection);
assert.equal(result.errorMessage, connectionResult.errorMessage);
verifyShowConnectionDialog(connectionProfileWithEmptySavedPassword, ConnectionType.default, uri, true, connectionResult, false);
done();
}).catch(err => {
done(err);
});
});
test('connect from editor when empty password when it is required and saved should not open the dialog', done => {
let uri = 'editor 3';
let expectedConnection: boolean = true;
let options: IConnectionCompletionOptions = {
params: {
connectionType: ConnectionType.editor,
input: {
onConnectSuccess: undefined,
onConnectReject: undefined,
onConnectStart: undefined,
onDisconnect: undefined,
onConnectCanceled: undefined,
uri: uri
},
querySelection: undefined,
runQueryOnCompletion: RunQueryOnConnectionMode.none
},
saveTheConnection: true,
showDashboard: false,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true
};
let connectionResult: IConnectionResult = {
connected: expectedConnection,
errorMessage: undefined,
errorCode: undefined,
callStack: undefined
};
connect(uri, options, false, connectionProfileWithEmptySavedPassword).then(result => {
assert.equal(result.connected, expectedConnection);
assert.equal(result.errorMessage, connectionResult.errorMessage);
verifyShowConnectionDialog(connectionProfileWithEmptySavedPassword, ConnectionType.editor, uri, true, connectionResult, false);
done();
}).catch(err => {
done(err);
});
});
test('doChangeLanguageFlavor should throw on unknown provider', done => {
// given a provider that will never exist
let invalidProvider = 'notaprovider';
// when I call doChangeLanguageFlavor
// Then I expect it to throw
assert.throws(() => connectionManagementService.doChangeLanguageFlavor('file://my.sql', 'sql', invalidProvider));
done();
});
test('doChangeLanguageFlavor should send event for known provider', done => {
// given a provider that is registered
let uri = 'file://my.sql';
let language = 'sql';
let flavor = 'MSSQL';
// when I call doChangeLanguageFlavor
try {
let called = false;
connectionManagementService.onLanguageFlavorChanged((changeParams: azdata.DidChangeLanguageFlavorParams) => {
called = true;
assert.equal(changeParams.uri, uri);
assert.equal(changeParams.language, language);
assert.equal(changeParams.flavor, flavor);
});
connectionManagementService.doChangeLanguageFlavor(uri, language, flavor);
assert.ok(called, 'expected onLanguageFlavorChanged event to be sent');
done();
} catch (error) {
done(error);
}
});
test('ensureDefaultLanguageFlavor should not send event if uri is connected', done => {
let uri: string = 'Editor Uri';
let options: IConnectionCompletionOptions = {
params: undefined,
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: false,
showFirewallRuleOnError: true
};
let connectionManagementService = createConnectionManagementService();
let called = false;
connectionManagementService.onLanguageFlavorChanged((changeParams: azdata.DidChangeLanguageFlavorParams) => {
called = true;
});
connect(uri, options).then(() => {
connectionManagementService.ensureDefaultLanguageFlavor(uri);
assert.equal(called, false, 'do not expect flavor change to be called');
done();
}).catch(err => {
done(err);
});
});
test('getConnectionId returns the URI associated with a connection that has had its database filled in', done => {
// Set up the connection management service with a connection corresponding to a default database
let dbName = 'master';
let serverName = 'test_server';
let userName = 'test_user';
let connectionProfileWithoutDb: IConnectionProfile = Object.assign(connectionProfile,
{ serverName: serverName, databaseName: '', userName: userName, getOptionsKey: () => undefined });
let connectionProfileWithDb: IConnectionProfile = Object.assign(connectionProfileWithoutDb, { databaseName: dbName });
// Save the database with a URI that has the database name filled in, to mirror Carbon's behavior
let ownerUri = Utils.generateUri(connectionProfileWithDb);
connect(ownerUri, undefined, false, connectionProfileWithoutDb).then(() => {
try {
// If I get the URI for the connection with or without a database from the connection management service
let actualUriWithDb = connectionManagementService.getConnectionUri(connectionProfileWithDb);
let actualUriWithoutDb = connectionManagementService.getConnectionUri(connectionProfileWithoutDb);
// Then the retrieved URIs should match the one on the connection
let expectedUri = Utils.generateUri(connectionProfileWithoutDb);
assert.equal(actualUriWithDb, expectedUri);
assert.equal(actualUriWithoutDb, expectedUri);
done();
} catch (err) {
done(err);
}
}, err => done(err));
});
test('getTabColorForUri returns undefined when there is no connection for the given URI', () => {
let connectionManagementService = createConnectionManagementService();
let color = connectionManagementService.getTabColorForUri('invalidUri');
assert.equal(color, undefined);
});
test('getTabColorForUri returns the group color corresponding to the connection for a URI', done => {
// Set up the connection store to give back a group for the expected connection profile
configResult['tabColorMode'] = 'border';
let expectedColor = 'red';
connectionStore.setup(x => x.getGroupFromId(connectionProfile.groupId)).returns(() => <IConnectionProfileGroup>{
color: expectedColor
});
let uri = 'testUri';
connect(uri).then(() => {
try {
let tabColor = connectionManagementService.getTabColorForUri(uri);
assert.equal(tabColor, expectedColor);
done();
} catch (e) {
done(e);
}
}, err => done(err));
});
test('getActiveConnectionCredentials returns the credentials dictionary for a connection profile', () => {
let profile = Object.assign({}, connectionProfile);
profile.options = { password: profile.password };
profile.id = 'test_id';
connectionStatusManager.addConnection(profile, 'test_uri');
(connectionManagementService as any)._connectionStatusManager = connectionStatusManager;
let credentials = connectionManagementService.getActiveConnectionCredentials(profile.id);
assert.equal(credentials['password'], profile.options['password']);
});
test('getConnectionUriFromId returns a URI of an active connection with the given id', () => {
let profile = Object.assign({}, connectionProfile);
profile.options = { password: profile.password };
profile.id = 'test_id';
let uri = 'test_initial_uri';
connectionStatusManager.addConnection(profile, uri);
(connectionManagementService as any)._connectionStatusManager = connectionStatusManager;
// If I call getConnectionUriFromId on the given connection
let foundUri = connectionManagementService.getConnectionUriFromId(profile.id);
// Then the returned URI matches the connection's original URI
assert.equal(foundUri, uri);
});
test('getConectionUriFromId returns undefined if the given connection is not active', () => {
let profile = Object.assign({}, connectionProfile);
profile.options = { password: profile.password };
profile.id = 'test_id';
connectionStatusManager.addConnection(profile, Utils.generateUri(profile));
(connectionManagementService as any)._connectionStatusManager = connectionStatusManager;
// If I call getConnectionUriFromId with a different URI than the connection's
let foundUri = connectionManagementService.getConnectionUriFromId('different_id');
// Then undefined is returned
assert.equal(foundUri, undefined);
});
test('addSavedPassword fills in Azure access tokens for Azure accounts', async () => {
// Set up a connection profile that uses Azure
let azureConnectionProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, connectionProfile);
azureConnectionProfile.authenticationType = 'AzureMFA';
let username = 'testuser@microsoft.com';
azureConnectionProfile.userName = username;
let servername = 'test-database.database.windows.net';
azureConnectionProfile.serverName = servername;
// Set up the account management service to return a token for the given user
accountManagementService.setup(x => x.getAccountsForProvider(TypeMoq.It.isAny())).returns(providerId => Promise.resolve<azdata.Account[]>([
{
key: {
accountId: username,
providerId: providerId
},
displayInfo: undefined,
isStale: false,
properties: undefined
}
]));
let testToken = 'testToken';
accountManagementService.setup(x => x.getSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({
azurePublicCloud: {
token: testToken
}
}));
connectionStore.setup(x => x.addSavedPassword(TypeMoq.It.is(profile => profile.authenticationType === 'AzureMFA'))).returns(profile => Promise.resolve({
profile: profile,
savedCred: false
}));
// If I call addSavedPassword
let profileWithCredentials = await connectionManagementService.addSavedPassword(azureConnectionProfile);
// Then the returned profile has the account token set
assert.equal(profileWithCredentials.userName, username);
assert.equal(profileWithCredentials.options['azureAccountToken'], testToken);
});
test('addSavedPassword fills in Azure access token for selected tenant', async () => {
// Set up a connection profile that uses Azure
let azureConnectionProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, connectionProfile);
azureConnectionProfile.authenticationType = 'AzureMFA';
let username = 'testuser@microsoft.com';
azureConnectionProfile.userName = username;
let servername = 'test-database.database.windows.net';
azureConnectionProfile.serverName = servername;
let azureTenantId = 'testTenant';
azureConnectionProfile.azureTenantId = azureTenantId;
// Set up the account management service to return a token for the given user
accountManagementService.setup(x => x.getAccountsForProvider(TypeMoq.It.isAny())).returns(providerId => Promise.resolve<azdata.Account[]>([
{
key: {
accountId: username,
providerId: providerId
},
displayInfo: undefined,
isStale: false,
properties: undefined
}
]));
let testToken = 'testToken';
let returnedTokens = {};
returnedTokens['azurePublicCloud'] = { token: 'badToken' };
returnedTokens[azureTenantId] = { token: testToken };
accountManagementService.setup(x => x.getSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(returnedTokens));
connectionStore.setup(x => x.addSavedPassword(TypeMoq.It.is(profile => profile.authenticationType === 'AzureMFA'))).returns(profile => Promise.resolve({
profile: profile,
savedCred: false
}));
// If I call addSavedPassword
let profileWithCredentials = await connectionManagementService.addSavedPassword(azureConnectionProfile);
// Then the returned profile has the account token set corresponding to the requested tenant
assert.equal(profileWithCredentials.userName, username);
assert.equal(profileWithCredentials.options['azureAccountToken'], testToken);
});
test('getConnections test', () => {
const connectionStatusManagerMock = TypeMoq.Mock.ofType(ConnectionStatusManager, TypeMoq.MockBehavior.Loose);
const connectionStoreMock = TypeMoq.Mock.ofType(ConnectionStore, TypeMoq.MockBehavior.Loose, new TestStorageService());
connectionStatusManagerMock.setup(x => x.getActiveConnectionProfiles(undefined)).returns(() => {
return [createConnectionProfile('1'), createConnectionProfile('2')];
});
connectionStoreMock.setup(x => x.getRecentlyUsedConnections(undefined)).returns(() => {
return [createConnectionProfile('1'), createConnectionProfile('3')];
});
const group1 = createConnectionGroup('group1');
const group2 = createConnectionGroup('group2');
group1.connections = [createConnectionProfile('1'), createConnectionProfile('4')];
group1.children = [group2];
group2.connections = [createConnectionProfile('5'), createConnectionProfile('6')];
connectionStoreMock.setup(x => x.getConnectionProfileGroups(TypeMoq.It.isAny(), undefined)).returns(() => {
return [group1];
});
const connectionManagementService = new ConnectionManagementService(connectionStoreMock.object, connectionStatusManagerMock.object, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
// dupe connections have been seeded the numbers below already reflected the de-duped results
const verifyConnections = (actualConnections: ConnectionProfile[], expectedConnectionIds: string[], scenario: string) => {
assert.equal(actualConnections.length, expectedConnectionIds.length, 'incorrect number of connections returned, ' + scenario);
assert.deepEqual(actualConnections.map(conn => conn.id).sort(), expectedConnectionIds.sort(), 'connections do not match expectation, ' + scenario);
};
// no parameter - default to false
let connections = connectionManagementService.getConnections();
verifyConnections(connections, ['1', '2', '3', '4', '5', '6'], 'no parameter provided');
// explicitly set to false
connections = connectionManagementService.getConnections(false);
verifyConnections(connections, ['1', '2', '3', '4', '5', '6'], 'parameter is false');
// active connections only
connections = connectionManagementService.getConnections(true);
verifyConnections(connections, ['1', '2'], 'parameter is true');
});
});
function createConnectionProfile(id: string): ConnectionProfile {
const capabilitiesService = new TestCapabilitiesService();
return new ConnectionProfile(capabilitiesService, {
connectionName: 'newName',
savePassword: false,
groupFullName: 'testGroup',
serverName: 'testServerName',
databaseName: 'testDatabaseName',
authenticationType: Constants.integrated,
password: 'test',
userName: 'testUsername',
groupId: undefined,
providerName: Constants.mssqlProviderName,
options: {},
saveProfile: true,
id: id
});
}
function createConnectionGroup(id: string): ConnectionProfileGroup {
return new ConnectionProfileGroup(id, undefined, id, undefined, undefined);
}

View File

@@ -9,8 +9,8 @@ import * as azdata from 'azdata';
import * as assert from 'assert';
import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService';
import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService';
suite('SQL ConnectionProfileInfo tests', () => {
let msSQLCapabilities: ConnectionProviderProperties;

View File

@@ -12,12 +12,12 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService';
import { TestCredentialsService } from 'sql/platform/credentials/test/common/testCredentialsService';
import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService';
import { deepClone, deepFreeze } from 'vs/base/common/objects';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { TestStorageService } from 'vs/workbench/test/workbenchTestServices';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService';
import { InMemoryStorageService } from 'vs/platform/storage/common/storage';
suite('ConnectionStore', () => {
let defaultNamedProfile: IConnectionProfile = deepFreeze({
@@ -142,7 +142,7 @@ suite('ConnectionStore', () => {
// Given 5 is the max # creds
const numCreds = 6;
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();
@@ -169,7 +169,7 @@ suite('ConnectionStore', () => {
});
test('getRecentlyUsedConnections should return connection for given provider', () => {
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();
const connectionStore = new ConnectionStore(storageService, configurationService,
@@ -181,7 +181,7 @@ suite('ConnectionStore', () => {
test('addActiveConnection should add same connection exactly once', async () => {
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();
@@ -204,7 +204,7 @@ suite('ConnectionStore', () => {
// Setup credential store to capture credentials sent to it
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();
@@ -251,7 +251,7 @@ suite('ConnectionStore', () => {
});
test('can clear connections list', async () => {
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();
@@ -268,7 +268,7 @@ suite('ConnectionStore', () => {
});
test('isPasswordRequired should return true for MSSQL SqlLogin', () => {
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();
@@ -279,7 +279,7 @@ suite('ConnectionStore', () => {
});
test('isPasswordRequired should return true for MSSQL SqlLogin for connection profile object', () => {
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();
@@ -291,7 +291,7 @@ suite('ConnectionStore', () => {
});
test('isPasswordRequired should return false if the password is not required in capabilities', () => {
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();
@@ -318,7 +318,7 @@ suite('ConnectionStore', () => {
});
test('saveProfile should save the password after the profile is saved', async () => {
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();
@@ -335,7 +335,7 @@ suite('ConnectionStore', () => {
});
test('getGroupFromId returns undefined when there is no group with the given ID', () => {
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();
@@ -346,7 +346,7 @@ suite('ConnectionStore', () => {
});
test('getGroupFromId returns the group that has the given ID', () => {
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();
@@ -384,7 +384,7 @@ suite('ConnectionStore', () => {
});
test('getProfileWithoutPassword can return the profile without credentials in the password property or options dictionary', () => {
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();
@@ -402,7 +402,7 @@ suite('ConnectionStore', () => {
});
test('addPassword gets the password from the credentials service', async () => {
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();
@@ -422,7 +422,7 @@ suite('ConnectionStore', () => {
});
test('getConnectionProfileGroups', async () => {
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();
@@ -459,7 +459,7 @@ suite('ConnectionStore', () => {
});
test('removing connection correctly removes', async () => {
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();
@@ -484,7 +484,7 @@ suite('ConnectionStore', () => {
});
test('getRecentlyUsedConnections correctly fills in group names', async () => {
const storageService = new TestStorageService();
const storageService = new InMemoryStorageService();
const configurationService = new TestConfigurationService();
const credentialsService = new TestCredentialsService();

View File

@@ -11,7 +11,7 @@ import { ConnectionProfile } from 'sql/platform/connection/common/connectionProf
import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo';
import * as azdata from 'azdata';
import { Event, Emitter } from 'vs/base/common/event';
import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
import { ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService';
// Test stubs for commonly used objects

View File

@@ -10,10 +10,11 @@ import * as Utils from 'sql/platform/connection/common/utils';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { NullLogService } from 'vs/platform/log/common/log';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
let connections: ConnectionStatusManager;
let capabilitiesService: TestCapabilitiesService;
@@ -78,7 +79,9 @@ suite('SQL ConnectionStatusManager tests', () => {
setup(() => {
capabilitiesService = new TestCapabilitiesService();
connectionProfileObject = new ConnectionProfile(capabilitiesService, connectionProfile);
connections = new ConnectionStatusManager(capabilitiesService, new NullLogService(), TestEnvironmentService, new TestNotificationService());
const environmentService = new EnvironmentService(parseArgs(process.argv, OPTIONS), process.execPath);
connections = new ConnectionStatusManager(capabilitiesService, new NullLogService(), environmentService, new TestNotificationService());
connection1Id = Utils.generateUri(connectionProfile);
connection2Id = 'connection2Id';
connection3Id = 'connection3Id';

View File

@@ -1,183 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 { IConfigurationRegistry, Extensions as ConfigurationExtension } from 'vs/platform/configuration/common/configurationRegistry';
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import * as nls from 'vs/nls';
import { IExtensionPointUser, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ProviderProperties } from 'sql/workbench/parts/dashboard/browser/widgets/properties/propertiesWidget.component';
import { DATABASE_DASHBOARD_TABS } from 'sql/workbench/parts/dashboard/browser/pages/databaseDashboardPage.contribution';
import { SERVER_DASHBOARD_TABS } from 'sql/workbench/parts/dashboard/browser/pages/serverDashboardPage.contribution';
import { DASHBOARD_CONFIG_ID, DASHBOARD_TABS_KEY_PROPERTY } from 'sql/workbench/parts/dashboard/browser/pages/dashboardPageContribution';
export const Extensions = {
DashboardContributions: 'dashboard.contributions'
};
export interface IDashboardTab {
id: string;
title: string;
provider: string | string[];
publisher: string;
description?: string;
container?: object;
when?: string;
alwaysShow?: boolean;
isHomeTab?: boolean;
}
export interface IDashboardRegistry {
registerDashboardProvider(id: string, properties: ProviderProperties): void;
getProperties(id: string): ProviderProperties;
registerTab(tab: IDashboardTab): void;
tabs: Array<IDashboardTab>;
}
class DashboardRegistry implements IDashboardRegistry {
private _properties = new Map<string, ProviderProperties>();
private _tabs = new Array<IDashboardTab>();
private _configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtension.Configuration);
private _dashboardTabContentSchemaProperties: IJSONSchemaMap = {};
/**
* Register a dashboard widget
* @param id id of the widget
*/
public registerDashboardProvider(id: string, properties: ProviderProperties): void {
this._properties.set(id, properties);
}
public getProperties(id: string): ProviderProperties {
return this._properties.get(id);
}
public registerTab(tab: IDashboardTab): void {
this._tabs.push(tab);
let dashboardConfig = this._configurationRegistry.getConfigurations().find(c => c.id === DASHBOARD_CONFIG_ID);
if (dashboardConfig) {
let dashboardDatabaseTabProperty = (<IJSONSchema>dashboardConfig.properties[DATABASE_DASHBOARD_TABS].items).properties[DASHBOARD_TABS_KEY_PROPERTY];
dashboardDatabaseTabProperty.enum.push(tab.id);
dashboardDatabaseTabProperty.enumDescriptions.push(tab.description || '');
let dashboardServerTabProperty = (<IJSONSchema>dashboardConfig.properties[SERVER_DASHBOARD_TABS].items).properties[DASHBOARD_TABS_KEY_PROPERTY];
dashboardServerTabProperty.enum.push(tab.id);
dashboardServerTabProperty.enumDescriptions.push(tab.description || '');
this._configurationRegistry.notifyConfigurationSchemaUpdated(dashboardConfig);
}
}
public get tabs(): Array<IDashboardTab> {
return this._tabs;
}
}
const dashboardRegistry = new DashboardRegistry();
Registry.add(Extensions.DashboardContributions, dashboardRegistry);
export function registerTab(tab: IDashboardTab): void {
dashboardRegistry.registerTab(tab);
}
const dashboardPropertiesPropertyContrib: IJSONSchema = {
description: nls.localize('dashboard.properties.property', "Defines a property to show on the dashboard"),
type: 'object',
properties: {
displayName: {
description: nls.localize('dashboard.properties.property.displayName', "What value to use as a label for the property"),
type: 'string'
},
value: {
description: nls.localize('dashboard.properties.property.value', "What value in the object to access for the value"),
type: 'string'
},
ignore: {
description: nls.localize('dashboard.properties.property.ignore', "Specify values to be ignored"),
type: 'array',
items: { type: 'string' }
},
default: {
description: nls.localize('dashboard.properties.property.default', "Default value to show if ignored or no value"),
type: 'string'
}
}
};
const dashboardPropertyFlavorContrib: IJSONSchema = {
description: nls.localize('dashboard.properties.flavor', "A flavor for defining dashboard properties"),
type: 'object',
properties: {
id: {
description: nls.localize('dashboard.properties.flavor.id', "Id of the flavor"),
type: 'string'
},
condition: {
description: nls.localize('dashboard.properties.flavor.condition', "Condition to use this flavor"),
type: 'object',
properties: {
field: {
description: nls.localize('dashboard.properties.flavor.condition.field', "Field to compare to"),
type: 'string'
},
operator: {
description: nls.localize('dashboard.properties.flavor.condition.operator', "Which operator to use for comparison"),
type: 'string',
enum: ['==', '<=', '>=', '!=']
},
value: {
description: nls.localize('dashboard.properties.flavor.condition.value', "Value to compare the field to"),
type: ['string', 'boolean']
}
}
},
databaseProperties: {
description: nls.localize('dashboard.properties.databaseProperties', "Properties to show for database page"),
type: 'array',
items: dashboardPropertiesPropertyContrib
},
serverProperties: {
description: nls.localize('dashboard.properties.serverProperties', "Properties to show for server page"),
type: 'array',
items: dashboardPropertiesPropertyContrib
}
}
};
const dashboardContrib: IJSONSchema = {
description: nls.localize('carbon.extension.dashboard', "Defines that this provider supports the dashboard"),
type: 'object',
properties: {
provider: {
description: nls.localize('dashboard.id', "Provider id (ex. MSSQL)"),
type: 'string'
},
flavors: {
description: nls.localize('dashboard.properties', "Property values to show on dashboard"),
type: 'array',
items: dashboardPropertyFlavorContrib
}
}
};
ExtensionsRegistry.registerExtensionPoint<ProviderProperties | ProviderProperties[]>({ extensionPoint: 'dashboard', jsonSchema: dashboardContrib }).setHandler(extensions => {
function handleCommand(contrib: ProviderProperties, extension: IExtensionPointUser<any>) {
dashboardRegistry.registerDashboardProvider(contrib.provider, contrib);
}
for (let extension of extensions) {
const { value } = extension;
if (Array.isArray<ProviderProperties>(value)) {
for (let command of value) {
handleCommand(command, extension);
}
} else {
handleCommand(value, extension);
}
}
});

View File

@@ -1,49 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DialogModal } from 'sql/platform/dialog/browser/dialogModal';
import { WizardModal } from 'sql/platform/dialog/browser/wizardModal';
import { Dialog, Wizard } from 'sql/platform/dialog/common/dialogTypes';
import { IModalOptions } from 'sql/workbench/browser/modal/modal';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
const defaultOptions: IModalOptions = { hasBackButton: false, isWide: false, hasErrors: true };
const defaultWizardOptions: IModalOptions = { hasBackButton: false, isWide: true, hasErrors: true };
export class CustomDialogService {
private _dialogModals = new Map<Dialog, DialogModal>();
private _wizardModals = new Map<Wizard, WizardModal>();
constructor(@IInstantiationService private _instantiationService: IInstantiationService) { }
public showDialog(dialog: Dialog, dialogName?: string, options?: IModalOptions): void {
let name = dialogName ? dialogName : 'CustomDialog';
let dialogModal = this._instantiationService.createInstance(DialogModal, dialog, name, options || defaultOptions);
this._dialogModals.set(dialog, dialogModal);
dialogModal.render();
dialogModal.open();
}
public showWizard(wizard: Wizard, options?: IModalOptions): void {
let wizardModal = this._instantiationService.createInstance(WizardModal, wizard, 'WizardPage', options || defaultWizardOptions);
this._wizardModals.set(wizard, wizardModal);
wizardModal.render();
wizardModal.open();
}
public closeDialog(dialog: Dialog): void {
let dialogModal = this._dialogModals.get(dialog);
if (dialogModal) {
dialogModal.cancel();
}
}
public closeWizard(wizard: Wizard): void {
let wizardModal = this._wizardModals.get(wizard);
if (wizardModal) {
wizardModal.cancel();
}
}
}

View File

@@ -1,79 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/dialogModal';
import { forwardRef, NgModule, ComponentFactoryResolver, Inject, ApplicationRef } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CommonModule, APP_BASE_HREF } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { DialogContainer } from 'sql/platform/dialog/browser/dialogContainer.component';
import { WizardNavigation } from 'sql/platform/dialog/browser/wizardNavigation.component';
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/browser/modelComponentRegistry';
import { ModelViewContent } from 'sql/workbench/browser/modelComponents/modelViewContent.component';
import { ModelComponentWrapper } from 'sql/workbench/browser/modelComponents/modelComponentWrapper.component';
import { ComponentHostDirective } from 'sql/workbench/parts/dashboard/browser/core/componentHost.directive';
import { providerIterator } from 'sql/platform/bootstrap/browser/bootstrapService';
import { CommonServiceInterface } from 'sql/platform/bootstrap/browser/commonServiceInterface.service';
import { EditableDropDown } from 'sql/platform/browser/editableDropdown/editableDropdown.component';
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
import { SelectBox } from 'sql/platform/browser/selectBox/selectBox.component';
import { InputBox } from 'sql/platform/browser/inputbox/inputBox.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { IBootstrapParams, ISelector } from 'sql/platform/bootstrap/common/bootstrapParams';
export const DialogModule = (params, selector: string, instantiationService: IInstantiationService): any => {
/* Model-backed components */
let extensionComponents = Registry.as<IComponentRegistry>(Extensions.ComponentContribution).getAllCtors();
@NgModule({
declarations: [
Checkbox,
SelectBox,
EditableDropDown,
InputBox,
DialogContainer,
WizardNavigation,
ModelViewContent,
ModelComponentWrapper,
ComponentHostDirective,
...extensionComponents
],
entryComponents: [DialogContainer, WizardNavigation, ...extensionComponents],
imports: [
FormsModule,
CommonModule,
BrowserModule
],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
CommonServiceInterface,
{ provide: IBootstrapParams, useValue: params },
{ provide: ISelector, useValue: selector },
...providerIterator(instantiationService)
]
})
class ModuleClass {
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
@Inject(ISelector) private selector: string
) {
}
ngDoBootstrap(appRef: ApplicationRef) {
let componentClass = this.selector.startsWith(WizardNavigation.SELECTOR) ? WizardNavigation : DialogContainer;
const factoryWrapper: any = this._resolver.resolveComponentFactory<WizardNavigation | DialogContainer>(componentClass);
factoryWrapper.factory.selector = this.selector;
appRef.bootstrap(factoryWrapper);
}
}
return ModuleClass;
};

View File

@@ -1,77 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/dialogModal';
import { Component, ViewChild, Inject, forwardRef, ElementRef, AfterViewInit } from '@angular/core';
import { ModelViewContent } from 'sql/workbench/browser/modelComponents/modelViewContent.component';
import { DialogPane } from 'sql/platform/dialog/browser/dialogPane';
import { ComponentEventType } from 'sql/workbench/browser/modelComponents/interfaces';
import { Event, Emitter } from 'vs/base/common/event';
import { IBootstrapParams } from 'sql/platform/bootstrap/common/bootstrapParams';
export interface LayoutRequestParams {
modelViewId?: string;
alwaysRefresh?: boolean;
}
export interface DialogComponentParams extends IBootstrapParams {
modelViewId: string;
validityChangedCallback: (valid: boolean) => void;
onLayoutRequested: Event<LayoutRequestParams>;
dialogPane: DialogPane;
}
@Component({
selector: 'dialog-modelview-container',
providers: [],
template: `
<div class="dialogContainer" *ngIf="_dialogPane && _dialogPane.displayPageTitle">
<div class="dialogModal-wizardHeader" *ngIf="_dialogPane && _dialogPane.displayPageTitle">
<div *ngIf="_dialogPane.pageNumber" class="wizardPageNumber">Step {{_dialogPane.pageNumber}}</div>
<h1 class="wizardPageTitle" role="alert">{{_dialogPane.title}}</h1>
<div *ngIf="_dialogPane.description">{{_dialogPane.description}}</div>
</div>
<div style="flex: 1 1 auto; position: relative;">
<modelview-content [modelViewId]="modelViewId" style="width: 100%; height: 100%; position: absolute;">
</modelview-content>
</div>
</div>
<modelview-content [modelViewId]="modelViewId" *ngIf="!_dialogPane || !_dialogPane.displayPageTitle">
</modelview-content>
`
})
export class DialogContainer implements AfterViewInit {
private _onResize = new Emitter<void>();
public readonly onResize: Event<void> = this._onResize.event;
private _dialogPane: DialogPane;
public modelViewId: string;
@ViewChild(ModelViewContent) private _modelViewContent: ModelViewContent;
constructor(
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
@Inject(IBootstrapParams) private _params: DialogComponentParams) {
this.modelViewId = this._params.modelViewId;
this._params.onLayoutRequested(layoutParams => {
if (layoutParams && (layoutParams.alwaysRefresh || layoutParams.modelViewId === this.modelViewId)) {
this.layout();
}
});
this._dialogPane = this._params.dialogPane;
}
ngAfterViewInit(): void {
this._modelViewContent.onEvent(event => {
if (event.isRootComponent && event.eventType === ComponentEventType.validityChanged) {
this._params.validityChangedCallback(event.args);
}
});
let element = <HTMLElement>this._el.nativeElement;
element.style.height = '100%';
element.style.width = '100%';
}
public layout(): void {
this._modelViewContent.layout();
}
}

View File

@@ -1,175 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/dialogModal';
import { Modal, IModalOptions } from 'sql/workbench/browser/modal/modal';
import { attachModalDialogStyler } from 'sql/platform/theme/common/styler';
import { Dialog, DialogButton } from 'sql/platform/dialog/common/dialogTypes';
import { DialogPane } from 'sql/platform/dialog/browser/dialogPane';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
import { Button } from 'vs/base/browser/ui/button/button';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { Emitter } from 'vs/base/common/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { DialogMessage } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { append, $ } from 'vs/base/browser/dom';
import { ILogService } from 'vs/platform/log/common/log';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
export class DialogModal extends Modal {
private _dialogPane: DialogPane;
private _onDone = new Emitter<void>();
private _onCancel = new Emitter<void>();
// Buttons
private _cancelButton: Button;
private _doneButton: Button;
constructor(
private _dialog: Dialog,
name: string,
options: IModalOptions,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IClipboardService clipboardService: IClipboardService,
@ILogService logService: ILogService,
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
super(_dialog.title, name, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, options);
}
public layout(): void {
this._dialogPane.layout();
}
public render() {
super.render();
attachModalDialogStyler(this, this._themeService);
if (this.backButton) {
this.backButton.onDidClick(() => this.cancel());
attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND });
}
if (this._dialog.customButtons) {
this._dialog.customButtons.forEach(button => {
let buttonElement = this.addDialogButton(button);
this.updateButtonElement(buttonElement, button);
});
}
this._doneButton = this.addDialogButton(this._dialog.okButton, () => this.done(), false, true);
this._dialog.okButton.registerClickEvent(this._onDone.event);
this._dialog.onValidityChanged(valid => {
this._doneButton.enabled = valid && this._dialog.okButton.enabled;
});
this._cancelButton = this.addDialogButton(this._dialog.cancelButton, () => this.cancel(), false);
this._dialog.cancelButton.registerClickEvent(this._onCancel.event);
let messageChangeHandler = (message: DialogMessage) => {
if (message && message.text) {
this.setError(message.text, message.level, message.description);
} else {
this.setError('');
}
};
messageChangeHandler(this._dialog.message);
this._dialog.onMessageChange(message => messageChangeHandler(message));
}
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true, requireDialogValid: boolean = false): Button {
let buttonElement = this.addFooterButton(button.label, onSelect, button.position);
buttonElement.enabled = button.enabled;
if (registerClickEvent) {
button.registerClickEvent(buttonElement.onDidClick);
}
button.onUpdate(() => {
this.updateButtonElement(buttonElement, button, requireDialogValid);
});
attachButtonStyler(buttonElement, this._themeService);
this.updateButtonElement(buttonElement, button, requireDialogValid);
return buttonElement;
}
private updateButtonElement(buttonElement: Button, dialogButton: DialogButton, requireDialogValid: boolean = false) {
buttonElement.label = dialogButton.label;
buttonElement.enabled = requireDialogValid ? dialogButton.enabled && this._dialog.valid : dialogButton.enabled;
dialogButton.hidden ? buttonElement.element.parentElement.classList.add('dialogModal-hidden') : buttonElement.element.parentElement.classList.remove('dialogModal-hidden');
}
protected renderBody(container: HTMLElement): void {
const body = append(container, $('div.dialogModal-body'));
this._dialogPane = new DialogPane(this._dialog.title, this._dialog.content,
valid => this._dialog.notifyValidityChanged(valid), this._instantiationService, this._themeService, false);
this._dialogPane.createBody(body);
}
public open(): void {
this.show();
}
public async done(): Promise<void> {
if (this._doneButton.enabled) {
let buttonSpinnerHandler = setTimeout(() => {
this._doneButton.enabled = false;
this._doneButton.element.innerHTML = '&nbsp';
this._doneButton.element.classList.add('validating');
}, 100);
if (await this._dialog.validateClose()) {
this._onDone.fire();
this.dispose();
this.hide();
}
clearTimeout(buttonSpinnerHandler);
this._doneButton.element.classList.remove('validating');
this.updateButtonElement(this._doneButton, this._dialog.okButton, true);
}
}
public cancel(): void {
this._onCancel.fire();
this.dispose();
this.hide();
}
protected hide(): void {
super.hide();
}
protected show(): void {
super.show();
}
/**
* Overridable to change behavior of escape key
*/
protected onClose(e: StandardKeyboardEvent) {
this.cancel();
}
/**
* Overridable to change behavior of enter key
*/
protected onAccept(e: StandardKeyboardEvent) {
this.done();
}
public dispose(): void {
super.dispose();
this._dialogPane.dispose();
}
}

View File

@@ -1,177 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/dialogModal';
import { NgModuleRef } from '@angular/core';
import { IModalDialogStyles } from 'sql/workbench/browser/modal/modal';
import { DialogTab } from 'sql/platform/dialog/common/dialogTypes';
import { TabbedPanel } from 'sql/base/browser/ui/panel/panel';
import { bootstrapAngular } from 'sql/platform/bootstrap/browser/bootstrapService';
import { DialogModule } from 'sql/platform/dialog/browser/dialog.module';
import { DialogComponentParams, LayoutRequestParams } from 'sql/platform/dialog/browser/dialogContainer.component';
import * as DOM from 'vs/base/browser/dom';
import { IThemable } from 'vs/platform/theme/common/styler';
import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Emitter } from 'vs/base/common/event';
import { attachTabbedPanelStyler } from 'sql/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
export class DialogPane extends Disposable implements IThemable {
private _tabbedPanel: TabbedPanel;
private _moduleRefs: NgModuleRef<{}>[] = [];
// Validation
private _modelViewValidityMap = new Map<string, boolean>();
private _body: HTMLElement;
private _selectedTabIndex: number = 0; //TODO: can be an option
private _onLayoutChange = new Emitter<LayoutRequestParams>();
private _selectedTabContent: string;
public pageNumber?: number;
constructor(
public title: string,
private _content: string | DialogTab[],
private _validityChangedCallback: (valid: boolean) => void,
private _instantiationService: IInstantiationService,
private _themeService: IThemeService,
public displayPageTitle: boolean,
public description?: string,
) {
super();
}
public createBody(container: HTMLElement): HTMLElement {
this._body = DOM.append(container, DOM.$('div.dialogModal-pane'));
if (typeof this._content === 'string' || this._content.length < 2) {
let modelViewId = typeof this._content === 'string' ? this._content : this._content[0].content;
this.initializeModelViewContainer(this._body, modelViewId);
} else {
this._tabbedPanel = new TabbedPanel(this._body);
attachTabbedPanelStyler(this._tabbedPanel, this._themeService);
this._content.forEach((tab, tabIndex) => {
if (this._selectedTabIndex === tabIndex) {
this._selectedTabContent = tab.content;
}
let tabContainer = document.createElement('div');
tabContainer.style.display = 'none';
this._body.appendChild(tabContainer);
this.initializeModelViewContainer(tabContainer, tab.content, tab);
this._tabbedPanel.onTabChange(e => {
tabContainer.style.height = (this.getTabDimension().height - this._tabbedPanel.headersize) + 'px';
this._onLayoutChange.fire({ modelViewId: tab.content });
});
this._tabbedPanel.pushTab({
title: tab.title,
identifier: 'dialogPane.' + this.title + '.' + tabIndex,
view: {
render: (container) => {
if (tabContainer.parentElement === this._body) {
this._body.removeChild(tabContainer);
}
container.appendChild(tabContainer);
tabContainer.style.display = 'block';
},
layout: (dimension) => { this.getTabDimension(); },
focus: () => { this.focus(); }
}
});
});
}
return this._body;
}
private getTabDimension(): DOM.Dimension {
return new DOM.Dimension(DOM.getContentWidth(this._body) - 5, DOM.getContentHeight(this._body) - 5);
}
public layout(alwaysRefresh: boolean = false): void {
let layoutParams: LayoutRequestParams = {
alwaysRefresh: alwaysRefresh,
modelViewId: this._selectedTabContent
};
if (this._tabbedPanel) {
this._tabbedPanel.layout(this.getTabDimension());
this._onLayoutChange.fire(layoutParams);
} else if (alwaysRefresh) {
this._onLayoutChange.fire(layoutParams);
}
}
/**
* Bootstrap angular for the dialog's model view controller with the given model view ID
*/
private initializeModelViewContainer(bodyContainer: HTMLElement, modelViewId: string, tab?: DialogTab) {
bootstrapAngular(this._instantiationService,
DialogModule,
bodyContainer,
'dialog-modelview-container',
{
modelViewId: modelViewId,
validityChangedCallback: (valid: boolean) => {
this._setValidity(modelViewId, valid);
if (tab) {
tab.notifyValidityChanged(valid);
}
},
onLayoutRequested: this._onLayoutChange.event,
dialogPane: this
} as DialogComponentParams,
undefined,
(moduleRef) => {
return this._moduleRefs.push(moduleRef);
});
}
public show(focus: boolean = false): void {
this._body.classList.remove('dialogModal-hidden');
if (focus) {
this.focus();
}
}
public hide(): void {
this._body.classList.add('dialogModal-hidden');
}
private focus(): void {
let focusedElement = <HTMLElement>this._body.querySelector('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled])');
focusedElement ? focusedElement.focus() : this._body.focus();
}
/**
* Called by the theme registry on theme change to style the component
*/
public style(styles: IModalDialogStyles): void {
this._body.style.backgroundColor = styles.dialogBodyBackground ? styles.dialogBodyBackground.toString() : undefined;
this._body.style.color = styles.dialogForeground ? styles.dialogForeground.toString() : undefined;
}
private _setValidity(modelViewId: string, valid: boolean) {
let oldValidity = this.isValid();
this._modelViewValidityMap.set(modelViewId, valid);
let newValidity = this.isValid();
if (newValidity !== oldValidity) {
this._validityChangedCallback(newValidity);
}
}
private isValid(): boolean {
let valid = true;
this._modelViewValidityMap.forEach(value => valid = valid && value);
return valid;
}
public dispose() {
super.dispose();
this._body.remove();
this._moduleRefs.forEach(moduleRef => moduleRef.destroy());
}
}

View File

@@ -1,79 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.dialogModal-body {
display: flex;
flex-direction: row;
height: 100%;
width: 100%;
min-width: 500px;
min-height: 600px;
}
.modal.wide .dialogModal-body {
min-width: 800px;
}
.dialog-message-and-page-container {
display: flex;
flex-direction: column;
flex: 1 1;
overflow: hidden;
}
.dialogModal-page-container {
flex: 1 1;
overflow: hidden;
}
.dialogModal-pane {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
}
.dialogModal-hidden {
display: none;
}
.footer-button.dialogModal-hidden {
margin: 0;
}
.footer-button .validating {
background-size: 15px;
background-repeat: no-repeat;
background-position: center;
}
.vs .footer-button .validating {
background-image: url("loading.svg");
}
.vs-dark .footer-button .validating,
.hc-black .footer-button .validating {
background-image: url("loading_inverse.svg");
}
.dialogModal-wizardHeader {
padding: 10px 30px;
}
.dialogModal-wizardHeader h1 {
margin-top: 10px;
margin-bottom: 10px;
font-size: 1.5em;
font-weight: bold;
}
.dialogContainer {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}

View File

@@ -1,31 +0,0 @@
<?xml version='1.0' standalone='no' ?>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
<style>
circle {
animation: ball 0.6s linear infinite;
}
circle:nth-child(2) { animation-delay: 0.075s; }
circle:nth-child(3) { animation-delay: 0.15s; }
circle:nth-child(4) { animation-delay: 0.225s; }
circle:nth-child(5) { animation-delay: 0.3s; }
circle:nth-child(6) { animation-delay: 0.375s; }
circle:nth-child(7) { animation-delay: 0.45s; }
circle:nth-child(8) { animation-delay: 0.525s; }
@keyframes ball {
from { opacity: 1; }
to { opacity: 0.3; }
}
</style>
<g>
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,31 +0,0 @@
<?xml version='1.0' standalone='no' ?>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
<style>
circle {
animation: ball 0.6s linear infinite;
}
circle:nth-child(2) { animation-delay: 0.075s; }
circle:nth-child(3) { animation-delay: 0.15s; }
circle:nth-child(4) { animation-delay: 0.225s; }
circle:nth-child(5) { animation-delay: 0.3s; }
circle:nth-child(6) { animation-delay: 0.375s; }
circle:nth-child(7) { animation-delay: 0.45s; }
circle:nth-child(8) { animation-delay: 0.525s; }
@keyframes ball {
from { opacity: 1; }
to { opacity: 0.3; }
}
</style>
<g style="fill:white;">
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,93 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.wizardNavigation-container {
display: flex;
flex-direction: column;
width: 80px;
height: 100%;
}
.hc-black .wizardNavigation-container {
border-right-color: #2b56f2;
border-right-style: solid;
border-right-width: 1px;
background-color: unset;
}
.wizardNavigation-pageNumber {
flex-grow: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
max-height: 100px;
}
.wizardNavigation-pageNumber a {
text-decoration: none;
}
.wizardNavigation-dot {
height: 30px;
width: 30px;
background-color: rgb(200, 200, 200);
color: white;
border-radius: 50%;
border-style: none;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
}
.hc-black .wizardNavigation-dot {
flex-grow: 1;
background-color: unset;
border-style: solid;
border-width: 1px;
border-color: white;
}
.wizardNavigation-connector {
width: 3px;
display: inline-block;
flex-grow: 1;
background-color: rgb(200, 200, 200);
}
.hc-black .wizardNavigation-connector {
display: none;
}
.wizardNavigation-connector.active,
.wizardNavigation-dot.active {
background-color: rgb(9, 109, 201);
}
.hc-black .wizardNavigation-dot.active {
border-color: #2b56f2;
background-color: unset;
border-style: solid;
}
.hc-black .wizardNavigation-dot.active:hover,
.hc-black .wizardNavigation-dot.active.currentPage:hover {
border-color: #F38518;
border-style: dashed;
}
.wizardNavigation-dot.active.currentPage {
border-style: double;
}
.hc-black .wizardNavigation-dot.active.currentPage {
border-style: solid;
border-color: #F38518;
}
.wizardNavigation-connector.invisible {
visibility: hidden;
}

View File

@@ -1,309 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/dialogModal';
import { Modal, IModalOptions } from 'sql/workbench/browser/modal/modal';
import { attachModalDialogStyler } from 'sql/platform/theme/common/styler';
import { Wizard, DialogButton, WizardPage } from 'sql/platform/dialog/common/dialogTypes';
import { DialogPane } from 'sql/platform/dialog/browser/dialogPane';
import { bootstrapAngular } from 'sql/platform/bootstrap/browser/bootstrapService';
import { DialogMessage } from 'sql/workbench/api/common/sqlExtHostTypes';
import { DialogModule } from 'sql/platform/dialog/browser/dialog.module';
import { Button } from 'vs/base/browser/ui/button/button';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Emitter } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { append, $ } from 'vs/base/browser/dom';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
export class WizardModal extends Modal {
private _dialogPanes = new Map<WizardPage, DialogPane>();
private _onDone = new Emitter<void>();
private _onCancel = new Emitter<void>();
// Wizard HTML elements
private _body: HTMLElement;
private _messageAndPageContainer: HTMLElement;
private _pageContainer: HTMLElement;
// Buttons
private _previousButton: Button;
private _nextButton: Button;
private _generateScriptButton: Button;
private _doneButton: Button;
private _cancelButton: Button;
constructor(
private _wizard: Wizard,
name: string,
options: IModalOptions,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IThemeService themeService: IThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IClipboardService clipboardService: IClipboardService,
@ILogService logService: ILogService,
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
) {
super(_wizard.title, name, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, options);
this._useDefaultMessageBoxLocation = false;
}
public layout(): void {
}
public render() {
super.render();
attachModalDialogStyler(this, this._themeService);
if (this.backButton) {
this.backButton.onDidClick(() => this.cancel());
attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND });
}
if (this._wizard.customButtons) {
this._wizard.customButtons.forEach(button => {
let buttonElement = this.addDialogButton(button);
this.updateButtonElement(buttonElement, button);
});
}
this._previousButton = this.addDialogButton(this._wizard.backButton, () => this.showPage(this._wizard.currentPage - 1));
this._nextButton = this.addDialogButton(this._wizard.nextButton, () => this.showPage(this._wizard.currentPage + 1, true, true), true, true);
this._generateScriptButton = this.addDialogButton(this._wizard.generateScriptButton, () => undefined);
this._doneButton = this.addDialogButton(this._wizard.doneButton, () => this.done(), false, true);
this._wizard.doneButton.registerClickEvent(this._onDone.event);
this._cancelButton = this.addDialogButton(this._wizard.cancelButton, () => this.cancel(), false);
this._wizard.cancelButton.registerClickEvent(this._onCancel.event);
let messageChangeHandler = (message: DialogMessage) => {
if (message && message.text) {
this.setError(message.text, message.level, message.description);
} else {
this.setError('');
}
};
messageChangeHandler(this._wizard.message);
this._wizard.onMessageChange(message => messageChangeHandler(message));
}
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true, requirePageValid: boolean = false): Button {
let buttonElement = this.addFooterButton(button.label, onSelect, button.position);
buttonElement.enabled = button.enabled;
if (registerClickEvent) {
button.registerClickEvent(buttonElement.onDidClick);
}
button.onUpdate(() => {
this.updateButtonElement(buttonElement, button, requirePageValid);
});
attachButtonStyler(buttonElement, this._themeService);
this.updateButtonElement(buttonElement, button, requirePageValid);
return buttonElement;
}
private updateButtonElement(buttonElement: Button, dialogButton: DialogButton, requirePageValid: boolean = false) {
buttonElement.label = dialogButton.label;
buttonElement.enabled = requirePageValid ? dialogButton.enabled && this._wizard.pages[this._wizard.currentPage].valid : dialogButton.enabled;
dialogButton.hidden ? buttonElement.element.parentElement.classList.add('dialogModal-hidden') : buttonElement.element.parentElement.classList.remove('dialogModal-hidden');
if (dialogButton.focused) {
buttonElement.focus();
}
this.setButtonsForPage(this._wizard.currentPage);
}
protected renderBody(container: HTMLElement): void {
this._body = append(container, $('div.dialogModal-body'));
this.initializeNavigation(this._body);
const mpContainer = append(this._body, $('div.dialog-message-and-page-container'));
this._messageAndPageContainer = mpContainer;
mpContainer.append(this._messageElement);
this._pageContainer = append(mpContainer, $('div.dialogModal-page-container'));
this._wizard.pages.forEach(page => {
this.registerPage(page);
});
this._wizard.onPageAdded(page => {
this.registerPage(page);
this.updatePageNumbers();
this.showPage(this._wizard.currentPage, false);
});
this._wizard.onPageRemoved(page => {
let dialogPane = this._dialogPanes.get(page);
this._dialogPanes.delete(page);
this.updatePageNumbers();
this.showPage(this._wizard.currentPage, false);
dialogPane.dispose();
});
this.updatePageNumbers();
}
private updatePageNumbers(): void {
this._wizard.pages.forEach((page, index) => {
let dialogPane = this._dialogPanes.get(page);
dialogPane.pageNumber = index + 1;
});
}
private registerPage(page: WizardPage): void {
let dialogPane = new DialogPane(page.title, page.content, valid => page.notifyValidityChanged(valid), this._instantiationService, this._themeService, this._wizard.displayPageTitles, page.description);
dialogPane.createBody(this._pageContainer);
this._dialogPanes.set(page, dialogPane);
page.onUpdate(() => this.setButtonsForPage(this._wizard.currentPage));
}
private async showPage(index: number, validate: boolean = true, focus: boolean = false): Promise<void> {
let pageToShow = this._wizard.pages[index];
if (!pageToShow) {
this.done(validate);
return;
}
if (validate && !await this.validateNavigation(index)) {
return;
}
this._dialogPanes.forEach((dialogPane, page) => {
if (page === pageToShow) {
dialogPane.show(focus);
} else {
dialogPane.hide();
}
});
this.setButtonsForPage(index);
this._wizard.setCurrentPage(index);
let currentPageValid = this._wizard.pages[this._wizard.currentPage].valid;
this._nextButton.enabled = this._wizard.nextButton.enabled && currentPageValid;
this._doneButton.enabled = this._wizard.doneButton.enabled && currentPageValid;
pageToShow.onValidityChanged(valid => {
if (index === this._wizard.currentPage) {
this._nextButton.enabled = this._wizard.nextButton.enabled && pageToShow.valid;
this._doneButton.enabled = this._wizard.doneButton.enabled && pageToShow.valid;
}
});
}
private setButtonsForPage(index: number) {
if (this._previousButton) {
if (this._wizard.pages[index - 1]) {
this._previousButton.element.parentElement.classList.remove('dialogModal-hidden');
this._previousButton.enabled = this._wizard.pages[index - 1].enabled;
} else {
this._previousButton.element.parentElement.classList.add('dialogModal-hidden');
}
}
if (this._nextButton && this._doneButton) {
if (this._wizard.pages[index + 1]) {
let isPageValid = this._wizard.pages[index] && this._wizard.pages[index].valid;
this._nextButton.element.parentElement.classList.remove('dialogModal-hidden');
this._nextButton.enabled = isPageValid && this._wizard.pages[index + 1].enabled;
this._doneButton.element.parentElement.classList.add('dialogModal-hidden');
} else {
this._nextButton.element.parentElement.classList.add('dialogModal-hidden');
this._doneButton.element.parentElement.classList.remove('dialogModal-hidden');
}
}
}
/**
* Bootstrap angular for the wizard's left nav bar
*/
private initializeNavigation(bodyContainer: HTMLElement) {
bootstrapAngular(this._instantiationService,
DialogModule,
bodyContainer,
'wizard-navigation',
{
wizard: this._wizard,
navigationHandler: (index: number) => this.showPage(index, index > this._wizard.currentPage, true)
},
undefined,
() => undefined);
}
public open(): void {
this.showPage(0, false, true);
this.show();
}
public async done(validate: boolean = true): Promise<void> {
if (this._doneButton.enabled) {
if (validate && !await this.validateNavigation(undefined)) {
return;
}
this._onDone.fire();
this.dispose();
this.hide();
}
}
public cancel(): void {
this._onCancel.fire();
this.dispose();
this.hide();
}
private async validateNavigation(newPage: number): Promise<boolean> {
let button = newPage === undefined ? this._doneButton : this._nextButton;
let buttonSpinnerHandler = setTimeout(() => {
button.enabled = false;
button.element.innerHTML = '&nbsp';
button.element.classList.add('validating');
}, 100);
let navigationValid = await this._wizard.validateNavigation(newPage);
clearTimeout(buttonSpinnerHandler);
button.element.classList.remove('validating');
this.updateButtonElement(button, newPage === undefined ? this._wizard.doneButton : this._wizard.nextButton, true);
return navigationValid;
}
protected hide(): void {
super.hide();
}
protected show(): void {
super.show();
}
/**
* Overridable to change behavior of escape key
*/
protected onClose(e: StandardKeyboardEvent) {
this.cancel();
}
/**
* Overridable to change behavior of enter key
*/
protected onAccept(e: StandardKeyboardEvent) {
if (this._wizard.currentPage === this._wizard.pages.length - 1) {
this.done();
} else {
if (this._nextButton.enabled) {
this.showPage(this._wizard.currentPage + 1, true, true);
}
}
}
public dispose(): void {
super.dispose();
this._dialogPanes.forEach(dialogPane => dialogPane.dispose());
}
}

View File

@@ -1,88 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/wizardNavigation';
import { Component, Inject, forwardRef, ElementRef, AfterViewInit, ChangeDetectorRef, ViewChild } from '@angular/core';
import { Event, Emitter } from 'vs/base/common/event';
import { Wizard } from '../common/dialogTypes';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { IBootstrapParams } from 'sql/platform/bootstrap/common/bootstrapParams';
export class WizardNavigationParams implements IBootstrapParams {
wizard: Wizard;
navigationHandler: (index: number) => void;
}
@Component({
selector: WizardNavigation.SELECTOR,
providers: [],
template: `
<div #container class="wizardNavigation-container">
<ng-container *ngFor="let item of _params.wizard.pages; let i = index">
<div class="wizardNavigation-pageNumber">
<div class="wizardNavigation-connector" [ngClass]="{'invisible': !hasTopConnector(i), 'active': isActive(i)}"></div>
<a [attr.href]="isActive(i) ? '' : null" [title]="item.title">
<span class="wizardNavigation-dot" [ngClass]="{'active': isActive(i), 'currentPage': isCurrentPage(i)}" (click)="navigate(i)">{{i+1}}</span>
</a>
<div class="wizardNavigation-connector" [ngClass]="{'invisible': !hasBottomConnector(i), 'active': isActive(i)}"></div>
</div>
</ng-container>
</div>
`
})
export class WizardNavigation implements AfterViewInit {
public static readonly SELECTOR = 'wizard-navigation';
private _onResize = new Emitter<void>();
public readonly onResize: Event<void> = this._onResize.event;
@ViewChild('container', { read: ElementRef }) private _container: ElementRef;
constructor(
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(IBootstrapParams) private _params: WizardNavigationParams,
@Inject(IWorkbenchThemeService) private _themeService: IWorkbenchThemeService) {
}
ngAfterViewInit() {
this._themeService.onThemeChange(() => this.style());
this.style();
this._params.wizard.onPageChanged(() => this._changeRef.detectChanges());
}
hasTopConnector(index: number): boolean {
return index > 0;
}
hasBottomConnector(index: number): boolean {
return index + 1 !== this._params.wizard.pages.length;
}
isActive(index: number): boolean {
return index <= this._params.wizard.currentPage;
}
isCurrentPage(index: number): boolean {
return index === this._params.wizard.currentPage;
}
navigate(index: number): void {
if (this.isActive(index)) {
this._params.navigationHandler(index);
}
}
private style(): void {
let theme = this._themeService.getTheme();
let navigationBackgroundColor = theme.getColor(SIDE_BAR_BACKGROUND);
if (theme.type === 'light') {
navigationBackgroundColor = navigationBackgroundColor.lighten(0.03);
} else if (theme.type === 'dark') {
navigationBackgroundColor = navigationBackgroundColor.darken(0.03);
}
(this._container.nativeElement as HTMLElement).style.backgroundColor = navigationBackgroundColor.toString();
}
}

View File

@@ -1,278 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 { localize } from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { DialogMessage } from 'sql/workbench/api/common/sqlExtHostTypes';
export class ModelViewPane {
private _valid: boolean = true;
private _validityChangedEmitter = new Emitter<boolean>();
public readonly onValidityChanged = this._validityChangedEmitter.event;
public get valid(): boolean {
return this._valid;
}
public notifyValidityChanged(valid: boolean) {
if (this._valid !== valid) {
this._valid = valid;
this._validityChangedEmitter.fire(this._valid);
}
}
}
export class DialogTab extends ModelViewPane {
public content: string;
constructor(public title: string, content?: string) {
super();
if (content) {
this.content = content;
}
}
}
export class Dialog extends ModelViewPane {
private static readonly DONE_BUTTON_LABEL = localize('dialogModalDoneButtonLabel', "Done");
private static readonly CANCEL_BUTTON_LABEL = localize('dialogModalCancelButtonLabel', "Cancel");
public content: string | DialogTab[];
public isWide: boolean;
public okButton: DialogButton = new DialogButton(Dialog.DONE_BUTTON_LABEL, true);
public cancelButton: DialogButton = new DialogButton(Dialog.CANCEL_BUTTON_LABEL, true);
public customButtons: DialogButton[];
private _onMessageChange = new Emitter<DialogMessage>();
public readonly onMessageChange = this._onMessageChange.event;
private _message: DialogMessage;
private _closeValidator: () => boolean | Thenable<boolean>;
constructor(public title: string, content?: string | DialogTab[]) {
super();
if (content) {
this.content = content;
}
}
public get message(): DialogMessage {
return this._message;
}
public set message(value: DialogMessage) {
this._message = value;
this._onMessageChange.fire(this._message);
}
public registerCloseValidator(validator: () => boolean | Thenable<boolean>): void {
this._closeValidator = validator;
}
public validateClose(): Thenable<boolean> {
if (this._closeValidator) {
return Promise.resolve(this._closeValidator());
} else {
return Promise.resolve(true);
}
}
}
export class DialogButton implements azdata.window.Button {
private _label: string;
private _enabled: boolean;
private _hidden: boolean;
private _focused: boolean;
private _position?: azdata.window.DialogButtonPosition;
private _onClick: Emitter<void> = new Emitter<void>();
public readonly onClick: Event<void> = this._onClick.event;
private _onUpdate: Emitter<void> = new Emitter<void>();
public readonly onUpdate: Event<void> = this._onUpdate.event;
constructor(label: string, enabled: boolean) {
this._label = label;
this._enabled = enabled;
this._hidden = false;
}
public get label(): string {
return this._label;
}
public set label(label: string) {
this._label = label;
this._onUpdate.fire();
}
public get enabled(): boolean {
return this._enabled;
}
public set enabled(enabled: boolean) {
this._enabled = enabled;
this._onUpdate.fire();
}
public get hidden(): boolean {
return this._hidden;
}
public set hidden(hidden: boolean) {
this._hidden = hidden;
this._onUpdate.fire();
}
public get focused(): boolean {
return this._focused;
}
public set focused(focused: boolean) {
this._focused = focused;
this._onUpdate.fire();
}
public get position(): azdata.window.DialogButtonPosition | undefined {
return this._position;
}
public set position(value: azdata.window.DialogButtonPosition | undefined) {
this._position = value;
this._onUpdate.fire();
}
/**
* Register an event that notifies the button that it has been clicked
*/
public registerClickEvent(clickEvent: Event<any>): void {
clickEvent(() => this._onClick.fire());
}
}
export class WizardPage extends DialogTab {
public customButtons: DialogButton[];
private _enabled: boolean;
private _description: string;
private _onUpdate: Emitter<void> = new Emitter<void>();
public readonly onUpdate: Event<void> = this._onUpdate.event;
constructor(public title: string, content?: string) {
super(title, content);
}
public get enabled(): boolean {
return this._enabled;
}
public set enabled(enabled: boolean) {
this._enabled = enabled;
this._onUpdate.fire();
}
public get description(): string {
return this._description;
}
public set description(description: string) {
this._description = description;
this._onUpdate.fire();
}
}
export class Wizard {
public pages: WizardPage[];
public nextButton: DialogButton;
public backButton: DialogButton;
public generateScriptButton: DialogButton;
public doneButton: DialogButton;
public cancelButton: DialogButton;
public customButtons: DialogButton[];
private _currentPage: number;
private _pageChangedEmitter = new Emitter<azdata.window.WizardPageChangeInfo>();
public readonly onPageChanged = this._pageChangedEmitter.event;
private _pageAddedEmitter = new Emitter<WizardPage>();
public readonly onPageAdded = this._pageAddedEmitter.event;
private _pageRemovedEmitter = new Emitter<WizardPage>();
public readonly onPageRemoved = this._pageRemovedEmitter.event;
private _navigationValidator: (pageChangeInfo: azdata.window.WizardPageChangeInfo) => boolean | Thenable<boolean>;
private _onMessageChange = new Emitter<DialogMessage>();
public readonly onMessageChange = this._onMessageChange.event;
private _message: DialogMessage;
public displayPageTitles: boolean;
constructor(public title: string) { }
public get currentPage(): number {
return this._currentPage;
}
public setCurrentPage(index: number): void {
if (index === undefined || index < 0 || index >= this.pages.length) {
throw new Error('Index is out of bounds');
}
let lastPage = this._currentPage;
this._currentPage = index;
if (lastPage !== undefined && this._currentPage !== undefined && lastPage !== this._currentPage) {
this._pageChangedEmitter.fire({
lastPage: lastPage,
newPage: this._currentPage
});
}
}
public addPage(page: WizardPage, index?: number): void {
if (index !== undefined && (index < 0 || index > this.pages.length)) {
throw new Error('Index is out of bounds');
}
if (index !== undefined && this.currentPage !== undefined && index <= this.currentPage) {
++this._currentPage;
}
if (index === undefined) {
this.pages.push(page);
} else {
this.pages = this.pages.slice(0, index).concat([page], this.pages.slice(index));
}
this._pageAddedEmitter.fire(page);
}
public removePage(index: number): void {
if (index === undefined || index < 0 || index >= this.pages.length) {
throw new Error('Index is out of bounds');
}
if (index === this.currentPage) {
// Switch to the new page before deleting the current page
let newPage = this._currentPage > 0 ? this._currentPage - 1 : this._currentPage + 1;
this.setCurrentPage(newPage);
}
if (this.currentPage !== undefined && index < this.currentPage) {
--this._currentPage;
}
let removedPage = this.pages[index];
this.pages.splice(index, 1);
this._pageRemovedEmitter.fire(removedPage);
}
public registerNavigationValidator(validator: (pageChangeInfo: azdata.window.WizardPageChangeInfo) => boolean | Thenable<boolean>): void {
this._navigationValidator = validator;
}
public validateNavigation(newPage: number): Thenable<boolean> {
if (this._navigationValidator) {
return Promise.resolve(this._navigationValidator({
lastPage: this._currentPage,
newPage: newPage
}));
} else {
return Promise.resolve(true);
}
}
public get message(): DialogMessage {
return this._message;
}
public set message(value: DialogMessage) {
this._message = value;
this._onMessageChange.fire(this._message);
}
}

View File

@@ -1,105 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Dialog, DialogTab } from 'sql/platform/dialog/common/dialogTypes';
import { DialogPane } from 'sql/platform/dialog/browser/dialogPane';
import { DialogComponentParams } from 'sql/platform/dialog/browser/dialogContainer.component';
import { bootstrapAngular } from 'sql/platform/bootstrap/browser/bootstrapService';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
interface BootstrapAngular {
(collection, moduleType, container, selectorString, params: DialogComponentParams, input, callbackSetModule): void;
}
suite('Dialog Pane Tests', () => {
let dialog: Dialog;
let container: HTMLElement;
let bootstrapSave: BootstrapAngular;
function setupBootstrap(fn: BootstrapAngular): void {
(<any>bootstrapAngular) = fn;
}
setup(() => {
dialog = new Dialog('test_dialog');
container = document.createElement('div');
bootstrapSave = bootstrapAngular;
});
test('Creating a pane from content without tabs initializes the model view content correctly', () => {
// If I fill in a dialog's content with the ID of a specific model view provider and then render the dialog
let modelViewId = 'test_content';
let bootstrapCalls = 0;
setupBootstrap((collection, moduleType, container, selectorString, params: DialogComponentParams, input, callbackSetModule) => {
assert.equal(params.modelViewId, modelViewId);
bootstrapCalls++;
});
dialog.content = modelViewId;
const themeService = new TestThemeService();
let dialogPane = new DialogPane(dialog.title, dialog.content, () => undefined, undefined, themeService, false);
dialogPane.createBody(container);
assert.equal(bootstrapCalls, 1);
});
test('Creating a pane from content with a single tab initializes without showing tabs', () => {
// If I fill in a dialog's content with a single tab and then render the dialog
let modelViewId = 'test_content';
let bootstrapCalls = 0;
setupBootstrap((collection, moduleType, container, selectorString, params: DialogComponentParams, input, callbackSetModule) => {
assert.equal(params.modelViewId, modelViewId);
bootstrapCalls++;
});
dialog.content = [new DialogTab('', modelViewId)];
const themeService = new TestThemeService();
let dialogPane = new DialogPane(dialog.title, dialog.content, () => undefined, undefined, themeService, false);
dialogPane.createBody(container);
assert.equal(bootstrapCalls, 1);
});
test('Dialog validation gets set based on the validity of the model view content', () => {
// Set up the mock bootstrap service to intercept validation callbacks
let validationCallbacks: ((valid: boolean) => void)[] = [];
setupBootstrap((collection, moduleType, container, selectorString, params: DialogComponentParams, input, callbackSetModule) => {
validationCallbacks.push(params.validityChangedCallback);
});
let modelViewId1 = 'test_content_1';
let modelViewId2 = 'test_content_2';
dialog.content = [new DialogTab('tab1', modelViewId1), new DialogTab('tab2', modelViewId2)];
const themeService = new TestThemeService();
let dialogPane = new DialogPane(dialog.title, dialog.content, valid => dialog.notifyValidityChanged(valid), undefined, themeService, false);
dialogPane.createBody(container);
let validityChanges: boolean[] = [];
dialog.onValidityChanged(valid => validityChanges.push(valid));
// If I set tab 2's validation to false
validationCallbacks[1](false);
// Then the whole dialog's validation is false
assert.equal(dialog.valid, false);
assert.equal(validityChanges.length, 1);
assert.equal(validityChanges[0], false);
// If I then set it back to true
validationCallbacks[1](true);
// Then the whole dialog's validation is true
assert.equal(dialog.valid, true);
assert.equal(validityChanges.length, 2);
assert.equal(validityChanges[1], true);
// If I set tab 1's validation to false
validationCallbacks[0](false);
// Then the whole dialog's validation is false
assert.equal(dialog.valid, false);
assert.equal(validityChanges.length, 3);
assert.equal(validityChanges[2], false);
});
teardown(() => {
(<any>bootstrapAngular) = bootstrapSave;
});
});

View File

@@ -14,7 +14,6 @@ import { IConnectionManagementService } from 'sql/platform/connection/common/con
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { IConfigurationChangedEvent } from 'sql/workbench/parts/profiler/browser/profilerFindWidget';
/**
* Service that collects the results of executed queries