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:
Anthony Dresser
2019-01-25 14:52:35 -08:00
committed by GitHub
parent c8986464ec
commit ea67859de7
338 changed files with 2036 additions and 7386 deletions

View File

@@ -7,7 +7,7 @@
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { SqlMainContext, MainThreadDashboardShape, ExtHostDashboardShape, SqlExtHostContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService';
@extHostNamedCustomer(SqlMainContext.MainThreadDashboard)
export class MainThreadDashboard implements MainThreadDashboardShape {

View File

@@ -20,7 +20,7 @@ import {
import { IConnectionProfile } from 'sqlops';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
@extHostNamedCustomer(SqlMainContext.MainThreadTasks)
export class MainThreadTasks implements MainThreadTasksShape {

View File

@@ -6,7 +6,7 @@
import * as sqlops from 'sqlops';
import { TPromise } from 'vs/base/common/winjs.base';
import { IAccountManagementService } from 'sql/services/accountManagement/interfaces';
import { IAccountManagementService } from 'sql/platform/accountManagement/common/interfaces';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import {
ExtHostAccountManagementShape,
@@ -16,7 +16,7 @@ import {
} from 'sql/workbench/api/node/sqlExtHost.protocol';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { UpdateAccountListEventParams } from 'sql/services/accountManagement/eventTypes';
import { UpdateAccountListEventParams } from 'sql/platform/accountManagement/common/eventTypes';
@extHostNamedCustomer(SqlMainContext.MainThreadAccountManagement)
export class MainThreadAccountManagement implements MainThreadAccountManagementShape {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ITaskService } from 'sql/parts/taskHistory/common/taskService';
import { ITaskService } from 'sql/platform/taskHistory/common/taskService';
import { MainThreadBackgroundTaskManagementShape, SqlMainContext, ExtHostBackgroundTaskManagementShape, SqlExtHostContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';

View File

@@ -8,16 +8,16 @@ import { SqlExtHostContext, SqlMainContext, ExtHostConnectionManagementShape, Ma
import * as sqlops from 'sqlops';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
import { IConnectionManagementService, IConnectionDialogService } from 'sql/platform/connection/common/connectionManagement';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { generateUuid } from 'vs/base/common/uuid';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
@extHostNamedCustomer(SqlMainContext.MainThreadConnectionManagement)
export class MainThreadConnectionManagement implements MainThreadConnectionManagementShape {

View File

@@ -10,7 +10,7 @@ import {
SqlExtHostContext, ExtHostCredentialManagementShape,
MainThreadCredentialManagementShape, SqlMainContext
} from 'sql/workbench/api/node/sqlExtHost.protocol';
import { ICredentialsService } from 'sql/services/credentials/credentialsService';
import { ICredentialsService } from 'sql/platform/credentials/common/credentialsService';
import * as sqlops from 'sqlops';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';

View File

@@ -7,7 +7,7 @@
import { MainThreadDashboardWebviewShape, SqlMainContext, ExtHostDashboardWebviewsShape, SqlExtHostContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { IDashboardViewService, IDashboardWebview } from 'sql/services/dashboard/common/dashboardViewService';
import { IDashboardViewService, IDashboardWebview } from 'sql/platform/dashboard/common/dashboardViewService';
@extHostNamedCustomer(SqlMainContext.MainThreadDashboardWebview)
export class MainThreadDashboardWebview implements MainThreadDashboardWebviewShape {

View File

@@ -10,23 +10,23 @@ import {
SqlExtHostContext, ExtHostDataProtocolShape,
MainThreadDataProtocolShape, SqlMainContext
} from 'sql/workbench/api/node/sqlExtHost.protocol';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { IQueryManagementService } from 'sql/parts/query/common/queryManagement';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { IQueryManagementService } from 'sql/platform/query/common/queryManagement';
import * as sqlops from 'sqlops';
import { IMetadataService } from 'sql/services/metadata/metadataService';
import { IMetadataService } from 'sql/platform/metadata/common/metadataService';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { IScriptingService } from 'sql/services/scripting/scriptingService';
import { IAdminService } from 'sql/parts/admin/common/adminService';
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
import { IBackupService } from 'sql/parts/disasterRecovery/backup/common/backupService';
import { IRestoreService } from 'sql/parts/disasterRecovery/restore/common/restoreService';
import { ITaskService } from 'sql/parts/taskHistory/common/taskService';
import { IProfilerService } from 'sql/parts/profiler/service/interfaces';
import { ISerializationService } from 'sql/services/serialization/serializationService';
import { IFileBrowserService } from 'sql/parts/fileBrowser/common/interfaces';
import { IScriptingService } from 'sql/platform/scripting/common/scriptingService';
import { IAdminService } from 'sql/workbench/services/admin/common/adminService';
import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces';
import { IBackupService } from 'sql/platform/backup/common/backupService';
import { IRestoreService } from 'sql/platform/restore/common/restoreService';
import { ITaskService } from 'sql/platform/taskHistory/common/taskService';
import { IProfilerService } from 'sql/workbench/services/profiler/common/interfaces';
import { ISerializationService } from 'sql/platform/serialization/common/serializationService';
import { IFileBrowserService } from 'sql/platform/fileBrowser/common/interfaces';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { IDacFxService } from 'sql/services/dacfx/dacFxService';
import { IDacFxService } from 'sql/platform/dacfx/common/dacFxService';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
/**

View File

@@ -10,9 +10,9 @@ import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { Disposable } from 'vs/base/common/lifecycle';
import { IModelViewService } from 'sql/services/modelComponents/modelViewService';
import { IModelViewService } from 'sql/platform/modelComponents/common/modelViewService';
import { IItemConfig, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IModelView } from 'sql/services/model/modelViewService';
import { IModelView } from 'sql/platform/model/common/modelViewService';
@extHostNamedCustomer(SqlMainContext.MainThreadModelView)

View File

@@ -5,8 +5,6 @@
'use strict';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditor } from 'vs/workbench/common/editor';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';

View File

@@ -12,9 +12,9 @@ import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { Event, Emitter } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import { INotebookService, INotebookProvider, INotebookManager } from 'sql/services/notebook/notebookService';
import { INotebookService, INotebookProvider, INotebookManager } from 'sql/workbench/services/notebook/common/notebookService';
import { INotebookManagerDetails, INotebookSessionDetails, INotebookKernelDetails, FutureMessageType, INotebookFutureDetails, INotebookFutureDone } from 'sql/workbench/api/common/sqlExtHostTypes';
import { LocalContentManager } from 'sql/services/notebook/localContentManager';
import { LocalContentManager } from 'sql/workbench/services/notebook/node/localContentManager';
import { Deferred } from 'sql/base/common/promise';
import { FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
@@ -348,11 +348,11 @@ class KernelWrapper implements sqlops.nb.IKernel {
requestExecute(content: sqlops.nb.IExecuteRequest, disposeOnDone?: boolean): sqlops.nb.IFuture {
let future = new FutureWrapper(this._proxy);
this._proxy.ext.$requestExecute(this.kernelDetails.kernelId, content, disposeOnDone)
.then(details => {
future.setDetails(details);
// Save the future in the main thread notebook so extension can call through and reference it
this._proxy.main.addFuture(details.futureId, future);
}, error => future.setError(error));
.then(details => {
future.setDetails(details);
// Save the future in the main thread notebook so extension can call through and reference it
this._proxy.main.addFuture(details.futureId, future);
}, error => future.setError(error));
return future;
}
@@ -370,7 +370,7 @@ class FutureWrapper implements FutureInternal {
private _inProgress: boolean;
constructor(private _proxy: Proxies) {
this._inProgress = true;
this._inProgress = true;
}
public setDetails(details: INotebookFutureDetails): void {

View File

@@ -23,7 +23,7 @@ import {
INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookShowOptions, INotebookModelAddedData, INotebookModelChangedData
} from 'sql/workbench/api/node/sqlExtHost.protocol';
import { NotebookInputModel, NotebookInput } from 'sql/parts/notebook/notebookInput';
import { INotebookService, INotebookEditor, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
import { INotebookService, INotebookEditor, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService';
import { TPromise } from 'vs/base/common/winjs.base';
import { getProvidersForFileName } from 'sql/parts/notebook/notebookUtils';
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';

View File

@@ -9,11 +9,11 @@ import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { IObjectExplorerService, NodeInfoWithConnection } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { TreeItemCollapsibleState } from 'sql/parts/objectExplorer/common/treeNode';

View File

@@ -7,7 +7,7 @@
import { SqlExtHostContext, SqlMainContext, ExtHostQueryEditorShape, MainThreadQueryEditorShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IConnectionManagementService, IConnectionCompletionOptions, ConnectionType, RunQueryOnConnectionMode } from 'sql/parts/connection/common/connectionManagement';
import { IConnectionManagementService, IConnectionCompletionOptions, ConnectionType, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';

View File

@@ -6,7 +6,7 @@
import * as sqlops from 'sqlops';
import { TPromise } from 'vs/base/common/winjs.base';
import { IResourceProviderService } from 'sql/parts/accountManagement/common/interfaces';
import { IResourceProviderService } from 'sql/workbench/services/resourceProvider/common/resourceProviderService';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import {
ExtHostResourceProviderShape,

View File

@@ -10,7 +10,7 @@ import {
SqlExtHostContext, ExtHostSerializationProviderShape,
MainThreadSerializationProviderShape, SqlMainContext
} from 'sql/workbench/api/node/sqlExtHost.protocol';
import { ISerializationService } from 'sql/services/serialization/serializationService';
import { ISerializationService } from 'sql/platform/serialization/common/serializationService';
import * as sqlops from 'sqlops';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';

View File

@@ -3,21 +3,21 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IConnectionManagementService, IErrorMessageService } from 'sql/parts/connection/common/connectionManagement';
import * as TaskUtilities from './taskUtilities';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { IInsightsConfig } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { IScriptingService } from 'sql/services/scripting/scriptingService';
import { IRestoreDialogController } from 'sql/parts/disasterRecovery/restore/common/restoreService';
import { IBackupUiService } from 'sql/parts/disasterRecovery/backup/common/backupService';
import { IAngularEventingService, AngularEventType } from 'sql/services/angularEventing/angularEventingService';
import { IScriptingService } from 'sql/platform/scripting/common/scriptingService';
import { IRestoreDialogController } from 'sql/platform/restore/common/restoreService';
import { IBackupUiService } from 'sql/platform/backup/common/backupService';
import { IAngularEventingService, AngularEventType } from 'sql/platform/angularEventing/common/angularEventingService';
import { IInsightsDialogService } from 'sql/parts/insights/common/interfaces';
import { IAdminService } from 'sql/parts/admin/common/adminService';
import { IAdminService } from 'sql/workbench/services/admin/common/adminService';
import * as Constants from 'sql/common/constants';
import { ScriptOperation } from 'sql/workbench/common/taskUtilities';
import { Task } from 'sql/platform/tasks/common/tasks';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
import { ObjectMetadata } from 'sqlops';
@@ -132,7 +132,7 @@ export class ScriptExecuteAction extends Action {
this._connectionManagementService,
this._queryEditorService,
this._scriptingService,
ScriptOperation.Execute,
TaskUtilities.ScriptOperation.Execute,
this._errorMessageService
).then(
result => {
@@ -168,7 +168,7 @@ export class ScriptAlterAction extends Action {
this._connectionManagementService,
this._queryEditorService,
this._scriptingService,
ScriptOperation.Alter,
TaskUtilities.ScriptOperation.Alter,
this._errorMessageService
).then(
result => {
@@ -237,7 +237,7 @@ export class ScriptCreateAction extends Action {
this._connectionManagementService,
this._queryEditorService,
this._scriptingService,
ScriptOperation.Create,
TaskUtilities.ScriptOperation.Create,
this._errorMessageService
).then(
result => {
@@ -273,7 +273,7 @@ export class ScriptDeleteAction extends Action {
this._connectionManagementService,
this._queryEditorService,
this._scriptingService,
ScriptOperation.Delete,
TaskUtilities.ScriptOperation.Delete,
this._errorMessageService
).then(
result => {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import ConnectionConstants = require('sql/parts/connection/common/constants');
import ConnectionConstants = require('sql/platform/connection/common/constants');
import { QueryInput } from 'sql/parts/query/common/queryInput';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';

View File

@@ -4,31 +4,35 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import * as os from 'os';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import {
IConnectableInput, IConnectionManagementService,
IConnectionCompletionOptions, ConnectionType, IErrorMessageService,
IConnectionCompletionOptions, ConnectionType,
RunQueryOnConnectionMode, IConnectionResult
} from 'sql/parts/connection/common/connectionManagement';
} from 'sql/platform/connection/common/connectionManagement';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { IScriptingService } from 'sql/services/scripting/scriptingService';
import { IScriptingService } from 'sql/platform/scripting/common/scriptingService';
import { EditDataInput } from 'sql/parts/editData/common/editDataInput';
import { IAdminService } from 'sql/parts/admin/common/adminService';
import { IRestoreDialogController } from 'sql/parts/disasterRecovery/restore/common/restoreService';
import { IBackupUiService } from 'sql/parts/disasterRecovery/backup/common/backupService';
import { IAdminService } from 'sql/workbench/services/admin/common/adminService';
import { IRestoreDialogController } from 'sql/platform/restore/common/restoreService';
import { IBackupUiService } from 'sql/platform/backup/common/backupService';
import { IInsightsConfig } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { IInsightsDialogService } from 'sql/parts/insights/common/interfaces';
import { ConnectionManagementInfo } from 'sql/parts/connection/common/connectionManagementInfo';
import Severity from 'vs/base/common/severity';
import * as sqlops from 'sqlops';
import nls = require('vs/nls');
import os = require('os');
import path = require('path');
import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { QueryInput } from 'sql/parts/query/common/queryInput';
import { DashboardInput } from 'sql/parts/dashboard/dashboardInput';
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
import * as sqlops from 'sqlops';
import Severity from 'vs/base/common/severity';
import * as nls from 'vs/nls';
import * as path from 'vs/base/common/paths';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
// map for the version of SQL Server (default is 140)
const scriptCompatibilityOptionMap = {

View File

@@ -0,0 +1,129 @@
/*---------------------------------------------------------------------------------------------
* 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 { Event, Emitter } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TPromise } from 'vs/base/common/winjs.base';
import * as sqlops from 'sqlops';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import * as ConnectionUtils from 'sql/platform/connection/common/utils';
import { ProviderConnectionInfo } from 'sql/platform/connection/common/providerConnectionInfo';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { BackupDialog } from 'sql/parts/disasterRecovery/backup/backupDialog';
import { OptionsDialog } from 'sql/base/browser/ui/modal/optionsDialog';
import { IBackupUiService, IBackupService, TaskExecutionMode } from 'sql/platform/backup/common/backupService';
export class BackupUiService implements IBackupUiService {
public _serviceBrand: any;
private _backupDialogs: { [providerName: string]: BackupDialog | OptionsDialog } = {};
private _currentProvider: string;
private _optionValues: { [optionName: string]: any } = {};
private _connectionUri: string;
private static _connectionUniqueId: number = 0;
private _onShowBackupEvent: Emitter<{ connection: IConnectionProfile, ownerUri: string }>;
public get onShowBackupEvent(): Event<{ connection: IConnectionProfile, ownerUri: string }> { return this._onShowBackupEvent.event; }
constructor(
@IInstantiationService private _instantiationService: IInstantiationService,
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
@IBackupService private _disasterRecoveryService: IBackupService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
this._onShowBackupEvent = new Emitter<{ connection: IConnectionProfile, ownerUri: string }>();
}
public showBackup(connection: IConnectionProfile): Promise<any> {
let self = this;
return new Promise<void>((resolve, reject) => {
self.showBackupDialog(connection).then(() => {
resolve(void 0);
}, error => {
reject();
});
});
}
private getOptions(provider: string): sqlops.ServiceOption[] {
let feature = this._capabilitiesService.getLegacyCapabilities(this._currentProvider).features.find(f => f.featureName === 'backup');
if (feature) {
return feature.optionsMetadata;
} else {
return undefined;
}
}
public showBackupDialog(connection: IConnectionProfile): TPromise<void> {
let self = this;
self._connectionUri = ConnectionUtils.generateUri(connection);
self._currentProvider = connection.providerName;
let backupDialog = self._backupDialogs[self._currentProvider];
if (!backupDialog) {
let backupOptions = this.getOptions(this._currentProvider);
if (backupOptions) {
backupDialog = self._instantiationService ? self._instantiationService.createInstance(
OptionsDialog, 'Backup database - ' + connection.serverName + ':' + connection.databaseName, 'BackupOptions', undefined) : undefined;
backupDialog.onOk(() => this.handleOptionDialogClosed());
}
else {
backupDialog = self._instantiationService ? self._instantiationService.createInstance(BackupDialog) : undefined;
}
backupDialog.render();
self._backupDialogs[self._currentProvider] = backupDialog;
}
let backupOptions = this.getOptions(this._currentProvider);
return new TPromise<void>((resolve) => {
let uri = this._connectionManagementService.getConnectionUri(connection)
+ ProviderConnectionInfo.idSeparator
+ ConnectionUtils.ConnectionUriBackupIdAttributeName
+ ProviderConnectionInfo.nameValueSeparator
+ BackupUiService._connectionUniqueId;
this._connectionUri = uri;
BackupUiService._connectionUniqueId++;
// Create connection if needed
if (!this._connectionManagementService.isConnected(uri)) {
this._connectionManagementService.connect(connection, uri).then(() => {
this._onShowBackupEvent.fire({ connection: connection, ownerUri: uri });
});
}
if (backupOptions) {
(backupDialog as OptionsDialog).open(backupOptions, self._optionValues);
} else {
(backupDialog as BackupDialog).open(connection);
}
resolve(void 0);
});
}
public onShowBackupDialog() {
let backupDialog = this._backupDialogs[this._currentProvider];
if (backupDialog) {
backupDialog.setFocusableElements();
}
}
public closeBackup() {
let self = this;
let backupDialog = self._backupDialogs[self._currentProvider];
if (backupDialog) {
backupDialog.close();
}
}
private handleOptionDialogClosed() {
this._disasterRecoveryService.backup(this._connectionUri, this._optionValues, TaskExecutionMode.executeAndScript);
}
}

View File

@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IViewlet } from 'vs/workbench/common/viewlet';
export interface IConnectionsViewlet extends IViewlet {
search(text: string): void;
}

View File

@@ -0,0 +1,162 @@
/*---------------------------------------------------------------------------------------------
* 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 'vs/css!./media/connectionViewlet';
import * as DOM from 'vs/base/browser/dom';
import { TPromise } from 'vs/base/common/winjs.base';
import { Builder } from 'vs/base/browser/builder';
import { Viewlet } from 'vs/workbench/browser/viewlet';
import { IAction } from 'vs/base/common/actions';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import Severity from 'vs/base/common/severity';
import { VIEWLET_ID } from 'sql/platform/connection/common/connectionManagement';
import { ServerTreeView } from 'sql/parts/objectExplorer/viewlet/serverTreeView';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ClearSearchAction, AddServerAction, AddServerGroupAction, ActiveConnectionsFilterAction } from 'sql/parts/objectExplorer/viewlet/connectionTreeAction';
import { warn } from 'sql/base/common/log';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { localize } from 'vs/nls';
import { IConnectionsViewlet } from 'sql/workbench/parts/connection/common/connectionViewlet';
export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet {
private _root: HTMLElement;
private _searchBox: InputBox;
private _toDisposeViewlet: IDisposable[] = [];
private _serverTreeView: ServerTreeView;
private _clearSearchAction: ClearSearchAction;
private _addServerAction: IAction;
private _addServerGroupAction: IAction;
private _activeConnectionsFilterAction: ActiveConnectionsFilterAction;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService private _themeService: IThemeService,
@IInstantiationService private _instantiationService: IInstantiationService,
@INotificationService private _notificationService: INotificationService,
@IObjectExplorerService private objectExplorerService: IObjectExplorerService,
@IPartService partService: IPartService
) {
super(VIEWLET_ID, partService, telemetryService, _themeService);
this._clearSearchAction = this._instantiationService.createInstance(ClearSearchAction, ClearSearchAction.ID, ClearSearchAction.LABEL, this);
this._addServerAction = this._instantiationService.createInstance(AddServerAction,
AddServerAction.ID,
AddServerAction.LABEL);
this._addServerGroupAction = this._instantiationService.createInstance(AddServerGroupAction,
AddServerGroupAction.ID,
AddServerGroupAction.LABEL);
this._serverTreeView = this._instantiationService.createInstance(ServerTreeView);
this._activeConnectionsFilterAction = this._serverTreeView.activeConnectionsFilterAction;
this.objectExplorerService.registerServerTreeView(this._serverTreeView);
}
private onError(err: any): void {
if (isPromiseCanceledError(err)) {
return;
}
this._notificationService.notify({
severity: Severity.Error,
message: err
});
}
public create(parent: HTMLElement): TPromise<void> {
return new TPromise<void>((resolve) => {
super.create(parent);
this._root = parent;
let parentBuilder = new Builder(parent);
parentBuilder.div({ class: 'server-explorer-viewlet' }, (viewletContainer) => {
viewletContainer.div({ class: 'search-box' }, (searchBoxContainer) => {
let searchServerString = localize('Search server names', 'Search server names');
this._searchBox = new InputBox(
searchBoxContainer.getHTMLElement(),
null,
{
placeholder: searchServerString,
actions: [this._clearSearchAction],
ariaLabel: searchServerString
}
);
this._searchBox.onDidChange(() => {
this.search(this._searchBox.value);
});
// Theme styler
this._toDisposeViewlet.push(attachInputBoxStyler(this._searchBox, this._themeService));
});
viewletContainer.div({ Class: 'object-explorer-view' }, (viewContainer) => {
this._serverTreeView.renderBody(viewContainer.getHTMLElement()).then(() => {
resolve(null);
}, error => {
warn('render registered servers: ' + error);
resolve(null);
});
});
});
});
}
public search(value: string): void {
if (value) {
this._clearSearchAction.enabled = true;
this._serverTreeView.searchTree(value);
} else {
this.clearSearch();
}
}
public setVisible(visible: boolean): TPromise<void> {
return super.setVisible(visible).then(() => {
this._serverTreeView.setVisible(visible);
});
}
/**
* Return actions for the viewlet
*/
public getActions(): IAction[] {
return [this._addServerAction, this._addServerGroupAction, this._activeConnectionsFilterAction];
}
public focus(): void {
super.focus();
}
public layout({ height, width }: DOM.Dimension): void {
this._searchBox.layout();
this._serverTreeView.layout(height - 36); // account for search box
DOM.toggleClass(this._root, 'narrow', width <= 350);
}
public getOptimalWidth(): number {
return 400;
}
public clearSearch() {
this._serverTreeView.refreshTree();
this._searchBox.value = '';
this._clearSearchAction.enabled = false;
this._searchBox.focus();
}
public dispose(): void {
this._serverTreeView.dispose();
this._toDisposeViewlet = dispose(this._toDisposeViewlet);
}
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#E8E8E8" d="M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z"/></svg>

After

Width:  |  Height:  |  Size: 139 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}.cls-2{fill:#3bb44a;}</style></defs><title>connected_active_server_16x16</title><path class="cls-1" d="M1.29.07V16h9.59a3.31,3.31,0,0,1-1.94-1H2.29V11h6a3.31,3.31,0,0,1,.54-.8A1.81,1.81,0,0,1,9,10H2.29v-9h8.53v8a3.68,3.68,0,0,1,.58,0l.39,0h0v-9Z"/><path class="cls-1" d="M3.3,1.8V4.25H5.75V1.8Zm2,2H3.8V2.3H5.25Z"/><circle class="cls-2" cx="11.24" cy="12.52" r="3.47"/></svg>

After

Width:  |  Height:  |  Size: 502 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#3bb44a;}</style></defs><title>connected_active_server_inverse_16x16</title><path class="cls-1" d="M1.29.07V16h9.59a3.31,3.31,0,0,1-1.94-1H2.29V11h6a3.31,3.31,0,0,1,.54-.8A1.81,1.81,0,0,1,9,10H2.29v-9h8.53v8a3.68,3.68,0,0,1,.58,0l.39,0h0v-9Z"/><path class="cls-1" d="M3.3,1.8V4.25H5.75V1.8Zm2,2H3.8V2.3H5.25Z"/><circle class="cls-2" cx="11.24" cy="12.52" r="3.47"/></svg>

After

Width:  |  Height:  |  Size: 507 B

View File

@@ -0,0 +1,138 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* --- Registered servers tree viewlet --- */
.server-explorer-viewlet .monaco-tree .monaco-tree-row .content .server-group {
cursor: default;
width: 100%;
display: flex;
align-items: center;
}
/* Bold font style does not go well with CJK fonts */
.server-explorer-viewlet:lang(zh-Hans) .monaco-tree .monaco-tree-row .server-group,
.server-explorer-viewlet:lang(zh-Hant) .monaco-tree .monaco-tree-row .server-group,
.server-explorer-viewlet:lang(ja) .monaco-tree .monaco-tree-row .server-group,
.server-explorer-viewlet:lang(ko) .monaco-tree .monaco-tree-row .server-group { font-weight: normal; }
/* High Contrast Theming */
.hc-black .monaco-workbench .server-explorer-viewlet .server-group {
line-height: 20px;
}
.monaco-workbench > .activitybar .monaco-action-bar .action-label.serverTree {
background-size: 22px;
background-repeat: no-repeat;
background-position: 50% !important;
}
.server-explorer-viewlet .object-explorer-view {
height: calc(100% - 36px);
}
.server-explorer-viewlet .server-group {
height: 38px;
line-height: 38px;
color: #ffffff;
}
.server-explorer-viewlet .monaco-action-bar .action-label {
margin-right: 0.3em;
margin-left: 0.3em;
line-height: 15px;
width: 10px !important;
height: 10px !important;
}
/* Add space beneath the button */
.new-connection .monaco-text-button {
margin-bottom: 2px;
}
/* display action buttons on hover */
.server-explorer-viewlet .monaco-tree .monaco-tree-row > .content {
display: flex;
}
/* Added to display the tree in connection dialog */
.server-explorer-viewlet {
height: 100%;
}
.explorer-servers {
height: 100%;
}
/* search box */
.server-explorer-viewlet .search-box {
padding-bottom: 4px;
margin: auto;
width: 95%;
}
/* OE and connection element group */
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile,
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .object-element-group {
padding: 5px;
overflow: hidden;
}
/* OE and connection label */
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .label,
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .object-element-group > .label {
text-overflow: ellipsis;
overflow: hidden;
}
/* OE and connection icon */
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon,
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .object-element-group > .icon {
float: left;
height: 16px;
width: 16px;
padding-right: 10px;
}
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.connected {
background: url('connected_active_server.svg') center center no-repeat;
}
.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.connected,
.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.connected{
background: url('connected_active_server_inverse.svg') center center no-repeat;
}
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.disconnected {
background: url('disconnected_server.svg') center center no-repeat;
}
.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.disconnected,
.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.disconnected{
background: url('disconnected_server_inverse.svg') center center no-repeat;
}
/* loading for OE node */
.server-explorer-viewlet .monaco-tree .monaco-tree-rows > .monaco-tree-row > .icon.in-progress .connection-tile:before,
.server-explorer-viewlet .monaco-tree .monaco-tree-rows > .monaco-tree-row > .icon.in-progress .object-element-group:before {
position: absolute;
display: block;
width: 36px;
height: 100%;
top: 0;
left: -35px;
}
.monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.expanded.has-children > .content.server-group:before {
background: url('expanded-dark.svg') 50% 50% no-repeat;
}
.monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content.server-group:before {
background: url('collapsed-dark.svg') 50% 50% no-repeat;
}
/* Add connection button */
.server-explorer-viewlet .button-section {
padding: 20px;
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}.cls-2{fill:#d02e00;}</style></defs><title>disconnected_server_16x16</title><path class="cls-1" d="M1.42.06V16H11a3.31,3.31,0,0,1-1.94-1H2.42V11h6a3.31,3.31,0,0,1,.54-.8,1.81,1.81,0,0,1,.19-.2H2.42v-9H11v8a3.68,3.68,0,0,1,.58,0l.39,0h0v-9Z"/><path class="cls-1" d="M3.43,1.79V4.24H5.89V1.79Zm2,2H3.93V2.29H5.39Z"/><path class="cls-2" d="M11.08,16a2.22,2.22,0,0,0,.45,0,2.59,2.59,0,0,0,.4,0Z"/><path class="cls-2" d="M12,9.08h0l-.39,0a3.68,3.68,0,0,0-.58,0A3.41,3.41,0,0,0,9.14,10a1.81,1.81,0,0,0-.19.2,3.46,3.46,0,0,0-.89,2.3,3.4,3.4,0,0,0,.85,2.26,1.29,1.29,0,0,0,.16.17A3.31,3.31,0,0,0,11,16H12a3.46,3.46,0,0,0,2.17-1.13,3.41,3.41,0,0,0,.88-2.3A3.47,3.47,0,0,0,12,9.08Zm0,5.72a1.72,1.72,0,0,1-.39,0,2.23,2.23,0,0,1-.77-.14,2.29,2.29,0,0,1-1.54-2.17A2.22,2.22,0,0,1,9.79,11a2.29,2.29,0,0,1,1-.67,2.23,2.23,0,0,1,.77-.14,1.72,1.72,0,0,1,.39,0h0a2.3,2.3,0,0,1,0,4.53Z"/></svg>

After

Width:  |  Height:  |  Size: 1002 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#d02e00;}</style></defs><title>disconnected_server_inverse_16x16</title><path class="cls-1" d="M1.42.06V16H11a3.31,3.31,0,0,1-1.94-1H2.42V11h6a3.31,3.31,0,0,1,.54-.8,1.81,1.81,0,0,1,.19-.2H2.42v-9H11v8a3.68,3.68,0,0,1,.58,0l.39,0h0v-9Z"/><path class="cls-1" d="M3.43,1.79V4.24H5.89V1.79Zm2,2H3.93V2.29H5.39Z"/><path class="cls-2" d="M11.08,16a2.22,2.22,0,0,0,.45,0,2.59,2.59,0,0,0,.4,0Z"/><path class="cls-2" d="M12,9.08h0l-.39,0a3.68,3.68,0,0,0-.58,0A3.41,3.41,0,0,0,9.14,10a1.81,1.81,0,0,0-.19.2,3.46,3.46,0,0,0-.89,2.3,3.4,3.4,0,0,0,.85,2.26,1.29,1.29,0,0,0,.16.17A3.31,3.31,0,0,0,11,16H12a3.46,3.46,0,0,0,2.17-1.13,3.41,3.41,0,0,0,.88-2.3A3.47,3.47,0,0,0,12,9.08Zm0,5.72a1.72,1.72,0,0,1-.39,0,2.23,2.23,0,0,1-.77-.14,2.29,2.29,0,0,1-1.54-2.17A2.22,2.22,0,0,1,9.79,11a2.29,2.29,0,0,1,1-.67,2.23,2.23,0,0,1,.77-.14,1.72,1.72,0,0,1,.39,0h0a2.3,2.3,0,0,1,0,4.53Z"/></svg>

After

Width:  |  Height:  |  Size: 1007 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#E8E8E8" d="M11 10H5.344L11 4.414V10z"/></svg>

After

Width:  |  Height:  |  Size: 118 B

View File

@@ -0,0 +1,443 @@
/*---------------------------------------------------------------------------------------------
* 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/platform/accountManagement/common/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/platform/accountManagement/common/eventTypes';
import { IAccountManagementService } from 'sql/platform/accountManagement/common/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[];
}

View File

@@ -0,0 +1,129 @@
/*---------------------------------------------------------------------------------------------
* 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 { localize } from 'vs/nls';
export const SERVICE_ID = 'adminService';
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { CreateLoginInput } from 'sql/parts/admin/security/createLoginInput';
import { TaskDialogInput } from 'sql/parts/tasks/dialog/taskDialogInput';
import { TPromise } from 'vs/base/common/winjs.base';
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import * as sqlops from 'sqlops';
export const IAdminService = createDecorator<IAdminService>(SERVICE_ID);
export interface IAdminService {
_serviceBrand: any;
registerProvider(providerId: string, provider: sqlops.AdminServicesProvider): void;
showCreateDatabaseWizard(uri: string, connection: IConnectionProfile): Promise<any>;
showCreateLoginWizard(uri: string, connection: IConnectionProfile): Promise<any>;
createDatabase(connectionUri: string, database: sqlops.DatabaseInfo): Thenable<sqlops.CreateDatabaseResponse>;
getDefaultDatabaseInfo(connectionUri: string): Thenable<sqlops.DatabaseInfo>;
getDatabaseInfo(connectionUri: string): Thenable<sqlops.DatabaseInfo>;
}
export class AdminService implements IAdminService {
_serviceBrand: any;
private _providers: { [handle: string]: sqlops.AdminServicesProvider; } = Object.create(null);
constructor(
@IInstantiationService private _instantiationService: IInstantiationService,
@IEditorService private _editorService: IEditorService,
@IConnectionManagementService private _connectionService: IConnectionManagementService
) {
}
private _runAction<T>(uri: string, action: (handler: sqlops.AdminServicesProvider) => Thenable<T>): Thenable<T> {
let providerId: string = this._connectionService.getProviderIdFromUri(uri);
if (!providerId) {
return TPromise.wrapError(new Error(localize('adminService.providerIdNotValidError', 'Connection is required in order to interact with adminservice')));
}
let handler = this._providers[providerId];
if (handler) {
return action(handler);
} else {
return TPromise.wrapError(new Error(localize('adminService.noHandlerRegistered', 'No Handler Registered')));
}
}
public showCreateDatabaseWizard(uri: string, connection: IConnectionProfile): Promise<any> {
const self = this;
return new Promise<boolean>((resolve, reject) => {
let input: TaskDialogInput = self._instantiationService ? self._instantiationService.createInstance(TaskDialogInput, uri, connection) : undefined;
self._editorService.openEditor(input, { pinned: true }, ACTIVE_GROUP);
resolve(true);
});
}
public createDatabase(connectionUri: string, database: sqlops.DatabaseInfo): Thenable<sqlops.CreateDatabaseResponse> {
let providerId: string = this._connectionService.getProviderIdFromUri(connectionUri);
if (providerId) {
let provider = this._providers[providerId];
if (provider) {
return provider.createDatabase(connectionUri, database);
}
}
return Promise.resolve(undefined);
}
public showCreateLoginWizard(uri: string, connection: IConnectionProfile): Promise<any> {
const self = this;
self.createLogin(uri, { name: 'TEST: login name' });
return new Promise<boolean>((resolve, reject) => {
let loginInput: CreateLoginInput = self._instantiationService ? self._instantiationService.createInstance(CreateLoginInput, uri, connection) : undefined;
self._editorService.openEditor(loginInput, { pinned: true }, ACTIVE_GROUP);
resolve(true);
});
}
public createLogin(connectionUri: string, login: sqlops.LoginInfo): Thenable<sqlops.CreateLoginResponse> {
let providerId: string = this._connectionService.getProviderIdFromUri(connectionUri);
if (providerId) {
let provider = this._providers[providerId];
if (provider) {
return provider.createLogin(connectionUri, login);
}
}
return Promise.resolve(undefined);
}
public getDefaultDatabaseInfo(connectionUri: string): Thenable<sqlops.DatabaseInfo> {
let providerId: string = this._connectionService.getProviderIdFromUri(connectionUri);
if (providerId) {
let provider = this._providers[providerId];
if (provider) {
return provider.getDefaultDatabaseInfo(connectionUri);
}
}
return Promise.resolve(undefined);
}
public getDatabaseInfo(connectionUri: string): Thenable<sqlops.DatabaseInfo> {
return this._runAction(connectionUri, (runner) => {
return runner.getDatabaseInfo(connectionUri);
});
}
public registerProvider(providerId: string, provider: sqlops.AdminServicesProvider): void {
this._providers[providerId] = provider;
}
}

View File

@@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* 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';
export interface ICommandLineProcessing {
_serviceBrand: any;
/**
* Interprets the various Azure Data Studio-specific command line switches and
* performs the requisite tasks such as connecting to a server
*/
processCommandLine(): Promise<void>;
}
export const ICommandLineProcessing = createDecorator<ICommandLineProcessing>('commandLineService');

View File

@@ -0,0 +1,127 @@
/*---------------------------------------------------------------------------------------------
* 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 { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { ICommandLineProcessing } from 'sql/workbench/services/commandLine/common/commandLine';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import * as Constants from 'sql/platform/connection/common/constants';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import * as platform from 'vs/platform/registry/common/platform';
import { IConnectionProviderRegistry, Extensions as ConnectionProviderExtensions } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { warn } from 'sql/base/common/log';
export class CommandLineService implements ICommandLineProcessing {
private _connectionProfile: ConnectionProfile;
private _showConnectionDialog: boolean;
private _commandName: string;
constructor(
@ICapabilitiesService _capabilitiesService: ICapabilitiesService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IEnvironmentService private _environmentService: IEnvironmentService,
@IQueryEditorService private _queryEditorService: IQueryEditorService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IEditorService private _editorService: IEditorService,
@ICommandService private _commandService: ICommandService,
@IWorkspaceConfigurationService private _configurationService: IWorkspaceConfigurationService
) {
let profile = null;
if (this._environmentService) {
if (this._commandService) {
this._commandName = this._environmentService.args.command;
}
if (this._environmentService.args.server) {
profile = new ConnectionProfile(_capabilitiesService, null);
// We want connection store to use any matching password it finds
profile.savePassword = true;
profile.providerName = Constants.mssqlProviderName;
profile.serverName = _environmentService.args.server;
profile.databaseName = _environmentService.args.database ? _environmentService.args.database : '';
profile.userName = _environmentService.args.user ? _environmentService.args.user : '';
profile.authenticationType = _environmentService.args.integrated ? 'Integrated' : 'SqlLogin';
profile.connectionName = '';
profile.setOptionValue('applicationName', Constants.applicationName);
profile.setOptionValue('databaseDisplayName', profile.databaseName);
profile.setOptionValue('groupId', profile.groupId);
}
}
this._connectionProfile = profile;
const registry = platform.Registry.as<IConnectionProviderRegistry>(ConnectionProviderExtensions.ConnectionProviderContributions);
let sqlProvider = registry.getProperties(Constants.mssqlProviderName);
// We can't connect to object explorer until the MSSQL connection provider is registered
if (sqlProvider) {
this.processCommandLine().catch(reason => { warn('processCommandLine failed: ' + reason); });
} else {
registry.onNewProvider(e => {
if (e.id === Constants.mssqlProviderName) {
this.processCommandLine().catch(reason => { warn('processCommandLine failed: ' + reason); });
}
});
}
}
public _serviceBrand: any;
// We base our logic on the combination of (server, command) values.
// (serverName, commandName) => Connect object explorer and execute the command, passing the connection profile to the command. Do not load query editor.
// (null, commandName) => Launch the command with a null connection. If the command implementation needs a connection, it will need to create it.
// (serverName, null) => Connect object explorer and open a new query editor
// (null, null) => Prompt for a connection unless there are registered servers
public processCommandLine(): Promise<void> {
let self = this;
return new Promise<void>((resolve, reject) => {
let showConnectDialogOnStartup: boolean = this._configurationService.getValue('workbench.showConnectDialogOnStartup');
if (showConnectDialogOnStartup && !self._commandName && !self._connectionProfile && !self._connectionManagementService.hasRegisteredServers()) {
// prompt the user for a new connection on startup if no profiles are registered
self._connectionManagementService.showConnectionDialog()
.then(() => {
resolve();
},
error => {
reject(error);
});
} else if (self._connectionProfile) {
if (!self._commandName) {
self._connectionManagementService.connectIfNotConnected(self._connectionProfile, 'connection', true)
.then(() => {
TaskUtilities.newQuery(self._connectionProfile,
self._connectionManagementService,
self._queryEditorService,
self._objectExplorerService,
self._editorService)
.then(() => {
resolve();
}, error => {
// ignore query editor failing to open.
// the tests don't mock this out
warn('unable to open query editor ' + error);
resolve();
});
}, error => {
reject(error);
});
} else {
self._connectionManagementService.connectIfNotConnected(self._connectionProfile, 'connection', true)
.then(() => {
self._commandService.executeCommand(self._commandName, self._connectionProfile).then(() => resolve(), error => reject(error));
}, error => {
reject(error);
});
}
} else if (self._commandName) {
self._commandService.executeCommand(self._commandName).then(() => resolve(), error => reject(error));
}
else {
resolve();
}
});
}
}

View File

@@ -8,11 +8,11 @@
import { localize } from 'vs/nls';
import Severity from 'vs/base/common/severity';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IErrorMessageService } from 'sql/parts/connection/common/connectionManagement';
import { ErrorMessageDialog } from 'sql/workbench/errorMessageDialog/errorMessageDialog';
import { IAction } from 'vs/base/common/actions';
import { ErrorMessageDialog } from 'sql/workbench/services/errorMessage/browser/errorMessageDialog';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
export class ErrorMessageService implements IErrorMessageService {
_serviceBrand: any;

View File

@@ -0,0 +1,116 @@
/*---------------------------------------------------------------------------------------------
* 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);
}
}
});

View File

@@ -0,0 +1,106 @@
/*---------------------------------------------------------------------------------------------
* 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/platform/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;
}

View File

@@ -0,0 +1,448 @@
/*---------------------------------------------------------------------------------------------
* 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/workbench/services/notebook/common/notebookService';
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
import { standardRendererFactories } from 'sql/parts/notebook/outputs/factories';
import { LocalContentManager } from 'sql/workbench/services/notebook/node/localContentManager';
import { SessionManager } from 'sql/workbench/services/notebook/common/sessionManager';
import { Extensions, INotebookProviderRegistry, NotebookProviderRegistration } from 'sql/workbench/services/notebook/common/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/workbench/services/notebook/common/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;
}
}

View File

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

View File

@@ -0,0 +1,325 @@
/*---------------------------------------------------------------------------------------------
* 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/platform/query/common/queryRunner';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import Severity from 'vs/base/common/severity';
import * as Utils from 'sql/platform/connection/common/utils';
import { Deferred } from 'sql/base/common/promise';
import { Disposable } from 'vs/base/common/lifecycle';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
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) {
let cell = hrow.insertCell();
cell.innerHTML = column;
}
for (let row in data.rows) {
let hrow = <HTMLTableRowElement>table.insertRow();
for (let column in data.columns) {
let 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
}
}

View File

@@ -0,0 +1,366 @@
/*---------------------------------------------------------------------------------------------
* 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;
}
}

View File

@@ -0,0 +1,179 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import * as sqlops from 'sqlops';
import { INewProfilerState } from 'sql/parts/profiler/editor/profilerState';
const PROFILER_SERVICE_ID = 'profilerService';
export const IProfilerService = createDecorator<IProfilerService>(PROFILER_SERVICE_ID);
export type ProfilerSessionID = string;
export const PROFILER_VIEW_TEMPLATE_SETTINGS = 'profiler.viewTemplates';
export const PROFILER_SESSION_TEMPLATE_SETTINGS = 'profiler.sessionTemplates';
export const PROFILER_SETTINGS = 'profiler';
/**
* A front end provider for a profiler session
*/
export interface IProfilerSession {
/**
* Called by the service when more rows are available to render
*/
onMoreRows(events: sqlops.ProfilerSessionEvents);
/**
* Called by the service when the session is closed unexpectedly
*/
onSessionStopped(events: sqlops.ProfilerSessionStoppedParams);
/**
* Called by the service when a new profiler session is created by the dialog
*/
onProfilerSessionCreated(events: sqlops.ProfilerSessionCreatedParams);
/**
* Called by the service when the session state is changed
*/
onSessionStateChanged(newState: INewProfilerState);
}
/**
* A Profiler Service that handles session communication between the backends and frontends
*/
export interface IProfilerService {
_serviceBrand: any;
/**
* Registers a backend provider for profiler session. ex: mssql
*/
registerProvider(providerId: string, provider: sqlops.ProfilerProvider): void;
/**
* Registers a session with the service that acts as the UI for a profiler session
* @returns An unique id that should be used to make subsequent calls to this service
*/
registerSession(uri: string, connectionProfile: IConnectionProfile, session: IProfilerSession): Promise<ProfilerSessionID>;
/**
* Connects the session specified by the id
*/
connectSession(sessionId: ProfilerSessionID): Thenable<boolean>;
/**
* Disconnected the session specified by the id
*/
disconnectSession(sessionId: ProfilerSessionID): Thenable<boolean>;
/**
* Creates a new session using the given create statement and session name
*/
createSession(id: string, createStatement: string, template: sqlops.ProfilerSessionTemplate): Thenable<boolean>;
/**
* Starts the session specified by the id
*/
startSession(sessionId: ProfilerSessionID, sessionName: string): Thenable<boolean>;
/**
* Pauses the session specified by the id
*/
pauseSession(sessionId: ProfilerSessionID): Thenable<boolean>;
/**
* Stops the session specified by the id
*/
stopSession(sessionId: ProfilerSessionID): Thenable<boolean>;
/**
* Gets a list of running XEvent sessions on the Profiler Session's target
*/
getXEventSessions(sessionId: ProfilerSessionID): Thenable<string[]>;
/**
* The method called by the service provider for when more rows are available to render
*/
onMoreRows(params: sqlops.ProfilerSessionEvents): void;
/**
* The method called by the service provider for when more rows are available to render
*/
onSessionStopped(params: sqlops.ProfilerSessionStoppedParams): void;
/**
* Called by the service when a new profiler session is created by the dialog
*/
onProfilerSessionCreated(events: sqlops.ProfilerSessionCreatedParams);
/**
* Gets a list of the view templates that are specified in the settings
* @param provider An optional string to limit the view templates to a specific provider
* @returns An array of view templates that match the provider passed, if passed, and generic ones (no provider specified),
* otherwise returns all view templates
*/
getViewTemplates(providerId?: string): Array<IProfilerViewTemplate>;
/**
* Gets a list of the session templates that are specified in the settings
* @param provider An optional string to limit the session template to a specific
* @returns An array of session templates that match the provider passed, if passed, and generic ones (no provider specified),
* otherwise returns all session templates
*/
getSessionTemplates(providerId?: string): Array<IProfilerSessionTemplate>;
/**
* Gets the session view state
* @param sessionId The session ID to get the view state for
* @returns Sessions view state
*/
getSessionViewState(sessionId: string): any;
/**
* Launches the dialog for editing the view columns of a profiler session template for the given input
* @param input input object that contains the necessary information which will be modified based on used input
*/
launchColumnEditor(input: ProfilerInput): Thenable<void>;
/**
* Launches the dialog for creating a new XEvent session from a template
* @param input input object that contains the necessary information which will be modified based on used input
*/
launchCreateSessionDialog(input: ProfilerInput): Thenable<void>;
/**
* Launches the dialog for collecting the filter object
* @param input input object
*/
launchFilterSessionDialog(input: ProfilerInput): void;
}
export interface IProfilerSettings {
viewTemplates: Array<IProfilerViewTemplate>;
sessionTemplates: Array<IProfilerSessionTemplate>;
}
export interface IColumnViewTemplate {
name: string;
eventsMapped: Array<string>;
}
export interface IProfilerViewTemplate {
name: string;
columns: Array<IColumnViewTemplate>;
}
export interface IProfilerSessionTemplate {
name: string;
defaultView: string;
createStatement: string;
}
export interface ProfilerFilter {
clauses: ProfilerFilterClause[];
}
export interface ProfilerFilterClause {
field: string;
operator: ProfilerFilterClauseOperator;
value: string;
}
export enum ProfilerFilterClauseOperator {
Equals,
NotEquals,
LessThan,
LessThanOrEquals,
GreaterThan,
GreaterThanOrEquals,
IsNull,
IsNotNull,
Contains,
NotContains,
StartsWith,
NotStartsWith
}

View File

@@ -0,0 +1,242 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IConnectionManagementService, IConnectionCompletionOptions, ConnectionType, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
import {
ProfilerSessionID, IProfilerSession, IProfilerService, IProfilerViewTemplate, IProfilerSessionTemplate,
PROFILER_SETTINGS, IProfilerSettings
} from './interfaces';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
import { ProfilerColumnEditorDialog } from 'sql/parts/profiler/dialog/profilerColumnEditorDialog';
import * as sqlops from 'sqlops';
import { TPromise } from 'vs/base/common/winjs.base';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Scope as MementoScope, Memento } from 'vs/workbench/common/memento';
import { ProfilerFilterDialog } from 'sql/parts/profiler/dialog/profilerFilterDialog';
class TwoWayMap<T, K> {
private forwardMap: Map<T, K>;
private reverseMap: Map<K, T>;
constructor() {
this.forwardMap = new Map<T, K>();
this.reverseMap = new Map<K, T>();
}
get(input: T): K {
return this.forwardMap.get(input);
}
reverseGet(input: K): T {
return this.reverseMap.get(input);
}
set(input: T, input2: K): TwoWayMap<T, K> {
this.forwardMap.set(input, input2);
this.reverseMap.set(input2, input);
return this;
}
}
export class ProfilerService implements IProfilerService {
private static readonly PROFILER_SERVICE_UI_STATE_STORAGE_KEY = 'profileservice.uiState';
public _serviceBrand: any;
private _providers = new Map<string, sqlops.ProfilerProvider>();
private _idMap = new TwoWayMap<ProfilerSessionID, string>();
private _sessionMap = new Map<ProfilerSessionID, IProfilerSession>();
private _connectionMap = new Map<ProfilerSessionID, IConnectionProfile>();
private _editColumnDialog: ProfilerColumnEditorDialog;
private _memento: any;
private _context: Memento;
constructor(
@IConnectionManagementService private _connectionService: IConnectionManagementService,
@IConfigurationService public _configurationService: IConfigurationService,
@IInstantiationService private _instantiationService: IInstantiationService,
@INotificationService private _notificationService: INotificationService,
@ICommandService private _commandService: ICommandService,
@IStorageService private _storageService: IStorageService
) {
this._context = new Memento('ProfilerEditor');
this._memento = this._context.getMemento(this._storageService, MementoScope.GLOBAL);
}
public registerProvider(providerId: string, provider: sqlops.ProfilerProvider): void {
this._providers.set(providerId, provider);
}
public async registerSession(uri: string, connectionProfile: IConnectionProfile, session: IProfilerSession): Promise<ProfilerSessionID> {
let options: IConnectionCompletionOptions = {
params: { connectionType: ConnectionType.default, runQueryOnCompletion: RunQueryOnConnectionMode.none, input: undefined },
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: false,
showFirewallRuleOnError: true
};
try {
await this._connectionService.connect(connectionProfile, uri, options);
} catch (connectionError) {
}
this._sessionMap.set(uri, session);
this._connectionMap.set(uri, connectionProfile);
this._idMap.set(uri, uri);
return TPromise.wrap(uri);
}
public onMoreRows(params: sqlops.ProfilerSessionEvents): void {
this._sessionMap.get(this._idMap.reverseGet(params.sessionId)).onMoreRows(params);
}
public onSessionStopped(params: sqlops.ProfilerSessionStoppedParams): void {
this._sessionMap.get(this._idMap.reverseGet(params.ownerUri)).onSessionStopped(params);
}
public onProfilerSessionCreated(params: sqlops.ProfilerSessionCreatedParams): void {
this._sessionMap.get(this._idMap.reverseGet(params.ownerUri)).onProfilerSessionCreated(params);
this.updateMemento(params.ownerUri, { previousSessionName: params.sessionName });
}
public connectSession(id: ProfilerSessionID): Thenable<boolean> {
return this._runAction(id, provider => provider.connectSession(this._idMap.get(id)));
}
public disconnectSession(id: ProfilerSessionID): Thenable<boolean> {
return this._runAction(id, provider => provider.disconnectSession(this._idMap.get(id)));
}
public createSession(id: string, createStatement: string, template: sqlops.ProfilerSessionTemplate): Thenable<boolean> {
return this._runAction(id, provider => provider.createSession(this._idMap.get(id), createStatement, template)).then(() => {
this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isRunning: true, isStopped: false, isPaused: false });
return true;
}, (reason) => {
this._notificationService.error(reason.message);
});
}
public startSession(id: ProfilerSessionID, sessionName: string): Thenable<boolean> {
this.updateMemento(id, { previousSessionName: sessionName });
return this._runAction(id, provider => provider.startSession(this._idMap.get(id), sessionName)).then(() => {
this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isRunning: true, isStopped: false, isPaused: false });
return true;
}, (reason) => {
this._notificationService.error(reason.message);
});
}
public pauseSession(id: ProfilerSessionID): Thenable<boolean> {
return this._runAction(id, provider => provider.pauseSession(this._idMap.get(id)));
}
public stopSession(id: ProfilerSessionID): Thenable<boolean> {
return this._runAction(id, provider => provider.stopSession(this._idMap.get(id))).then(() => {
this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isStopped: true, isPaused: false, isRunning: false });
return true;
}, (reason) => {
// The error won't be actionable to the user, so only log it to console.
// In case of error, the state of the UI is not usable, makes more sense to
// set it to stopped so that user can restart it or pick a different session
this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isStopped: true, isPaused: false, isRunning: false });
});
}
public getXEventSessions(id: ProfilerSessionID): Thenable<string[]> {
return this._runAction(id, provider => provider.getXEventSessions(this._idMap.get(id))).then((r) => {
return r;
}, (reason) => {
this._notificationService.error(reason.message);
});
}
private _runAction<T>(id: ProfilerSessionID, action: (handler: sqlops.ProfilerProvider) => Thenable<T>): Thenable<T> {
// let providerId = this._connectionService.getProviderIdFromUri(this._idMap.get(id));
let providerId = 'MSSQL';
if (!providerId) {
return TPromise.wrapError(new Error('Connection is required in order to interact with queries'));
}
let handler = this._providers.get(providerId);
if (handler) {
return action(handler);
} else {
return TPromise.wrapError(new Error('No Handler Registered'));
}
}
public getViewTemplates(provider?: string): Array<IProfilerViewTemplate> {
let config = <IProfilerSettings>this._configurationService.getValue(PROFILER_SETTINGS);
if (provider) {
return config.viewTemplates;
} else {
return config.viewTemplates;
}
}
public getSessionTemplates(provider?: string): Array<IProfilerSessionTemplate> {
let config = <IProfilerSettings>this._configurationService.getValue(PROFILER_SETTINGS);
if (provider) {
return config.sessionTemplates;
} else {
return config.sessionTemplates;
}
}
public getSessionViewState(ownerUri: string): any {
let mementoKey = this.getMementoKey(ownerUri);
let uiStateMap = this._memento[ProfilerService.PROFILER_SERVICE_UI_STATE_STORAGE_KEY];
if (uiStateMap && mementoKey) {
return uiStateMap[mementoKey];
}
return undefined;
}
private getMementoKey(ownerUri: string): string {
let mementoKey = undefined;
let connectionProfile: IConnectionProfile = this._connectionMap.get(ownerUri);
if (connectionProfile) {
mementoKey = connectionProfile.serverName;
}
return mementoKey;
}
private updateMemento(ownerUri: string, uiState: any) {
// update persisted session state
let mementoKey = this.getMementoKey(ownerUri);
let uiStateMap = this._memento[ProfilerService.PROFILER_SERVICE_UI_STATE_STORAGE_KEY];
if (uiStateMap && mementoKey) {
uiStateMap[mementoKey] = uiState;
this._memento[ProfilerService.PROFILER_SERVICE_UI_STATE_STORAGE_KEY] = uiStateMap;
this._context.saveMemento();
}
}
public launchColumnEditor(input?: ProfilerInput): Thenable<void> {
if (!this._editColumnDialog) {
this._editColumnDialog = this._instantiationService.createInstance(ProfilerColumnEditorDialog);
this._editColumnDialog.render();
}
this._editColumnDialog.open(input);
return TPromise.as(null);
}
public launchCreateSessionDialog(input?: ProfilerInput): Thenable<void> {
return this._commandService.executeCommand('profiler.openCreateSessionDialog', input.id, input.providerType, this.getSessionTemplates());
}
public launchFilterSessionDialog(input: ProfilerInput): void {
let dialog = this._instantiationService.createInstance(ProfilerFilterDialog);
dialog.open(input);
}
}

View File

@@ -0,0 +1,106 @@
/*---------------------------------------------------------------------------------------------
* 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 { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { IResourceProviderService, IHandleFirewallRuleResult } from 'sql/workbench/services/resourceProvider/common/resourceProviderService';
import * as Constants from 'sql/common/constants';
import * as TelemetryKeys from 'sql/common/telemetryKeys';
import * as TelemetryUtils from 'sql/common/telemetryUtilities';
import { FirewallRuleDialogController } from 'sql/parts/accountManagement/firewallRuleDialog/firewallRuleDialogController';
import * as sqlops from 'sqlops';
export class ResourceProviderService implements IResourceProviderService {
public _serviceBrand: any;
private _providers: { [handle: string]: sqlops.ResourceProvider; } = Object.create(null);
private _firewallRuleDialogController: FirewallRuleDialogController;
constructor(
@ITelemetryService private _telemetryService: ITelemetryService,
@IInstantiationService private _instantiationService: IInstantiationService,
) {
}
/**
* Opens the firewall rule dialog
*/
public showFirewallRuleDialog(connection: IConnectionProfile, ipAddress: string, resourceProviderId: string): Promise<boolean> {
let self = this;
// If the firewall rule dialog hasn't been defined, create a new one
if (!self._firewallRuleDialogController) {
self._firewallRuleDialogController = self._instantiationService.createInstance(FirewallRuleDialogController);
}
return self._firewallRuleDialogController.openFirewallRuleDialog(connection, ipAddress, resourceProviderId);
}
/**
* Create a firewall rule
*/
public createFirewallRule(selectedAccount: sqlops.Account, firewallruleInfo: sqlops.FirewallRuleInfo, resourceProviderId: string): Promise<sqlops.CreateFirewallRuleResponse> {
return new Promise<sqlops.CreateFirewallRuleResponse>((resolve, reject) => {
let provider = this._providers[resourceProviderId];
if (provider) {
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.FirewallRuleRequested, { provider: resourceProviderId });
provider.createFirewallRule(selectedAccount, firewallruleInfo).then(result => {
resolve(result);
}, error => {
reject(error);
});
} else {
reject(Constants.InvalidProvider);
}
});
}
/**
* Handle a firewall rule
*/
public handleFirewallRule(errorCode: number, errorMessage: string, connectionTypeId: string): Promise<IHandleFirewallRuleResult> {
let self = this;
return new Promise<IHandleFirewallRuleResult>((resolve, reject) => {
let handleFirewallRuleResult: IHandleFirewallRuleResult;
let promises = [];
if (self._providers) {
for (let key in self._providers) {
let provider = self._providers[key];
promises.push(provider.handleFirewallRule(errorCode, errorMessage, connectionTypeId)
.then(response => {
if (response.result) {
handleFirewallRuleResult = { canHandleFirewallRule: response.result, ipAddress: response.ipAddress, resourceProviderId: key };
}
},
() => { /* Swallow failures at getting accounts, we'll just hide that provider */
}));
}
}
Promise.all(promises).then(() => {
if (handleFirewallRuleResult) {
resolve(handleFirewallRuleResult);
} else {
handleFirewallRuleResult = { canHandleFirewallRule: false, ipAddress: undefined, resourceProviderId: undefined };
resolve(handleFirewallRuleResult);
}
});
});
}
/**
* Register a resource provider
*/
public registerProvider(providerId: string, provider: sqlops.ResourceProvider): void {
this._providers[providerId] = provider;
}
public unregisterProvider(providerId: string): void {
delete this._providers[providerId];
}
}

