SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,369 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as data from 'data';
import * as nls from 'vs/nls';
import * as platform from 'vs/platform/registry/common/platform';
import * as statusbar from 'vs/workbench/browser/parts/statusbar/statusbar';
import AccountStore from 'sql/services/accountManagement/accountStore';
import Event, { Emitter } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Memento, Scope as MementoScope } from 'vs/workbench/common/memento';
import { ISqlOAuthService } from 'sql/common/sqlOAuthService';
import { AccountDialogController } from 'sql/parts/accountManagement/accountDialog/accountDialogController';
import { AccountListStatusbarItem } from 'sql/parts/accountManagement/accountListStatusbar/accountListStatusbarItem';
import { AccountProviderAddedEventParams, UpdateAccountListEventParams } from 'sql/services/accountManagement/eventTypes';
import { IAccountManagementService } from 'sql/services/accountManagement/interfaces';
import { warn } from 'sql/base/common/log';
export class AccountManagementService implements IAccountManagementService {
// CONSTANTS ///////////////////////////////////////////////////////////
private static ACCOUNT_MEMENTO = 'AccountManagement';
// MEMBER VARIABLES ////////////////////////////////////////////////////
public _providers: { [id: string]: AccountProviderWithMetadata } = {};
public _serviceBrand: any;
private _accountStore: AccountStore;
private _accountDialogController: AccountDialogController;
private _mementoContext: Memento;
private _oAuthCallbacks: { [eventId: string]: { resolve, reject } } = {};
private _oAuthEventId: number = 0;
// EVENT EMITTERS //////////////////////////////////////////////////////
private _addAccountProviderEmitter: Emitter<AccountProviderAddedEventParams>;
public get addAccountProviderEvent(): Event<AccountProviderAddedEventParams> { return this._addAccountProviderEmitter.event; }
private _removeAccountProviderEmitter: Emitter<data.AccountProviderMetadata>;
public get removeAccountProviderEvent(): Event<data.AccountProviderMetadata> { return this._removeAccountProviderEmitter.event; }
private _updateAccountListEmitter: Emitter<UpdateAccountListEventParams>;
public get updateAccountListEvent(): Event<UpdateAccountListEventParams> { return this._updateAccountListEmitter.event; }
// CONSTRUCTOR /////////////////////////////////////////////////////////
constructor(
private _mementoObj: object,
@IInstantiationService private _instantiationService: IInstantiationService,
@IStorageService private _storageService: IStorageService,
@ISqlOAuthService private _oAuthService: ISqlOAuthService
) {
let self = this;
// Create the account store
if (!this._mementoObj) {
this._mementoContext = new Memento(AccountManagementService.ACCOUNT_MEMENTO);
this._mementoObj = this._mementoContext.getMemento(this._storageService, MementoScope.GLOBAL);
}
this._accountStore = this._instantiationService.createInstance(AccountStore, this._mementoObj);
// Setup the event emitters
this._addAccountProviderEmitter = new Emitter<AccountProviderAddedEventParams>();
this._removeAccountProviderEmitter = new Emitter<data.AccountProviderMetadata>();
this._updateAccountListEmitter = new Emitter<UpdateAccountListEventParams>();
// Register status bar item
// FEATURE FLAG TOGGLE
if (process.env['VSCODE_DEV']) {
let statusbarDescriptor = new statusbar.StatusbarItemDescriptor(
AccountListStatusbarItem,
statusbar.StatusbarAlignment.LEFT,
15000 /* Highest Priority */
);
(<statusbar.IStatusbarRegistry>platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(statusbarDescriptor);
}
// Register event handler for OAuth completion
this._oAuthService.registerOAuthCallback((event, args) => {
self.onOAuthResponse(args);
});
}
// PUBLIC METHODS //////////////////////////////////////////////////////
/**
* Asks the requested provider to prompt for an account
* @param {string} providerId ID of the provider to ask to prompt for an account
* @return {Thenable<Account>} Promise to return an account
*/
public addAccount(providerId: string): Thenable<data.Account> {
let self = this;
return this.doWithProvider(providerId, (provider) => {
return provider.provider.prompt()
.then(account => self._accountStore.addOrUpdate(account))
.then(result => {
if (result.accountAdded) {
// Add the account to the list
provider.accounts.push(result.changedAccount);
}
if (result.accountModified) {
// Find the updated account and splice the updated on in
let indexToRemove: number = provider.accounts.findIndex(account => {
return account.key.accountId === result.changedAccount.key.accountId;
});
if (indexToRemove >= 0) {
provider.accounts.splice(indexToRemove, 1, result.changedAccount);
}
}
self.fireAccountListUpdate(provider, result.accountAdded);
return result.changedAccount;
});
});
}
/**
* Retrieves metadata of all providers that have been registered
* @returns {Thenable<AccountProviderMetadata[]>} Registered account providers
*/
public getAccountProviderMetadata(): Thenable<data.AccountProviderMetadata[]> {
return Promise.resolve(Object.values(this._providers).map(provider => provider.metadata));
}
/**
* Retrieves the accounts that belong to a specific provider
* @param {string} providerId ID of the provider the returned accounts belong to
* @returns {Thenable<Account[]>} Promise to return a list of accounts
*/
public getAccountsForProvider(providerId: string): Thenable<data.Account[]> {
let self = this;
// Make sure the provider exists before attempting to retrieve accounts
if (!this._providers[providerId]) {
return Promise.reject(new Error(nls.localize('accountManagementNoProvider', 'Account provider does not exist'))).then();
}
// 1) Get the accounts from the store
// 2) Update our local cache of accounts
return this.doWithProvider(providerId, provider => {
return self._accountStore.getAccountsByProvider(provider.metadata.id)
.then(accounts => {
self._providers[providerId].accounts = accounts;
return accounts;
});
});
}
/**
* Generates a security token by asking the account's provider
* @param {Account} account Account to generate security token for
* @return {Thenable<{}>} Promise to return the security token
*/
public getSecurityToken(account: data.Account): Thenable<{}> {
return this.doWithProvider(account.key.providerId, provider => {
return provider.provider.getSecurityToken(account);
});
}
/**
* Removes an account from the account store and clears sensitive data in the provider
* @param {AccountKey} accountKey Key for the account to remove
* @returns {Thenable<void>} Promise with result of account removal, true if account was
* removed, false otherwise.
*/
public removeAccount(accountKey: data.AccountKey): Thenable<boolean> {
let self = this;
// Step 1) Remove the account
// Step 2) Clear the sensitive data from the provider (regardless of whether the account was removed)
// Step 3) Update the account cache and fire an event
return this.doWithProvider(accountKey.providerId, provider => {
return this._accountStore.remove(accountKey)
.then(result => {
provider.provider.clear(accountKey);
return result;
})
.then(result => {
if (!result) {
return result;
}
let indexToRemove: number = provider.accounts.findIndex(account => {
return account.key.accountId === accountKey.accountId;
});
if (indexToRemove >= 0) {
provider.accounts.splice(indexToRemove, 1);
self.fireAccountListUpdate(provider, false);
}
return result;
});
});
}
// UI METHODS //////////////////////////////////////////////////////////
/**
* Opens the account list dialog
* @return {TPromise<any>} Promise that finishes when the account list dialog opens
*/
public openAccountListDialog(): Thenable<void> {
let self = this;
return new Promise((resolve, reject) => {
try {
// If the account list dialog hasn't been defined, create a new one
if (!self._accountDialogController) {
self._accountDialogController = self._instantiationService.createInstance(AccountDialogController);
}
self._accountDialogController.openAccountDialog();
resolve();
} catch(e) {
reject(e);
}
});
}
/**
* Opens a browser window to perform the OAuth authentication
* @param {string} url URL to visit that will perform the OAuth authentication
* @param {boolean} silent Whether or not to perform authentication silently using browser's cookies
* @return {Thenable<string>} Promise to return a authentication token on successful authentication
*/
public performOAuthAuthorization(url: string, silent: boolean): Thenable<string> {
let self = this;
return new Promise<string>((resolve, reject) => {
// TODO: replace with uniqid
let eventId: string = `oauthEvent${self._oAuthEventId++}`;
self._oAuthCallbacks[eventId] = {
resolve: resolve,
reject: reject
};
self._oAuthService.performOAuthAuthorization(eventId, url, silent);
});
}
// SERVICE MANAGEMENT METHODS //////////////////////////////////////////
/**
* Called by main thread to register an account provider from extension
* @param {data.AccountProviderMetadata} providerMetadata Metadata of the provider that is being registered
* @param {data.AccountProvider} provider References to the methods of the provider
*/
public registerProvider(providerMetadata: data.AccountProviderMetadata, provider: data.AccountProvider): Thenable<void> {
let self = this;
// Store the account provider
this._providers[providerMetadata.id] = {
metadata: providerMetadata,
provider: provider,
accounts: []
};
// Initialize the provider:
// 1) Get all the accounts that were stored
// 2) Give those accounts to the provider for rehydration
// 3) Add the accounts to our local store of accounts
// 4) Write the accounts back to the store
// 5) Fire the event to let folks know we have another account provider now
return this._accountStore.getAccountsByProvider(providerMetadata.id)
.then((accounts: data.Account[]) => {
return provider.initialize(accounts);
})
.then((accounts: data.Account[]) => {
self._providers[providerMetadata.id].accounts = accounts;
let writePromises = accounts.map(account => {
return self._accountStore.addOrUpdate(account);
});
return Promise.all(writePromises);
})
.then(() => {
let provider = self._providers[providerMetadata.id];
self._addAccountProviderEmitter.fire({
addedProvider: provider.metadata,
initialAccounts: provider.accounts.slice(0) // Slice here to make sure no one can modify our cache
});
});
// TODO: Add stale event handling to the providers
}
/**
* Handler for when shutdown of the application occurs. Writes out the memento.
*/
public shutdown(): void {
if (this._mementoContext) {
this._mementoContext.saveMemento();
}
}
public unregisterProvider(providerMetadata: data.AccountProviderMetadata): void {
// Delete this account provider
delete this._providers[providerMetadata.id];
// Alert our listeners that we've removed a provider
this._removeAccountProviderEmitter.fire(providerMetadata);
}
// TODO: Support for orphaned accounts (accounts with no provider)
// PRIVATE HELPERS /////////////////////////////////////////////////////
private doWithProvider<T>(providerId: string, op: (provider: AccountProviderWithMetadata) => Thenable<T>): Thenable<T> {
// Make sure the provider exists before attempting to retrieve accounts
let provider = this._providers[providerId];
if (!provider) {
return Promise.reject(new Error(nls.localize('accountManagementNoProvider', 'Account provider does not exist'))).then();
}
return op(provider);
}
private fireAccountListUpdate(provider: AccountProviderWithMetadata, sort: boolean) {
// Step 1) Get and sort the list
if (sort) {
provider.accounts.sort((a: data.Account, b: data.Account) => {
if (a.displayInfo.displayName < b.displayInfo.displayName) {
return -1;
}
if (a.displayInfo.displayName > b.displayInfo.displayName) {
return 1;
}
return 0;
});
}
// Step 2) Fire the event
let eventArg: UpdateAccountListEventParams = {
providerId: provider.metadata.id,
accountList: provider.accounts
};
this._updateAccountListEmitter.fire(eventArg);
}
private onOAuthResponse(args: object): void {
// Verify the arguments are correct
if (!args || args['eventId'] === undefined) {
warn('Received invalid OAuth event response args');
return;
}
// Find the event
let eventId: string = args['eventId'];
let eventCallbacks = this._oAuthCallbacks[eventId];
if (!eventCallbacks) {
warn('Received OAuth event response for non-existent eventId');
return;
}
// Parse the args
let error: string = args['error'];
let code: string = args['code'];
if (error) {
eventCallbacks.reject(error);
} else {
eventCallbacks.resolve(code);
}
}
}
/**
* Joins together an account provider, its metadata, and its accounts, used in the provider list
*/
export interface AccountProviderWithMetadata {
metadata: data.AccountProviderMetadata;
provider: data.AccountProvider;
accounts: data.Account[];
}

View File

@@ -0,0 +1,204 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as data from 'data';
import { AccountAdditionResult } from 'sql/services/accountManagement/eventTypes';
import { IAccountStore } from 'sql/services/accountManagement/interfaces';
export default class AccountStore implements IAccountStore {
// CONSTANTS ///////////////////////////////////////////////////////////
public static MEMENTO_KEY: string = 'Microsoft.SqlTools.Accounts';
// MEMBER VARIABLES ////////////////////////////////////////////////////
private _activeOperation: Thenable<any>;
constructor(private _memento: object) { }
// PUBLIC METHODS //////////////////////////////////////////////////////
public addOrUpdate(newAccount: data.Account): Thenable<AccountAdditionResult> {
let self = this;
return this.doOperation(() => {
return self.readFromMemento()
.then(accounts => {
// Determine if account exists and proceed accordingly
let match = accounts.findIndex(account => AccountStore.findAccountByKey(account.key, newAccount.key));
return match < 0
? self.addToAccountList(accounts, newAccount)
: self.updateAccountList(accounts, newAccount.key, (matchAccount) => { AccountStore.mergeAccounts(newAccount, matchAccount); });
})
.then(result => self.writeToMemento(result.updatedAccounts).then(() => result))
.then(result => <AccountAdditionResult>result);
});
}
public getAccountsByProvider(providerId: string): Thenable<data.Account[]> {
let self = this;
return this.doOperation(() => {
return self.readFromMemento()
.then(accounts => accounts.filter(account => account.key.providerId === providerId));
});
}
public getAllAccounts(): Thenable<data.Account[]> {
let self = this;
return this.doOperation(() => {
return self.readFromMemento();
});
}
public remove(key: data.AccountKey): Thenable<boolean> {
let self = this;
return this.doOperation(() => {
return self.readFromMemento()
.then(accounts => self.removeFromAccountList(accounts, key))
.then(result => self.writeToMemento(result.updatedAccounts).then(() => result))
.then(result => result.accountRemoved);
});
}
public update(key: data.AccountKey, updateOperation: (account: data.Account) => void): Thenable<boolean> {
let self = this;
return this.doOperation(() => {
return self.readFromMemento()
.then(accounts => self.updateAccountList(accounts, key, updateOperation))
.then(result => self.writeToMemento(result.updatedAccounts).then(() => result))
.then(result => result.accountModified);
});
}
// PRIVATE METHODS /////////////////////////////////////////////////////
private static findAccountByKey(key1: data.AccountKey, key2: data.AccountKey): boolean {
// Provider ID and Account ID must match
return key1.providerId === key2.providerId && key1.accountId === key2.accountId;
}
private static mergeAccounts(source: data.Account, target: data.Account): void {
// Take any display info changes
target.displayInfo = source.displayInfo;
// Take all property changes
target.properties = source.properties;
// Take any stale changes
target.isStale = source.isStale;
}
private doOperation<T>(op: () => Thenable<T>) {
// Initialize the active operation to an empty promise if necessary
let activeOperation = this._activeOperation || Promise.resolve<any>(null);
// Chain the operation to perform to the end of the existing promise
activeOperation = activeOperation.then(op);
// Add a catch at the end to make sure we can continue after any errors
activeOperation = activeOperation.then(null, (err) => {
// TODO: Log the error
});
// Point the current active operation to this one
this._activeOperation = activeOperation;
return <Promise<T>>this._activeOperation;
}
private addToAccountList(accounts: data.Account[], accountToAdd: data.Account): AccountListOperationResult {
// Check if the entry already exists
let match = accounts.findIndex(account => AccountStore.findAccountByKey(account.key, accountToAdd.key));
if (match >= 0) {
// Account already exists, we won't do anything
return {
accountAdded: false,
accountModified: false,
accountRemoved: false,
changedAccount: null,
updatedAccounts: accounts
};
}
// Add the account to the store
accounts.push(accountToAdd);
return {
accountAdded: true,
accountModified: false,
accountRemoved: false,
changedAccount: accountToAdd,
updatedAccounts: accounts
};
}
private removeFromAccountList(accounts: data.Account[], accountToRemove: data.AccountKey): AccountListOperationResult {
// Check if the entry exists
let match = accounts.findIndex(account => AccountStore.findAccountByKey(account.key, accountToRemove));
if (match >= 0) {
// Account exists, remove it from the account list
accounts.splice(match, 1);
}
return {
accountAdded: false,
accountModified: false,
accountRemoved: match >= 0,
changedAccount: null,
updatedAccounts: accounts
};
}
private updateAccountList(accounts: data.Account[], accountToUpdate: data.AccountKey, updateOperation: (account: data.Account) => void): AccountListOperationResult {
// Check if the entry exists
let match = accounts.findIndex(account => AccountStore.findAccountByKey(account.key, accountToUpdate));
if (match < 0) {
// Account doesn't exist, we won't do anything
return {
accountAdded: false,
accountModified: false,
accountRemoved: false,
changedAccount: null,
updatedAccounts: accounts
};
}
// Account exists, apply the update operation to it
updateOperation(accounts[match]);
return {
accountAdded: false,
accountModified: true,
accountRemoved: false,
changedAccount: accounts[match],
updatedAccounts: accounts
};
}
// MEMENTO IO METHODS //////////////////////////////////////////////////
private readFromMemento(): Thenable<data.Account[]> {
// Initialize the account list if it isn't already
let accounts = this._memento[AccountStore.MEMENTO_KEY];
if (!accounts) {
accounts = [];
}
// Make a deep copy of the account list to ensure that the memento list isn't obliterated
accounts = JSON.parse(JSON.stringify(accounts));
return Promise.resolve(accounts);
}
private writeToMemento(accounts: data.Account[]): Thenable<void> {
// Store a shallow copy of the account list to disconnect the memento list from the active list
this._memento[AccountStore.MEMENTO_KEY] = JSON.parse(JSON.stringify(accounts));
return Promise.resolve();
}
}
interface AccountListOperationResult extends AccountAdditionResult {
accountRemoved: boolean;
updatedAccounts: data.Account[];
}

View File

@@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as data from 'data';
/**
* Result from calling add/update on the account store
*/
export interface AccountAdditionResult {
/**
* Whether or not an account was added in the add/update process
*/
accountAdded: boolean;
/**
* Whether or not an account was updated in the add/update process
*/
accountModified: boolean;
/**
* The account that was added/updated (with any updates applied)
*/
changedAccount: data.Account;
}
/**
* Parameters that go along with an account provider being added
*/
export interface AccountProviderAddedEventParams {
/**
* The provider that was registered
*/
addedProvider: data.AccountProviderMetadata;
/**
* The accounts that were rehydrated with the provider
*/
initialAccounts: data.Account[];
}
/**
* Parameters that go along when a provider's account list changes
*/
export interface UpdateAccountListEventParams {
/**
* ID of the provider who's account list changes
*/
providerId: string;
/**
* Updated list of accounts, sorted appropriately
*/
accountList: data.Account[];
}

View File

@@ -0,0 +1,81 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as data from 'data';
import Event from 'vs/base/common/event';
import { AccountAdditionResult, AccountProviderAddedEventParams, UpdateAccountListEventParams } from 'sql/services/accountManagement/eventTypes';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const SERVICE_ID = 'accountManagementService';
export const IAccountManagementService = createDecorator<IAccountManagementService>(SERVICE_ID);
export interface IAccountManagementService {
_serviceBrand: any;
// ACCOUNT MANAGEMENT METHODS //////////////////////////////////////////
addAccount(providerId: string): Thenable<data.Account>;
getAccountProviderMetadata(): Thenable<data.AccountProviderMetadata[]>;
getAccountsForProvider(providerId: string): Thenable<data.Account[]>;
getSecurityToken(account: data.Account): Thenable<{}>;
removeAccount(accountKey: data.AccountKey): Thenable<boolean>;
// UI METHODS //////////////////////////////////////////////////////////
openAccountListDialog(): Thenable<void>;
performOAuthAuthorization(url: string, silent: boolean): Thenable<string>;
// SERVICE MANAGEMENT METHODS /////////////////////////////////////////
registerProvider(providerMetadata: data.AccountProviderMetadata, provider: data.AccountProvider): void;
shutdown(): void;
unregisterProvider(providerMetadata: data.AccountProviderMetadata): void;
// EVENTING ////////////////////////////////////////////////////////////
readonly addAccountProviderEvent: Event<AccountProviderAddedEventParams>;
readonly removeAccountProviderEvent: Event<data.AccountProviderMetadata>;
readonly updateAccountListEvent: Event<UpdateAccountListEventParams>;
}
export interface IAccountStore {
/**
* Adds the provided account if the account doesn't exist. Updates the account if it already exists
* @param {Account} account Account to add/update
* @return {Thenable<AccountAdditionResult>} Results of the add/update operation
*/
addOrUpdate(account: data.Account): Thenable<AccountAdditionResult>;
/**
* Retrieves all accounts, filtered by provider ID
* @param {string} providerId ID of the provider to filter by
* @return {Thenable<Account[]>} Promise to return all accounts that belong to the provided provider
*/
getAccountsByProvider(providerId: string): Thenable<data.Account[]>;
/**
* Retrieves all accounts in the store. Returns empty array if store is not initialized
* @return {Thenable<Account[]>} Promise to return all accounts
*/
getAllAccounts(): Thenable<data.Account[]>;
/**
* Removes an account.
* Returns false if the account was not found.
* Otherwise, returns true.
* @param key - The key of an account.
* @returns True if the account was removed, false if the account doesn't exist
*/
remove(key: data.AccountKey): Thenable<boolean>;
/**
* Updates the custom properties stored with an account.
* Returns null if no account was found to update.
* Otherwise, returns a new updated account instance.
* @param key - The key of an account.
* @param updateOperation - Operation to perform on the matching account
* @returns True if the account was modified, false if the account doesn't exist
*/
update(key: data.AccountKey, updateOperation: (account: data.Account) => void): Thenable<boolean>;
}

View File

@@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Subscription } from 'rxjs/Subscription';
import { Subject } from 'rxjs/Subject';
import { warn } from 'sql/base/common/log';
const ANGULAREVENTING_SERVICE_ID = 'angularEventingService';
export const IAngularEventingService = createDecorator<IAngularEventingService>(ANGULAREVENTING_SERVICE_ID);
export enum AngularEventType {
NAV_DATABASE,
NAV_SERVER
}
export interface IAngularEventingService {
_serviceBrand: any;
/**
* Adds a listener for the dashboard to send events, should only be called once for each dashboard by the dashboard itself
* @param uri Uri of the dashboard
* @param cb Listening function
* @returns
*/
onAngularEvent(uri: string, cb: (event: AngularEventType) => void): Subscription;
/**
* Send an event to the dashboard; no op if the dashboard has not started listening yet
* @param uri Uri of the dashboard to send the event to
* @param event event to send
*/
sendAngularEvent(uri: string, event: AngularEventType): void;
}
export class AngularEventingService implements IAngularEventingService {
public _serviceBrand: any;
private _angularMap = new Map<string, Subject<AngularEventType>>();
public onAngularEvent(uri: string, cb: (event: AngularEventType) => void): Subscription {
let subject: Subject<AngularEventType>;
if (!this._angularMap.has(uri)) {
subject = new Subject<AngularEventType>();
this._angularMap.set(uri, subject);
} else {
subject = this._angularMap.get(uri);
}
let sub = subject.subscribe(cb);
return sub;
}
public sendAngularEvent(uri: string, event: AngularEventType): void {
if (!this._angularMap.has(uri)) {
warn('Got request to send an event to a dashboard that has not started listening');
} else {
this._angularMap.get(uri).next(event);
}
}
}

