mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-19 09:35:36 -05:00
Initial Code Layering (#3788)
* working on formatting * fixed basic lint errors; starting moving things to their appropriate location * formatting * update tslint to match the version of vscode we have * remove unused code * work in progress fixing layering * formatting * moved connection management service to platform * formatting * add missing file * moving more servies * formatting * moving more services * formatting * wip * moving more services * formatting * revert back tslint rules * move css file * add missing svgs
This commit is contained in:
@@ -1,443 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as platform from 'vs/platform/registry/common/platform';
|
||||
import * as statusbar from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
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 AccountStore from 'sql/services/accountManagement/accountStore';
|
||||
import { AccountDialogController } from 'sql/parts/accountManagement/accountDialog/accountDialogController';
|
||||
import { AutoOAuthDialogController } from 'sql/parts/accountManagement/autoOAuthDialog/autoOAuthDialogController';
|
||||
import { AccountListStatusbarItem } from 'sql/parts/accountManagement/accountListStatusbar/accountListStatusbarItem';
|
||||
import { AccountProviderAddedEventParams, UpdateAccountListEventParams } from 'sql/services/accountManagement/eventTypes';
|
||||
import { IAccountManagementService } from 'sql/services/accountManagement/interfaces';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
|
||||
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 _autoOAuthDialogController: AutoOAuthDialogController;
|
||||
private _mementoContext: Memento;
|
||||
|
||||
// EVENT EMITTERS //////////////////////////////////////////////////////
|
||||
private _addAccountProviderEmitter: Emitter<AccountProviderAddedEventParams>;
|
||||
public get addAccountProviderEvent(): Event<AccountProviderAddedEventParams> { return this._addAccountProviderEmitter.event; }
|
||||
|
||||
private _removeAccountProviderEmitter: Emitter<sqlops.AccountProviderMetadata>;
|
||||
public get removeAccountProviderEvent(): Event<sqlops.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,
|
||||
@IClipboardService private _clipboardService: IClipboardService,
|
||||
) {
|
||||
// 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<sqlops.AccountProviderMetadata>();
|
||||
this._updateAccountListEmitter = new Emitter<UpdateAccountListEventParams>();
|
||||
|
||||
// Register status bar item
|
||||
let statusbarDescriptor = new statusbar.StatusbarItemDescriptor(
|
||||
AccountListStatusbarItem,
|
||||
statusbar.StatusbarAlignment.LEFT,
|
||||
15000 /* Highest Priority */
|
||||
);
|
||||
(<statusbar.IStatusbarRegistry>platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(statusbarDescriptor);
|
||||
}
|
||||
|
||||
private get autoOAuthDialogController(): AutoOAuthDialogController {
|
||||
// If the add account dialog hasn't been defined, create a new one
|
||||
if (!this._autoOAuthDialogController) {
|
||||
this._autoOAuthDialogController = this._instantiationService.createInstance(AutoOAuthDialogController);
|
||||
}
|
||||
return this._autoOAuthDialogController;
|
||||
}
|
||||
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
/**
|
||||
* Called from an account provider (via extension host -> main thread interop) when an
|
||||
* account's properties have been updated (usually when the account goes stale).
|
||||
* @param {Account} updatedAccount Account with the updated properties
|
||||
*/
|
||||
public accountUpdated(updatedAccount: sqlops.Account): Thenable<void> {
|
||||
let self = this;
|
||||
|
||||
// 1) Update the account in the store
|
||||
// 2a) If the account was added, then the account provider incorrectly called this method.
|
||||
// Remove the account
|
||||
// 2b) If the account was modified, then update it in the local cache and notify any
|
||||
// listeners that the account provider's list changed
|
||||
// 3) Handle any errors
|
||||
return this.doWithProvider(updatedAccount.key.providerId, provider => {
|
||||
return self._accountStore.addOrUpdate(updatedAccount)
|
||||
.then(result => {
|
||||
if (result.accountAdded) {
|
||||
self._accountStore.remove(updatedAccount.key);
|
||||
return Promise.reject('Called with a new account!');
|
||||
}
|
||||
if (result.accountModified) {
|
||||
self.spliceModifiedAccount(provider, result.changedAccount);
|
||||
self.fireAccountListUpdate(provider, false);
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
}).then(
|
||||
() => { },
|
||||
reason => {
|
||||
console.warn(`Account update handler encountered error: ${reason}`);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<void> {
|
||||
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) {
|
||||
self.spliceModifiedAccount(provider, result.changedAccount);
|
||||
}
|
||||
|
||||
self.fireAccountListUpdate(provider, result.accountAdded);
|
||||
})
|
||||
.then(null, err => {
|
||||
// On error, check to see if the error is because the user cancelled. If so, just ignore
|
||||
if (err && 'userCancelledSignIn' in err) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the requested provider to refresh an account
|
||||
* @param {Account} account account to refresh
|
||||
* @return {Thenable<Account>} Promise to return an account
|
||||
*/
|
||||
public refreshAccount(account: sqlops.Account): Thenable<sqlops.Account> {
|
||||
let self = this;
|
||||
|
||||
return this.doWithProvider(account.key.providerId, (provider) => {
|
||||
return provider.provider.refresh(account)
|
||||
.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<sqlops.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<sqlops.Account[]> {
|
||||
let self = this;
|
||||
|
||||
// 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
|
||||
* @param {sqlops.AzureResource} resource The resource to get the security token for
|
||||
* @return {Thenable<{}>} Promise to return the security token
|
||||
*/
|
||||
public getSecurityToken(account: sqlops.Account, resource: sqlops.AzureResource): Thenable<{}> {
|
||||
return this.doWithProvider(account.key.providerId, provider => {
|
||||
return provider.provider.getSecurityToken(account, resource);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: sqlops.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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin auto OAuth device code open add account dialog
|
||||
* @return {TPromise<any>} Promise that finishes when the account list dialog opens
|
||||
*/
|
||||
public beginAutoOAuthDeviceCode(providerId: string, title: string, message: string, userCode: string, uri: string): Thenable<void> {
|
||||
let self = this;
|
||||
|
||||
return this.doWithProvider(providerId, provider => {
|
||||
return self.autoOAuthDialogController.openAutoOAuthDialog(providerId, title, message, userCode, uri);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* End auto OAuth Devide code closes add account dialog
|
||||
*/
|
||||
public endAutoOAuthDeviceCode(): void {
|
||||
this.autoOAuthDialogController.closeAutoOAuthDialog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the UI when a user cancels the auto OAuth dialog
|
||||
*/
|
||||
public cancelAutoOAuthDeviceCode(providerId: string): void {
|
||||
this.doWithProvider(providerId, provider => provider.provider.autoOAuthCancelled())
|
||||
.then( // Swallow errors
|
||||
null,
|
||||
err => { console.warn(`Error when cancelling auto OAuth: ${err}`); }
|
||||
)
|
||||
.then(() => this.autoOAuthDialogController.closeAutoOAuthDialog());
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the user code to the clipboard and open a browser to the verification URI
|
||||
*/
|
||||
public copyUserCodeAndOpenBrowser(userCode: string, uri: string): void {
|
||||
this._clipboardService.writeText(userCode);
|
||||
window.open(uri);
|
||||
}
|
||||
|
||||
// SERVICE MANAGEMENT METHODS //////////////////////////////////////////
|
||||
/**
|
||||
* Called by main thread to register an account provider from extension
|
||||
* @param {sqlops.AccountProviderMetadata} providerMetadata Metadata of the provider that is being registered
|
||||
* @param {sqlops.AccountProvider} provider References to the methods of the provider
|
||||
*/
|
||||
public registerProvider(providerMetadata: sqlops.AccountProviderMetadata, provider: sqlops.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: sqlops.Account[]) => {
|
||||
return provider.initialize(accounts);
|
||||
})
|
||||
.then((accounts: sqlops.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: sqlops.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> {
|
||||
let provider = this._providers[providerId];
|
||||
if (!provider) {
|
||||
// If the provider doesn't already exist wait until it gets registered
|
||||
let deferredPromise = new Deferred<T>();
|
||||
let toDispose = this.addAccountProviderEvent(params => {
|
||||
if (params.addedProvider.id === providerId) {
|
||||
toDispose.dispose();
|
||||
deferredPromise.resolve(op(this._providers[providerId]));
|
||||
}
|
||||
});
|
||||
return deferredPromise;
|
||||
}
|
||||
|
||||
return op(provider);
|
||||
}
|
||||
|
||||
private fireAccountListUpdate(provider: AccountProviderWithMetadata, sort: boolean) {
|
||||
// Step 1) Get and sort the list
|
||||
if (sort) {
|
||||
provider.accounts.sort((a: sqlops.Account, b: sqlops.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 spliceModifiedAccount(provider: AccountProviderWithMetadata, modifiedAccount: sqlops.Account) {
|
||||
// Find the updated account and splice the updated one in
|
||||
let indexToRemove: number = provider.accounts.findIndex(account => {
|
||||
return account.key.accountId === modifiedAccount.key.accountId;
|
||||
});
|
||||
if (indexToRemove >= 0) {
|
||||
provider.accounts.splice(indexToRemove, 1, modifiedAccount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins together an account provider, its metadata, and its accounts, used in the provider list
|
||||
*/
|
||||
export interface AccountProviderWithMetadata {
|
||||
metadata: sqlops.AccountProviderMetadata;
|
||||
provider: sqlops.AccountProvider;
|
||||
accounts: sqlops.Account[];
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
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: sqlops.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<sqlops.Account[]> {
|
||||
let self = this;
|
||||
|
||||
return this.doOperation(() => {
|
||||
return self.readFromMemento()
|
||||
.then(accounts => accounts.filter(account => account.key.providerId === providerId));
|
||||
});
|
||||
}
|
||||
|
||||
public getAllAccounts(): Thenable<sqlops.Account[]> {
|
||||
let self = this;
|
||||
|
||||
return this.doOperation(() => {
|
||||
return self.readFromMemento();
|
||||
});
|
||||
}
|
||||
|
||||
public remove(key: sqlops.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: sqlops.AccountKey, updateOperation: (account: sqlops.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: sqlops.AccountKey, key2: sqlops.AccountKey): boolean {
|
||||
// Provider ID and Account ID must match
|
||||
return key1.providerId === key2.providerId && key1.accountId === key2.accountId;
|
||||
}
|
||||
|
||||
private static mergeAccounts(source: sqlops.Account, target: sqlops.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: sqlops.Account[], accountToAdd: sqlops.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: sqlops.Account[], accountToRemove: sqlops.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: sqlops.Account[], accountToUpdate: sqlops.AccountKey, updateOperation: (account: sqlops.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<sqlops.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: sqlops.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: sqlops.Account[];
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
|
||||
/**
|
||||
* 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: sqlops.Account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters that go along with an account provider being added
|
||||
*/
|
||||
export interface AccountProviderAddedEventParams {
|
||||
/**
|
||||
* The provider that was registered
|
||||
*/
|
||||
addedProvider: sqlops.AccountProviderMetadata;
|
||||
|
||||
/**
|
||||
* The accounts that were rehydrated with the provider
|
||||
*/
|
||||
initialAccounts: sqlops.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: sqlops.Account[];
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
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 //////////////////////////////////////////
|
||||
accountUpdated(account: sqlops.Account): Thenable<void>;
|
||||
addAccount(providerId: string): Thenable<void>;
|
||||
getAccountProviderMetadata(): Thenable<sqlops.AccountProviderMetadata[]>;
|
||||
getAccountsForProvider(providerId: string): Thenable<sqlops.Account[]>;
|
||||
getSecurityToken(account: sqlops.Account, resource: sqlops.AzureResource): Thenable<{}>;
|
||||
removeAccount(accountKey: sqlops.AccountKey): Thenable<boolean>;
|
||||
refreshAccount(account: sqlops.Account): Thenable<sqlops.Account>;
|
||||
|
||||
// UI METHODS //////////////////////////////////////////////////////////
|
||||
openAccountListDialog(): Thenable<void>;
|
||||
beginAutoOAuthDeviceCode(providerId: string, title: string, message: string, userCode: string, uri: string): Thenable<void>;
|
||||
endAutoOAuthDeviceCode(): void;
|
||||
cancelAutoOAuthDeviceCode(providerId: string): void;
|
||||
copyUserCodeAndOpenBrowser(userCode: string, uri: string): void;
|
||||
|
||||
// SERVICE MANAGEMENT METHODS /////////////////////////////////////////
|
||||
registerProvider(providerMetadata: sqlops.AccountProviderMetadata, provider: sqlops.AccountProvider): void;
|
||||
shutdown(): void;
|
||||
unregisterProvider(providerMetadata: sqlops.AccountProviderMetadata): void;
|
||||
|
||||
// EVENTING ////////////////////////////////////////////////////////////
|
||||
readonly addAccountProviderEvent: Event<AccountProviderAddedEventParams>;
|
||||
readonly removeAccountProviderEvent: Event<sqlops.AccountProviderMetadata>;
|
||||
readonly updateAccountListEvent: Event<UpdateAccountListEventParams>;
|
||||
}
|
||||
|
||||
// Enum matching the AzureResource enum from sqlops.d.ts
|
||||
export enum AzureResource {
|
||||
ResourceManagement = 0,
|
||||
Sql = 1
|
||||
}
|
||||
|
||||
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: sqlops.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<sqlops.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<sqlops.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: sqlops.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: sqlops.AccountKey, updateOperation: (account: sqlops.Account) => void): Thenable<boolean>;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { 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,
|
||||
DELETE_WIDGET,
|
||||
PINUNPIN_TAB,
|
||||
NEW_TABS,
|
||||
CLOSE_TAB,
|
||||
COLLAPSE_WIDGET
|
||||
}
|
||||
|
||||
export interface IDeleteWidgetPayload {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface IAngularEvent {
|
||||
event: AngularEventType;
|
||||
payload: any;
|
||||
}
|
||||
|
||||
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: IAngularEvent) => 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, payload?: any): void;
|
||||
}
|
||||
|
||||
export class AngularEventingService implements IAngularEventingService {
|
||||
public _serviceBrand: any;
|
||||
private _angularMap = new Map<string, Subject<IAngularEvent>>();
|
||||
|
||||
public onAngularEvent(uri: string, cb: (event: IAngularEvent) => void): Subscription {
|
||||
let subject: Subject<IAngularEvent>;
|
||||
if (!this._angularMap.has(uri)) {
|
||||
subject = new Subject<IAngularEvent>();
|
||||
this._angularMap.set(uri, subject);
|
||||
} else {
|
||||
subject = this._angularMap.get(uri);
|
||||
}
|
||||
let sub = subject.subscribe(cb);
|
||||
return sub;
|
||||
}
|
||||
|
||||
public sendAngularEvent(uri: string, event: AngularEventType, payload?: any): 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, payload });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DataService } from 'sql/parts/grid/services/dataService';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ConnectionContextKey } from 'sql/parts/connection/common/connectionContextKey';
|
||||
import { IBootstrapParams } from './bootstrapService';
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Deferred } from 'sql/base/common/promise';
|
||||
import { ConnectionProviderProperties, IConnectionProviderRegistry, Extensions as ConnectionExtensions } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
|
||||
import { toObject } from 'sql/base/common/map';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { Memento } from 'vs/workbench/common/memento';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { getIdFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
|
||||
export const SERVICE_ID = 'capabilitiesService';
|
||||
export const HOST_NAME = 'sqlops';
|
||||
export const HOST_VERSION = '1.0';
|
||||
|
||||
const connectionRegistry = Registry.as<IConnectionProviderRegistry>(ConnectionExtensions.ConnectionProviderContributions);
|
||||
|
||||
interface ConnectionCache {
|
||||
[id: string]: ConnectionProviderProperties;
|
||||
}
|
||||
|
||||
interface CapabilitiesMomento {
|
||||
connectionProviderCache: ConnectionCache;
|
||||
}
|
||||
|
||||
export const clientCapabilities = {
|
||||
hostName: HOST_NAME,
|
||||
hostVersion: HOST_VERSION
|
||||
};
|
||||
|
||||
export interface ProviderFeatures {
|
||||
connection: ConnectionProviderProperties;
|
||||
}
|
||||
|
||||
|
||||
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(provider: string): ProviderFeatures;
|
||||
|
||||
/**
|
||||
* get the old version of provider information
|
||||
*/
|
||||
getLegacyCapabilities(provider: string): sqlops.DataProtocolServerCapabilities;
|
||||
|
||||
/**
|
||||
* Register a capabilities provider
|
||||
*/
|
||||
registerProvider(provider: sqlops.CapabilitiesProvider): void;
|
||||
|
||||
/**
|
||||
* Returns true if the feature is available for given connection
|
||||
*/
|
||||
isFeatureAvailable(action: IAction, connectionManagementInfo: ConnectionManagementInfo): boolean;
|
||||
|
||||
/**
|
||||
* When a new capabilities is registered, it emits the provider name, be to use to get the new capabilities
|
||||
*/
|
||||
readonly onCapabilitiesRegistered: Event<ProviderFeatures>;
|
||||
|
||||
/**
|
||||
* Get an array of all known providers
|
||||
*/
|
||||
readonly providers: { [id: string]: ProviderFeatures };
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Capabilities service implementation class. This class provides the ability
|
||||
* to discover the DMP capabilties that a DMP provider offers.
|
||||
*/
|
||||
export class CapabilitiesService extends Disposable implements ICapabilitiesService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private _momento = new Memento('capabilities');
|
||||
private _providers = new Map<string, ProviderFeatures>();
|
||||
private _featureUpdateEvents = new Map<string, Emitter<ProviderFeatures>>();
|
||||
private _legacyProviders = new Map<string, sqlops.DataProtocolServerCapabilities>();
|
||||
|
||||
private _onCapabilitiesRegistered = this._register(new Emitter<ProviderFeatures>());
|
||||
public readonly onCapabilitiesRegistered = this._onCapabilitiesRegistered.event;
|
||||
|
||||
constructor(
|
||||
@IStorageService private _storageService: IStorageService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IExtensionManagementService extentionManagementService: IExtensionManagementService
|
||||
) {
|
||||
super();
|
||||
|
||||
if (!this.capabilities.connectionProviderCache) {
|
||||
this.capabilities.connectionProviderCache = {};
|
||||
}
|
||||
|
||||
// handle in case some extensions have already registered (unlikley)
|
||||
Object.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)
|
||||
Object.entries(this.capabilities.connectionProviderCache).map(v => {
|
||||
this.handleConnectionProvider({ id: v[0], properties: v[1] }, false);
|
||||
});
|
||||
|
||||
extensionService.whenInstalledExtensionsRegistered().then(() => {
|
||||
this.cleanupProviders();
|
||||
});
|
||||
|
||||
this._register(extentionManagementService.onDidUninstallExtension(({ identifier }) => {
|
||||
let extensionid = getIdFromLocalExtensionId(identifier.id);
|
||||
extensionService.getExtensions().then(i => {
|
||||
let extension = i.find(c => c.id === extensionid);
|
||||
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 {
|
||||
return this._providers.get(provider);
|
||||
}
|
||||
|
||||
public getLegacyCapabilities(provider: string): sqlops.DataProtocolServerCapabilities {
|
||||
return this._legacyProviders.get(provider);
|
||||
}
|
||||
|
||||
public get providers(): { [id: string]: ProviderFeatures } {
|
||||
return toObject(this._providers);
|
||||
}
|
||||
|
||||
private get capabilities(): CapabilitiesMomento {
|
||||
return this._momento.getMemento(this._storageService) as CapabilitiesMomento;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the capabilities provider and query the provider for its capabilities
|
||||
* @param provider
|
||||
*/
|
||||
public registerProvider(provider: sqlops.CapabilitiesProvider): void {
|
||||
// request the capabilities from server
|
||||
provider.getServerCapabilities(clientCapabilities).then(serverCapabilities => {
|
||||
this._legacyProviders.set(serverCapabilities.providerName, serverCapabilities);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: IAction, 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;
|
||||
}
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this._momento.saveMemento();
|
||||
}
|
||||
}
|
||||
@@ -4,25 +4,25 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Node Modules */
|
||||
import { Injectable, Inject, forwardRef, OnDestroy } from '@angular/core';
|
||||
import { Injectable, Inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
/* SQL imports */
|
||||
import { IDefaultComponentParams } from 'sql/services/bootstrap/bootstrapParams';
|
||||
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { IMetadataService } from 'sql/services/metadata/metadataService';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { ConnectionManagementInfo } from 'sql/parts/connection/common/connectionManagementInfo';
|
||||
import { IAdminService } from 'sql/parts/admin/common/adminService';
|
||||
import { IQueryManagementService } from 'sql/parts/query/common/queryManagement';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
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/node/lifecycle';
|
||||
import { ConnectionContextKey } from 'sql/parts/connection/common/connectionContextKey';
|
||||
|
||||
import { ProviderMetadata, DatabaseInfo, SimpleExecuteResult } from 'sqlops';
|
||||
|
||||
/* VS imports */
|
||||
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
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 {
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as sqlops from 'sqlops';
|
||||
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<sqlops.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<sqlops.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<sqlops.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);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { localize } from 'vs/nls';
|
||||
import { data } from 'vs/base/test/common/filters.perf.data';
|
||||
|
||||
export const SERVICE_ID = 'dacFxService';
|
||||
export const IDacFxService = createDecorator<IDacFxService>(SERVICE_ID);
|
||||
|
||||
export interface IDacFxService {
|
||||
_serviceBrand: any;
|
||||
|
||||
registerProvider(providerId: string, provider: sqlops.DacFxServicesProvider): void;
|
||||
exportBacpac(sourceDatabaseName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): void;
|
||||
importBacpac(packageFilePath: string, targetDatabaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): void;
|
||||
extractDacpac(sourceDatabaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): void;
|
||||
deployDacpac(packageFilePath: string, targetDatabaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): void;
|
||||
}
|
||||
|
||||
export class DacFxService implements IDacFxService {
|
||||
_serviceBrand: any;
|
||||
private _providers: { [handle: string]: sqlops.DacFxServicesProvider; } = Object.create(null);
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionService: IConnectionManagementService
|
||||
) {
|
||||
}
|
||||
|
||||
registerProvider(providerId: string, provider: sqlops.DacFxServicesProvider): void {
|
||||
this._providers[providerId] = provider;
|
||||
}
|
||||
|
||||
exportBacpac(databasesName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.exportBacpac(databasesName, packageFilePath, ownerUri, taskExecutionMode);
|
||||
});
|
||||
}
|
||||
|
||||
importBacpac(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.importBacpac(packageFilePath, databaseName, ownerUri, taskExecutionMode);
|
||||
});
|
||||
}
|
||||
|
||||
extractDacpac(databaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.extractDacpac(databaseName, packageFilePath, applicationName, applicationVersion, ownerUri, taskExecutionMode);
|
||||
});
|
||||
}
|
||||
|
||||
deployDacpac(packageFilePath: string, databaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.deployDacpac(packageFilePath, databaseName, upgradeExisting, ownerUri, taskExecutionMode);
|
||||
});
|
||||
}
|
||||
|
||||
private _runAction<T>(uri: string, action: (handler: sqlops.DacFxServicesProvider) => Thenable<T>): Thenable<T> {
|
||||
let providerId: string = this._connectionService.getProviderIdFromUri(uri);
|
||||
|
||||
if (!providerId) {
|
||||
return TPromise.wrapError(new Error(localize('providerIdNotValidError', "Connection is required in order to interact with DacFxService")));
|
||||
}
|
||||
let handler = this._providers[providerId];
|
||||
if (handler) {
|
||||
return action(handler);
|
||||
} else {
|
||||
return TPromise.wrapError(new Error(localize('noHandlerRegistered', "No Handler Registered")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
export const IDashboardService = createDecorator<IDashboardService>('dashboardService');
|
||||
|
||||
export interface IDashboardService {
|
||||
|
||||
_serviceBrand: any;
|
||||
readonly onDidOpenDashboard: Event<sqlops.DashboardDocument>;
|
||||
readonly onDidChangeToDashboard: Event<sqlops.DashboardDocument>;
|
||||
readonly onLayout: Event<DOM.Dimension>;
|
||||
|
||||
openDashboard(document: sqlops.DashboardDocument): void;
|
||||
|
||||
changeToDashboard(document: sqlops.DashboardDocument): void;
|
||||
|
||||
layout(dimension: DOM.Dimension): void;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IDashboardService } from './dashboardService';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
export class DashboardService implements IDashboardService {
|
||||
public _serviceBrand: any;
|
||||
private _onDidOpenDashboard = new Emitter<sqlops.DashboardDocument>();
|
||||
public readonly onDidOpenDashboard: Event<sqlops.DashboardDocument> = this._onDidOpenDashboard.event;
|
||||
|
||||
private _onDidChangeToDashboard = new Emitter<sqlops.DashboardDocument>();
|
||||
public readonly onDidChangeToDashboard: Event<sqlops.DashboardDocument> = this._onDidChangeToDashboard.event;
|
||||
|
||||
private _onLayout = new Emitter<DOM.Dimension>();
|
||||
public readonly onLayout: Event<DOM.Dimension> = this._onLayout.event;
|
||||
|
||||
public openDashboard(document: sqlops.DashboardDocument): void {
|
||||
this._onDidOpenDashboard.fire(document);
|
||||
}
|
||||
|
||||
public changeToDashboard(document: sqlops.DashboardDocument): void {
|
||||
this._onDidChangeToDashboard.fire(document);
|
||||
}
|
||||
|
||||
public layout(dimension: DOM.Dimension): void {
|
||||
this._onLayout.fire(dimension);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { IItemConfig, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IView, IModelView } from 'sql/services/model/modelViewService';
|
||||
|
||||
export const SERVICE_ID = 'dashboardViewService';
|
||||
|
||||
export interface IDashboardWebview extends IView {
|
||||
setHtml(html: string): void;
|
||||
onMessage: Event<string>;
|
||||
sendMessage(message: string);
|
||||
}
|
||||
|
||||
export interface IDashboardViewService {
|
||||
_serviceBrand: any;
|
||||
onRegisteredWebview: Event<IDashboardWebview>;
|
||||
registerWebview(widget: IDashboardWebview);
|
||||
onRegisteredModelView: Event<IModelView>;
|
||||
registerModelView(widget: IModelView);
|
||||
}
|
||||
|
||||
export const IDashboardViewService = createDecorator<IDashboardViewService>(SERVICE_ID);
|
||||
@@ -1,28 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IDashboardViewService, IDashboardWebview } from 'sql/services/dashboard/common/dashboardViewService';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IModelView } from 'sql/services/model/modelViewService';
|
||||
|
||||
export class DashboardViewService implements IDashboardViewService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private _onRegisteredWebview = new Emitter<IDashboardWebview>();
|
||||
public readonly onRegisteredWebview: Event<IDashboardWebview> = this._onRegisteredWebview.event;
|
||||
|
||||
private _onRegisteredModelView = new Emitter<IModelView>();
|
||||
public readonly onRegisteredModelView: Event<IModelView> = this._onRegisteredModelView.event;
|
||||
|
||||
public registerWebview(widget: IDashboardWebview) {
|
||||
this._onRegisteredWebview.fire(widget);
|
||||
}
|
||||
|
||||
registerModelView(view: IModelView) {
|
||||
this._onRegisteredModelView.fire(view);
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'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 * as sqlops from 'sqlops';
|
||||
|
||||
export const SERVICE_ID = 'metadataService';
|
||||
|
||||
export const IMetadataService = createDecorator<IMetadataService>(SERVICE_ID);
|
||||
|
||||
export interface IMetadataService {
|
||||
_serviceBrand: any;
|
||||
|
||||
getMetadata(connectionUri: string): Thenable<sqlops.ProviderMetadata>;
|
||||
|
||||
getDatabaseNames(connectionUri: string): Thenable<string[]>;
|
||||
|
||||
getTableInfo(connectionUri: string, metadata: sqlops.ObjectMetadata): Thenable<sqlops.ColumnMetadata[]>;
|
||||
|
||||
getViewInfo(connectionUri: string, metadata: sqlops.ObjectMetadata): Thenable<sqlops.ColumnMetadata[]>;
|
||||
|
||||
/**
|
||||
* Register a metadata provider
|
||||
*/
|
||||
registerProvider(providerId: string, provider: sqlops.MetadataProvider): void;
|
||||
}
|
||||
|
||||
export class MetadataService implements IMetadataService {
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
private _providers: { [handle: string]: sqlops.MetadataProvider; } = Object.create(null);
|
||||
|
||||
constructor( @IConnectionManagementService private _connectionService: IConnectionManagementService) {
|
||||
}
|
||||
|
||||
public getMetadata(connectionUri: string): Thenable<sqlops.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([]);
|
||||
}
|
||||
|
||||
public getTableInfo(connectionUri: string, metadata: sqlops.ObjectMetadata): Thenable<sqlops.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: sqlops.ObjectMetadata): Thenable<sqlops.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: sqlops.MetadataProvider): void {
|
||||
this._providers[providerId] = provider;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._disposables = dispose(this._disposables);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import { IItemConfig, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IComponentEventArgs } from 'sql/parts/modelComponents/interfaces';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export interface IView {
|
||||
readonly id: string;
|
||||
readonly connection: sqlops.connection.Connection;
|
||||
readonly serverInfo: sqlops.ServerInfo;
|
||||
}
|
||||
|
||||
export interface IModelViewEventArgs extends IComponentEventArgs {
|
||||
isRootComponent: boolean;
|
||||
}
|
||||
|
||||
export interface IModelView extends IView {
|
||||
initializeModel(rootComponent: IComponentShape, validationCallback?: (componentId: string) => Thenable<boolean>): void;
|
||||
clearContainer(componentId: string): void;
|
||||
addToContainer(containerId: string, item: IItemConfig, index?: number): void;
|
||||
removeFromContainer(containerId: string, item: IItemConfig): void;
|
||||
setLayout(componentId: string, layout: any): void;
|
||||
setProperties(componentId: string, properties: { [key: string]: any }): void;
|
||||
setDataProvider(handle: number, componentId: string, context: any): void;
|
||||
refreshDataProvider(componentId: string, item: any): void;
|
||||
registerEvent(componentId: string);
|
||||
onEvent: Event<IModelViewEventArgs>;
|
||||
validate(componentId: string): Thenable<boolean>;
|
||||
readonly onDestroy: Event<void>;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { IItemConfig, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IView, IModelView } from 'sql/services/model/modelViewService';
|
||||
|
||||
export const SERVICE_ID = 'modelViewService';
|
||||
|
||||
export interface IModelViewService {
|
||||
_serviceBrand: any;
|
||||
onRegisteredModelView: Event<IModelView>;
|
||||
registerModelView(widget: IModelView);
|
||||
}
|
||||
|
||||
export const IModelViewService = createDecorator<IModelViewService>(SERVICE_ID);
|
||||
@@ -1,21 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IModelViewService } from 'sql/services/modelComponents/modelViewService';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IModelView } from 'sql/services/model/modelViewService';
|
||||
|
||||
export class ModelViewService implements IModelViewService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private _onRegisteredModelView = new Emitter<IModelView>();
|
||||
public readonly onRegisteredModelView: Event<IModelView> = this._onRegisteredModelView.event;
|
||||
|
||||
public registerModelView(view: IModelView) {
|
||||
this._onRegisteredModelView.fire(view);
|
||||
}
|
||||
}
|
||||
@@ -1,366 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// Note: the code in the v3 and v4 namespaces has been adapted (with significant changes) from https://github.com/nteract/nteract/tree/master/packages/commutable
|
||||
|
||||
'use strict';
|
||||
|
||||
import { nb } from 'sqlops';
|
||||
|
||||
import * as json from 'vs/base/common/json';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
import { JSONObject } from 'sql/parts/notebook/models/jsonext';
|
||||
import { OutputTypes } from 'sql/parts/notebook/models/contracts';
|
||||
import { nbversion } from 'sql/parts/notebook/notebookConstants';
|
||||
|
||||
type MimeBundle = { [key: string]: string | string[] | undefined };
|
||||
|
||||
export class LocalContentManager implements nb.ContentManager {
|
||||
public async getNotebookContents(notebookUri: URI): Promise<nb.INotebookContents> {
|
||||
if (!notebookUri) {
|
||||
return undefined;
|
||||
}
|
||||
// TODO validate this is an actual file URI, and error if not
|
||||
let path = notebookUri.fsPath;
|
||||
// Note: intentionally letting caller handle exceptions
|
||||
let notebookFileBuffer = await pfs.readFile(path);
|
||||
let contents: JSONObject = json.parse(notebookFileBuffer.toString());
|
||||
|
||||
if (contents) {
|
||||
if (contents.nbformat === 4) {
|
||||
return v4.readNotebook(<any>contents);
|
||||
} else if (contents.nbformat === 3) {
|
||||
return v3.readNotebook(<any>contents);
|
||||
}
|
||||
if (contents.nbformat) {
|
||||
throw new TypeError(localize('nbformatNotRecognized', 'nbformat v{0}.{1} not recognized', contents.nbformat, contents.nbformat_minor));
|
||||
}
|
||||
}
|
||||
// else, fallthrough condition
|
||||
throw new TypeError(localize('nbNotSupported', 'This notebook format is not supported'));
|
||||
|
||||
}
|
||||
|
||||
public async save(notebookUri: URI, notebook: nb.INotebookContents): Promise<nb.INotebookContents> {
|
||||
// Convert to JSON with pretty-print functionality
|
||||
let contents = JSON.stringify(notebook, undefined, ' ');
|
||||
let path = notebookUri.fsPath;
|
||||
await pfs.writeFile(path, contents);
|
||||
return notebook;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace v4 {
|
||||
export function readNotebook(contents: nb.INotebookContents): nb.INotebookContents {
|
||||
let notebook: nb.INotebookContents = {
|
||||
cells: [],
|
||||
metadata: contents.metadata,
|
||||
nbformat: 4,
|
||||
nbformat_minor: contents.nbformat_minor
|
||||
};
|
||||
|
||||
for (let cell of contents.cells) {
|
||||
notebook.cells.push(readCell(cell));
|
||||
}
|
||||
|
||||
return notebook;
|
||||
}
|
||||
|
||||
function readCell(cell: nb.ICellContents): nb.ICellContents {
|
||||
switch (cell.cell_type) {
|
||||
case 'markdown':
|
||||
case 'raw':
|
||||
return createDefaultCell(cell);
|
||||
case 'code':
|
||||
return createCodeCell(cell);
|
||||
default:
|
||||
throw new TypeError(localize('unknownCellType', 'Cell type {0} unknown', cell.cell_type));
|
||||
}
|
||||
}
|
||||
|
||||
export function createDefaultCell(cell: nb.ICellContents): nb.ICellContents {
|
||||
return {
|
||||
cell_type: cell.cell_type,
|
||||
source: demultiline(cell.source),
|
||||
metadata: cell.metadata
|
||||
};
|
||||
}
|
||||
|
||||
function createCodeCell(cell: nb.ICellContents): nb.ICellContents {
|
||||
return {
|
||||
cell_type: cell.cell_type,
|
||||
source: demultiline(cell.source),
|
||||
metadata: cell.metadata,
|
||||
execution_count: cell.execution_count,
|
||||
outputs: createOutputs(cell)
|
||||
};
|
||||
}
|
||||
|
||||
function createOutputs(cell: nb.ICellContents): nb.ICellOutput[] {
|
||||
return cell.outputs && cell.outputs.length > 0 ? cell.outputs.map(output => createOutput(output as nb.Output)) : [];
|
||||
}
|
||||
|
||||
function createOutput(output: nb.Output): nb.ICellOutput {
|
||||
switch (output.output_type) {
|
||||
case OutputTypes.ExecuteResult:
|
||||
return <nb.IExecuteResult>{
|
||||
output_type: output.output_type,
|
||||
execution_count: output.execution_count,
|
||||
data: createMimeBundle(output.data),
|
||||
metadata: output.metadata
|
||||
};
|
||||
case OutputTypes.DisplayData:
|
||||
case OutputTypes.UpdateDisplayData:
|
||||
return <nb.IDisplayResult>{
|
||||
output_type: output.output_type,
|
||||
data: createMimeBundle(output.data),
|
||||
metadata: output.metadata
|
||||
};
|
||||
case 'stream':
|
||||
return <nb.IStreamResult>{
|
||||
output_type: output.output_type,
|
||||
name: output.name,
|
||||
text: demultiline(output.text)
|
||||
};
|
||||
case 'error':
|
||||
return <nb.IErrorResult>{
|
||||
output_type: 'error',
|
||||
ename: output.ename,
|
||||
evalue: output.evalue,
|
||||
// Note: this is one of the cases where the Array of strings (for
|
||||
// traceback) is part of the format, not a multiline string
|
||||
traceback: output.traceback
|
||||
};
|
||||
default:
|
||||
// Should never get here
|
||||
throw new TypeError(localize('unrecognizedOutput', 'Output type {0} not recognized', (<any>output).output_type));
|
||||
}
|
||||
}
|
||||
|
||||
function createMimeBundle(oldMimeBundle: MimeBundle): MimeBundle {
|
||||
let mimeBundle: MimeBundle = {};
|
||||
for (let key of Object.keys(oldMimeBundle)) {
|
||||
mimeBundle[key] = cleanMimeData(key, oldMimeBundle[key]);
|
||||
}
|
||||
return mimeBundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans mimedata, primarily converts an array of strings into a single string
|
||||
* joined by newlines.
|
||||
*
|
||||
* @param key The key, usually a mime type, that is associated with the mime data.
|
||||
* @param data The mime data to clean.
|
||||
*
|
||||
* @returns The cleaned mime data.
|
||||
*/
|
||||
export function cleanMimeData(key: string, data: string | string[] | undefined) {
|
||||
// See https://github.com/jupyter/nbformat/blob/62d6eb8803616d198eaa2024604d1fe923f2a7b3/nbformat/v4/nbformat.v4.schema.json#L368
|
||||
if (isJSONKey(key)) {
|
||||
// Data stays as is for JSON types
|
||||
return data;
|
||||
}
|
||||
|
||||
if (typeof data === 'string' || Array.isArray(data)) {
|
||||
return demultiline(data);
|
||||
}
|
||||
|
||||
throw new TypeError(localize('invalidMimeData', 'Data for {0} is expected to be a string or an Array of strings', key));
|
||||
}
|
||||
|
||||
export function demultiline(value: nb.MultilineString): string {
|
||||
return Array.isArray(value) ? value.join('') : value;
|
||||
}
|
||||
|
||||
function isJSONKey(key: string): boolean {
|
||||
return /^application\/(.*\+)?json$/.test(key);
|
||||
}
|
||||
}
|
||||
|
||||
namespace v3 {
|
||||
|
||||
export function readNotebook(contents: Notebook): nb.INotebookContents {
|
||||
let notebook: nb.INotebookContents = {
|
||||
cells: [],
|
||||
metadata: contents.metadata,
|
||||
// Note: upgrading to v4 as we're converting to our codebase
|
||||
nbformat: 4,
|
||||
nbformat_minor: nbversion.MINOR_VERSION
|
||||
};
|
||||
|
||||
if (contents.worksheets) {
|
||||
for (let worksheet of contents.worksheets) {
|
||||
if (worksheet.cells) {
|
||||
notebook.cells.push(...worksheet.cells.map(cell => createCell(cell)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return notebook;
|
||||
}
|
||||
|
||||
function createCell(cell: Cell): nb.ICellContents {
|
||||
switch (cell.cell_type) {
|
||||
case 'markdown':
|
||||
case 'raw':
|
||||
return v4.createDefaultCell(cell);
|
||||
case 'code':
|
||||
return createCodeCell(cell as CodeCell);
|
||||
case 'heading':
|
||||
return createHeadingCell(cell);
|
||||
default:
|
||||
throw new TypeError(`Cell type ${(cell as any).cell_type} unknown`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createMimeBundle(oldMimeBundle: MimeOutput): MimeBundle {
|
||||
let mimeBundle: MimeBundle = {};
|
||||
for (let key of Object.keys(oldMimeBundle)) {
|
||||
// v3 had non-media types for rich media
|
||||
if (key in VALID_MIMETYPES) {
|
||||
let newKey = VALID_MIMETYPES[key as MimeTypeKey];
|
||||
mimeBundle[newKey] = v4.cleanMimeData(newKey, oldMimeBundle[key]);
|
||||
}
|
||||
}
|
||||
return mimeBundle;
|
||||
}
|
||||
|
||||
const createOutput = (output: Output): nb.ICellOutput => {
|
||||
switch (output.output_type) {
|
||||
case 'pyout':
|
||||
return <nb.IExecuteResult> {
|
||||
output_type: OutputTypes.ExecuteResult,
|
||||
execution_count: output.prompt_number,
|
||||
data: createMimeBundle(output),
|
||||
metadata: output.metadata
|
||||
};
|
||||
case 'display_data':
|
||||
return <nb.IDisplayData> {
|
||||
output_type: OutputTypes.DisplayData,
|
||||
data: createMimeBundle(output),
|
||||
metadata: output.metadata
|
||||
};
|
||||
case 'stream':
|
||||
// Default to stdout in all cases unless it's stderr
|
||||
const name = output.stream === 'stderr' ? 'stderr' : 'stdout';
|
||||
return <nb.IStreamResult> {
|
||||
output_type: OutputTypes.Stream,
|
||||
name: name,
|
||||
text: v4.demultiline(output.text)
|
||||
};
|
||||
case 'pyerr':
|
||||
return <nb.IErrorResult> {
|
||||
output_type: OutputTypes.Error,
|
||||
ename: output.ename,
|
||||
evalue: output.evalue,
|
||||
traceback: output.traceback
|
||||
};
|
||||
default:
|
||||
throw new TypeError(localize('unrecognizedOutputType', 'Output type {0} not recognized', output.output_type));
|
||||
}
|
||||
};
|
||||
|
||||
function createCodeCell(cell: CodeCell): nb.ICellContents {
|
||||
return <nb.ICellContents> {
|
||||
cell_type: cell.cell_type,
|
||||
source: v4.demultiline(cell.input),
|
||||
outputs: cell.outputs.map(createOutput),
|
||||
execution_count: cell.prompt_number,
|
||||
metadata: cell.metadata
|
||||
};
|
||||
}
|
||||
|
||||
function createHeadingCell(cell: HeadingCell): nb.ICellContents {
|
||||
// v3 heading cells are just markdown cells in v4+
|
||||
return <nb.ICellContents> {
|
||||
cell_type: 'markdown',
|
||||
source: Array.isArray(cell.source)
|
||||
? v4.demultiline(
|
||||
cell.source.map(line =>
|
||||
Array(cell.level)
|
||||
.join('#')
|
||||
.concat(' ')
|
||||
.concat(line)
|
||||
)
|
||||
)
|
||||
: cell.source,
|
||||
metadata: cell.metadata
|
||||
};
|
||||
}
|
||||
|
||||
const VALID_MIMETYPES = {
|
||||
text: 'text/plain',
|
||||
latex: 'text/latex',
|
||||
png: 'image/png',
|
||||
jpeg: 'image/jpeg',
|
||||
svg: 'image/svg+xml',
|
||||
html: 'text/html',
|
||||
javascript: 'application/x-javascript',
|
||||
json: 'application/javascript',
|
||||
pdf: 'application/pdf'
|
||||
};
|
||||
type MimeTypeKey = keyof typeof VALID_MIMETYPES;
|
||||
type MimePayload = { [P in MimeTypeKey]?: nb.MultilineString };
|
||||
|
||||
interface MimeOutput<T extends string = string> extends MimePayload {
|
||||
output_type: T;
|
||||
prompt_number?: number;
|
||||
metadata: object;
|
||||
}
|
||||
|
||||
export interface ExecuteResult extends MimeOutput<'pyout'> { }
|
||||
export interface DisplayData extends MimeOutput<'display_data'> { }
|
||||
|
||||
export interface StreamOutput {
|
||||
output_type: 'stream';
|
||||
stream: string;
|
||||
text: nb.MultilineString;
|
||||
}
|
||||
|
||||
export interface ErrorOutput {
|
||||
output_type: 'error' | 'pyerr';
|
||||
ename: string;
|
||||
evalue: string;
|
||||
traceback: string[];
|
||||
}
|
||||
|
||||
export type Output = ExecuteResult | DisplayData | StreamOutput | ErrorOutput;
|
||||
|
||||
export interface HeadingCell {
|
||||
cell_type: 'heading';
|
||||
metadata: JSONObject;
|
||||
source: nb.MultilineString;
|
||||
level: number;
|
||||
}
|
||||
|
||||
export interface CodeCell {
|
||||
cell_type: 'code';
|
||||
language: string;
|
||||
collapsed: boolean;
|
||||
metadata: JSONObject;
|
||||
input: nb.MultilineString;
|
||||
prompt_number: number;
|
||||
outputs: Array<Output>;
|
||||
}
|
||||
|
||||
export type Cell = nb.ICellContents | HeadingCell | CodeCell;
|
||||
|
||||
export interface Worksheet {
|
||||
cells: Cell[];
|
||||
metadata: object;
|
||||
}
|
||||
|
||||
export interface Notebook {
|
||||
worksheets: Worksheet[];
|
||||
metadata: nb.INotebookMetadata;
|
||||
nbformat: 3;
|
||||
nbformat_minor: number;
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { localize } from 'vs/nls';
|
||||
import * as platform from 'vs/platform/registry/common/platform';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
|
||||
export const Extensions = {
|
||||
NotebookProviderContribution: 'notebook.providers'
|
||||
};
|
||||
|
||||
export interface NotebookProviderRegistration {
|
||||
provider: string;
|
||||
fileExtensions: string | string[];
|
||||
standardKernels: string | string[];
|
||||
}
|
||||
|
||||
let notebookProviderType: IJSONSchema = {
|
||||
type: 'object',
|
||||
default: { provider: '', fileExtensions: [], standardKernels: [] },
|
||||
properties: {
|
||||
provider: {
|
||||
description: localize('carbon.extension.contributes.notebook.provider', 'Identifier of the notebook provider.'),
|
||||
type: 'string'
|
||||
},
|
||||
fileExtensions: {
|
||||
description: localize('carbon.extension.contributes.notebook.fileExtensions', 'What file extensions should be registered to this notebook provider'),
|
||||
oneOf: [
|
||||
{ type: 'string' },
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
standardKernels: {
|
||||
description: localize('carbon.extension.contributes.notebook.standardKernels', 'What kernels should be standard with this notebook provider'),
|
||||
oneOf: [
|
||||
{ type: 'string' },
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let notebookContrib: IJSONSchema = {
|
||||
description: localize('vscode.extension.contributes.notebook.providers', "Contributes notebook providers."),
|
||||
oneOf: [
|
||||
notebookProviderType,
|
||||
{
|
||||
type: 'array',
|
||||
items: notebookProviderType
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export interface INotebookProviderRegistry {
|
||||
readonly registrations: NotebookProviderRegistration[];
|
||||
readonly onNewRegistration: Event<{ id: string, registration: NotebookProviderRegistration }>;
|
||||
|
||||
registerNotebookProvider(registration: NotebookProviderRegistration): void;
|
||||
}
|
||||
|
||||
class NotebookProviderRegistry implements INotebookProviderRegistry {
|
||||
private providerIdToRegistration = new Map<string, NotebookProviderRegistration>();
|
||||
private _onNewRegistration = new Emitter<{ id: string, registration: NotebookProviderRegistration }>();
|
||||
public readonly onNewRegistration: Event<{ id: string, registration: NotebookProviderRegistration }> = this._onNewRegistration.event;
|
||||
|
||||
registerNotebookProvider(registration: NotebookProviderRegistration): void {
|
||||
// Note: this method intentionally overrides default provider for a file type.
|
||||
// This means that any built-in provider will be overridden by registered extensions
|
||||
this.providerIdToRegistration.set(registration.provider, registration);
|
||||
this._onNewRegistration.fire( { id: registration.provider, registration: registration });
|
||||
}
|
||||
|
||||
public get registrations(): NotebookProviderRegistration[] {
|
||||
let registrationArray: NotebookProviderRegistration[] = [];
|
||||
this.providerIdToRegistration.forEach(p => registrationArray.push(p));
|
||||
return registrationArray;
|
||||
}
|
||||
}
|
||||
|
||||
const notebookProviderRegistry = new NotebookProviderRegistry();
|
||||
platform.Registry.add(Extensions.NotebookProviderContribution, notebookProviderRegistry);
|
||||
|
||||
|
||||
ExtensionsRegistry.registerExtensionPoint<NotebookProviderRegistration | NotebookProviderRegistration[]>(Extensions.NotebookProviderContribution, [], notebookContrib).setHandler(extensions => {
|
||||
|
||||
function handleExtension(contrib: NotebookProviderRegistration, extension: IExtensionPointUser<any>) {
|
||||
notebookProviderRegistry.registerNotebookProvider(contrib);
|
||||
}
|
||||
|
||||
for (let extension of extensions) {
|
||||
const { value } = extension;
|
||||
if (Array.isArray<NotebookProviderRegistration>(value)) {
|
||||
for (let command of value) {
|
||||
handleExtension(command, extension);
|
||||
}
|
||||
} else {
|
||||
handleExtension(value, extension);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,106 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
|
||||
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { NotebookInput } from 'sql/parts/notebook/notebookInput';
|
||||
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { ICellModel, INotebookModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
|
||||
export const SERVICE_ID = 'notebookService';
|
||||
export const INotebookService = createDecorator<INotebookService>(SERVICE_ID);
|
||||
|
||||
export const DEFAULT_NOTEBOOK_PROVIDER = 'builtin';
|
||||
export const DEFAULT_NOTEBOOK_FILETYPE = 'IPYNB';
|
||||
export const SQL_NOTEBOOK_PROVIDER = 'sql';
|
||||
|
||||
export interface INotebookService {
|
||||
_serviceBrand: any;
|
||||
|
||||
readonly onNotebookEditorAdd: Event<INotebookEditor>;
|
||||
readonly onNotebookEditorRemove: Event<INotebookEditor>;
|
||||
onNotebookEditorRename: Event<INotebookEditor>;
|
||||
|
||||
readonly isRegistrationComplete: boolean;
|
||||
readonly registrationComplete: Promise<void>;
|
||||
/**
|
||||
* Register a metadata provider
|
||||
*/
|
||||
registerProvider(providerId: string, provider: INotebookProvider): void;
|
||||
|
||||
/**
|
||||
* Register a metadata provider
|
||||
*/
|
||||
unregisterProvider(providerId: string): void;
|
||||
|
||||
getSupportedFileExtensions(): string[];
|
||||
|
||||
getProvidersForFileType(fileType: string): string[];
|
||||
|
||||
/**
|
||||
* Initializes and returns a Notebook manager that can handle all important calls to open, display, and
|
||||
* run cells in a notebook.
|
||||
* @param providerId ID for the provider to be used to instantiate a backend notebook service
|
||||
* @param uri URI for a notebook that is to be opened. Based on this an existing manager may be used, or
|
||||
* a new one may need to be created
|
||||
*/
|
||||
getOrCreateNotebookManager(providerId: string, uri: URI): Thenable<INotebookManager>;
|
||||
|
||||
addNotebookEditor(editor: INotebookEditor): void;
|
||||
|
||||
removeNotebookEditor(editor: INotebookEditor): void;
|
||||
|
||||
listNotebookEditors(): INotebookEditor[];
|
||||
|
||||
shutdown(): void;
|
||||
|
||||
getMimeRegistry(): RenderMimeRegistry;
|
||||
|
||||
renameNotebookEditor(oldUri: URI, newUri: URI, currentEditor: INotebookEditor): void;
|
||||
}
|
||||
|
||||
export interface INotebookProvider {
|
||||
readonly providerId: string;
|
||||
getNotebookManager(notebookUri: URI): Thenable<INotebookManager>;
|
||||
handleNotebookClosed(notebookUri: URI): void;
|
||||
}
|
||||
|
||||
export interface INotebookManager {
|
||||
providerId: string;
|
||||
readonly contentManager: sqlops.nb.ContentManager;
|
||||
readonly sessionManager: sqlops.nb.SessionManager;
|
||||
readonly serverManager: sqlops.nb.ServerManager;
|
||||
}
|
||||
|
||||
export interface INotebookParams extends IBootstrapParams {
|
||||
notebookUri: URI;
|
||||
input: NotebookInput;
|
||||
providerId: string;
|
||||
providers: string[];
|
||||
isTrusted: boolean;
|
||||
profile?: IConnectionProfile;
|
||||
modelFactory?: ModelFactory;
|
||||
}
|
||||
|
||||
export interface INotebookEditor {
|
||||
readonly notebookParams: INotebookParams;
|
||||
readonly id: string;
|
||||
readonly cells?: ICellModel[];
|
||||
readonly modelReady: Promise<INotebookModel>;
|
||||
isDirty(): boolean;
|
||||
isActive(): boolean;
|
||||
isVisible(): boolean;
|
||||
save(): Promise<boolean>;
|
||||
executeEdits(edits: ISingleNotebookEditOperation[]): boolean;
|
||||
}
|
||||
@@ -1,449 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { nb } from 'sqlops';
|
||||
import { localize } from 'vs/nls';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
|
||||
import {
|
||||
INotebookService, INotebookManager, INotebookProvider, DEFAULT_NOTEBOOK_PROVIDER,
|
||||
DEFAULT_NOTEBOOK_FILETYPE, INotebookEditor, SQL_NOTEBOOK_PROVIDER
|
||||
} from 'sql/services/notebook/notebookService';
|
||||
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
|
||||
import { standardRendererFactories } from 'sql/parts/notebook/outputs/factories';
|
||||
import { LocalContentManager } from 'sql/services/notebook/localContentManager';
|
||||
import { SessionManager } from 'sql/services/notebook/sessionManager';
|
||||
import { Extensions, INotebookProviderRegistry, NotebookProviderRegistration } from 'sql/services/notebook/notebookRegistry';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Memento } from 'vs/workbench/common/memento';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IExtensionManagementService, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { getIdFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
import { SqlSessionManager } from 'sql/services/notebook/sqlSessionManager';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { sqlNotebooksEnabled } from 'sql/parts/notebook/notebookUtils';
|
||||
|
||||
export interface NotebookProviderProperties {
|
||||
provider: string;
|
||||
fileExtensions: string[];
|
||||
}
|
||||
|
||||
interface NotebookProviderCache {
|
||||
[id: string]: NotebookProviderProperties;
|
||||
}
|
||||
|
||||
interface NotebookProvidersMemento {
|
||||
notebookProviderCache: NotebookProviderCache;
|
||||
}
|
||||
|
||||
const notebookRegistry = Registry.as<INotebookProviderRegistry>(Extensions.NotebookProviderContribution);
|
||||
|
||||
class ProviderDescriptor {
|
||||
private _instanceReady = new Deferred<INotebookProvider>();
|
||||
constructor(private providerId: string, private _instance?: INotebookProvider) {
|
||||
if (_instance) {
|
||||
this._instanceReady.resolve(_instance);
|
||||
}
|
||||
}
|
||||
|
||||
public get instanceReady(): Promise<INotebookProvider> {
|
||||
return this._instanceReady.promise;
|
||||
}
|
||||
|
||||
public get instance(): INotebookProvider {
|
||||
return this._instance;
|
||||
}
|
||||
public set instance(value: INotebookProvider) {
|
||||
this._instance = value;
|
||||
this._instanceReady.resolve(value);
|
||||
}
|
||||
}
|
||||
|
||||
export class NotebookService extends Disposable implements INotebookService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private _memento = new Memento('notebookProviders');
|
||||
private _mimeRegistry: RenderMimeRegistry;
|
||||
private _providers: Map<string, ProviderDescriptor> = new Map();
|
||||
private _managersMap: Map<string, INotebookManager[]> = new Map();
|
||||
private _onNotebookEditorAdd = new Emitter<INotebookEditor>();
|
||||
private _onNotebookEditorRemove = new Emitter<INotebookEditor>();
|
||||
private _onCellChanged = new Emitter<INotebookEditor>();
|
||||
private _onNotebookEditorRename = new Emitter<INotebookEditor>();
|
||||
private _editors = new Map<string, INotebookEditor>();
|
||||
private _fileToProviders = new Map<string, NotebookProviderRegistration[]>();
|
||||
private _registrationComplete = new Deferred<void>();
|
||||
private _isRegistrationComplete = false;
|
||||
|
||||
constructor(
|
||||
@IStorageService private _storageService: IStorageService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
this._register(notebookRegistry.onNewRegistration(this.updateRegisteredProviders, this));
|
||||
this.registerBuiltInProvider();
|
||||
|
||||
if (extensionService) {
|
||||
extensionService.whenInstalledExtensionsRegistered().then(() => {
|
||||
this.cleanupProviders();
|
||||
this._isRegistrationComplete = true;
|
||||
this._registrationComplete.resolve();
|
||||
});
|
||||
}
|
||||
if (extensionManagementService) {
|
||||
this._register(extensionManagementService.onDidUninstallExtension(({ identifier }) => this.removeContributedProvidersFromCache(identifier, extensionService)));
|
||||
}
|
||||
}
|
||||
|
||||
private updateRegisteredProviders(p: { id: string; registration: NotebookProviderRegistration; }) {
|
||||
let registration = p.registration;
|
||||
|
||||
if (!this._providers.has(p.id)) {
|
||||
this._providers.set(p.id, new ProviderDescriptor(p.id));
|
||||
}
|
||||
if (registration.fileExtensions) {
|
||||
if (Array.isArray<string>(registration.fileExtensions)) {
|
||||
for (let fileType of registration.fileExtensions) {
|
||||
this.addFileProvider(fileType, registration);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.addFileProvider(registration.fileExtensions, registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerProvider(providerId: string, instance: INotebookProvider): void {
|
||||
let providerDescriptor = this._providers.get(providerId);
|
||||
if (providerDescriptor) {
|
||||
// Update, which will resolve the promise for anyone waiting on the instance to be registered
|
||||
providerDescriptor.instance = instance;
|
||||
} else {
|
||||
this._providers.set(providerId, new ProviderDescriptor(providerId, instance));
|
||||
}
|
||||
}
|
||||
|
||||
unregisterProvider(providerId: string): void {
|
||||
this._providers.delete(providerId);
|
||||
}
|
||||
|
||||
get isRegistrationComplete(): boolean {
|
||||
return this._isRegistrationComplete;
|
||||
}
|
||||
|
||||
get registrationComplete(): Promise<void> {
|
||||
return this._registrationComplete.promise;
|
||||
}
|
||||
|
||||
private addFileProvider(fileType: string, provider: NotebookProviderRegistration) {
|
||||
let providers = this._fileToProviders.get(fileType.toUpperCase());
|
||||
if (!providers) {
|
||||
providers = [];
|
||||
}
|
||||
providers.push(provider);
|
||||
this._fileToProviders.set(fileType.toUpperCase(), providers);
|
||||
}
|
||||
|
||||
getSupportedFileExtensions(): string[] {
|
||||
return Array.from(this._fileToProviders.keys());
|
||||
}
|
||||
|
||||
getProvidersForFileType(fileType: string): string[] {
|
||||
fileType = fileType.toUpperCase();
|
||||
let providers = this._fileToProviders.get(fileType);
|
||||
|
||||
return providers ? providers.map(provider => provider.provider) : undefined;
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this._managersMap.forEach(manager => {
|
||||
manager.forEach(m => {
|
||||
if (m.serverManager) {
|
||||
// TODO should this thenable be awaited?
|
||||
m.serverManager.stopServer();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getOrCreateNotebookManager(providerId: string, uri: URI): Promise<INotebookManager> {
|
||||
if (!uri) {
|
||||
throw new Error(localize('notebookUriNotDefined', 'No URI was passed when creating a notebook manager'));
|
||||
}
|
||||
let uriString = uri.toString();
|
||||
let managers: INotebookManager[] = this._managersMap.get(uriString);
|
||||
// If manager already exists for a given notebook, return it
|
||||
if (managers) {
|
||||
let index = managers.findIndex(m => m.providerId === providerId);
|
||||
if (index && index >= 0) {
|
||||
return managers[index];
|
||||
}
|
||||
}
|
||||
let newManager = await this.doWithProvider(providerId, (provider) => provider.getNotebookManager(uri));
|
||||
|
||||
managers = managers || [];
|
||||
managers.push(newManager);
|
||||
this._managersMap.set(uriString, managers);
|
||||
return newManager;
|
||||
}
|
||||
|
||||
get onNotebookEditorAdd(): Event<INotebookEditor> {
|
||||
return this._onNotebookEditorAdd.event;
|
||||
}
|
||||
get onNotebookEditorRemove(): Event<INotebookEditor> {
|
||||
return this._onNotebookEditorRemove.event;
|
||||
}
|
||||
get onCellChanged(): Event<INotebookEditor> {
|
||||
return this._onCellChanged.event;
|
||||
}
|
||||
|
||||
get onNotebookEditorRename(): Event<INotebookEditor> {
|
||||
return this._onNotebookEditorRename.event;
|
||||
}
|
||||
|
||||
addNotebookEditor(editor: INotebookEditor): void {
|
||||
this._editors.set(editor.id, editor);
|
||||
this._onNotebookEditorAdd.fire(editor);
|
||||
}
|
||||
|
||||
removeNotebookEditor(editor: INotebookEditor): void {
|
||||
if (this._editors.delete(editor.id)) {
|
||||
this._onNotebookEditorRemove.fire(editor);
|
||||
}
|
||||
// Remove the manager from the tracked list, and let the notebook provider know that it should update its mappings
|
||||
this.sendNotebookCloseToProvider(editor);
|
||||
}
|
||||
|
||||
listNotebookEditors(): INotebookEditor[] {
|
||||
let editors = [];
|
||||
this._editors.forEach(e => editors.push(e));
|
||||
return editors;
|
||||
}
|
||||
|
||||
renameNotebookEditor(oldUri: URI, newUri: URI, currentEditor: INotebookEditor): void {
|
||||
let oldUriKey = oldUri.toString();
|
||||
if(this._editors.has(oldUriKey))
|
||||
{
|
||||
this._editors.delete(oldUriKey);
|
||||
currentEditor.notebookParams.notebookUri = newUri;
|
||||
this._editors.set(newUri.toString(), currentEditor);
|
||||
this._onNotebookEditorRename.fire(currentEditor);
|
||||
}
|
||||
}
|
||||
|
||||
private sendNotebookCloseToProvider(editor: INotebookEditor): void {
|
||||
let notebookUri = editor.notebookParams.notebookUri;
|
||||
let uriString = notebookUri.toString();
|
||||
let manager = this._managersMap.get(uriString);
|
||||
if (manager) {
|
||||
// As we have a manager, we can assume provider is ready
|
||||
this._managersMap.delete(uriString);
|
||||
manager.forEach(m => {
|
||||
let provider = this._providers.get(m.providerId);
|
||||
provider.instance.handleNotebookClosed(notebookUri);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// PRIVATE HELPERS /////////////////////////////////////////////////////
|
||||
private async doWithProvider<T>(providerId: string, op: (provider: INotebookProvider) => Thenable<T>): Promise<T> {
|
||||
// Make sure the provider exists before attempting to retrieve accounts
|
||||
let provider: INotebookProvider = await this.getProviderInstance(providerId);
|
||||
return op(provider);
|
||||
}
|
||||
|
||||
private async getProviderInstance(providerId: string, timeout?: number): Promise<INotebookProvider> {
|
||||
let providerDescriptor = this._providers.get(providerId);
|
||||
let instance: INotebookProvider;
|
||||
|
||||
// Try get from actual provider, waiting on its registration
|
||||
if (providerDescriptor) {
|
||||
if (!providerDescriptor.instance) {
|
||||
instance = await this.waitOnProviderAvailability(providerDescriptor);
|
||||
} else {
|
||||
instance = providerDescriptor.instance;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to default if this failed
|
||||
if (!instance) {
|
||||
providerDescriptor = this._providers.get(DEFAULT_NOTEBOOK_PROVIDER);
|
||||
instance = providerDescriptor ? providerDescriptor.instance : undefined;
|
||||
}
|
||||
|
||||
// Should never happen, but if default wasn't registered we should throw
|
||||
if (!instance) {
|
||||
throw new Error(localize('notebookServiceNoProvider', 'Notebook provider does not exist'));
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private waitOnProviderAvailability(providerDescriptor: ProviderDescriptor, timeout?: number): Promise<INotebookProvider> {
|
||||
// Wait up to 10 seconds for the provider to be registered
|
||||
timeout = timeout || 10000;
|
||||
let promises: Promise<INotebookProvider>[] = [
|
||||
providerDescriptor.instanceReady,
|
||||
new Promise<INotebookProvider>((resolve, reject) => setTimeout(() => resolve(), timeout))
|
||||
];
|
||||
return Promise.race(promises);
|
||||
}
|
||||
|
||||
//Returns an instantiation of RenderMimeRegistry class
|
||||
getMimeRegistry(): RenderMimeRegistry {
|
||||
if (!this._mimeRegistry) {
|
||||
return new RenderMimeRegistry({
|
||||
initialFactories: standardRendererFactories
|
||||
});
|
||||
}
|
||||
return this._mimeRegistry;
|
||||
}
|
||||
|
||||
private get providersMemento(): NotebookProvidersMemento {
|
||||
return this._memento.getMemento(this._storageService) as NotebookProvidersMemento;
|
||||
}
|
||||
|
||||
private cleanupProviders(): void {
|
||||
let knownProviders = Object.keys(notebookRegistry.registrations);
|
||||
let cache = this.providersMemento.notebookProviderCache;
|
||||
for (let key in cache) {
|
||||
if (!knownProviders.includes(key)) {
|
||||
this._providers.delete(key);
|
||||
delete cache[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private registerBuiltInProvider() {
|
||||
if (!sqlNotebooksEnabled()) {
|
||||
let defaultProvider = new BuiltinProvider();
|
||||
this.registerProvider(defaultProvider.providerId, defaultProvider);
|
||||
notebookRegistry.registerNotebookProvider({
|
||||
provider: defaultProvider.providerId,
|
||||
fileExtensions: DEFAULT_NOTEBOOK_FILETYPE,
|
||||
standardKernels: []
|
||||
});
|
||||
} else {
|
||||
let sqlProvider = new SqlNotebookProvider(this._instantiationService);
|
||||
this.registerProvider(sqlProvider.providerId, sqlProvider);
|
||||
notebookRegistry.registerNotebookProvider({
|
||||
provider: sqlProvider.providerId,
|
||||
fileExtensions: DEFAULT_NOTEBOOK_FILETYPE,
|
||||
standardKernels: ['SQL']
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private removeContributedProvidersFromCache(identifier: IExtensionIdentifier, extensionService: IExtensionService) {
|
||||
let extensionid = getIdFromLocalExtensionId(identifier.id);
|
||||
extensionService.getExtensions().then(i => {
|
||||
let extension = i.find(c => c.id === extensionid);
|
||||
if (extension && extension.contributes['notebookProvider']) {
|
||||
let id = extension.contributes['notebookProvider'].providerId;
|
||||
delete this.providersMemento.notebookProviderCache[id];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class BuiltinProvider implements INotebookProvider {
|
||||
private manager: BuiltInNotebookManager;
|
||||
|
||||
constructor() {
|
||||
this.manager = new BuiltInNotebookManager();
|
||||
}
|
||||
|
||||
public get providerId(): string {
|
||||
return DEFAULT_NOTEBOOK_PROVIDER;
|
||||
}
|
||||
|
||||
getNotebookManager(notebookUri: URI): Thenable<INotebookManager> {
|
||||
return Promise.resolve(this.manager);
|
||||
}
|
||||
handleNotebookClosed(notebookUri: URI): void {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
|
||||
export class BuiltInNotebookManager implements INotebookManager {
|
||||
private _contentManager: nb.ContentManager;
|
||||
private _sessionManager: nb.SessionManager;
|
||||
|
||||
constructor() {
|
||||
this._contentManager = new LocalContentManager();
|
||||
this._sessionManager = new SessionManager();
|
||||
}
|
||||
|
||||
public get providerId(): string {
|
||||
return DEFAULT_NOTEBOOK_PROVIDER;
|
||||
}
|
||||
|
||||
public get contentManager(): nb.ContentManager {
|
||||
return this._contentManager;
|
||||
}
|
||||
|
||||
public get serverManager(): nb.ServerManager {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public get sessionManager(): nb.SessionManager {
|
||||
return this._sessionManager;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class SqlNotebookProvider implements INotebookProvider {
|
||||
private manager: SqlNotebookManager;
|
||||
|
||||
constructor(private _instantiationService: IInstantiationService) {
|
||||
this.manager = new SqlNotebookManager(this._instantiationService);
|
||||
}
|
||||
|
||||
public get providerId(): string {
|
||||
return SQL_NOTEBOOK_PROVIDER;
|
||||
}
|
||||
|
||||
getNotebookManager(notebookUri: URI): Thenable<INotebookManager> {
|
||||
return Promise.resolve(this.manager);
|
||||
}
|
||||
|
||||
handleNotebookClosed(notebookUri: URI): void {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
|
||||
export class SqlNotebookManager implements INotebookManager {
|
||||
private _contentManager: nb.ContentManager;
|
||||
private _sessionManager: nb.SessionManager;
|
||||
|
||||
constructor(private _instantiationService: IInstantiationService) {
|
||||
this._contentManager = new LocalContentManager();
|
||||
this._sessionManager = new SqlSessionManager(this._instantiationService);
|
||||
}
|
||||
|
||||
public get providerId(): string {
|
||||
return SQL_NOTEBOOK_PROVIDER;
|
||||
}
|
||||
|
||||
public get contentManager(): nb.ContentManager {
|
||||
return this._contentManager;
|
||||
}
|
||||
|
||||
public get serverManager(): nb.ServerManager {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public get sessionManager(): nb.SessionManager {
|
||||
return this._sessionManager;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { nb } from 'sqlops';
|
||||
import { localize } from 'vs/nls';
|
||||
import { FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
|
||||
export const noKernel: string = localize('noKernel', 'No Kernel');
|
||||
const runNotebookDisabled = localize('runNotebookDisabled', 'Cannot run cells as no kernel has been configured');
|
||||
|
||||
let noKernelSpec: nb.IKernelSpec = ({
|
||||
name: noKernel,
|
||||
language: 'python',
|
||||
display_name: noKernel
|
||||
});
|
||||
|
||||
export class SessionManager implements nb.SessionManager {
|
||||
public get isReady(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get ready(): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public get specs(): nb.IAllKernels {
|
||||
let allKernels: nb.IAllKernels = {
|
||||
defaultKernel: noKernel,
|
||||
kernels: [noKernelSpec]
|
||||
};
|
||||
return allKernels;
|
||||
}
|
||||
|
||||
startNew(options: nb.ISessionOptions): Thenable<nb.ISession> {
|
||||
let session = new EmptySession(options);
|
||||
return Promise.resolve(session);
|
||||
}
|
||||
|
||||
shutdown(id: string): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export class EmptySession implements nb.ISession {
|
||||
private _kernel: EmptyKernel;
|
||||
private _defaultKernelLoaded = false;
|
||||
|
||||
public set defaultKernelLoaded(value) {
|
||||
this._defaultKernelLoaded = value;
|
||||
}
|
||||
|
||||
public get defaultKernelLoaded(): boolean {
|
||||
return this._defaultKernelLoaded;
|
||||
}
|
||||
|
||||
constructor(private options: nb.ISessionOptions) {
|
||||
this._kernel = new EmptyKernel();
|
||||
}
|
||||
|
||||
public get canChangeKernels(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.options.kernelId || '';
|
||||
}
|
||||
|
||||
public get path(): string {
|
||||
return this.options.path;
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this.options.name || '';
|
||||
}
|
||||
|
||||
public get type(): string {
|
||||
return this.options.type || '';
|
||||
}
|
||||
|
||||
public get status(): nb.KernelStatus {
|
||||
return 'connected';
|
||||
}
|
||||
|
||||
public get kernel(): nb.IKernel {
|
||||
return this._kernel;
|
||||
}
|
||||
|
||||
changeKernel(kernelInfo: nb.IKernelSpec): Thenable<nb.IKernel> {
|
||||
return Promise.resolve(this.kernel);
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyKernel implements nb.IKernel {
|
||||
public get id(): string {
|
||||
return '-1';
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return noKernel;
|
||||
}
|
||||
|
||||
public get supportsIntellisense(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public get isReady(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get ready(): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public get info(): nb.IInfoReply {
|
||||
let info: nb.IInfoReply = {
|
||||
protocol_version: '',
|
||||
implementation: '',
|
||||
implementation_version: '',
|
||||
language_info: {
|
||||
name: '',
|
||||
version: '',
|
||||
},
|
||||
banner: '',
|
||||
help_links: [{
|
||||
text: '',
|
||||
url: ''
|
||||
}]
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
getSpec(): Thenable<nb.IKernelSpec> {
|
||||
return Promise.resolve(noKernelSpec);
|
||||
}
|
||||
|
||||
requestExecute(content: nb.IExecuteRequest, disposeOnDone?: boolean): nb.IFuture {
|
||||
return new EmptyFuture();
|
||||
}
|
||||
|
||||
requestComplete(content: nb.ICompleteRequest): Thenable<nb.ICompleteReplyMsg> {
|
||||
let response: Partial<nb.ICompleteReplyMsg> = { };
|
||||
return Promise.resolve(response as nb.ICompleteReplyMsg);
|
||||
}
|
||||
|
||||
interrupt(): Thenable<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
export class EmptyFuture implements FutureInternal {
|
||||
|
||||
|
||||
get inProgress(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
get msg(): nb.IMessage {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get done(): Thenable<nb.IShellMessage> {
|
||||
let msg: nb.IShellMessage = {
|
||||
channel: 'shell',
|
||||
type: 'shell',
|
||||
content: runNotebookDisabled,
|
||||
header: undefined,
|
||||
metadata: undefined,
|
||||
parent_header: undefined
|
||||
};
|
||||
|
||||
return Promise.resolve(msg);
|
||||
}
|
||||
|
||||
sendInputReply(content: nb.IInputReply): void {
|
||||
// no-op
|
||||
}
|
||||
dispose() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
setReplyHandler(handler: nb.MessageHandler<nb.IShellMessage>): void {
|
||||
// no-op
|
||||
}
|
||||
setStdInHandler(handler: nb.MessageHandler<nb.IStdinMessage>): void {
|
||||
// no-op
|
||||
}
|
||||
setIOPubHandler(handler: nb.MessageHandler<nb.IIOPubMessage>): void {
|
||||
setTimeout(() => {
|
||||
let msg: nb.IIOPubMessage = {
|
||||
channel: 'iopub',
|
||||
type: 'iopub',
|
||||
header: <nb.IHeader> {
|
||||
msg_id: '0',
|
||||
msg_type: 'error'
|
||||
},
|
||||
content: <nb.IErrorResult> {
|
||||
ename: localize('errorName', 'Error'),
|
||||
evalue: runNotebookDisabled,
|
||||
output_type: 'error'
|
||||
},
|
||||
metadata: undefined,
|
||||
parent_header: undefined
|
||||
};
|
||||
handler.handle(msg);
|
||||
}, 10);
|
||||
}
|
||||
registerMessageHook(hook: (msg: nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
|
||||
// no-op
|
||||
}
|
||||
removeMessageHook(hook: (msg: nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
@@ -1,325 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { nb, QueryExecuteSubsetResult, IDbColumn, DbCellValue } from 'sqlops';
|
||||
import { localize } from 'vs/nls';
|
||||
import { FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import QueryRunner, { EventType } from 'sql/parts/query/execution/queryRunner';
|
||||
import { IConnectionManagementService, IErrorMessageService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import * as Utils from 'sql/parts/connection/common/utils';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { mssqlProviderName } from 'sql/parts/connection/common/constants';
|
||||
|
||||
export const sqlKernel: string = localize('sqlKernel', 'SQL');
|
||||
export const sqlKernelError: string = localize("sqlKernelError", "SQL kernel error");
|
||||
|
||||
let sqlKernelSpec: nb.IKernelSpec = ({
|
||||
name: sqlKernel,
|
||||
language: 'sql',
|
||||
display_name: sqlKernel
|
||||
});
|
||||
|
||||
export interface SQLData {
|
||||
columns: Array<string>;
|
||||
rows: Array<Array<string>>;
|
||||
}
|
||||
|
||||
export class SqlSessionManager implements nb.SessionManager {
|
||||
constructor(private _instantiationService: IInstantiationService) {}
|
||||
|
||||
public get isReady(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get ready(): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public get specs(): nb.IAllKernels {
|
||||
let allKernels: nb.IAllKernels = {
|
||||
defaultKernel: sqlKernel,
|
||||
kernels: [sqlKernelSpec]
|
||||
};
|
||||
return allKernels;
|
||||
}
|
||||
|
||||
startNew(options: nb.ISessionOptions): Thenable<nb.ISession> {
|
||||
let session = new SqlSession(options, this._instantiationService);
|
||||
return Promise.resolve(session);
|
||||
}
|
||||
|
||||
shutdown(id: string): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export class SqlSession implements nb.ISession {
|
||||
private _kernel: SqlKernel;
|
||||
private _defaultKernelLoaded = false;
|
||||
|
||||
public set defaultKernelLoaded(value) {
|
||||
this._defaultKernelLoaded = value;
|
||||
}
|
||||
|
||||
public get defaultKernelLoaded(): boolean {
|
||||
return this._defaultKernelLoaded;
|
||||
}
|
||||
|
||||
constructor(private options: nb.ISessionOptions, private _instantiationService: IInstantiationService) {
|
||||
this._kernel = this._instantiationService.createInstance(SqlKernel);
|
||||
}
|
||||
|
||||
public get canChangeKernels(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.options.kernelId || '';
|
||||
}
|
||||
|
||||
public get path(): string {
|
||||
return this.options.path;
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this.options.name || '';
|
||||
}
|
||||
|
||||
public get type(): string {
|
||||
return this.options.type || '';
|
||||
}
|
||||
|
||||
public get status(): nb.KernelStatus {
|
||||
return 'connected';
|
||||
}
|
||||
|
||||
public get kernel(): nb.IKernel {
|
||||
return this._kernel;
|
||||
}
|
||||
|
||||
changeKernel(kernelInfo: nb.IKernelSpec): Thenable<nb.IKernel> {
|
||||
return Promise.resolve(this.kernel);
|
||||
}
|
||||
}
|
||||
|
||||
class SqlKernel extends Disposable implements nb.IKernel {
|
||||
private _queryRunner: QueryRunner;
|
||||
private _columns: IDbColumn[];
|
||||
private _rows: DbCellValue[][];
|
||||
|
||||
constructor(@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService) {
|
||||
super();
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return '-1';
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return sqlKernel;
|
||||
}
|
||||
|
||||
public get supportsIntellisense(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get isReady(): boolean {
|
||||
// should we be checking on the tools service status here?
|
||||
return true;
|
||||
}
|
||||
|
||||
public get ready(): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public get info(): nb.IInfoReply {
|
||||
let info: nb.IInfoReply = {
|
||||
protocol_version: '',
|
||||
implementation: '',
|
||||
implementation_version: '',
|
||||
language_info: {
|
||||
name: 'sql',
|
||||
version: '',
|
||||
},
|
||||
banner: '',
|
||||
help_links: [{
|
||||
text: '',
|
||||
url: ''
|
||||
}]
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
getSpec(): Thenable<nb.IKernelSpec> {
|
||||
return Promise.resolve(sqlKernelSpec);
|
||||
}
|
||||
|
||||
requestExecute(content: nb.IExecuteRequest, disposeOnDone?: boolean): nb.IFuture {
|
||||
if (this._queryRunner) {
|
||||
this._queryRunner.runQuery(content.code);
|
||||
} else {
|
||||
let connections = this._connectionManagementService.getActiveConnections();
|
||||
let connectionProfile = connections.find(connection => connection.providerName === mssqlProviderName);
|
||||
let connectionUri = Utils.generateUri(connectionProfile, 'notebook');
|
||||
this._queryRunner = this._instantiationService.createInstance(QueryRunner, connectionUri, undefined);
|
||||
this._connectionManagementService.connect(connectionProfile, connectionUri).then((result) =>
|
||||
{
|
||||
this.addQueryEventListeners(this._queryRunner);
|
||||
this._queryRunner.runQuery(content.code);
|
||||
});
|
||||
}
|
||||
|
||||
return new SQLFuture(this._queryRunner);
|
||||
}
|
||||
|
||||
requestComplete(content: nb.ICompleteRequest): Thenable<nb.ICompleteReplyMsg> {
|
||||
let response: Partial<nb.ICompleteReplyMsg> = { };
|
||||
return Promise.resolve(response as nb.ICompleteReplyMsg);
|
||||
}
|
||||
|
||||
interrupt(): Thenable<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
private addQueryEventListeners(queryRunner: QueryRunner): void {
|
||||
this._register(queryRunner.addListener(EventType.COMPLETE, () => {
|
||||
this.queryComplete().catch(error => {
|
||||
this._errorMessageService.showDialog(Severity.Error, sqlKernelError, error);
|
||||
});
|
||||
}));
|
||||
this._register(queryRunner.addListener(EventType.MESSAGE, message => {
|
||||
if (message.isError) {
|
||||
this._errorMessageService.showDialog(Severity.Error, sqlKernelError, message.message);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private async queryComplete(): Promise<void> {
|
||||
let batches = this._queryRunner.batchSets;
|
||||
// currently only support 1 batch set 1 resultset
|
||||
if (batches.length > 0) {
|
||||
let batch = batches[0];
|
||||
if (batch.resultSetSummaries.length > 0
|
||||
&& batch.resultSetSummaries[0].rowCount > 0
|
||||
) {
|
||||
let resultset = batch.resultSetSummaries[0];
|
||||
this._columns = resultset.columnInfo;
|
||||
let rows: QueryExecuteSubsetResult;
|
||||
try {
|
||||
rows = await this._queryRunner.getQueryRows(0, resultset.rowCount, batch.id, resultset.id);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
this._rows = rows.resultSubset.rows;
|
||||
}
|
||||
}
|
||||
// TODO issue #2746 should ideally show a warning inside the dialog if have no data
|
||||
}
|
||||
}
|
||||
|
||||
export class SQLFuture extends Disposable implements FutureInternal {
|
||||
private _msg: nb.IMessage = undefined;
|
||||
|
||||
constructor(private _queryRunner: QueryRunner) {
|
||||
super();
|
||||
}
|
||||
get inProgress(): boolean {
|
||||
return !this._queryRunner.hasCompleted;
|
||||
}
|
||||
|
||||
get msg(): nb.IMessage {
|
||||
return this._msg;
|
||||
}
|
||||
|
||||
get done(): Thenable<nb.IShellMessage> {
|
||||
let deferred = new Deferred<nb.IShellMessage> ();
|
||||
try {
|
||||
this._register(this._queryRunner.onBatchEnd(e => {
|
||||
let msg: nb.IShellMessage = {
|
||||
channel: 'shell',
|
||||
type: 'execute_reply',
|
||||
content: { status: 'ok' },
|
||||
header: undefined,
|
||||
metadata: {},
|
||||
parent_header: undefined
|
||||
};
|
||||
this._msg = msg;
|
||||
deferred.resolve(msg);
|
||||
}));
|
||||
} catch {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
sendInputReply(content: nb.IInputReply): void {
|
||||
// no-op
|
||||
}
|
||||
|
||||
setReplyHandler(handler: nb.MessageHandler<nb.IShellMessage>): void {
|
||||
// no-op
|
||||
}
|
||||
setStdInHandler(handler: nb.MessageHandler<nb.IStdinMessage>): void {
|
||||
// no-op
|
||||
}
|
||||
setIOPubHandler(handler: nb.MessageHandler<nb.IIOPubMessage>): void {
|
||||
this._register(this._queryRunner.onBatchEnd(batch => {
|
||||
this._queryRunner.getQueryRows(0, batch.resultSetSummaries[0].rowCount, 0, 0).then(d => {
|
||||
let data:SQLData = {
|
||||
columns: batch.resultSetSummaries[0].columnInfo.map(c => c.columnName),
|
||||
rows: d.resultSubset.rows.map(r => r.map(c => c.displayValue))
|
||||
};
|
||||
let table: HTMLTableElement = document.createElement('table');
|
||||
table.createTHead();
|
||||
table.createTBody();
|
||||
let hrow = <HTMLTableRowElement>table.insertRow();
|
||||
// headers
|
||||
for (let column of data.columns) {
|
||||
var cell = hrow.insertCell();
|
||||
cell.innerHTML = column;
|
||||
}
|
||||
|
||||
for (let row in data.rows) {
|
||||
let hrow = <HTMLTableRowElement>table.insertRow();
|
||||
for (let column in data.columns) {
|
||||
var cell = hrow.insertCell();
|
||||
cell.innerHTML = data.rows[row][column];
|
||||
}
|
||||
}
|
||||
let tableHtml = '<table>' + table.innerHTML + '</table>';
|
||||
|
||||
let msg: nb.IIOPubMessage = {
|
||||
channel: 'iopub',
|
||||
type: 'iopub',
|
||||
header: <nb.IHeader> {
|
||||
msg_id: undefined,
|
||||
msg_type: 'execute_result'
|
||||
},
|
||||
content: <nb.IExecuteResult> {
|
||||
output_type: 'execute_result',
|
||||
metadata: {},
|
||||
execution_count: 0,
|
||||
data: { 'text/html' : tableHtml},
|
||||
},
|
||||
metadata: undefined,
|
||||
parent_header: undefined
|
||||
};
|
||||
handler.handle(msg);
|
||||
});
|
||||
}));
|
||||
}
|
||||
registerMessageHook(hook: (msg: nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
|
||||
// no-op
|
||||
}
|
||||
removeMessageHook(hook: (msg: nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as sqlops from 'sqlops';
|
||||
import { 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: sqlops.ObjectMetadata, operation: ScriptOperation, paramDetails: sqlops.ScriptingParamDetails): Thenable<sqlops.ScriptingResult>;
|
||||
|
||||
/**
|
||||
* Register a scripting provider
|
||||
*/
|
||||
registerProvider(providerId: string, provider: sqlops.ScriptingProvider): void;
|
||||
|
||||
/**
|
||||
* Specifies whether a provider with a given ID has been registered or not
|
||||
*/
|
||||
isProviderRegistered(providerId: string): boolean;
|
||||
|
||||
/**
|
||||
* Callback method for when scripting is complete
|
||||
*/
|
||||
onScriptingComplete(handle: number, scriptingCompleteResult: sqlops.ScriptingCompleteResult): void;
|
||||
|
||||
/**
|
||||
* Returns the result for an operation if the operation failed
|
||||
*/
|
||||
getOperationFailedResult(operationId: string): sqlops.ScriptingCompleteResult;
|
||||
}
|
||||
|
||||
export class ScriptingService implements IScriptingService {
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
private _providers: { [handle: string]: sqlops.ScriptingProvider; } = Object.create(null);
|
||||
|
||||
private failedScriptingOperations: { [operationId: string]: sqlops.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: sqlops.ObjectMetadata, operation: ScriptOperation, paramDetails: sqlops.ScriptingParamDetails): Thenable<sqlops.ScriptingResult> {
|
||||
let providerId: string = this._connectionService.getProviderIdFromUri(connectionUri);
|
||||
|
||||
if (providerId) {
|
||||
let provider = this._providers[providerId];
|
||||
if (provider) {
|
||||
return provider.scriptAsOperation(connectionUri, operation, metadata, paramDetails);
|
||||
}
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method for when scripting is complete
|
||||
* @param handle
|
||||
* @param scriptingCompleteResult
|
||||
*/
|
||||
public onScriptingComplete(handle: number, scriptingCompleteResult: sqlops.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): sqlops.ScriptingCompleteResult {
|
||||
if (operationId && operationId in this.failedScriptingOperations) {
|
||||
return this.failedScriptingOperations[operationId];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a scripting provider
|
||||
*/
|
||||
public registerProvider(providerId: string, provider: sqlops.ScriptingProvider): void {
|
||||
this._providers[providerId] = provider;
|
||||
}
|
||||
|
||||
public isProviderRegistered(providerId: string): boolean {
|
||||
let provider = this._providers[providerId];
|
||||
return !!provider;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
|
||||
export const SERVICE_ID = 'serializationService';
|
||||
|
||||
export interface SerializationProviderEvents {
|
||||
onSaveAs(saveFormat: string, savePath: string, results: string, appendToFile: boolean): Thenable<sqlops.SaveResultRequestResult>;
|
||||
}
|
||||
|
||||
export const ISerializationService = createDecorator<ISerializationService>(SERVICE_ID);
|
||||
|
||||
export interface ISerializationService {
|
||||
_serviceBrand: any;
|
||||
|
||||
saveAs(saveFormat: string, savePath: string, results: string, appendToFile: boolean): Thenable<sqlops.SaveResultRequestResult>;
|
||||
|
||||
disabledSaveAs(): Thenable<sqlops.SaveResultRequestResult>;
|
||||
|
||||
addEventListener(handle: number, events: SerializationProviderEvents): IDisposable;
|
||||
|
||||
getSerializationFeatureMetadataProvider(ownerUri: string): sqlops.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<sqlops.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<sqlops.SaveResultRequestResult> {
|
||||
return Promise.resolve({ messages: Constants.SerializationDisabled });
|
||||
|
||||
}
|
||||
|
||||
public getSerializationFeatureMetadataProvider(ownerUri: string): sqlops.FeatureMetadataProvider {
|
||||
let providerId: string = this._connectionService.getProviderIdFromUri(ownerUri);
|
||||
let providerCapabilities = this._capabilitiesService.getLegacyCapabilities(providerId);
|
||||
|
||||
if (providerCapabilities) {
|
||||
return providerCapabilities.features.find(f => f.featureName === SERVICE_ID);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user