View File

@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* 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 * as sqlops from 'sqlops';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
export const SERVICE_ID = 'resourceProviderService';
export const IResourceProviderService = createDecorator<IResourceProviderService>(SERVICE_ID);
export interface IHandleFirewallRuleResult {
canHandleFirewallRule: boolean;
ipAddress: string;
resourceProviderId: string;
}
export interface IResourceProviderService {
_serviceBrand: any;
/**
* Register a resource provider
*/
registerProvider(providerId: string, provider: sqlops.ResourceProvider): void;
/**
* Unregister a resource provider
*/
unregisterProvider(ProviderId: string): void;
/**
* Create a firewall rule
*/
createFirewallRule(selectedAccount: sqlops.Account, firewallruleInfo: sqlops.FirewallRuleInfo, resourceProviderId: string): Promise<sqlops.CreateFirewallRuleResponse>;
/**
* handle a firewall rule
*/
handleFirewallRule(errorCode: number, errorMessage: string, connectionTypeId: string): Promise<IHandleFirewallRuleResult>;
/**
* Show firewall rule dialog
*/
showFirewallRuleDialog(connection: IConnectionProfile, ipAddress: string, resourceProviderId: string): Promise<boolean>;
}

View File

@@ -0,0 +1,112 @@
/*---------------------------------------------------------------------------------------------
* 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 { TPromise } from 'vs/base/common/winjs.base';
import Severity from 'vs/base/common/severity';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { SERVER_GROUP_CONFIG, SERVER_GROUP_COLORS_CONFIG } from 'sql/parts/objectExplorer/serverGroupDialog/serverGroup.contribution';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
import { IServerGroupController, IServerGroupDialogCallbacks } from 'sql/platform/serverGroup/common/serverGroupController';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { ServerGroupDialog } from 'sql/parts/objectExplorer/serverGroupDialog/serverGroupDialog';
import { ServerGroupViewModel } from 'sql/parts/objectExplorer/serverGroupDialog/serverGroupViewModel';
import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
export class ServerGroupController implements IServerGroupController {
_serviceBrand: any;
private _serverGroupDialog: ServerGroupDialog;
private _connectionManagementService: IConnectionManagementService;
private _callbacks: IServerGroupDialogCallbacks;
private _group: ConnectionProfileGroup;
private _viewModel: ServerGroupViewModel;
constructor(
@IErrorMessageService private _errorMessageService: IErrorMessageService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IConfigurationService private _configurationService: IConfigurationService
) {
}
private handleOnAddServerGroup(): void {
if (this._group) {
let tempGroup: ConnectionProfileGroup = this.copyConnectionProfileGroup(this._group);
this._group.name = this._viewModel.groupName;
this._group.color = this._viewModel.groupColor;
this._group.description = this._viewModel.groupDescription;
this._connectionManagementService.editGroup(this._group).then(() => {
this._serverGroupDialog.close();
}).catch(err => {
// rollback changes made
this._group = tempGroup;
this._errorMessageService.showDialog(Severity.Error, '', err);
});
} else {
let newGroup: IConnectionProfileGroup = {
name: this._viewModel.groupName,
id: undefined,
parentId: undefined,
color: this._viewModel.groupColor,
description: this._viewModel.groupDescription
};
this._connectionManagementService.saveProfileGroup(newGroup).then(groupId => {
if (this._callbacks) {
this._callbacks.onAddGroup(this._serverGroupDialog.groupName);
}
this._serverGroupDialog.close();
}).catch(err => {
this._errorMessageService.showDialog(Severity.Error, '', err);
});
}
}
private copyConnectionProfileGroup(group: ConnectionProfileGroup): ConnectionProfileGroup {
return new ConnectionProfileGroup(group.name, group.parent, group.id, group.color, group.description);
}
private handleOnClose(): void {
if (this._callbacks) {
this._callbacks.onClose();
}
}
public showCreateGroupDialog(connectionManagementService: IConnectionManagementService, callbacks?: IServerGroupDialogCallbacks): TPromise<void> {
this._connectionManagementService = connectionManagementService;
this._group = null;
this._viewModel = new ServerGroupViewModel(undefined, this._configurationService.getValue(SERVER_GROUP_CONFIG)[SERVER_GROUP_COLORS_CONFIG]);
this._callbacks = callbacks ? callbacks : undefined;
return this.openServerGroupDialog();
}
public showEditGroupDialog(connectionManagementService: IConnectionManagementService, group: ConnectionProfileGroup): TPromise<void> {
this._connectionManagementService = connectionManagementService;
this._group = group;
this._viewModel = new ServerGroupViewModel(group, this._configurationService.getValue(SERVER_GROUP_CONFIG)[SERVER_GROUP_COLORS_CONFIG]);
return this.openServerGroupDialog();
}
private openServerGroupDialog(): TPromise<void> {
if (!this._serverGroupDialog) {
this._serverGroupDialog = this._instantiationService.createInstance(ServerGroupDialog);
this._serverGroupDialog.viewModel = this._viewModel;
this._serverGroupDialog.onCancel(() => { });
this._serverGroupDialog.onAddServerGroup(() => this.handleOnAddServerGroup());
this._serverGroupDialog.onCloseEvent(() => this.handleOnClose());
this._serverGroupDialog.render();
} else {
// reset the view model in the view
this._serverGroupDialog.viewModel = this._viewModel;
}
return new TPromise<void>(() => {
this._serverGroupDialog.open();
});
}
}

View File

@@ -10,15 +10,10 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
import pkg from 'vs/platform/node/package';
import product from 'vs/platform/node/product';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import URI from 'vs/base/common/uri';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { AbstractShowReleaseNotesAction } from 'vs/workbench/parts/update/electron-browser/update';
import { INotification, INotificationService, INotificationActions } from 'vs/platform/notification/common/notification';
import Severity from 'vs/base/common/severity';
export class OpenGettingStartedInBrowserAction extends Action {