View File

@@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* 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/parts/grid/services/dataService';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
export interface BootstrapParams {
}
export interface QueryComponentParams extends BootstrapParams {
dataService: DataService;
}
export interface EditDataComponentParams extends BootstrapParams {
dataService: DataService;
}
export interface DashboardComponentParams extends BootstrapParams {
connection: IConnectionProfile;
ownerUri: string;
}
export interface TaskDialogComponentParams extends BootstrapParams {
ownerUri: string;
}
export interface QueryPlanParams extends BootstrapParams {
planXml: string;
}

View File

@@ -0,0 +1,114 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NgModuleRef } from '@angular/core';
import { BootstrapParams } from 'sql/services/bootstrap/bootstrapParams';
import { IConnectionManagementService, IConnectionDialogService, IErrorMessageService }
from 'sql/parts/connection/common/connectionManagement';
import { IMetadataService } from 'sql/services/metadata/metadataService';
import { IObjectExplorerService } from 'sql/parts/registeredServer/common/objectExplorerService';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { IAngularEventingService } from 'sql/services/angularEventing/angularEventingService';
import { IScriptingService } from 'sql/services/scripting/scriptingService';
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
import { IQueryManagementService } from 'sql/parts/query/common/queryManagement';
import { IAdminService } from 'sql/parts/admin/common/adminService';
import { IDisasterRecoveryService, IDisasterRecoveryUiService, IRestoreDialogController } from 'sql/parts/disasterRecovery/common/interfaces';
import { IInsightsDialogService } from 'sql/parts/insights/common/interfaces';
import { ISqlOAuthService } from 'sql/common/sqlOAuthService';
import { IFileBrowserService, IFileBrowserDialogController } from 'sql/parts/fileBrowser/common/interfaces';
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IEditorInput } from 'vs/platform/editor/common/editor';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IMessageService } from 'vs/platform/message/common/message';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IAccountManagementService } from 'sql/services/accountManagement/interfaces';
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStorageService } from 'vs/platform/storage/common/storage';
export const BOOTSTRAP_SERVICE_ID = 'bootstrapService';
export const IBootstrapService = createDecorator<IBootstrapService>(BOOTSTRAP_SERVICE_ID);
/*
* Handles logic for bootstrapping and passing singleton services to Angular components.
*/
export interface IBootstrapService {
_serviceBrand: any;
connectionManagementService: IConnectionManagementService;
metadataService: IMetadataService;
objectExplorerService: IObjectExplorerService;
scriptingService: IScriptingService;
queryEditorService: IQueryEditorService;
connectionDialogService: IConnectionDialogService;
queryModelService: IQueryModelService;
adminService: IAdminService;
disasterRecoveryService: IDisasterRecoveryService;
disasterRecoveryUiService: IDisasterRecoveryUiService;
keybindingService: IKeybindingService;
contextKeyService: IContextKeyService;
contextMenuService: IContextMenuService;
themeService: IWorkbenchThemeService;
editorService: IWorkbenchEditorService;
errorMessageService: IErrorMessageService;
partService: IPartService;
queryManagementService: IQueryManagementService;
instantiationService: IInstantiationService;
angularEventingService: IAngularEventingService;
configurationService: IConfigurationService;
insightsDialogService: IInsightsDialogService;
contextViewService: IContextViewService;
restoreDialogService: IRestoreDialogController;
messageService: IMessageService;
workspaceContextService: IWorkspaceContextService;
accountManagementService: IAccountManagementService;
windowsService: IWindowsService;
sqlOAuthService: ISqlOAuthService;
windowService: IWindowService;
fileBrowserService: IFileBrowserService;
fileBrowserDialogService: IFileBrowserDialogController;
telemetryService: ITelemetryService;
storageService: IStorageService;
clipboardService: IClipboardService;
capabilitiesService: ICapabilitiesService;
/*
* Bootstraps the Angular module described. Components that need singleton services should inject the
* 'BootstrapService' dependency to obtain a reference to this class. Components that need dynamic parameters
* should wrap them in an object and pass them in through the "params" parameter.
*
* moduleType: The TypeScript type of the module to bootstrap
* container: The HTML container to append the selector HTMLElement
* selectorString: The tag name and class used to create the element, e.g. 'tagName.cssClassName'
* params: The parameters to be associated with the given id
* input: Optional editor input. If specified, will listen to its onDispose event and destroy the module when this happens
* callbackSetModule:Optional. If specified, will be used to set the moduleRef
* Returns the unique selector string that this module will bootstrap with.
*/
bootstrap(moduleType: any, container: HTMLElement, selectorString: string, params: BootstrapParams, input?: IEditorInput, callbackSetModule?: (value: NgModuleRef<{}>) => void): string;
/*
* Gets the "params" entry associated with the given id and unassociates the id/entry pair.
* Returns undefined if no entry is found.
*/
getBootstrapParams(id: string): any;
/*
* Gets the next unique selector given the baseSelectorString. A unique selector is the baseSelectorString with a
* number appended. E.g. if baseSelectorString='query', valid unique selectors could be query0, query1, query2, etc.
*/
getUniqueSelector(baseSelectorString: string): string;
}

