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

@@ -12,8 +12,8 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
import { Memento } from 'vs/workbench/common/memento';
import AccountStore from 'sql/platform/accounts/common/accountStore';
import { AccountDialogController } from 'sql/platform/accounts/browser/accountDialogController';
import { AutoOAuthDialogController } from 'sql/platform/accounts/browser/autoOAuthDialogController';
import { AccountDialogController } from 'sql/workbench/parts/accounts/browser/accountDialogController';
import { AutoOAuthDialogController } from 'sql/workbench/parts/accounts/browser/autoOAuthDialogController';
import { AccountProviderAddedEventParams, UpdateAccountListEventParams } from 'sql/platform/accounts/common/eventTypes';
import { IAccountManagementService } from 'sql/platform/accounts/common/interfaces';
import { Deferred } from 'sql/base/common/promise';

View File

@@ -7,14 +7,14 @@ import * as assert from 'assert';
import * as azdata from 'azdata';
import * as TypeMoq from 'typemoq';
import AccountStore from 'sql/platform/accounts/common/accountStore';
import { AccountDialogController } from 'sql/platform/accounts/browser/accountDialogController';
import { AccountDialogController } from 'sql/workbench/parts/accounts/browser/accountDialogController';
import { AccountManagementService } from 'sql/workbench/services/accountManagement/browser/accountManagementService';
import { AccountAdditionResult, AccountProviderAddedEventParams, UpdateAccountListEventParams } from 'sql/platform/accounts/common/eventTypes';
import { IAccountStore } from 'sql/platform/accounts/common/interfaces';
import { AccountProviderStub } from 'sql/platform/accounts/test/common/testAccountManagementService';
import { EventVerifierSingle } from 'sqltest/utils/eventVerifier';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { TestStorageService } from 'vs/workbench/test/workbenchTestServices';
import { EventVerifierSingle } from 'sql/base/test/common/event';
// SUITE CONSTANTS /////////////////////////////////////////////////////////
const hasAccountProvider: azdata.AccountProviderMetadata = {

View File

@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* 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/workbench/services/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

@@ -0,0 +1,171 @@
/*---------------------------------------------------------------------------------------------
* 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/workbench/services/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

@@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* 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

@@ -0,0 +1,154 @@
/*---------------------------------------------------------------------------------------------
* 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 { IConnectionProviderRegistry, Extensions as ConnectionExtensions } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
import { ICapabilitiesService, ProviderFeatures, clientCapabilities, ConnectionProviderProperties } 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

@@ -6,11 +6,11 @@
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { IConnectionComponentCallbacks } from 'sql/workbench/services/connection/browser/connectionDialogService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
import { ConnectionController } from 'sql/workbench/services/connection/browser/connectionController';
import { CmsConnectionWidget } from 'sql/workbench/services/connection/browser/cmsConnectionWidget';
import { IServerGroupController } from 'sql/platform/serverGroup/common/serverGroupController';
import { ILogService } from 'vs/platform/log/common/log';
import { ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService';
/**
* Connection Controller for CMS Connections

View File

@@ -13,10 +13,10 @@ import * as azdata from 'azdata';
import * as Utils from 'sql/platform/connection/common/utils';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes';
import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
import { ConnectionWidget } from 'sql/workbench/services/connection/browser/connectionWidget';
import { IServerGroupController } from 'sql/platform/serverGroup/common/serverGroupController';
import { ILogService } from 'vs/platform/log/common/log';
import { ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService';
export class ConnectionController implements IConnectionComponentController {
private _advancedController: AdvancedPropertiesController;

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
import { ConnectionDialogService } from 'sql/workbench/services/connection/browser/connectionDialogService';
import { ConnectionDialogWidget } from 'sql/workbench/services/connection/browser/connectionDialogWidget';
import { ConnectionManagementService } from 'sql/platform/connection/browser/connectionManagementService';
import { ConnectionManagementService } from 'sql/workbench/services/connection/browser/connectionManagementService';
import { ConnectionType, IConnectableInput, IConnectionResult, INewConnectionParams } from 'sql/platform/connection/common/connectionManagement';
import { TestErrorMessageService } from 'sql/platform/errorMessage/test/common/testErrorMessageService';

View File

@@ -0,0 +1,992 @@
/*---------------------------------------------------------------------------------------------
* 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/workbench/services/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

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDashboardTab } from 'sql/platform/dashboard/browser/dashboardRegistry';
import { IDashboardTab } from 'sql/workbench/parts/dashboard/browser/dashboardRegistry';
export const INewDashboardTabDialogService = createDecorator<INewDashboardTabDialogService>('addNewDashboardTabService');
export interface INewDashboardTabDialogService {

View File

@@ -23,7 +23,7 @@ import { Modal } from 'sql/workbench/browser/modal/modal';
import { attachModalDialogStyler, attachButtonStyler } from 'sql/platform/theme/common/styler';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { NewDashboardTabViewModel, IDashboardUITab } from 'sql/workbench/services/dashboard/browser/newDashboardTabViewModel';
import { IDashboardTab } from 'sql/platform/dashboard/browser/dashboardRegistry';
import { IDashboardTab } from 'sql/workbench/parts/dashboard/browser/dashboardRegistry';
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';

View File

@@ -5,8 +5,8 @@
import { INewDashboardTabDialogService } from 'sql/workbench/services/dashboard/browser/newDashboardTabDialog';
import { NewDashboardTabDialog } from 'sql/workbench/services/dashboard/browser/newDashboardTabDialogImpl';
import { IDashboardTab } from 'sql/platform/dashboard/browser/dashboardRegistry';
import { IAngularEventingService, AngularEventType } from 'sql/platform/angularEventing/common/angularEventingService';
import { IDashboardTab } from 'sql/workbench/parts/dashboard/browser/dashboardRegistry';
import { IAngularEventingService, AngularEventType } from 'sql/platform/angularEventing/browser/angularEventingService';
import { IDashboardUITab } from 'sql/workbench/services/dashboard/browser/newDashboardTabViewModel';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';

View File

@@ -5,7 +5,7 @@
import { Event, Emitter } from 'vs/base/common/event';
import { IDashboardTab } from 'sql/platform/dashboard/browser/dashboardRegistry';
import { IDashboardTab } from 'sql/workbench/parts/dashboard/browser/dashboardRegistry';
export interface IDashboardUITab {

View File

@@ -0,0 +1,49 @@
/*---------------------------------------------------------------------------------------------
* 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/workbench/services/dialog/browser/dialogModal';
import { WizardModal } from 'sql/workbench/services/dialog/browser/wizardModal';
import { Dialog, Wizard } from 'sql/workbench/services/dialog/common/dialogTypes';
import { IModalOptions } from 'sql/workbench/browser/modal/modal';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
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

@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* 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/workbench/services/dialog/browser/dialogContainer.component';
import { WizardNavigation } from 'sql/workbench/services/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/workbench/services/bootstrap/browser/bootstrapService';
import { CommonServiceInterface } from 'sql/workbench/services/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/workbench/services/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

@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* 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/workbench/services/dialog/browser/dialogPane';
import { ComponentEventType } from 'sql/workbench/browser/modelComponents/interfaces';
import { Event, Emitter } from 'vs/base/common/event';
import { IBootstrapParams } from 'sql/workbench/services/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

@@ -0,0 +1,175 @@
/*---------------------------------------------------------------------------------------------
* 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/workbench/services/dialog/common/dialogTypes';
import { DialogPane } from 'sql/workbench/services/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

@@ -0,0 +1,177 @@
/*---------------------------------------------------------------------------------------------
* 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/workbench/services/dialog/common/dialogTypes';
import { TabbedPanel } from 'sql/base/browser/ui/panel/panel';
import { bootstrapAngular } from 'sql/workbench/services/bootstrap/browser/bootstrapService';
import { DialogModule } from 'sql/workbench/services/dialog/browser/dialog.module';
import { DialogComponentParams, LayoutRequestParams } from 'sql/workbench/services/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

@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* 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

@@ -0,0 +1,31 @@
<?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>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,31 @@
<?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>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,93 @@
/*---------------------------------------------------------------------------------------------
* 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

@@ -0,0 +1,309 @@
/*---------------------------------------------------------------------------------------------
* 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/workbench/services/dialog/common/dialogTypes';
import { DialogPane } from 'sql/workbench/services/dialog/browser/dialogPane';
import { bootstrapAngular } from 'sql/workbench/services/bootstrap/browser/bootstrapService';
import { DialogMessage } from 'sql/workbench/api/common/sqlExtHostTypes';
import { DialogModule } from 'sql/workbench/services/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

@@ -0,0 +1,88 @@
/*---------------------------------------------------------------------------------------------
* 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/workbench/services/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

@@ -0,0 +1,278 @@
/*---------------------------------------------------------------------------------------------
* 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

@@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* 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/workbench/services/dialog/common/dialogTypes';
import { DialogPane } from 'sql/workbench/services/dialog/browser/dialogPane';
import { DialogComponentParams } from 'sql/workbench/services/dialog/browser/dialogContainer.component';
import { bootstrapAngular } from 'sql/workbench/services/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

@@ -5,7 +5,7 @@
import { InsightsDialogController } from 'sql/workbench/services/insights/browser/insightsDialogController';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import { ConnectionManagementService } from 'sql/platform/connection/browser/connectionManagementService';
import { ConnectionManagementService } from 'sql/workbench/services/connection/browser/connectionManagementService';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';

View File

@@ -15,7 +15,7 @@ import { NotebookInput } from 'sql/workbench/parts/notebook/browser/models/noteb
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
import { ICellModel, INotebookModel } from 'sql/workbench/parts/notebook/browser/models/modelInterfaces';
import { NotebookChangeType } from 'sql/workbench/parts/notebook/common/models/contracts';
import { IBootstrapParams } from 'sql/platform/bootstrap/common/bootstrapParams';
import { IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
export const SERVICE_ID = 'notebookService';
export const INotebookService = createDecorator<INotebookService>(SERVICE_ID);

View File

@@ -10,7 +10,7 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { IResourceProviderService, IHandleFirewallRuleResult } from 'sql/workbench/services/resourceProvider/common/resourceProviderService';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import * as TelemetryUtils from 'sql/platform/telemetry/common/telemetryUtilities';
import { FirewallRuleDialogController } from 'sql/platform/accounts/browser/firewallRuleDialogController';
import { FirewallRuleDialogController } from 'sql/workbench/parts/accounts/browser/firewallRuleDialogController';
import * as azdata from 'azdata';
import { invalidProvider } from 'sql/base/common/errors';