mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-19 17:22:48 -05:00
Initial Code Layering (#3788)
* working on formatting * fixed basic lint errors; starting moving things to their appropriate location * formatting * update tslint to match the version of vscode we have * remove unused code * work in progress fixing layering * formatting * moved connection management service to platform * formatting * add missing file * moving more servies * formatting * moving more services * formatting * wip * moving more services * formatting * revert back tslint rules * move css file * add missing svgs
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
129
src/sql/workbench/parts/backup/browser/backupUiService.ts
Normal file
129
src/sql/workbench/parts/backup/browser/backupUiService.ts
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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;
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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[];
|
||||
}
|
||||
129
src/sql/workbench/services/admin/common/adminService.ts
Normal file
129
src/sql/workbench/services/admin/common/adminService.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
17
src/sql/workbench/services/commandLine/common/commandLine.ts
Normal file
17
src/sql/workbench/services/commandLine/common/commandLine.ts
Normal 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');
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
116
src/sql/workbench/services/notebook/common/notebookRegistry.ts
Normal file
116
src/sql/workbench/services/notebook/common/notebookRegistry.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
106
src/sql/workbench/services/notebook/common/notebookService.ts
Normal file
106
src/sql/workbench/services/notebook/common/notebookService.ts
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
216
src/sql/workbench/services/notebook/common/sessionManager.ts
Normal file
216
src/sql/workbench/services/notebook/common/sessionManager.ts
Normal 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
|
||||
}
|
||||
}
|
||||
325
src/sql/workbench/services/notebook/common/sqlSessionManager.ts
Normal file
325
src/sql/workbench/services/notebook/common/sqlSessionManager.ts
Normal 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
|
||||
}
|
||||
}
|
||||
366
src/sql/workbench/services/notebook/node/localContentManager.ts
Normal file
366
src/sql/workbench/services/notebook/node/localContentManager.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
179
src/sql/workbench/services/profiler/common/interfaces.ts
Normal file
179
src/sql/workbench/services/profiler/common/interfaces.ts
Normal 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
|
||||
}
|
||||
242
src/sql/workbench/services/profiler/common/profilerService.ts
Normal file
242
src/sql/workbench/services/profiler/common/profilerService.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user