View File

@@ -0,0 +1,174 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NgModuleRef } from '@angular/core';
import { platformBrowserDynamic, } from '@angular/platform-browser-dynamic';
import { BootstrapParams } from 'sql/services/bootstrap/bootstrapParams';
import { IConnectionManagementService, IConnectionDialogService, IErrorMessageService }
from 'sql/parts/connection/common/connectionManagement';
import { IMetadataService } from 'sql/services/metadata/metadataService';
import { IObjectExplorerService } from 'sql/parts/registeredServer/common/objectExplorerService';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { IScriptingService } from 'sql/services/scripting/scriptingService';
import { IQueryManagementService } from 'sql/parts/query/common/queryManagement';
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
import { IAdminService } from 'sql/parts/admin/common/adminService';
import { IDisasterRecoveryService, IDisasterRecoveryUiService, IRestoreDialogController } from 'sql/parts/disasterRecovery/common/interfaces';
import { IAngularEventingService } from 'sql/services/angularEventing/angularEventingService';
import { IInsightsDialogService } from 'sql/parts/insights/common/interfaces';
import { ISqlOAuthService } from 'sql/common/sqlOAuthService';
import { IFileBrowserService, IFileBrowserDialogController } from 'sql/parts/fileBrowser/common/interfaces';
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { $ } from 'vs/base/browser/dom';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { IEditorInput } from 'vs/platform/editor/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from './bootstrapService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IMessageService } from 'vs/platform/message/common/message';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IAccountManagementService } from 'sql/services/accountManagement/interfaces';
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStorageService } from 'vs/platform/storage/common/storage';
export class BootstrapService implements IBootstrapService {
public _serviceBrand: any;
// Maps uniqueSelectors (as opposed to selectors) to BootstrapParams
private _bootstrapParameterMap: Map<string, BootstrapParams>;
// Maps selectors (as opposed to uniqueSelectors) to a queue of uniqueSelectors
private _selectorQueueMap: Map<string, string[]>;
// Maps selectors (as opposed to uniqueSelectors) to the next available uniqueSelector ID number
private _selectorCountMap: Map<string, number>;
constructor(
@IConnectionManagementService public connectionManagementService: IConnectionManagementService,
@IMetadataService public metadataService: IMetadataService,
@IObjectExplorerService public objectExplorerService: IObjectExplorerService,
@IScriptingService public scriptingService: IScriptingService,
@IQueryEditorService public queryEditorService: IQueryEditorService,
@IAdminService public adminService: IAdminService,
@IWorkbenchThemeService public themeService: IWorkbenchThemeService,
@IWorkbenchEditorService public editorService: IWorkbenchEditorService,
@IDisasterRecoveryService public disasterRecoveryService: IDisasterRecoveryService,
@IDisasterRecoveryUiService public disasterRecoveryUiService: IDisasterRecoveryUiService,
@IRestoreDialogController public restoreDialogService: IRestoreDialogController,
@IConnectionDialogService public connectionDialogService: IConnectionDialogService,
@IQueryModelService public queryModelService: IQueryModelService,
@IKeybindingService public keybindingService: IKeybindingService,
@IContextKeyService public contextKeyService: IContextKeyService,
@IContextMenuService public contextMenuService: IContextMenuService,
@IErrorMessageService public errorMessageService: IErrorMessageService,
@IPartService public partService: IPartService,
@IQueryManagementService public queryManagementService: IQueryManagementService,
@IInstantiationService public instantiationService: IInstantiationService,
@IAngularEventingService public angularEventingService: IAngularEventingService,
@IConfigurationService public configurationService: IConfigurationService,
@IInsightsDialogService public insightsDialogService: IInsightsDialogService,
@IContextViewService public contextViewService: IContextViewService,
@IMessageService public messageService: IMessageService,
@IWorkspaceContextService public workspaceContextService: IWorkspaceContextService,
@IAccountManagementService public accountManagementService: IAccountManagementService,
@IWindowsService public windowsService: IWindowsService,
@ISqlOAuthService public sqlOAuthService: ISqlOAuthService,
@IFileBrowserService public fileBrowserService: IFileBrowserService,
@IFileBrowserDialogController public fileBrowserDialogService: IFileBrowserDialogController,
@IWindowService public windowService: IWindowService,
@ITelemetryService public telemetryService: ITelemetryService,
@IStorageService public storageService: IStorageService,
@IClipboardService public clipboardService: IClipboardService,
@ICapabilitiesService public capabilitiesService: ICapabilitiesService
) {
this._bootstrapParameterMap = new Map<string, BootstrapParams>();
this._selectorQueueMap = new Map<string, string[]>();
this._selectorCountMap = new Map<string, number>();
}
public bootstrap(moduleType: any, container: HTMLElement, selectorString: string, params: BootstrapParams, input?: IEditorInput, callbackSetModule?: (value: NgModuleRef<{}>) => void): string {
// Create the uniqueSelectorString
let uniqueSelectorString: string = this._getUniqueSelectorString(selectorString);
let selector: HTMLElement = $(uniqueSelectorString);
container.appendChild(selector);
// Associate the elementId
this._setUniqueSelector(selectorString, uniqueSelectorString);
// Associate the params
this._bootstrapParameterMap.set(uniqueSelectorString, params);
// Perform the bootsrap
let providers = [{ provide: BOOTSTRAP_SERVICE_ID, useValue: this }];
platformBrowserDynamic(providers).bootstrapModule(moduleType).then(moduleRef => {
if (input) {
input.onDispose(() => {
moduleRef.destroy();
});
}
if (callbackSetModule) {
callbackSetModule(moduleRef);
}
});
return uniqueSelectorString;
}
public getBootstrapParams(id: string): any {
let idLowercase = id.toLowerCase();
let params: BootstrapParams = this._bootstrapParameterMap.get(idLowercase);
this._bootstrapParameterMap.delete(idLowercase);
return params;
}
public getUniqueSelector(selectorString: string): string {
let idArray = this._selectorQueueMap.get(selectorString);
if (!idArray) {
return undefined;
}
let id: string = idArray.shift();
if (idArray.length === 0) {
this._selectorQueueMap.delete(selectorString);
} else {
this._selectorQueueMap.set(selectorString, idArray);
}
return id;
}
private _getUniqueSelectorString(selectorString: string): string {
let count: number = this._selectorCountMap.get(selectorString);
if (!count) {
this._selectorCountMap.set(selectorString, 1);
count = 0;
} else {
this._selectorCountMap.set(selectorString, count + 1);
}
let casedString = selectorString + count.toString();
return casedString.toLowerCase();
}
private _setUniqueSelector(selectorString: string, elementId: string) {
let idArray = this._selectorQueueMap.get(selectorString);
if (!idArray) {
idArray = [];
}
idArray.push(elementId);
this._selectorQueueMap.set(selectorString, idArray);
}
}

View File

@@ -0,0 +1,166 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ConnectionManagementInfo } from 'sql/parts/connection/common/connectionManagementInfo';
import * as Constants from 'sql/common/constants';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import data = require('data');
import Event, { Emitter } from 'vs/base/common/event';
import { Action } from 'vs/base/common/actions';
import { Deferred } from 'sql/base/common/promise';
export const SERVICE_ID = 'capabilitiesService';
export const HOST_NAME = 'sqlops';
export const HOST_VERSION = '1.0';
export const ICapabilitiesService = createDecorator<ICapabilitiesService>(SERVICE_ID);
/**
* Interface for managing provider capabilities
*/
export interface ICapabilitiesService {
_serviceBrand: any;
/**
* Retrieve a list of registered capabilities providers
*/
getCapabilities(): data.DataProtocolServerCapabilities[];
/**
* Register a capabilities provider
*/
registerProvider(provider: data.CapabilitiesProvider): void;
/**
* Returns true if the feature is available for given connection
*/
isFeatureAvailable(action: Action, connectionManagementInfo: ConnectionManagementInfo): boolean;
/**
* Event raised when a provider is registered
*/
onProviderRegisteredEvent: Event<data.DataProtocolServerCapabilities>;
/**
* Promise fulfilled when Capabilities are ready
*/
onCapabilitiesReady(): Promise<void>;
}
/**
* Capabilities service implementation class. This class provides the ability
* to discover the DMP capabilties that a DMP provider offers.
*/
export class CapabilitiesService implements ICapabilitiesService {
public _serviceBrand: any;
private _providers: data.CapabilitiesProvider[] = [];
private _capabilities: data.DataProtocolServerCapabilities[] = [];
private _onProviderRegistered: Emitter<data.DataProtocolServerCapabilities>;
private _clientCapabilties: data.DataProtocolClientCapabilities = {
hostName: HOST_NAME,
hostVersion: HOST_VERSION
};
private disposables: IDisposable[] = [];
private _onCapabilitiesReady: Deferred<void>;
// Due to absence of a way to infer the number of expected data tools extensions from the package.json, this is being hard-coded
// TODO: a better mechanism to populate the expected number of capabilities
private _expectedCapabilitiesCount: number = 1;
private _registeredCapabilities: number = 0;
constructor() {
this._onProviderRegistered = new Emitter<data.DataProtocolServerCapabilities>();
this.disposables.push(this._onProviderRegistered);
this._onCapabilitiesReady = new Deferred();
}
public onCapabilitiesReady(): Promise<void> {
return this._onCapabilitiesReady.promise;
}
/**
* Retrieve a list of registered server capabilities
*/
public getCapabilities(): data.DataProtocolServerCapabilities[] {
return this._capabilities;
}
/**
* Register the capabilities provider and query the provider for its capabilities
* @param provider
*/
public registerProvider(provider: data.CapabilitiesProvider): void {
this._providers.push(provider);
// request the capabilities from server
provider.getServerCapabilities(this._clientCapabilties).then(serverCapabilities => {
this._capabilities.push(serverCapabilities);
this._onProviderRegistered.fire(serverCapabilities);
this._registeredCapabilities++;
this.resolveCapabilitiesIfReady();
});
}
private resolveCapabilitiesIfReady(): void {
if (this._registeredCapabilities === this._expectedCapabilitiesCount) {
this._onCapabilitiesReady.resolve();
}
}
/**
* Returns true if the feature is available for given connection
* @param featureComponent a component which should have the feature name
* @param connectionManagementInfo connectionManagementInfo
*/
public isFeatureAvailable(action: Action, connectionManagementInfo: ConnectionManagementInfo): boolean {
let isCloud = connectionManagementInfo && connectionManagementInfo.serverInfo && connectionManagementInfo.serverInfo.isCloud;
let isMssql = connectionManagementInfo.connectionProfile.providerName === 'MSSQL';
// TODO: The logic should from capabilities service.
if (action) {
let featureName: string = action.id;
switch (featureName) {
case Constants.BackupFeatureName:
if (isMssql) {
return connectionManagementInfo.connectionProfile.databaseName && !isCloud;
} else {
return !!connectionManagementInfo.connectionProfile.databaseName;
}
case Constants.RestoreFeatureName:
if (isMssql) {
return !isCloud;
} else {
return !!connectionManagementInfo.connectionProfile.databaseName;
}
default:
return true;
}
} else {
return true;
}
}
// Event Emitters
public get onProviderRegisteredEvent(): Event<data.DataProtocolServerCapabilities> {
return this._onProviderRegistered.event;
}
public dispose(): void {
this.disposables = dispose(this.disposables);
}
}

View File

@@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import data = require('data');
import { Deferred } from 'sql/base/common/promise';
export const SERVICE_ID = 'credentialsService';
export interface CredentialManagementEvents {
onSaveCredential(credentialId: string, password: string): Thenable<boolean>;
onReadCredential(credentialId: string): Thenable<data.Credential>;
onDeleteCredential(credentialId: string): Thenable<boolean>;
}
export const ICredentialsService = createDecorator<ICredentialsService>(SERVICE_ID);
export interface ICredentialsService {
_serviceBrand: any;
saveCredential(credentialId: string, password: string): Thenable<boolean>;
readCredential(credentialId: string): Thenable<data.Credential>;
deleteCredential(credentialId: string): Thenable<boolean>;
addEventListener(handle: number, events: CredentialManagementEvents): IDisposable;
}
export class CredentialsService implements ICredentialsService {
_serviceBrand: any;
private disposables: IDisposable[] = [];
private _serverEvents: { [handle: number]: CredentialManagementEvents; } = Object.create(null);
private _lastHandle: number;
private _onServerEventsReady: Deferred<void> = new Deferred<void>();
constructor() {
}
public addEventListener(handle: number, events: CredentialManagementEvents): IDisposable {
this._lastHandle = handle;
this._serverEvents[handle] = events;
this._onServerEventsReady.resolve();
return {
dispose: () => {
}
};
}
public saveCredential(credentialId: string, password: string): Thenable<boolean> {
return this._onServerEventsReady.promise.then(() => this._serverEvents[this._lastHandle].onSaveCredential(credentialId, password));
}
public readCredential(credentialId: string): Thenable<data.Credential> {
return this._onServerEventsReady.promise.then(() => this._serverEvents[this._lastHandle].onReadCredential(credentialId));
}
public deleteCredential(credentialId: string): Thenable<boolean> {
return this._onServerEventsReady.promise.then(() => this._serverEvents[this._lastHandle].onDeleteCredential(credentialId));
}
public dispose(): void {
this.disposables = dispose(this.disposables);
}
}

View File

@@ -0,0 +1,103 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import data = require('data');
export const SERVICE_ID = 'metadataService';
export const IMetadataService = createDecorator<IMetadataService>(SERVICE_ID);
export interface IMetadataService {
_serviceBrand: any;
getMetadata(connectionUri: string): Thenable<data.ProviderMetadata>;
getDatabaseNames(connectionUri: string): Thenable<string[]>;
getTableInfo(connectionUri: string, metadata: data.ObjectMetadata): Thenable<data.ColumnMetadata[]>;
getViewInfo(connectionUri: string, metadata: data.ObjectMetadata): Thenable<data.ColumnMetadata[]>;
/**
* Register a metadata provider
*/
registerProvider(providerId: string, provider: data.MetadataProvider): void;
}
export class MetadataService implements IMetadataService {
public _serviceBrand: any;
private _disposables: IDisposable[] = [];
private _providers: { [handle: string]: data.MetadataProvider; } = Object.create(null);
constructor(@IConnectionManagementService private _connectionService: IConnectionManagementService) {
}
public getMetadata(connectionUri: string): Thenable<data.ProviderMetadata> {
let providerId: string = this._connectionService.getProviderIdFromUri(connectionUri);
if (providerId) {
let provider = this._providers[providerId];
if (provider) {
return provider.getMetadata(connectionUri);
}
}
return Promise.resolve(undefined);
}
public getDatabaseNames(connectionUri: string): Thenable<string[]> {
let providerId: string = this._connectionService.getProviderIdFromUri(connectionUri);
if (providerId) {
let provider = this._providers[providerId];
if (provider) {
return provider.getDatabases(connectionUri);
}
}
return Promise.resolve(undefined);
}
public getTableInfo(connectionUri: string, metadata: data.ObjectMetadata): Thenable<data.ColumnMetadata[]> {
let providerId: string = this._connectionService.getProviderIdFromUri(connectionUri);
if (providerId) {
let provider = this._providers[providerId];
if (provider) {
return provider.getTableInfo(connectionUri, metadata);
}
}
return Promise.resolve(undefined);
}
public getViewInfo(connectionUri: string, metadata: data.ObjectMetadata): Thenable<data.ColumnMetadata[]> {
let providerId: string = this._connectionService.getProviderIdFromUri(connectionUri);
if (providerId) {
let provider = this._providers[providerId];
if (provider) {
return provider.getViewInfo(connectionUri, metadata);
}
}
return Promise.resolve(undefined);
}
/**
* Register a metadata provider
*/
public registerProvider(providerId: string, provider: data.MetadataProvider): void {
this._providers[providerId] = provider;
}
public dispose(): void {
this._disposables = dispose(this._disposables);
}
}

View File

@@ -0,0 +1,114 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { ScriptOperation } from 'sql/workbench/common/taskUtilities';
import data = require('data');
import { warn, error } from 'sql/base/common/log';
export const SERVICE_ID = 'scriptingService';
export const IScriptingService = createDecorator<IScriptingService>(SERVICE_ID);
export interface IScriptingService {
_serviceBrand: any;
script(connectionUri: string, metadata: data.ObjectMetadata, operation: ScriptOperation, paramDetails: data.ScriptingParamDetails): Thenable<data.ScriptingResult>;
/**
* Register a scripting provider
*/
registerProvider(providerId: string, provider: data.ScriptingProvider): void;
/**
* Callback method for when scripting is complete
*/
onScriptingComplete(handle: number, scriptingCompleteResult: data.ScriptingCompleteResult): void;
/**
* Returns the result for an operation if the operation failed
*/
getOperationFailedResult(operationId: string): data.ScriptingCompleteResult;
}
export class ScriptingService implements IScriptingService {
public _serviceBrand: any;
private disposables: IDisposable[] = [];
private _providers: { [handle: string]: data.ScriptingProvider; } = Object.create(null);
private failedScriptingOperations: { [operationId: string]: data.ScriptingCompleteResult } = {};
constructor( @IConnectionManagementService private _connectionService: IConnectionManagementService) { }
/**
* Call the service for scripting based on provider and scripting operation
* @param connectionUri
* @param metadata
* @param operation
* @param paramDetails
*/
public script(connectionUri: string, metadata: data.ObjectMetadata, operation: ScriptOperation, paramDetails: data.ScriptingParamDetails): Thenable<data.ScriptingResult> {
let providerId: string = this._connectionService.getProviderIdFromUri(connectionUri);
if (providerId) {
let provider = this._providers[providerId];
if (provider) {
switch (operation) {
case (ScriptOperation.Select):
return provider.scriptAsSelect(connectionUri, metadata, paramDetails);
case (ScriptOperation.Create):
return provider.scriptAsCreate(connectionUri, metadata, paramDetails);
case (ScriptOperation.Delete):
return provider.scriptAsDelete(connectionUri, metadata, paramDetails);
default:
return Promise.resolve(undefined);
}
}
}
return Promise.resolve(undefined);
}
/**
* Callback method for when scripting is complete
* @param handle
* @param scriptingCompleteResult
*/
public onScriptingComplete(handle: number, scriptingCompleteResult: data.ScriptingCompleteResult): void {
if (scriptingCompleteResult && scriptingCompleteResult.hasError && scriptingCompleteResult.errorMessage) {
error(`Scripting failed. error: ${scriptingCompleteResult.errorMessage}`);
if (scriptingCompleteResult.operationId) {
this.failedScriptingOperations[scriptingCompleteResult.operationId] = scriptingCompleteResult;
}
}
}
/**
* Returns the result for an operation if the operation failed
* @param operationId Operation Id
*/
public getOperationFailedResult(operationId: string): data.ScriptingCompleteResult {
if (operationId && operationId in this.failedScriptingOperations) {
return this.failedScriptingOperations[operationId];
} else {
return undefined;
}
}
/**
* Register a scripting provider
*/
public registerProvider(providerId: string, provider: data.ScriptingProvider): void {
this._providers[providerId] = provider;
}
public dispose(): void {
this.disposables = dispose(this.disposables);
}
}

View File

@@ -0,0 +1,89 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import * as Constants from 'sql/common/constants';
import * as data from 'data';
export const SERVICE_ID = 'serializationService';
export interface SerializationProviderEvents {
onSaveAs(saveFormat: string, savePath: string, results: string, appendToFile: boolean): Thenable<data.SaveResultRequestResult>;
}
export const ISerializationService = createDecorator<ISerializationService>(SERVICE_ID);
export interface ISerializationService {
_serviceBrand: any;
saveAs(saveFormat: string, savePath: string, results: string, appendToFile: boolean): Thenable<data.SaveResultRequestResult>;
disabledSaveAs(): Thenable<data.SaveResultRequestResult>;
addEventListener(handle: number, events: SerializationProviderEvents): IDisposable;
getSerializationFeatureMetadataProvider(ownerUri: string): data.FeatureMetadataProvider;
}
export class SerializationService implements ISerializationService {
_serviceBrand: any;
private disposables: IDisposable[] = [];
private _serverEvents: { [handle: number]: SerializationProviderEvents; } = Object.create(null);
private _lastHandle: number;
constructor(
@IConnectionManagementService private _connectionService: IConnectionManagementService,
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService
) {
}
public addEventListener(handle: number, events: SerializationProviderEvents): IDisposable {
this._lastHandle = handle;
this._serverEvents[handle] = events;
return {
dispose: () => {
}
};
}
public saveAs(saveFormat: string, savePath: string, results: string, appendToFile: boolean): Thenable<data.SaveResultRequestResult> {
if (this._serverEvents === undefined || this._serverEvents[this._lastHandle] === undefined) {
return this.disabledSaveAs();
}
return this._serverEvents[this._lastHandle].onSaveAs(saveFormat, savePath, results, appendToFile);
}
public disabledSaveAs(): Thenable<data.SaveResultRequestResult> {
return Promise.resolve({messages: Constants.SerializationDisabled});
}
public getSerializationFeatureMetadataProvider(ownerUri: string) : data.FeatureMetadataProvider {
let providerId: string = this._connectionService.getProviderIdFromUri(ownerUri);
let providerCapabilities = this._capabilitiesService.getCapabilities().find(c => c.providerName === providerId);
if (providerCapabilities) {
return providerCapabilities.features.find(f => f.featureName === SERVICE_ID);
}
return undefined;
}
public dispose(): void {
this.disposables = dispose(this.disposables);
}
}