Rename registeredServers to objectExplorer (#1093)

This commit is contained in:
Karl Burtram
2018-04-06 13:33:49 -07:00
committed by GitHub
parent 071b510fba
commit 3990719054
60 changed files with 95 additions and 95 deletions

View File

@@ -0,0 +1,97 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export class NodeType {
public static Folder = 'Folder';
public static Root = 'root';
public static Database = 'Database';
public static Server = 'Server';
public static ScalarValuedFunction = 'ScalarValuedFunction';
public static TableValuedFunction = 'TableValuedFunction';
public static AggregateFunction = 'AggregateFunction';
public static FileGroup = 'FileGroup';
public static StoredProcedure = 'StoredProcedure';
public static UserDefinedTableType = 'UserDefinedTableType';
public static View = 'View';
public static Table = 'Table';
public static HistoryTable = 'HistoryTable';
public static ServerLevelLinkedServerLogin = 'ServerLevelLinkedServerLogin';
public static ServerLevelServerAudit = 'ServerLevelServerAudit';
public static ServerLevelCryptographicProvider = 'ServerLevelCryptographicProvider';
public static ServerLevelCredential = 'ServerLevelCredential';
public static ServerLevelServerRole = 'ServerLevelServerRole';
public static ServerLevelLogin = 'ServerLevelLogin';
public static ServerLevelServerAuditSpecification = 'ServerLevelServerAuditSpecification';
public static ServerLevelServerTrigger = 'ServerLevelServerTrigger';
public static ServerLevelLinkedServer = 'ServerLevelLinkedServer';
public static ServerLevelEndpoint = 'ServerLevelEndpoint';
public static Synonym = 'Synonym';
public static DatabaseTrigger = 'DatabaseTrigger';
public static Assembly = 'Assembly';
public static MessageType = 'MessageType';
public static Contract = 'Contract';
public static Queue = 'Queue';
public static Service = 'Service';
public static Route = 'Route';
public static DatabaseAndQueueEventNotification = 'DatabaseAndQueueEventNotification';
public static RemoteServiceBinding = 'RemoteServiceBinding';
public static BrokerPriority = 'BrokerPriority';
public static FullTextCatalog = 'FullTextCatalog';
public static FullTextStopList = 'FullTextStopList';
public static SqlLogFile = 'SqlLogFile';
public static PartitionFunction = 'PartitionFunction';
public static PartitionScheme = 'PartitionScheme';
public static SearchPropertyList = 'SearchPropertyList';
public static User = 'User';
public static Schema = 'Schema';
public static AsymmetricKey = 'AsymmetricKey';
public static Certificate = 'Certificate';
public static SymmetricKey = 'SymmetricKey';
public static DatabaseEncryptionKey = 'DatabaseEncryptionKey';
public static MasterKey = 'MasterKey';
public static DatabaseAuditSpecification = 'DatabaseAuditSpecification';
public static Column = 'Column';
public static Key = 'Key';
public static Constraint = 'Constraint';
public static Trigger = 'Trigger';
public static Index = 'Index';
public static Statistic = 'Statistic';
public static UserDefinedDataType = 'UserDefinedDataType';
public static UserDefinedType = 'UserDefinedType';
public static XmlSchemaCollection = 'XmlSchemaCollection';
public static SystemExactNumeric = 'SystemExactNumeric';
public static SystemApproximateNumeric = 'SystemApproximateNumeric';
public static SystemDateAndTime = 'SystemDateAndTime';
public static SystemCharacterString = 'SystemCharacterString';
public static SystemUnicodeCharacterString = 'SystemUnicodeCharacterString';
public static SystemBinaryString = 'SystemBinaryString';
public static SystemOtherDataType = 'SystemOtherDataType';
public static SystemClrDataType = 'SystemClrDataType';
public static SystemSpatialDataType = 'SystemSpatialDataType';
public static UserDefinedTableTypeColumn = 'UserDefinedTableTypeColumn';
public static UserDefinedTableTypeKey = 'UserDefinedTableTypeKey';
public static UserDefinedTableTypeConstraint = 'UserDefinedTableTypeConstraint';
public static StoredProcedureParameter = 'StoredProcedureParameter';
public static TableValuedFunctionParameter = 'TableValuedFunctionParameter';
public static ScalarValuedFunctionParameter = 'ScalarValuedFunctionParameter';
public static AggregateFunctionParameter = 'AggregateFunctionParameter';
public static DatabaseRole = 'DatabaseRole';
public static ApplicationRole = 'ApplicationRole';
public static FileGroupFile = 'FileGroupFile';
public static SystemMessageType = 'SystemMessageType';
public static SystemContract = 'SystemContract';
public static SystemService = 'SystemService';
public static SystemQueue = 'SystemQueue';
public static Sequence = 'Sequence';
public static SecurityPolicy = 'SecurityPolicy';
public static DatabaseScopedCredential = 'DatabaseScopedCredential';
public static ExternalResource = 'ExternalResource';
public static ExternalDataSource = 'ExternalDataSource';
public static ExternalFileFormat = 'ExternalFileFormat';
public static ExternalTable = 'ExternalTable';
public static ColumnMasterKey = 'ColumnMasterKey';
public static ColumnEncryptionKey = 'ColumnEncryptionKey';
}

View File

@@ -0,0 +1,561 @@
/*---------------------------------------------------------------------------------------------
* 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 { NodeType } from 'sql/parts/objectExplorer/common/nodeType';
import { TreeNode, TreeItemCollapsibleState, ObjectExplorerCallbacks } from 'sql/parts/objectExplorer/common/treeNode';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import Event, { Emitter } from 'vs/base/common/event';
import * as sqlops from 'sqlops';
import * as nls from 'vs/nls';
import * as TelemetryKeys from 'sql/common/telemetryKeys';
import * as TelemetryUtils from 'sql/common/telemetryUtilities';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { warn, error } from 'sql/base/common/log';
import { ServerTreeView } from 'sql/parts/objectExplorer/viewlet/serverTreeView';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import * as vscode from 'vscode';
export const SERVICE_ID = 'ObjectExplorerService';
export const IObjectExplorerService = createDecorator<IObjectExplorerService>(SERVICE_ID);
export interface IObjectExplorerService {
_serviceBrand: any;
createNewSession(providerId: string, connection: ConnectionProfile): Thenable<sqlops.ObjectExplorerSessionResponse>;
closeSession(providerId: string, session: sqlops.ObjectExplorerSession): Thenable<sqlops.ObjectExplorerCloseSessionResponse>;
expandNode(providerId: string, session: sqlops.ObjectExplorerSession, nodePath: string): Thenable<sqlops.ObjectExplorerExpandInfo>;
refreshNode(providerId: string, session: sqlops.ObjectExplorerSession, nodePath: string): Thenable<sqlops.ObjectExplorerExpandInfo>;
resolveTreeNodeChildren(session: sqlops.ObjectExplorerSession, parentTree: TreeNode): Thenable<TreeNode[]>;
refreshTreeNode(session: sqlops.ObjectExplorerSession, parentTree: TreeNode): Thenable<TreeNode[]>;
onSessionCreated(handle: number, sessionResponse: sqlops.ObjectExplorerSession);
onNodeExpanded(handle: number, sessionResponse: sqlops.ObjectExplorerExpandInfo);
/**
* Register a ObjectExplorer provider
*/
registerProvider(providerId: string, provider: sqlops.ObjectExplorerProvider): void;
getObjectExplorerNode(connection: IConnectionProfile): TreeNode;
updateObjectExplorerNodes(connectionProfile: IConnectionProfile): Promise<void>;
deleteObjectExplorerNode(connection: IConnectionProfile): void;
onUpdateObjectExplorerNodes: Event<ObjectExplorerNodeEventArgs>;
registerServerTreeView(view: ServerTreeView): void;
getSelectedProfileAndDatabase(): { profile: ConnectionProfile, databaseName: string };
isFocused(): boolean;
onSelectionOrFocusChange: Event<void>;
getServerTreeView(): ServerTreeView;
findNodes(connectionId: string, type: string, schema: string, name: string, database: string, parentObjectNames?: string[]): Thenable<sqlops.NodeInfo[]>;
getActiveConnectionNodes(): TreeNode[];
getTreeNode(connectionId: string, nodePath: string): Thenable<TreeNode>;
}
interface SessionStatus {
nodes: { [nodePath: string]: NodeStatus };
connection: ConnectionProfile;
}
interface NodeStatus {
expandEmitter: Emitter<sqlops.ObjectExplorerExpandInfo>;
}
export interface ObjectExplorerNodeEventArgs {
connection: IConnectionProfile;
errorMessage: string;
}
export interface NodeInfoWithConnection {
connectionId: string;
nodeInfo: sqlops.NodeInfo;
}
export class ObjectExplorerService implements IObjectExplorerService {
public _serviceBrand: any;
private _disposables: IDisposable[] = [];
private _providers: { [handle: string]: sqlops.ObjectExplorerProvider; } = Object.create(null);
private _activeObjectExplorerNodes: { [id: string]: TreeNode };
private _sessions: { [sessionId: string]: SessionStatus };
private _onUpdateObjectExplorerNodes: Emitter<ObjectExplorerNodeEventArgs>;
private _serverTreeView: ServerTreeView;
private _onSelectionOrFocusChange: Emitter<void>;
constructor(
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@ITelemetryService private _telemetryService: ITelemetryService,
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService
) {
this._onUpdateObjectExplorerNodes = new Emitter<ObjectExplorerNodeEventArgs>();
this._activeObjectExplorerNodes = {};
this._sessions = {};
this._providers = {};
this._onSelectionOrFocusChange = new Emitter<void>();
}
public get onUpdateObjectExplorerNodes(): Event<ObjectExplorerNodeEventArgs> {
return this._onUpdateObjectExplorerNodes.event;
}
/**
* Event fired when the selection or focus of Object Explorer changes
*/
public get onSelectionOrFocusChange(): Event<void> {
return this._onSelectionOrFocusChange.event;
}
public updateObjectExplorerNodes(connection: IConnectionProfile): Promise<void> {
return this._connectionManagementService.addSavedPassword(connection).then(withPassword => {
let connectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, withPassword);
return this.updateNewObjectExplorerNode(connectionProfile);
});
}
public deleteObjectExplorerNode(connection: IConnectionProfile): void {
let self = this;
var connectionUri = connection.id;
var nodeTree = this._activeObjectExplorerNodes[connectionUri];
if (nodeTree) {
self.closeSession(connection.providerName, nodeTree.getSession()).then(() => {
delete self._activeObjectExplorerNodes[connectionUri];
delete self._sessions[nodeTree.getSession().sessionId];
});
}
}
/**
* Gets called when expanded node response is ready
*/
public onNodeExpanded(handle: number, expandResponse: sqlops.ObjectExplorerExpandInfo) {
if (expandResponse.errorMessage) {
error(expandResponse.errorMessage);
}
let nodeStatus = this._sessions[expandResponse.sessionId].nodes[expandResponse.nodePath];
if (nodeStatus && nodeStatus.expandEmitter) {
nodeStatus.expandEmitter.fire(expandResponse);
} else {
warn(`Cannot find node status for session: ${expandResponse.sessionId} and node path: ${expandResponse.nodePath}`);
}
}
/**
* Gets called when session is created
*/
public onSessionCreated(handle: number, session: sqlops.ObjectExplorerSession) {
let connection: ConnectionProfile = undefined;
let errorMessage: string = undefined;
if (this._sessions[session.sessionId]) {
connection = this._sessions[session.sessionId].connection;
if (session && session.success && session.rootNode) {
let server = this.toTreeNode(session.rootNode, null);
server.connection = connection;
server.session = session;
this._activeObjectExplorerNodes[connection.id] = server;
} else {
errorMessage = session && session.errorMessage ? session.errorMessage :
nls.localize('OeSessionFailedError', 'Failed to create Object Explorer session');
error(errorMessage);
}
} else {
warn(`cannot find session ${session.sessionId}`);
}
this.sendUpdateNodeEvent(connection, errorMessage);
}
private sendUpdateNodeEvent(connection: ConnectionProfile, errorMessage: string = undefined) {
let eventArgs: ObjectExplorerNodeEventArgs = {
connection: <IConnectionProfile>connection,
errorMessage: errorMessage
};
this._onUpdateObjectExplorerNodes.fire(eventArgs);
}
private updateNewObjectExplorerNode(connection: ConnectionProfile): Promise<void> {
let self = this;
return new Promise<void>((resolve, reject) => {
if (self._activeObjectExplorerNodes[connection.id]) {
this.sendUpdateNodeEvent(connection);
resolve();
} else {
// Create session will send the event or reject the promise
this.createNewSession(connection.providerName, connection).then(response => {
resolve();
}, error => {
this.sendUpdateNodeEvent(connection, error);
reject(error);
});
}
});
}
public getObjectExplorerNode(connection: IConnectionProfile): TreeNode {
return this._activeObjectExplorerNodes[connection.id];
}
public createNewSession(providerId: string, connection: ConnectionProfile): Thenable<sqlops.ObjectExplorerSessionResponse> {
let self = this;
return new Promise<sqlops.ObjectExplorerSessionResponse>((resolve, reject) => {
let provider = this._providers[providerId];
if (provider) {
provider.createNewSession(connection.toConnectionInfo()).then(result => {
self._sessions[result.sessionId] = {
connection: connection,
nodes: {}
};
resolve(result);
}, error => {
reject(error);
});
} else {
reject(`Provider doesn't exist. id: ${providerId}`);
}
});
}
public expandNode(providerId: string, session: sqlops.ObjectExplorerSession, nodePath: string): Thenable<sqlops.ObjectExplorerExpandInfo> {
return new Promise<sqlops.ObjectExplorerExpandInfo>((resolve, reject) => {
let provider = this._providers[providerId];
if (provider) {
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.ObjectExplorerExpand, { refresh: 0, provider: providerId });
this.expandOrRefreshNode(provider, session, nodePath).then(result => {
resolve(result);
}, error => {
reject(error);
});
} else {
reject(`Provider doesn't exist. id: ${providerId}`);
}
});
}
private callExpandOrRefreshFromProvider(provider: sqlops.ObjectExplorerProvider, nodeInfo: sqlops.ExpandNodeInfo, refresh: boolean = false) {
if (refresh) {
return provider.refreshNode(nodeInfo);
} else {
return provider.expandNode(nodeInfo);
}
}
private expandOrRefreshNode(
provider: sqlops.ObjectExplorerProvider,
session: sqlops.ObjectExplorerSession,
nodePath: string,
refresh: boolean = false): Thenable<sqlops.ObjectExplorerExpandInfo> {
let self = this;
return new Promise<sqlops.ObjectExplorerExpandInfo>((resolve, reject) => {
if (session.sessionId in self._sessions && self._sessions[session.sessionId]) {
let newRequest = false;
if (!self._sessions[session.sessionId].nodes[nodePath]) {
self._sessions[session.sessionId].nodes[nodePath] = {
expandEmitter: new Emitter<sqlops.ObjectExplorerExpandInfo>()
};
newRequest = true;
}
self._sessions[session.sessionId].nodes[nodePath].expandEmitter.event(((expandResult) => {
if (expandResult && !expandResult.errorMessage) {
resolve(expandResult);
}
else {
reject(expandResult ? expandResult.errorMessage : undefined);
}
if (newRequest) {
delete self._sessions[session.sessionId].nodes[nodePath];
}
}));
if (newRequest) {
self.callExpandOrRefreshFromProvider(provider, {
sessionId: session.sessionId,
nodePath: nodePath
}, refresh).then(result => {
}, error => {
reject(error);
});
}
} else {
reject(`session cannot find to expand node. id: ${session.sessionId} nodePath: ${nodePath}`);
}
});
}
public refreshNode(providerId: string, session: sqlops.ObjectExplorerSession, nodePath: string): Thenable<sqlops.ObjectExplorerExpandInfo> {
let provider = this._providers[providerId];
if (provider) {
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.ObjectExplorerExpand, { refresh: 1, provider: providerId });
return this.expandOrRefreshNode(provider, session, nodePath, true);
}
return Promise.resolve(undefined);
}
public closeSession(providerId: string, session: sqlops.ObjectExplorerSession): Thenable<sqlops.ObjectExplorerCloseSessionResponse> {
let provider = this._providers[providerId];
if (provider) {
return provider.closeSession({
sessionId: session ? session.sessionId : undefined
});
}
return Promise.resolve(undefined);
}
/**
* Register a ObjectExplorer provider
*/
public registerProvider(providerId: string, provider: sqlops.ObjectExplorerProvider): void {
this._providers[providerId] = provider;
}
public dispose(): void {
this._disposables = dispose(this._disposables);
}
public resolveTreeNodeChildren(session: sqlops.ObjectExplorerSession, parentTree: TreeNode): Thenable<TreeNode[]> {
return this.expandOrRefreshTreeNode(session, parentTree);
}
public refreshTreeNode(session: sqlops.ObjectExplorerSession, parentTree: TreeNode): Thenable<TreeNode[]> {
return this.expandOrRefreshTreeNode(session, parentTree, true);
}
private callExpandOrRefreshFromService(providerId: string, session: sqlops.ObjectExplorerSession, nodePath: string, refresh: boolean = false): Thenable<sqlops.ObjectExplorerExpandInfo> {
if (refresh) {
return this.refreshNode(providerId, session, nodePath);
} else {
return this.expandNode(providerId, session, nodePath);
}
}
private expandOrRefreshTreeNode(
session: sqlops.ObjectExplorerSession,
parentTree: TreeNode,
refresh: boolean = false): Thenable<TreeNode[]> {
return new Promise<TreeNode[]>((resolve, reject) => {
this.callExpandOrRefreshFromService(parentTree.getConnectionProfile().providerName, session, parentTree.nodePath, refresh).then(expandResult => {
let children: TreeNode[] = [];
if (expandResult && expandResult.nodes) {
children = expandResult.nodes.map(node => {
return this.toTreeNode(node, parentTree);
});
parentTree.children = children.filter(c => c !== undefined);
resolve(children);
} else {
reject(expandResult && expandResult.errorMessage ? expandResult.errorMessage : 'Failed to expand node');
}
}, error => {
reject(error);
});
});
}
private toTreeNode(nodeInfo: sqlops.NodeInfo, parent: TreeNode): TreeNode {
// Show the status for database nodes with a status field
let isLeaf: boolean = nodeInfo.isLeaf;
if (nodeInfo.nodeType === NodeType.Database) {
if (nodeInfo.nodeStatus) {
nodeInfo.label = nodeInfo.label + ' (' + nodeInfo.nodeStatus + ')';
}
if (isLeaf) {
// set to common status so we can have a single 'Unavailable' db icon
nodeInfo.nodeStatus = 'Unavailable';
} else {
nodeInfo.nodeStatus = undefined;
}
}
return new TreeNode(nodeInfo.nodeType, nodeInfo.label, isLeaf, nodeInfo.nodePath,
nodeInfo.nodeSubType, nodeInfo.nodeStatus, parent, nodeInfo.metadata, {
getChildren: treeNode => this.getChildren(treeNode),
isExpanded: treeNode => this.isExpanded(treeNode),
setNodeExpandedState: (treeNode, expandedState) => this.setNodeExpandedState(treeNode, expandedState),
setNodeSelected: (treeNode, selected, clearOtherSelections: boolean = undefined) => this.setNodeSelected(treeNode, selected, clearOtherSelections)
});
}
public registerServerTreeView(view: ServerTreeView): void {
if (this._serverTreeView) {
throw new Error('The object explorer server tree view is already registered');
}
this._serverTreeView = view;
this._serverTreeView.onSelectionOrFocusChange(() => this._onSelectionOrFocusChange.fire());
}
/**
* Returns the connection profile corresponding to the current Object Explorer selection,
* or undefined if there are multiple selections or no such connection
*/
public getSelectedProfileAndDatabase(): { profile: ConnectionProfile, databaseName: string } {
if (!this._serverTreeView) {
return undefined;
}
let selection = this._serverTreeView.getSelection();
if (selection.length === 1) {
let selectedNode = selection[0];
if (selectedNode instanceof ConnectionProfile) {
return { profile: selectedNode, databaseName: undefined };
} else if (selectedNode instanceof TreeNode) {
let profile = selectedNode.getConnectionProfile();
let database = selectedNode.getDatabaseName();
// If the database is unavailable, use the server connection
if (selectedNode.nodeTypeId === 'Database' && selectedNode.isAlwaysLeaf) {
database = undefined;
}
return { profile: profile, databaseName: database };
}
}
return undefined;
}
/**
* Returns a boolean indicating whether the Object Explorer tree has focus
*/
public isFocused(): boolean {
return this._serverTreeView.isFocused();
}
public getServerTreeView() {
return this._serverTreeView;
}
public findNodes(connectionId: string, type: string, schema: string, name: string, database: string, parentObjectNames?: string[]): Thenable<sqlops.NodeInfo[]> {
let rootNode = this._activeObjectExplorerNodes[connectionId];
if (!rootNode) {
return Promise.resolve([]);
}
let sessionId = rootNode.session.sessionId;
return this._providers[this._sessions[sessionId].connection.providerName].findNodes({
type: type,
name: name,
schema: schema,
database: database,
parentObjectNames: parentObjectNames,
sessionId: sessionId
}).then(response => {
return response.nodes;
});
}
public getActiveConnectionNodes(): TreeNode[] {
return Object.values(this._activeObjectExplorerNodes);
}
private async setNodeExpandedState(treeNode: TreeNode, expandedState: TreeItemCollapsibleState): Promise<void> {
treeNode = await this.getUpdatedTreeNode(treeNode);
let expandNode = this.getTreeItem(treeNode);
if (expandedState === TreeItemCollapsibleState.Expanded) {
await this._serverTreeView.reveal(expandNode);
}
return this._serverTreeView.setExpandedState(expandNode, expandedState);
}
private async setNodeSelected(treeNode: TreeNode, selected: boolean, clearOtherSelections: boolean = undefined): Promise<void> {
treeNode = await this.getUpdatedTreeNode(treeNode);
let selectNode = this.getTreeItem(treeNode);
if (selected) {
await this._serverTreeView.reveal(selectNode);
}
return this._serverTreeView.setSelected(selectNode, selected, clearOtherSelections);
}
private async getChildren(treeNode: TreeNode): Promise<TreeNode[]> {
treeNode = await this.getUpdatedTreeNode(treeNode);
if (treeNode.isAlwaysLeaf) {
return [];
}
if (!treeNode.children) {
await this.resolveTreeNodeChildren(treeNode.getSession(), treeNode);
}
return treeNode.children;
}
private async isExpanded(treeNode: TreeNode): Promise<boolean> {
treeNode = await this.getUpdatedTreeNode(treeNode);
do {
let expandNode = this.getTreeItem(treeNode);
if (!this._serverTreeView.isExpanded(expandNode)) {
return false;
}
treeNode = treeNode.parent;
} while (treeNode);
return true;
}
private getTreeItem(treeNode: TreeNode): TreeNode | ConnectionProfile {
let rootNode = this._activeObjectExplorerNodes[treeNode.getConnectionProfile().id];
if (treeNode === rootNode) {
return treeNode.connection;
}
return treeNode;
}
private getUpdatedTreeNode(treeNode: TreeNode): Promise<TreeNode> {
return this.getTreeNode(treeNode.getConnectionProfile().id, treeNode.nodePath).then(treeNode => {
if (!treeNode) {
throw new Error(nls.localize('treeNodeNoLongerExists', 'The given tree node no longer exists'));
}
return treeNode;
});
}
public async getTreeNode(connectionId: string, nodePath: string): Promise<TreeNode> {
let parentNode = this._activeObjectExplorerNodes[connectionId];
if (!parentNode) {
return undefined;
}
if (!nodePath) {
return parentNode;
}
let currentNode = parentNode;
while (currentNode.nodePath !== nodePath) {
let nextNode = undefined;
if (!currentNode.isAlwaysLeaf && !currentNode.children) {
await this.resolveTreeNodeChildren(currentNode.getSession(), currentNode);
}
if (currentNode.children) {
// Look at the next node in the path, which is the child object with the longest path where the desired path starts with the child path
let children = currentNode.children.filter(child => nodePath.startsWith(child.nodePath));
if (children.length > 0) {
nextNode = children.reduce((currentMax, candidate) => currentMax.nodePath.length < candidate.nodePath.length ? candidate : currentMax);
}
}
if (!nextNode) {
return undefined;
}
currentNode = nextNode;
}
return currentNode;
}
}

View File

@@ -0,0 +1,86 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!sql/media/actionBarLabel';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { localize } from 'vs/nls';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ToggleViewletAction } from 'vs/workbench/browser/viewlet';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { VIEWLET_ID } from 'sql/parts/connection/common/connectionManagement';
import { ConnectionViewlet } from 'sql/parts/objectExplorer/viewlet/connectionViewlet';
// Viewlet Action
export class OpenConnectionsViewletAction extends ToggleViewletAction {
public static ID = VIEWLET_ID;
public static LABEL = 'Show Servers';
constructor(
id: string,
label: string,
@IViewletService viewletService: IViewletService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService
) {
super(id, label, VIEWLET_ID, viewletService, editorService);
}
}
// Viewlet
const viewletDescriptor = new ViewletDescriptor(
ConnectionViewlet,
VIEWLET_ID,
'Servers',
'connectionViewlet',
-100
);
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor);
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).setDefaultViewletId(VIEWLET_ID);
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(
new SyncActionDescriptor(
OpenConnectionsViewletAction,
OpenConnectionsViewletAction.ID,
OpenConnectionsViewletAction.LABEL,
{ primary: KeyMod.CtrlCmd | KeyCode.Shift | KeyCode.KEY_C }),
'View: Show Servers',
localize('registeredServers.view', "View")
);
let configurationRegistry = <IConfigurationRegistry>Registry.as(Extensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'databaseConnections',
'title': localize('databaseConnections', 'Database Connections'),
'type': 'object',
'properties': {
'datasource.connections': {
'description': localize('datasource.connections', 'data source connections'),
'type': 'array'
},
'datasource.connectionGroups': {
'description': localize('datasource.connectionGroups', 'data source groups'),
'type': 'array'
}
}
});
configurationRegistry.registerConfiguration({
'id': 'startupConfig',
'title': localize('startupConfig', 'Startup Configuration'),
'type': 'object',
'properties': {
'startup.alwaysShowServersView': {
'type': 'boolean',
'description': localize('startup.alwaysShowServersView', 'True for the Servers view to be shown on launch of SQL Operations Studio default; false if the last opened view should be shown'),
'default': true
}
}
});

View File

@@ -0,0 +1,166 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { NodeType } from 'sql/parts/objectExplorer/common/nodeType';
import * as sqlops from 'sqlops';
import * as UUID from 'vs/base/common/uuid';
export enum TreeItemCollapsibleState {
None = 0,
Collapsed = 1,
Expanded = 2
}
export interface ObjectExplorerCallbacks {
getChildren(treeNode: TreeNode): Thenable<TreeNode[]>;
isExpanded(treeNode: TreeNode): Thenable<boolean>;
setNodeExpandedState(TreeNode: TreeNode, expandedState: TreeItemCollapsibleState): Thenable<void>;
setNodeSelected(TreeNode: TreeNode, selected: boolean, clearOtherSelections?: boolean): Thenable<void>;
}
export class TreeNode {
/**
* id for TreeNode
*/
public id: string;
/**
* string defining the type of the node - for example Server, Database, Folder, Table
*/
public nodeTypeId: string;
/**
* Label to display to the user, describing this node
*/
public label: string;
/**
* Is this a leaf node (in which case no children can be generated) or is it expandable?
*/
public isAlwaysLeaf: boolean;
/**
* Message to show if this Node is in an error state. This indicates
* that children could be retrieved
*/
public errorStateMessage: string;
/**
* Parent of this node
*/
public parent: TreeNode;
/**
* Path identifying this node
*/
public nodePath: string;
/**
* Node sub type
*/
public nodeSubType: string;
/**
* Node Status
*/
public nodeStatus: string;
/**
* Children of this node
*/
public children: TreeNode[];
public connection: ConnectionProfile;
public session: sqlops.ObjectExplorerSession;
public metadata: sqlops.ObjectMetadata;
public getConnectionProfile(): ConnectionProfile {
var currentNode: TreeNode = this;
while (!currentNode.connection && currentNode.parent) {
currentNode = currentNode.parent;
}
return currentNode.connection;
}
public getDatabaseName(): string {
if (this.connection) {
return undefined;
}
var currentNode: TreeNode = this;
while (currentNode.nodeTypeId !== NodeType.Database && currentNode.nodeTypeId !== NodeType.Server) {
currentNode = currentNode.parent;
}
if (currentNode.nodeTypeId === NodeType.Database) {
return currentNode.metadata ? currentNode.metadata.name : null;
}
return undefined;
}
public getSession(): sqlops.ObjectExplorerSession {
var currentNode: TreeNode = this;
while (!currentNode.session && currentNode.parent) {
currentNode = currentNode.parent;
}
return currentNode.session;
}
public isTopLevel(): boolean {
if (this.parent && this.parent.nodeTypeId === NodeType.Root) {
return true;
}
return false;
}
public toNodeInfo(): sqlops.NodeInfo {
return <sqlops.NodeInfo> {
nodePath: this.nodePath,
nodeType: this.nodeTypeId,
nodeSubType: this.nodeSubType,
nodeStatus: this.nodeStatus,
label: this.label,
isLeaf: this.isAlwaysLeaf,
metadata: this.metadata,
errorMessage: this.errorStateMessage
};
}
public getChildren(): Thenable<TreeNode[]> {
return this._objectExplorerCallbacks.getChildren(this);
}
public isExpanded(): Thenable<boolean> {
return this._objectExplorerCallbacks.isExpanded(this);
}
public setExpandedState(expandedState: TreeItemCollapsibleState): Thenable<void> {
return this._objectExplorerCallbacks.setNodeExpandedState(this, expandedState);
}
public setSelected(selected: boolean, clearOtherSelections?: boolean): Thenable<void> {
return this._objectExplorerCallbacks.setNodeSelected(this, selected, clearOtherSelections);
}
constructor(nodeTypeId: string, label: string, isAlwaysLeaf: boolean, nodePath: string,
nodeSubType: string, nodeStatus: string, parent: TreeNode, metadata: sqlops.ObjectMetadata,
private _objectExplorerCallbacks: ObjectExplorerCallbacks) {
this.nodeTypeId = nodeTypeId;
this.label = label;
this.isAlwaysLeaf = isAlwaysLeaf;
this.nodePath = nodePath;
this.parent = parent;
this.metadata = metadata;
this.id = UUID.generateUuid();
this.nodeSubType = nodeSubType;
this.nodeStatus = nodeStatus;
}
}

View File

@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.group-color-options {
display: flex;
width: 100%;
}
.group-color-options .server-group-color {
flex: 1 1 auto;
height: 20px;
margin: 5px;
}
.server-group-dialog {
padding: 15px
}

View File

@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IConfigurationRegistry, Extensions, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
export const SERVER_GROUP_CONFIG = 'serverGroup';
export const SERVER_GROUP_COLORS_CONFIG = 'colors';
const serverGroupConfig: IConfigurationNode = {
id: 'Server Groups',
type: 'object',
properties: {
[SERVER_GROUP_CONFIG + '.' + SERVER_GROUP_COLORS_CONFIG]: <IJSONSchema>{
type: 'array',
items: 'string',
default: [
'#A1634D',
'#7F0000',
'#914576',
'#85AE72',
'#98AFC7',
'#4452A6',
'#6A6599',
'#515151'
]
}
}
};
configurationRegistry.registerConfiguration(serverGroupConfig);

View File

@@ -0,0 +1,114 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import {
IConnectionManagementService, IErrorMessageService,
IServerGroupController, IServerGroupDialogCallbacks
} from 'sql/parts/connection/common/connectionManagement';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { ServerGroupDialog } from 'sql/parts/objectExplorer/serverGroupDialog/serverGroupDialog';
import { ServerGroupViewModel } from 'sql/parts/objectExplorer/serverGroupDialog/serverGroupViewModel';
import { TPromise } from 'vs/base/common/winjs.base';
import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
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 './serverGroup.contribution';
export class ServerGroupController implements IServerGroupController {
_serviceBrand: any;
private _serverGroupDialog: ServerGroupDialog;
private _connectionManagementService: IConnectionManagementService;
private _callbacks: IServerGroupDialogCallbacks;
private _group: ConnectionProfileGroup;
private _viewModel: ServerGroupViewModel;
constructor(
@IPartService private _partService: IPartService,
@IErrorMessageService private _errorMessageService: IErrorMessageService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IConfigurationService private _configurationService: IConfigurationService
) {
}
private handleOnAddServerGroup(): void {
if (this._group) {
let tempGroup: ConnectionProfileGroup = this.copyConnectionProfileGroup(this._group);
this._group.name = this._viewModel.groupName;
this._group.color = this._viewModel.groupColor;
this._group.description = this._viewModel.groupDescription;
this._connectionManagementService.editGroup(this._group).then(() => {
this._serverGroupDialog.close();
}).catch(err => {
// rollback changes made
this._group = tempGroup;
this._errorMessageService.showDialog(Severity.Error, '', err);
});
} else {
let newGroup: IConnectionProfileGroup = {
name: this._viewModel.groupName,
id: undefined,
parentId: undefined,
color: this._viewModel.groupColor,
description: this._viewModel.groupDescription
};
this._connectionManagementService.saveProfileGroup(newGroup).then(groupId => {
if (this._callbacks) {
this._callbacks.onAddGroup(this._serverGroupDialog.groupName);
}
this._serverGroupDialog.close();
}).catch(err => {
this._errorMessageService.showDialog(Severity.Error, '', err);
});
}
}
private copyConnectionProfileGroup(group: ConnectionProfileGroup): ConnectionProfileGroup {
return new ConnectionProfileGroup(group.name, group.parent, group.id, group.color, group.description);
}
private handleOnClose(): void {
if (this._callbacks) {
this._callbacks.onClose();
}
}
public showCreateGroupDialog(connectionManagementService: IConnectionManagementService, callbacks?: IServerGroupDialogCallbacks): TPromise<void> {
this._connectionManagementService = connectionManagementService;
this._group = null;
this._viewModel = new ServerGroupViewModel(undefined, this._configurationService.getValue(SERVER_GROUP_CONFIG)[SERVER_GROUP_COLORS_CONFIG]);
this._callbacks = callbacks ? callbacks : undefined;
return this.openServerGroupDialog();
}
public showEditGroupDialog(connectionManagementService: IConnectionManagementService, group: ConnectionProfileGroup): TPromise<void> {
this._connectionManagementService = connectionManagementService;
this._group = group;
this._viewModel = new ServerGroupViewModel(group, this._configurationService.getValue(SERVER_GROUP_CONFIG)[SERVER_GROUP_COLORS_CONFIG]);
return this.openServerGroupDialog();
}
private openServerGroupDialog(): TPromise<void> {
if (!this._serverGroupDialog) {
this._serverGroupDialog = this._instantiationService.createInstance(ServerGroupDialog);
this._serverGroupDialog.viewModel = this._viewModel;
this._serverGroupDialog.onCancel(() => { });
this._serverGroupDialog.onAddServerGroup(() => this.handleOnAddServerGroup());
this._serverGroupDialog.onCloseEvent(() => this.handleOnClose());
this._serverGroupDialog.render();
} else {
// reset the view model in the view
this._serverGroupDialog.viewModel = this._viewModel;
}
return new TPromise<void>(() => {
this._serverGroupDialog.open();
});
}
}

View File

@@ -0,0 +1,359 @@
/*---------------------------------------------------------------------------------------------
* 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/serverGroupDialog';
import { Builder } from 'vs/base/browser/builder';
import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import * as DOM from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachInputBoxStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import Event, { Emitter } from 'vs/base/common/event';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { localize } from 'vs/nls';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Button } from 'sql/base/browser/ui/button/button';
import { Modal } from 'sql/base/browser/ui/modal/modal';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import { ServerGroupViewModel } from 'sql/parts/objectExplorer/serverGroupDialog/serverGroupViewModel';
import { attachButtonStyler, attachModalDialogStyler } from 'sql/common/theme/styler';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/common/telemetryKeys';
export class ServerGroupDialog extends Modal {
private _bodyBuilder: Builder;
private _addServerButton: Button;
private _closeButton: Button;
private _colorCheckBoxesMap: Array<{ color: string, checkbox: Checkbox }> = [];
private _selectedColorOption: number;
private _groupNameInputBox: InputBox;
private _groupDescriptionInputBox: InputBox;
private _viewModel: ServerGroupViewModel;
private _skipGroupNameValidation: boolean = false;
private $serverGroupContainer: Builder;
private _onAddServerGroup = new Emitter<void>();
public onAddServerGroup: Event<void> = this._onAddServerGroup.event;
private _onCancel = new Emitter<void>();
public onCancel: Event<void> = this._onCancel.event;
private _onCloseEvent = new Emitter<void>();
public onCloseEvent: Event<void> = this._onCloseEvent.event;
constructor(
@IPartService partService: IPartService,
@IThemeService private _themeService: IThemeService,
@IContextViewService private _contextViewService: IContextViewService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService
) {
super(localize('ServerGroupsDialogTitle', 'Server Groups'), TelemetryKeys.ServerGroups, partService, telemetryService, contextKeyService);
}
public render() {
super.render();
attachModalDialogStyler(this, this._themeService);
let okLabel = localize('serverGroup.ok', 'OK');
let cancelLabel = localize('serverGroup.cancel', 'Cancel');
this._addServerButton = this.addFooterButton(okLabel, () => this.addGroup());
this._closeButton = this.addFooterButton(cancelLabel, () => this.cancel());
this.registerListeners();
}
protected layout(height?: number): void {
// NO OP
}
protected renderBody(container: HTMLElement) {
new Builder(container).div({ class: 'server-group-dialog' }, (builder) => {
this._bodyBuilder = builder;
});
// Connection Group Name
this._bodyBuilder.div({ class: 'dialog-label' }, (labelContainer) => {
let serverGroupNameLabel = localize('connectionGroupName', 'Server group name');
labelContainer.innerHtml(serverGroupNameLabel);
});
this._bodyBuilder.div({ class: 'input-divider' }, (inputCellContainer) => {
let errorMessage = localize('MissingGroupNameError', 'Group name is required.');
this._groupNameInputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, {
validationOptions: {
validation: (value: string) => !value && !this._skipGroupNameValidation ? ({ type: MessageType.ERROR, content: errorMessage }) : null
}
});
});
// Connection Group Description
this._bodyBuilder.div({ class: 'dialog-label' }, (labelContainer) => {
let groupDescriptionLabel = localize('groupDescription', 'Group description');
labelContainer.innerHtml(groupDescriptionLabel);
});
this._bodyBuilder.div({ class: 'input-divider' }, (inputCellContainer) => {
this._groupDescriptionInputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService);
});
// Connection Group Color
this._bodyBuilder.div({ class: 'dialog-label' }, (labelContainer) => {
let groupColorLabel = localize('groupColor', 'Group color');
labelContainer.innerHtml(groupColorLabel);
});
this._bodyBuilder.div({ class: 'group-color-options' }, (groupColorContainer) => {
this.$serverGroupContainer = groupColorContainer;
this.fillGroupColors(groupColorContainer.getHTMLElement());
});
this._bodyBuilder.on(DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
let event = new StandardKeyboardEvent(e);
if (event.equals(KeyMod.Shift | KeyCode.Tab)) {
this.preventDefaultKeyboardEvent(e);
this.focusPrevious();
} else if (event.equals(KeyCode.Tab)) {
this.preventDefaultKeyboardEvent(e);
this.focusNext();
} else if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.LeftArrow)) {
this.preventDefaultKeyboardEvent(e);
this.focusNextColor(event.equals(KeyCode.RightArrow));
}
});
}
private preventDefaultKeyboardEvent(e: KeyboardEvent) {
e.preventDefault();
e.stopPropagation();
}
private isFocusOnColors(): boolean {
var result = false;
this._colorCheckBoxesMap.forEach(({ checkbox }) => {
if (document.activeElement === checkbox.domNode) {
result = true;
}
});
return result;
}
private focusNext(): void {
if (this._groupNameInputBox.hasFocus()) {
this._groupDescriptionInputBox.focus();
} else if (this._groupDescriptionInputBox.hasFocus()) {
this._colorCheckBoxesMap[this._selectedColorOption].checkbox.focus();
} else if (this.isFocusOnColors()) {
this._addServerButton.enabled ? this._addServerButton.focus() : this._closeButton.focus();
} else if (document.activeElement === this._addServerButton.element) {
this._closeButton.focus();
}
else if (document.activeElement === this._closeButton.element) {
this._groupNameInputBox.focus();
}
}
private focusPrevious(): void {
if (document.activeElement === this._closeButton.element) {
this._addServerButton.enabled ? this._addServerButton.focus() : this._colorCheckBoxesMap[this._selectedColorOption].checkbox.focus();
} else if (document.activeElement === this._addServerButton.element) {
this._colorCheckBoxesMap[this._selectedColorOption].checkbox.focus();
} else if (this.isFocusOnColors()) {
this._groupDescriptionInputBox.focus();
} else if (this._groupDescriptionInputBox.hasFocus()) {
this._groupNameInputBox.focus();
} else if (this._groupNameInputBox.hasFocus()) {
this._closeButton.focus();
}
}
private focusNextColor(moveRight: boolean): void {
let focusIndex: number = -1;
for (let i = 0; i < this._colorCheckBoxesMap.length; i++) {
if (document.activeElement === this._colorCheckBoxesMap[i].checkbox.domNode) {
focusIndex = i;
break;
}
}
if (focusIndex >= 0) {
if (moveRight) {
focusIndex++;
}
else {
focusIndex--;
}
// check for wraps
if (focusIndex < 0) {
focusIndex = this._colorCheckBoxesMap.length - 1;
} else if (focusIndex >= this._colorCheckBoxesMap.length) {
focusIndex = 0;
}
this._colorCheckBoxesMap[focusIndex].checkbox.focus();
}
}
private onSelectGroupColor(colorToSelect: string): void {
this._viewModel.groupColor = colorToSelect;
this._selectedColorOption = this._viewModel.colors.indexOf(colorToSelect);
this.updateView();
}
private registerListeners(): void {
// Theme styler
this._register(attachInputBoxStyler(this._groupNameInputBox, this._themeService));
this._register(attachInputBoxStyler(this._groupDescriptionInputBox, this._themeService));
this._register(attachButtonStyler(this._addServerButton, this._themeService));
this._register(attachButtonStyler(this._closeButton, this._themeService));
// handler for name change events
this._register(this._groupNameInputBox.onDidChange(groupName => {
this.groupNameChanged(groupName);
}));
// handler for description change events
this._register(this._groupDescriptionInputBox.onDidChange(groupDescription => {
this.groupDescriptionChanged(groupDescription);
}));
}
private fillGroupColors(container: HTMLElement): void {
for (let i = 0; i < this._viewModel.colors.length; i++) {
let color = this._viewModel.colors[i];
let colorCheckBox = new Checkbox({
actionClassName: 'server-group-color',
title: color,
isChecked: false,
onChange: (viaKeyboard) => {
this.onSelectGroupColor(color);
}
});
colorCheckBox.domNode.style.backgroundColor = color;
container.appendChild(colorCheckBox.domNode);
// Theme styler
this._register(attachCheckboxStyler(colorCheckBox, this._themeService));
// add the new checkbox to the color map
this._colorCheckBoxesMap[i] = { color, checkbox: colorCheckBox };
}
}
private groupNameChanged(groupName: string) {
this._viewModel.groupName = groupName;
this.updateView();
}
private groupDescriptionChanged(groupDescription: string) {
this._viewModel.groupDescription = groupDescription;
this.updateView();
}
public get groupName(): string {
return this._groupNameInputBox.value;
}
public get groupDescription(): string {
return this._groupDescriptionInputBox.value;
}
public get selectedColor(): string {
return this._colorCheckBoxesMap[this._selectedColorOption].color;
}
public get viewModel(): ServerGroupViewModel {
return this._viewModel;
}
public set viewModel(theViewModel: ServerGroupViewModel) {
this._viewModel = theViewModel;
if (this.$serverGroupContainer) {
this.$serverGroupContainer.clearChildren();
this.fillGroupColors(this.$serverGroupContainer.getHTMLElement());
}
}
public addGroup(): void {
if (this._addServerButton.enabled) {
if (this.validateInputs()) {
this._onAddServerGroup.fire();
}
}
}
public hideError() {
this.setError('');
}
private validateInputs(): boolean {
let validate = this._groupNameInputBox.validate();
if (!validate) {
this._groupNameInputBox.focus();
}
return validate;
}
// initialize the view based on the current state of the view model
private initializeView(): void {
this.title = this._viewModel.getDialogTitle();
this._skipGroupNameValidation = true;
this._groupNameInputBox.value = this._viewModel.groupName;
this._skipGroupNameValidation = false;
this._groupDescriptionInputBox.value = this._viewModel.groupDescription;
this.updateView();
}
// update UI elements that have derivative behaviors based on other state changes
private updateView(): void {
// check the color buttons and if their checked state does not match the view model state then correct it
for (let i = 0; i < this._colorCheckBoxesMap.length; i++) {
let { checkbox, color } = this._colorCheckBoxesMap[i];
if ((this._viewModel.groupColor === color) && (checkbox.checked === false)) {
checkbox.checked = true;
this._selectedColorOption = i;
} else if ((this._viewModel.groupColor !== color) && (checkbox.checked === true)) {
checkbox.checked = false;
}
}
// OK button state - enabled if there are pending changes that can be saved
this._addServerButton.enabled = this._viewModel.hasPendingChanges();
}
/* Overwrite escape key behavior */
protected onClose() {
this.cancel();
}
/* Overwrite enter key behavior */
protected onAccept() {
this.addGroup();
}
public cancel() {
this._onCancel.fire();
this.close();
}
public close() {
this.hide();
this._groupNameInputBox.hideMessage();
this._onCloseEvent.fire();
}
public open() {
// reset the dialog
this.hideError();
this.initializeView();
this.show();
this._groupNameInputBox.focus();
}
}

View File

@@ -0,0 +1,71 @@
/*---------------------------------------------------------------------------------------------
* 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 { IConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
import * as TypeChecker from 'vs/base/common/types';
import { localize } from 'vs/nls';
import * as strings from 'vs/base/common/strings';
export class ServerGroupViewModel {
public groupName: string;
public groupDescription: string;
public groupColor: string;
public colors: string[] = ['#515151', '#004760', '#771b00', '#700060', '#a17d01', '#006749', '#654502', '#3A0293'];
private _domainModel: IConnectionProfileGroup;
private _editMode: boolean;
private readonly _addServerGroupTitle: string = localize('serverGroup.addServerGroup', 'Add server group');
private readonly _editServerGroupTitle: string = localize('serverGroup.editServerGroup', 'Edit server group');
private readonly _defaultColor: string = '#515151';
constructor(domainModel?: IConnectionProfileGroup, colors?: string[]) {
// keep reference to domain model to be able to see if there are pending changes
if (domainModel) {
this._domainModel = domainModel;
// initialize the view model properties
this.groupName = domainModel.name;
this.groupColor = domainModel.color;
this.groupDescription = domainModel.description;
this._editMode = true;
}
else {
// initialize defaults for a new group
this.groupName = '';
this.groupDescription = '';
this.groupColor = this._defaultColor;
this._editMode = false;
}
if (colors) {
this.colors = colors;
}
}
// check to see if the current state of the view model is different than the data in the domain model
public hasPendingChanges(): boolean {
if (!TypeChecker.isUndefinedOrNull(this._domainModel)) {
return ((strings.isFalsyOrWhitespace(this.groupName) === false) &&
((this.groupName !== this._domainModel.name) ||
(this.groupDescription !== this._domainModel.description) ||
(this.groupColor !== this._domainModel.color)));
}
else {
return (strings.isFalsyOrWhitespace(this.groupName) === false);
}
}
public getDialogTitle(): string {
if (this._editMode === true) {
return this._editServerGroupTitle;
} else {
return this._addServerGroupTitle;
}
}
}

View File

@@ -0,0 +1,456 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { IConnectionManagementService, IErrorMessageService } from 'sql/parts/connection/common/connectionManagement';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { ServerTreeView } from 'sql/parts/objectExplorer/viewlet/serverTreeView';
import { ConnectionViewlet } from 'sql/parts/objectExplorer/viewlet/connectionViewlet';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { ConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import * as Constants from 'sql/parts/connection/common/constants';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
import Severity from 'vs/base/common/severity';
import { ObjectExplorerActionsContext, ObjectExplorerActionUtilities } from 'sql/parts/objectExplorer/viewlet/objectExplorerActions';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
export class RefreshAction extends Action {
public static ID = 'objectExplorer.refresh';
public static LABEL = localize('connectionTree.refresh', 'Refresh');
private _tree: ITree;
constructor(
id: string,
label: string,
tree: ITree,
private element: ConnectionProfile | TreeNode,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IErrorMessageService private _errorMessageService: IErrorMessageService
) {
super(id, label);
this._tree = tree;
}
public run(): TPromise<boolean> {
var treeNode: TreeNode;
if (this.element instanceof ConnectionProfile) {
let connection: ConnectionProfile = this.element;
if (this._connectionManagementService.isConnected(undefined, connection)) {
treeNode = this._objectExplorerService.getObjectExplorerNode(connection);
if (treeNode === undefined) {
this._objectExplorerService.updateObjectExplorerNodes(connection.toIConnectionProfile()).then(() => {
treeNode = this._objectExplorerService.getObjectExplorerNode(connection);
});
}
}
} else if (this.element instanceof TreeNode) {
treeNode = this.element;
}
if (treeNode) {
this._tree.collapse(this.element).then(() => {
this._objectExplorerService.refreshTreeNode(treeNode.getSession(), treeNode).then(() => {
this._tree.refresh(this.element).then(() => {
this._tree.expand(this.element);
}, refreshError => {
return TPromise.as(true);
});
}, error => {
this.showError(error);
return TPromise.as(true);
});
}, collapseError => {
return TPromise.as(true);
});
}
return TPromise.as(true);
}
private showError(errorMessage: string) {
if (this._errorMessageService) {
this._errorMessageService.showDialog(Severity.Error, '', errorMessage);
}
}
}
export class DisconnectConnectionAction extends Action {
public static ID = 'objectExplorer.disconnect';
public static LABEL = localize('DisconnectAction', 'Disconnect');
private _disposables: IDisposable[] = [];
private _connectionProfile: ConnectionProfile;
private _container: HTMLElement;
constructor(
id: string,
label: string,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IErrorMessageService private _errorMessageService: IErrorMessageService
) {
super(id, label);
const self = this;
this._disposables.push(this._connectionManagementService.onConnect(() => {
self.setLabel();
})
);
this._disposables.push(this._connectionManagementService.onDisconnect((disconnectParams) => {
if (this._connectionProfile) {
this._connectionProfile.isDisconnecting = false;
}
self.setLabel();
self._connectionManagementService.closeDashboard(disconnectParams.connectionUri);
})
);
if (this._objectExplorerService && this._objectExplorerService.onUpdateObjectExplorerNodes) {
this._disposables.push(this._objectExplorerService.onUpdateObjectExplorerNodes((args) => {
self.removeSpinning(args.connection);
if (args.errorMessage !== undefined) {
self.showError(args.errorMessage);
}
})
);
}
}
private showError(errorMessage: string) {
if (this._errorMessageService) {
this._errorMessageService.showDialog(Severity.Error, '', errorMessage);
}
}
private setLabel(): void {
if (!this._connectionProfile) {
this.label = 'Connect';
return;
}
this.label = this._connectionManagementService.isProfileConnected(this._connectionProfile) ? 'Disconnect' : 'Connect';
}
private removeSpinning(connection: IConnectionProfile): void {
if (this._connectionProfile) {
if (connection.id === this._connectionProfile.id && this._container) {
ObjectExplorerActionUtilities.hideLoadingIcon(this._container, ObjectExplorerActionUtilities.connectionElementClass);
}
}
}
run(actionContext: ObjectExplorerActionsContext): TPromise<any> {
return new TPromise<boolean>((resolve, reject) => {
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._connectionProfile = actionContext.connectionProfile;
this._container = actionContext.container;
resolve(true);
}
if (!this._connectionProfile) {
resolve(true);
}
if (this._connectionManagementService.isProfileConnected(this._connectionProfile)) {
this._connectionProfile.isDisconnecting = true;
this._connectionManagementService.disconnect(this._connectionProfile).then((value) => {
resolve(true);
}
).catch(disconnectError => {
reject(disconnectError);
});
} else {
resolve(true);
}
});
}
dispose(): void {
super.dispose();
this._disposables = dispose(this._disposables);
}
}
/**
* Actions to add a server to the group
*/
export class AddServerAction extends Action {
public static ID = 'registeredServers.addConnection';
public static LABEL = localize('connectionTree.addConnection', 'New Connection');
constructor(
id: string,
label: string,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super(id, label);
this.class = 'add-server-action';
}
public run(element: ConnectionProfileGroup): TPromise<boolean> {
let connection: IConnectionProfile = element === undefined ? undefined : {
serverName: undefined,
databaseName: undefined,
userName: undefined,
password: undefined,
authenticationType: undefined,
groupId: undefined,
groupFullName: element.fullName,
savePassword: undefined,
getOptionsKey: undefined,
matches: undefined,
providerName: '',
options: {},
saveProfile: true,
id: element.id
};
this._connectionManagementService.showConnectionDialog(undefined, connection);
return TPromise.as(true);
}
}
/**
* Actions to add a server to the group
*/
export class AddServerGroupAction extends Action {
public static ID = 'registeredServers.addServerGroup';
public static LABEL = localize('connectionTree.addServerGroup', 'New Server Group');
constructor(
id: string,
label: string,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super(id, label);
this.class = 'add-server-group-action';
}
public run(): TPromise<boolean> {
this._connectionManagementService.showCreateServerGroupDialog();
return TPromise.as(true);
}
}
/**
* Actions to edit a server group
*/
export class EditServerGroupAction extends Action {
public static ID = 'registeredServers.editServerGroup';
public static LABEL = localize('connectionTree.editServerGroup', 'Edit Server Group');
constructor(
id: string,
label: string,
private _group: ConnectionProfileGroup,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super(id, label);
this.class = 'edit-server-group-action';
}
public run(): TPromise<boolean> {
this._connectionManagementService.showEditServerGroupDialog(this._group);
return TPromise.as(true);
}
}
/**
* Display active connections in the tree
*/
export class ActiveConnectionsFilterAction extends Action {
public static ID = 'registeredServers.recentConnections';
public static LABEL = localize('activeConnections', 'Show Active Connections');
private static enabledClass = 'active-connections-action';
private static disabledClass = 'icon server-page';
private static clearAllLabel = localize('clearAll', 'Clear All');
private _isSet: boolean;
public static readonly ACTIVE = 'active';
public get isSet(): boolean {
return this._isSet;
}
public set isSet(value: boolean) {
this._isSet = value;
this.class = (!this._isSet) ?
ActiveConnectionsFilterAction.enabledClass : ActiveConnectionsFilterAction.disabledClass;
}
constructor(
id: string,
label: string,
private view: ServerTreeView,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super(id, label);
this.class = ActiveConnectionsFilterAction.enabledClass;
}
public run(): TPromise<boolean> {
if (!this.view) {
// return without doing anything
return TPromise.as(true);
}
if (this.class === ActiveConnectionsFilterAction.enabledClass) {
// show active connections in the tree
this.view.showFilteredTree(ActiveConnectionsFilterAction.ACTIVE);
this.isSet = true;
this.label = ActiveConnectionsFilterAction.clearAllLabel;
} else {
// show full tree
this.view.refreshTree();
this.isSet = false;
this.label = ActiveConnectionsFilterAction.LABEL;
}
return TPromise.as(true);
}
}
/**
* Display recent connections in the tree
*/
export class RecentConnectionsFilterAction extends Action {
public static ID = 'registeredServers.recentConnections';
public static LABEL = localize('recentConnections', 'Recent Connections');
private static enabledClass = 'recent-connections-action';
private static disabledClass = 'recent-connections-action-set';
private _isSet: boolean;
public get isSet(): boolean {
return this._isSet;
}
public set isSet(value: boolean) {
this._isSet = value;
this.class = (!this._isSet) ?
RecentConnectionsFilterAction.enabledClass : RecentConnectionsFilterAction.disabledClass;
}
constructor(
id: string,
label: string,
private view: ServerTreeView,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super(id, label);
this.class = RecentConnectionsFilterAction.enabledClass;
this._isSet = false;
}
public run(): TPromise<boolean> {
if (!this.view) {
// return without doing anything
return TPromise.as(true);
}
if (this.class === RecentConnectionsFilterAction.enabledClass) {
// show recent connections in the tree
this.view.showFilteredTree('recent');
this.isSet = true;
} else {
// show full tree
this.view.refreshTree();
this.isSet = false;
}
return TPromise.as(true);
}
}
export class NewQueryAction extends Action {
public static ID = 'registeredServers.newQuery';
public static LABEL = localize('registeredServers.newQuery', 'New Query');
private _connectionProfile: ConnectionProfile;
get connectionProfile(): ConnectionProfile {
return this._connectionProfile;
}
set connectionProfile(profile: ConnectionProfile) {
this._connectionProfile = profile;
}
constructor(
id: string,
label: string,
@IQueryEditorService private queryEditorService: IQueryEditorService,
@IConnectionManagementService private connectionManagementService: IConnectionManagementService,
@IObjectExplorerService protected _objectExplorerService: IObjectExplorerService,
@IWorkbenchEditorService protected _workbenchEditorService: IWorkbenchEditorService
) {
super(id, label);
this.class = 'extension-action update';
}
public run(actionContext: ObjectExplorerActionsContext): TPromise<boolean> {
if (actionContext instanceof ObjectExplorerActionsContext) {
this._connectionProfile = actionContext.connectionProfile;
}
TaskUtilities.newQuery(this._connectionProfile, this.connectionManagementService, this.queryEditorService, this._objectExplorerService, this._workbenchEditorService);
return TPromise.as(true);
}
}
/**
* Actions to delete a server/group
*/
export class DeleteConnectionAction extends Action {
public static ID = 'registeredServers.deleteConnection';
public static DELETE_CONNECTION_LABEL = localize('deleteConnection', 'Delete Connection');
public static DELETE_CONNECTION_GROUP_LABEL = localize('deleteConnectionGroup', 'Delete Group');
constructor(
id: string,
label: string,
private element: ConnectionProfile | ConnectionProfileGroup,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super(id, label);
this.class = 'delete-connection-action';
if (element instanceof ConnectionProfileGroup && element.id === Constants.unsavedGroupId) {
this.enabled = false;
}
if (element instanceof ConnectionProfile) {
element = <ConnectionProfile>element;
let parent: ConnectionProfileGroup = element.parent;
if (parent && parent.id === Constants.unsavedGroupId) {
this.enabled = false;
}
}
}
public run(): TPromise<boolean> {
if (this.element instanceof ConnectionProfile) {
this._connectionManagementService.deleteConnection(this.element);
} else if (this.element instanceof ConnectionProfileGroup) {
this._connectionManagementService.deleteConnectionGroup(this.element);
}
return TPromise.as(true);
}
}
/**
* Action to clear search results
*/
export class ClearSearchAction extends Action {
public static ID = 'registeredServers.clearSearch';
public static LABEL = localize('clearSearch', 'Clear Search');
constructor(
id: string,
label: string,
private _viewlet: ConnectionViewlet,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super(id, label);
this.class = 'icon close';
this.enabled = false;
}
public run(): TPromise<boolean> {
this._viewlet.clearSearch();
return TPromise.as(true);
}
}

View File

@@ -0,0 +1,170 @@
/*---------------------------------------------------------------------------------------------
* 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 { ThrottledDelayer } from 'vs/base/common/async';
import { TPromise } from 'vs/base/common/winjs.base';
import { Builder, Dimension } from 'vs/base/browser/builder';
import { Viewlet } from 'vs/workbench/browser/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IAction } from 'vs/base/common/actions';
import { toggleClass } from 'vs/base/browser/dom';
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 { IConnectionsViewlet, IConnectionManagementService, VIEWLET_ID } from 'sql/parts/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';
export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet {
private _searchDelayer: ThrottledDelayer<any>;
private _root: HTMLElement;
private _searchBox: InputBox;
private _toDisposeViewlet: IDisposable[] = [];
private _serverTreeView: ServerTreeView;
private _viewletContainer: Builder;
private _searchBoxContainer: Builder;
private _clearSearchAction: ClearSearchAction;
private _addServerAction: IAction;
private _addServerGroupAction: IAction;
private _activeConnectionsFilterAction: ActiveConnectionsFilterAction;
private _searchTerm: string;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService private _themeService: IThemeService,
@IConnectionManagementService private connectionManagementService: IConnectionManagementService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IViewletService private viewletService: IViewletService,
@INotificationService private _notificationService: INotificationService,
@IObjectExplorerService private objectExplorerService: IObjectExplorerService,
@IPartService partService: IPartService
) {
super(VIEWLET_ID, partService, telemetryService, _themeService);
this._searchDelayer = new ThrottledDelayer(500);
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: Builder): TPromise<void> {
return new TPromise<void>((resolve) => {
super.create(parent);
this._root = parent.getHTMLElement();
parent.div({ class: 'server-explorer-viewlet' }, (viewletContainer) => {
this._viewletContainer = viewletContainer;
viewletContainer.div({ class: 'search-box' }, (searchBoxContainer) => {
this._searchBoxContainer = searchBoxContainer;
this._searchBox = new InputBox(
searchBoxContainer.getHTMLElement(),
null,
{
placeholder: 'Search server names',
actions: [this._clearSearchAction]
}
);
this._searchTerm = '';
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 }: Dimension): void {
this._searchBox.layout();
this._serverTreeView.layout(height - 36); // account for search box
toggleClass(this._root, 'narrow', width <= 350);
}
public getOptimalWidth(): number {
return 400;
}
public clearSearch() {
this._searchTerm = '';
this._serverTreeView.refreshTree();
this._searchBox.value = '';
this._clearSearchAction.enabled = false;
this._searchBox.focus();
}
public dispose(): void {
this._serverTreeView.dispose();
this._toDisposeViewlet = dispose(this._toDisposeViewlet);
}
}

View File

@@ -0,0 +1,206 @@
/*---------------------------------------------------------------------------------------------
* 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 { ConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { ITree, IDragAndDrop, IDragAndDropData, IDragOverReaction, DRAG_OVER_ACCEPT_BUBBLE_DOWN, DRAG_OVER_REJECT } from 'vs/base/parts/tree/browser/tree';
import * as Constants from 'sql/parts/connection/common/constants';
import { DragMouseEvent } from 'vs/base/browser/mouseEvent';
import { TreeUpdateUtils } from 'sql/parts/objectExplorer/viewlet/treeUpdateUtils';
/**
* Implements drag and drop for the server tree
*/
export class ServerTreeDragAndDrop implements IDragAndDrop {
constructor(@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
}
/**
* Returns a uri if the given element should be allowed to drag.
* Returns null, otherwise.
*/
public getDragURI(tree: ITree, element: any): string {
if (element instanceof ConnectionProfile) {
return (<ConnectionProfile>element).id;
}
else if (element instanceof ConnectionProfileGroup) {
return (<ConnectionProfileGroup>element).id;
}
return null;
}
/**
* Returns a label(name) to display when dragging the element.
*/
public getDragLabel(tree: ITree, elements: any[]): string {
if (elements[0] instanceof ConnectionProfile) {
return (<ConnectionProfile>elements[0]).serverName;
} else if (elements[0] instanceof ConnectionProfileGroup) {
return (<ConnectionProfileGroup>elements[0]).name;
} else {
return undefined;
}
}
/**
* Called when the drag operation starts.
*/
public onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: DragMouseEvent): void {
TreeUpdateUtils.isInDragAndDrop = true;
return;
}
/**
* Returns a DragOverReaction indicating whether sources can be
* dropped into target or some parent of the target.
* Returns DRAG_OVER_ACCEPT_BUBBLE_DOWN when element is a connection group or connection
*/
public onDragOver(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: DragMouseEvent): IDragOverReaction {
let canDragOver: boolean = true;
if (targetElement instanceof ConnectionProfile || targetElement instanceof ConnectionProfileGroup) {
let targetConnectionProfileGroup = this.getTargetGroup(targetElement);
//Verify if the connection can be moved to the target group
const source = data.getData()[0];
if (source instanceof ConnectionProfile) {
if (!this._connectionManagementService.canChangeConnectionConfig(source, targetConnectionProfileGroup.id)) {
canDragOver = false;
}
}
} else {
canDragOver = false;
}
if (canDragOver) {
return DRAG_OVER_ACCEPT_BUBBLE_DOWN(true);
} else {
return DRAG_OVER_REJECT;
}
}
/**
* Handle a drop in the server tree.
*/
public drop(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: DragMouseEvent): void {
TreeUpdateUtils.isInDragAndDrop = false;
let targetConnectionProfileGroup: ConnectionProfileGroup = this.getTargetGroup(targetElement);
const source = data.getData()[0];
let oldParent: ConnectionProfileGroup = source.getParent();
const self = this;
if (this.isDropAllowed(targetConnectionProfileGroup, oldParent, source)) {
if (source instanceof ConnectionProfile) {
// Change group id of profile
this._connectionManagementService.changeGroupIdForConnection(source, targetConnectionProfileGroup.id).then(() => {
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService, targetConnectionProfileGroup);
});
} else if (source instanceof ConnectionProfileGroup) {
// Change parent id of group
this._connectionManagementService.changeGroupIdForConnectionGroup(source, targetConnectionProfileGroup).then(() => {
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService);
});
}
}
return;
}
public dropAbort(tree: ITree, data: IDragAndDropData): void {
TreeUpdateUtils.isInDragAndDrop = false;
}
private getTargetGroup(targetElement: any): ConnectionProfileGroup {
let targetConnectionProfileGroup: ConnectionProfileGroup;
if (targetElement instanceof ConnectionProfile) {
targetConnectionProfileGroup = (<ConnectionProfile>targetElement).getParent();
}
else {
targetConnectionProfileGroup = <ConnectionProfileGroup>targetElement;
}
return targetConnectionProfileGroup;
}
private isDropAllowed(targetConnectionProfileGroup: ConnectionProfileGroup,
oldParent: ConnectionProfileGroup,
source: ConnectionProfile | ConnectionProfileGroup): boolean {
let isDropToItself = source && targetConnectionProfileGroup && (source instanceof ConnectionProfileGroup) && source.name === targetConnectionProfileGroup.name;
let isDropToSameLevel = oldParent && oldParent.equals(targetConnectionProfileGroup);
let isUnsavedDrag = source && (source instanceof ConnectionProfileGroup) && (source.id === Constants.unsavedGroupId);
return (!isDropToSameLevel && !isDropToItself && !isUnsavedDrag);
}
}
/**
* Implements drag and drop for the connection tree
*/
export class RecentConnectionsDragAndDrop implements IDragAndDrop {
constructor( @IConnectionManagementService private connectionManagementService: IConnectionManagementService,
@IInstantiationService private instantiationService: IInstantiationService
) {
}
/**
* Returns a uri if the given element should be allowed to drag.
* Returns null, otherwise.
*/
public getDragURI(tree: ITree, element: any): string {
if (element instanceof ConnectionProfile) {
return (<ConnectionProfile>element).id;
}
else if (element instanceof ConnectionProfileGroup) {
return (<ConnectionProfileGroup>element).id;
}
return null;
}
/**
* Returns a label(name) to display when dragging the element.
*/
public getDragLabel(tree: ITree, elements: any[]): string {
if (elements[0] instanceof ConnectionProfile) {
return (<ConnectionProfile>elements[0]).serverName;
}
else if (elements[0] instanceof ConnectionProfileGroup) {
return (<ConnectionProfileGroup>elements[0]).name;
}
return undefined;
}
/**
* Sent when the drag operation is starting.
*/
public onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: DragMouseEvent): void {
return;
}
/**
* Returns a DragOverReaction indicating whether sources can be
* dropped into target or some parent of the target.
*/
public onDragOver(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: DragMouseEvent): IDragOverReaction {
return DRAG_OVER_REJECT;
}
/**
* Handle drop in the server tree.
*/
public drop(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: DragMouseEvent): void {
// No op
}
public dropAbort(tree: ITree, data: IDragAndDropData): void { }
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>add_server_16x16</title><path d="M1.37.16V15.84H9.89v-1H2.37V10.13H10.5v.46h1V.16Zm9.13,9H2.37v-8H10.5Z"/><path d="M6,4.68H3.47V2.17H6Zm-2-.5H5.49V2.67H4Z"/><path d="M11.88,15.84V13.91H9.95v-.82h1.93V11.16h.82v1.93h1.93v.82H12.7v1.93Z"/></svg>

After

Width:  |  Height:  |  Size: 343 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>add_server_inverse_16x16</title><path class="cls-1" d="M.23.16V15.84H8.75v-1H1.23V10.13H9.36v.46h1V.16Zm9.13,9H1.23v-8H9.36Z"/><path class="cls-1" d="M4.84,4.69H2.32V2.17H4.84Zm-2-.5H4.34V2.67H2.82Z"/><path class="cls-1" d="M10.73,15.84V13.91H8.8v-.82h1.93V11.16h.82v1.93h1.93v.82H11.55v1.93Z"/></svg>

After

Width:  |  Height:  |  Size: 447 B

View File

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

After

Width:  |  Height:  |  Size: 139 B

View File

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

After

Width:  |  Height:  |  Size: 502 B

View File

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

After

Width:  |  Height:  |  Size: 507 B

View File

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

View File

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

After

Width:  |  Height:  |  Size: 1002 B

View File

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

After

Width:  |  Height:  |  Size: 1007 B

View File

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

After

Width:  |  Height:  |  Size: 118 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>new_servergroup_16x16</title><path d="M10.2,13h-5v-.75a1.12,1.12,0,0,1,.07-.43,1.81,1.81,0,0,1,.18-.34,3.89,3.89,0,0,1,.24-.32,2.14,2.14,0,0,0,.24-.36,2.26,2.26,0,0,0,.18-.45,2.16,2.16,0,0,0,.07-.61v-5a1,1,0,0,0-.07-.39A1,1,0,0,0,5.87,4l-3-3h8.29V7.08h1V1a1,1,0,0,0-.08-.39,1,1,0,0,0-.54-.54A1,1,0,0,0,11.16,0h-10V13.41L3.45,15.7a1,1,0,0,0,.71.3,1,1,0,0,0,.38-.08A1,1,0,0,0,5,15.56,1,1,0,0,0,5.16,15V14h5ZM5.09,10.18a1.81,1.81,0,0,1-.18.34,3.89,3.89,0,0,1-.24.32,2.14,2.14,0,0,0-.24.36,2.26,2.26,0,0,0-.18.45,2.16,2.16,0,0,0-.07.61V15l-2-2V1.71l3,3v5A1.12,1.12,0,0,1,5.09,10.18Z"/><path d="M11,12.67V10.75H9.11V9.93H11V8h.82V9.93h1.93v.82H11.85v1.93Z"/></svg>

After

Width:  |  Height:  |  Size: 759 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>new_servergroup_inverse_16x16</title><path class="cls-1" d="M10.2,13h-5v-.75a1.12,1.12,0,0,1,.07-.43,1.81,1.81,0,0,1,.18-.34,3.89,3.89,0,0,1,.24-.32,2.14,2.14,0,0,0,.24-.36,2.26,2.26,0,0,0,.18-.45,2.16,2.16,0,0,0,.07-.61v-5a1,1,0,0,0-.07-.39A1,1,0,0,0,5.87,4l-3-3h8.29V7.08h1V1a1,1,0,0,0-.08-.39,1,1,0,0,0-.54-.54A1,1,0,0,0,11.16,0h-10V13.41L3.45,15.7a1,1,0,0,0,.71.3,1,1,0,0,0,.38-.08A1,1,0,0,0,5,15.56,1,1,0,0,0,5.16,15V14h5ZM5.09,10.18a1.81,1.81,0,0,1-.18.34,3.89,3.89,0,0,1-.24.32,2.14,2.14,0,0,0-.24.36,2.26,2.26,0,0,0-.18.45,2.16,2.16,0,0,0-.07.61V15l-2-2V1.71l3,3v5A1.12,1.12,0,0,1,5.09,10.18Z"/><path class="cls-1" d="M11,12.67V10.75H9.11V9.93H11V8h.82V9.93h1.93v.82H11.85v1.93Z"/></svg>

After

Width:  |  Height:  |  Size: 841 B

View File

@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Icons for various registered servers actions */
.monaco-workbench .add-server-action {
background-image: url('add_server.svg');
}
.vs-dark .monaco-workbench .add-server-action,
.hc-black .monaco-workbench .add-server-action {
background-image: url('add_server_inverse.svg');
}
.monaco-workbench .add-server-group-action {
background-image: url('new_servergroup.svg');
}
.vs-dark .monaco-workbench .add-server-group-action,
.hc-black .monaco-workbench .add-server-group-action {
background-image: url('new_servergroup_inverse.svg');
}
.monaco-workbench .active-connections-action {
background-image: url('connected_active_server.svg');
}
.vs-dark .monaco-workbench .active-connections-action,
.hc-black .monaco-workbench .active-connections-action{
background-image: url('connected_active_server_inverse.svg');
}

View File

@@ -0,0 +1,470 @@
/*---------------------------------------------------------------------------------------------
* 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';
import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { IConnectionManagementService, IConnectionCompletionOptions, IErrorMessageService } from 'sql/parts/connection/common/connectionManagement';
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import {
NewQueryAction, ScriptSelectAction, EditDataAction, ScriptCreateAction,
ScriptExecuteAction, ScriptDeleteAction, ScriptAlterAction
} from 'sql/workbench/common/actions';
import { NodeType } from 'sql/parts/objectExplorer/common/nodeType';
import { TreeUpdateUtils } from 'sql/parts/objectExplorer/viewlet/treeUpdateUtils';
import { TreeSelectionHandler } from 'sql/parts/objectExplorer/viewlet/treeSelectionHandler';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IScriptingService } from 'sql/services/scripting/scriptingService';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import * as Constants from 'sql/parts/connection/common/constants';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ExecuteCommandAction } from 'vs/platform/actions/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
export class ObjectExplorerActionsContext {
public treeNode: TreeNode;
public connectionProfile: ConnectionProfile;
public container: HTMLElement;
public tree: ITree;
}
export class OEAction extends ExecuteCommandAction {
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
id: string, label: string,
@IInstantiationService private _instantiationService: IInstantiationService,
@ICommandService commandService: ICommandService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super(id, label, commandService);
}
public run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
let profile: IConnectionProfile;
if (actionContext.connectionProfile) {
profile = actionContext.connectionProfile;
} else {
profile = TreeUpdateUtils.getConnectionProfile(<TreeNode>actionContext.treeNode);
}
this._treeSelectionHandler.onTreeActionStateChange(true);
return super.run(profile).then(() => {
this._treeSelectionHandler.onTreeActionStateChange(false);
return true;
});
}
}
export class ManageConnectionAction extends Action {
public static ID = 'objectExplorer.manage';
public static LABEL = localize('ManageAction', 'Manage');
private _connectionProfile: ConnectionProfile;
private _objectExplorerTreeNode: TreeNode;
private _treeSelectionHandler: TreeSelectionHandler;
protected _container: HTMLElement;
constructor(
id: string,
label: string,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IObjectExplorerService private _objectExplorerService?: IObjectExplorerService,
) {
super(id, label);
}
run(actionContext: ObjectExplorerActionsContext): TPromise<any> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
this._treeSelectionHandler.onTreeActionStateChange(true);
let promise = new TPromise<boolean>((resolve, reject) => {
this.doManage(actionContext).then((success) => {
this.done();
resolve(success);
}, error => {
this.done();
reject(error);
});
});
return promise;
}
private doManage(actionContext: ObjectExplorerActionsContext): Thenable<boolean> {
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._connectionProfile = actionContext.connectionProfile;
this._objectExplorerTreeNode = actionContext.treeNode;
if (this._connectionProfile === undefined && TreeUpdateUtils.isDatabaseNode(this._objectExplorerTreeNode)) {
this._connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
}
this._container = actionContext.container;
}
if (!this._connectionProfile) {
// This should never happen. There should be always a valid connection if the manage action is called for
// an OE node or a database node
return TPromise.wrap(true);
}
let options: IConnectionCompletionOptions = {
params: undefined,
saveTheConnection: false,
showConnectionDialogOnError: true,
showDashboard: true,
showFirewallRuleOnError: true
};
// If it's a database node just open a database connection and open dashboard,
// the node is already from an open OE session we don't need to create new session
if (TreeUpdateUtils.isAvailableDatabaseNode(this._objectExplorerTreeNode)) {
return this._connectionManagementService.showDashboard(this._connectionProfile);
} else {
return TreeUpdateUtils.connectAndCreateOeSession(this._connectionProfile, options, this._connectionManagementService, this._objectExplorerService, actionContext.tree);
}
}
private done() {
this._treeSelectionHandler.onTreeActionStateChange(false);
}
dispose(): void {
super.dispose();
}
}
export class OEScriptSelectAction extends ScriptSelectAction {
public static ID = 'objectExplorer.' + ScriptSelectAction.ID;
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
id: string, label: string,
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService);
}
public run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode;
this._container = actionContext.container;
}
this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
var ownerUri = this._connectionManagementService.getConnectionId(connectionProfile);
ownerUri = this._connectionManagementService.getFormattedUri(ownerUri, connectionProfile);
var metadata = (<TreeNode>this._objectExplorerTreeNode).metadata;
return super.run({ profile: connectionProfile, object: metadata }).then((result) => {
this._treeSelectionHandler.onTreeActionStateChange(false);
return result;
});
}
}
export class OEEditDataAction extends EditDataAction {
public static ID = 'objectExplorer.' + EditDataAction.ID;
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
id: string, label: string,
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
super(id, label, _queryEditorService, _connectionManagementService);
}
public run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode;
this._container = actionContext.container;
}
this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
var metadata = (<TreeNode>this._objectExplorerTreeNode).metadata;
return super.run({ profile: connectionProfile, object: metadata }).then((result) => {
this._treeSelectionHandler.onTreeActionStateChange(false);
return true;
});
}
}
export class OEScriptCreateAction extends ScriptCreateAction {
public static ID = 'objectExplorer.' + ScriptCreateAction.ID;
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
id: string, label: string,
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IErrorMessageService protected _errorMessageService: IErrorMessageService
) {
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService);
}
public run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode;
this._container = actionContext.container;
}
this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
var metadata = (<TreeNode>this._objectExplorerTreeNode).metadata;
var ownerUri = this._connectionManagementService.getConnectionId(connectionProfile);
ownerUri = this._connectionManagementService.getFormattedUri(ownerUri, connectionProfile);
return super.run({ profile: connectionProfile, object: metadata }).then((result) => {
this._treeSelectionHandler.onTreeActionStateChange(false);
return result;
});
}
}
export class OEScriptExecuteAction extends ScriptExecuteAction {
public static ID = 'objectExplorer.' + ScriptExecuteAction.ID;
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
id: string, label: string,
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IErrorMessageService protected _errorMessageService: IErrorMessageService
) {
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService);
}
public run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode;
this._container = actionContext.container;
}
this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
var metadata = (<TreeNode>this._objectExplorerTreeNode).metadata;
var ownerUri = this._connectionManagementService.getConnectionId(connectionProfile);
ownerUri = this._connectionManagementService.getFormattedUri(ownerUri, connectionProfile);
return super.run({ profile: connectionProfile, object: metadata }).then((result) => {
this._treeSelectionHandler.onTreeActionStateChange(false);
return result;
});
}
}
export class OEScriptAlterAction extends ScriptAlterAction {
public static ID = 'objectExplorer.' + ScriptAlterAction.ID;
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
id: string, label: string,
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IErrorMessageService protected _errorMessageService: IErrorMessageService
) {
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService);
}
public run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode;
this._container = actionContext.container;
}
this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
var metadata = (<TreeNode>this._objectExplorerTreeNode).metadata;
var ownerUri = this._connectionManagementService.getConnectionId(connectionProfile);
ownerUri = this._connectionManagementService.getFormattedUri(ownerUri, connectionProfile);
return super.run({ profile: connectionProfile, object: metadata }).then((result) => {
this._treeSelectionHandler.onTreeActionStateChange(false);
return result;
});
}
}
export class OEScriptDeleteAction extends ScriptDeleteAction {
public static ID = 'objectExplorer.' + ScriptDeleteAction.ID;
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
id: string, label: string,
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IErrorMessageService protected _errorMessageService: IErrorMessageService
) {
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService);
}
public run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode;
this._container = actionContext.container;
}
this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
var metadata = (<TreeNode>this._objectExplorerTreeNode).metadata;
var ownerUri = this._connectionManagementService.getConnectionId(connectionProfile);
ownerUri = this._connectionManagementService.getFormattedUri(ownerUri, connectionProfile);
return super.run({ profile: connectionProfile, object: metadata }).then((result) => {
this._treeSelectionHandler.onTreeActionStateChange(false);
return result;
});
}
}
export class DisconnectAction extends Action {
public static ID = 'objectExplorer.disconnect';
public static LABEL = localize('objectExplorAction.disconnect', 'Disconnect');
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
id: string,
label: string,
@IConnectionManagementService private connectionManagementService: IConnectionManagementService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
super(id, label);
}
public run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode;
this._container = actionContext.container;
}
var connectionProfile = (<TreeNode>this._objectExplorerTreeNode).getConnectionProfile();
if (this.connectionManagementService.isProfileConnected(connectionProfile)) {
this._treeSelectionHandler.onTreeActionStateChange(true);
this.connectionManagementService.disconnect(connectionProfile).then(() => {
this._treeSelectionHandler.onTreeActionStateChange(false);
});
}
return TPromise.as(true);
}
}
export class ObjectExplorerActionUtilities {
public static readonly objectExplorerElementClass = 'object-element-group';
public static readonly connectionElementClass = 'connection-tile';
private static getGroupContainer(container: HTMLElement, elementName: string): HTMLElement {
var element = container;
while (element && element.className !== elementName) {
element = element.parentElement;
}
return element ? element.parentElement : undefined;
}
public static showLoadingIcon(container: HTMLElement, elementName: string): void {
if (container) {
let groupContainer = this.getGroupContainer(container, elementName);
if (groupContainer) {
groupContainer.classList.add('icon');
groupContainer.classList.add('in-progress');
}
}
}
public static hideLoadingIcon(container: HTMLElement, elementName: string): void {
if (container) {
let element = this.getGroupContainer(container, elementName);
if (element && element.classList) {
element.classList.remove('icon');
element.classList.remove('in-progress');
}
}
}
public static getScriptMap(treeNode: TreeNode): Map<NodeType, any[]> {
let scriptMap = new Map<NodeType, any[]>();
let isMssqlProvider: boolean = true;
if (treeNode) {
let connectionProfile = treeNode.getConnectionProfile();
if (connectionProfile) {
isMssqlProvider = connectionProfile.providerName === Constants.mssqlProviderName;
}
}
let basicScripting = [OEScriptCreateAction, OEScriptDeleteAction];
let storedProcedureScripting = isMssqlProvider ? [OEScriptCreateAction, OEScriptAlterAction, OEScriptDeleteAction, OEScriptExecuteAction] :
basicScripting;
let viewScripting = isMssqlProvider ? [OEScriptSelectAction, OEScriptCreateAction, OEScriptAlterAction, OEScriptDeleteAction] :
[OEScriptSelectAction, OEScriptCreateAction, OEScriptDeleteAction];
let functionScripting = isMssqlProvider ? [OEScriptCreateAction, OEScriptAlterAction, OEScriptDeleteAction] :
basicScripting;
scriptMap.set(NodeType.AggregateFunction, functionScripting);
scriptMap.set(NodeType.PartitionFunction, functionScripting);
scriptMap.set(NodeType.ScalarValuedFunction, functionScripting);
scriptMap.set(NodeType.Schema, basicScripting);
scriptMap.set(NodeType.StoredProcedure, storedProcedureScripting);
scriptMap.set(NodeType.Table, [OEScriptSelectAction, OEEditDataAction, OEScriptCreateAction, OEScriptDeleteAction]);
scriptMap.set(NodeType.TableValuedFunction, functionScripting);
scriptMap.set(NodeType.User, basicScripting);
scriptMap.set(NodeType.UserDefinedTableType, basicScripting);
scriptMap.set(NodeType.View, viewScripting);
return scriptMap;
}
}

View File

@@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* 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 { ConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { ITree, IDataSource } from 'vs/base/parts/tree/browser/tree';
import { TPromise } from 'vs/base/common/winjs.base';
/**
* Implements the DataSource(that returns a parent/children of an element) for the recent connection tree
*/
export class RecentConnectionDataSource implements IDataSource {
/**
* Returns the unique identifier of the given element.
* No more than one element may use a given identifier.
*/
public getId(tree: ITree, element: any): string {
if (element instanceof ConnectionProfile) {
return (<ConnectionProfile>element).id;
} else if (element instanceof ConnectionProfileGroup) {
return (<ConnectionProfileGroup>element).id;
} else {
return undefined;
}
}
/**
* Returns a boolean value indicating whether the element has children.
*/
public hasChildren(tree: ITree, element: any): boolean {
if (element instanceof ConnectionProfile) {
return false;
} else if (element instanceof ConnectionProfileGroup) {
return (<ConnectionProfileGroup>element).hasChildren();
}
return false;
}
/**
* Returns the element's children as an array in a promise.
*/
public getChildren(tree: ITree, element: any): TPromise<any> {
if (element instanceof ConnectionProfile) {
return TPromise.as(null);
} else if (element instanceof ConnectionProfileGroup) {
return TPromise.as((<ConnectionProfileGroup>element).getChildren());
} else {
return TPromise.as(null);
}
}
/**
* Returns the element's parent in a promise.
*/
public getParent(tree: ITree, element: any): TPromise<any> {
if (element instanceof ConnectionProfile) {
return TPromise.as((<ConnectionProfile>element).getParent());
} else if (element instanceof ConnectionProfileGroup) {
return TPromise.as((<ConnectionProfileGroup>element).getParent());
} else {
return TPromise.as(null);
}
}
}

View File

@@ -0,0 +1,137 @@
/*---------------------------------------------------------------------------------------------
* 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 { ITree } from 'vs/base/parts/tree/browser/tree';
import { ContributableActionProvider } from 'vs/workbench/browser/actions';
import { IAction } from 'vs/base/common/actions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import {
DisconnectConnectionAction, AddServerAction,
DeleteConnectionAction, RefreshAction, EditServerGroupAction
}
from 'sql/parts/objectExplorer/viewlet/connectionTreeAction';
import {
DisconnectAction, ObjectExplorerActionUtilities,
ManageConnectionAction,
OEAction
} from 'sql/parts/objectExplorer/viewlet/objectExplorerActions';
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
import { NodeType } from 'sql/parts/objectExplorer/common/nodeType';
import { ConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { NewProfilerAction } from 'sql/parts/profiler/contrib/profilerActions';
import { TreeUpdateUtils } from 'sql/parts/objectExplorer/viewlet/treeUpdateUtils';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { ExecuteCommandAction } from 'vs/platform/actions/common/actions';
import { NewQueryAction } from 'sql/workbench/common/actions';
/**
* Provides actions for the server tree elements
*/
export class ServerTreeActionProvider extends ContributableActionProvider {
constructor(
@IInstantiationService private _instantiationService: IInstantiationService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super();
}
public hasActions(tree: ITree, element: any): boolean {
return element instanceof ConnectionProfileGroup || (element instanceof ConnectionProfile) || (element instanceof TreeNode);
}
/**
* Return actions given an element in the tree
*/
public getActions(tree: ITree, element: any): TPromise<IAction[]> {
if (element instanceof ConnectionProfile) {
return TPromise.as(this.getConnectionActions(tree, element));
}
if (element instanceof ConnectionProfileGroup) {
return TPromise.as(this.getConnectionProfileGroupActions(tree, element));
}
if (element instanceof TreeNode) {
var treeNode = <TreeNode>element;
return TPromise.as(this.getObjectExplorerNodeActions(tree, treeNode));
}
return TPromise.as([]);
}
public hasSecondaryActions(tree: ITree, element: any): boolean {
return false;
}
public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
return super.getSecondaryActions(tree, element);
}
/**
* Return actions for connection elements
*/
public getConnectionActions(tree: ITree, element: ConnectionProfile): IAction[] {
let actions: IAction[] = [];
actions.push(this._instantiationService.createInstance(ManageConnectionAction, ManageConnectionAction.ID, ManageConnectionAction.LABEL));
actions.push(this._instantiationService.createInstance(OEAction, NewQueryAction.ID, NewQueryAction.LABEL));
if (this._connectionManagementService.isProfileConnected(element)) {
actions.push(this._instantiationService.createInstance(DisconnectConnectionAction, DisconnectConnectionAction.ID, DisconnectConnectionAction.LABEL));
}
actions.push(this._instantiationService.createInstance(DeleteConnectionAction, DeleteConnectionAction.ID, DeleteConnectionAction.DELETE_CONNECTION_LABEL, element));
actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, tree, element));
if (process.env['VSCODE_DEV']) {
actions.push(this._instantiationService.createInstance(OEAction, NewProfilerAction.ID, NewProfilerAction.LABEL));
}
return actions;
}
/**
* Return actions for connection group elements
*/
public getConnectionProfileGroupActions(tree: ITree, element: ConnectionProfileGroup): IAction[] {
return [
this._instantiationService.createInstance(AddServerAction, AddServerAction.ID, AddServerAction.LABEL),
this._instantiationService.createInstance(EditServerGroupAction, EditServerGroupAction.ID, EditServerGroupAction.LABEL, element),
this._instantiationService.createInstance(DeleteConnectionAction, DeleteConnectionAction.ID, DeleteConnectionAction.DELETE_CONNECTION_GROUP_LABEL, element)
];
}
/**
* Return actions for OE elements
*/
public getObjectExplorerNodeActions(tree: ITree, treeNode: TreeNode): IAction[] {
let actions = [];
if (TreeUpdateUtils.isDatabaseNode(treeNode)) {
if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
actions.push(this._instantiationService.createInstance(ManageConnectionAction, ManageConnectionAction.ID, ManageConnectionAction.LABEL));
} else {
return actions;
}
}
actions.push(this._instantiationService.createInstance(OEAction, NewQueryAction.ID, NewQueryAction.LABEL));
let scriptMap: Map<NodeType, any[]> = ObjectExplorerActionUtilities.getScriptMap(treeNode);
let supportedActions = scriptMap.get(treeNode.nodeTypeId);
let self = this;
if (supportedActions !== null && supportedActions !== undefined) {
supportedActions.forEach(action => {
actions.push(self._instantiationService.createInstance(action, action.ID, action.LABEL));
});
}
actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, tree, treeNode));
if (treeNode.isTopLevel()) {
actions.push(this._instantiationService.createInstance(DisconnectAction, DisconnectAction.ID, DisconnectAction.LABEL));
}
return actions;
}
}

View File

@@ -0,0 +1,110 @@
/*---------------------------------------------------------------------------------------------
* 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 { ITree, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
import treedefaults = require('vs/base/parts/tree/browser/treeDefaults');
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { ServerTreeActionProvider } from 'sql/parts/objectExplorer/viewlet/serverTreeActionProvider';
import { ObjectExplorerActionsContext } from 'sql/parts/objectExplorer/viewlet/objectExplorerActions';
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
/**
* Extends the tree controller to handle clicks on the tree elements
*/
export class ServerTreeController extends treedefaults.DefaultController {
constructor(private actionProvider: ServerTreeActionProvider,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IContextMenuService private contextMenuService: IContextMenuService,
@ITelemetryService private telemetryService: ITelemetryService,
@IKeybindingService private keybindingService: IKeybindingService
) {
super({ clickBehavior: treedefaults.ClickBehavior.ON_MOUSE_DOWN });
}
public onClick(tree: ITree, element: any, event: IMouseEvent): boolean {
return super.onClick(tree, element, event);
}
protected onLeftClick(tree: ITree, element: any, event: IMouseEvent, origin: string = 'mouse'): boolean {
return super.onLeftClick(tree, element, event, origin);
}
// Do not allow left / right to expand and collapse groups #7848
protected onLeft(tree: ITree, event: IKeyboardEvent): boolean {
return true;
}
protected onRight(tree: ITree, event: IKeyboardEvent): boolean {
return true;
}
protected onEnter(tree: ITree, event: IKeyboardEvent): boolean {
return super.onEnter(tree, event);
}
/**
* Return actions in the context menu
*/
public onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean {
if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
return false;
}
// Check if clicked on some element
if (element === tree.getInput()) {
return false;
}
event.preventDefault();
event.stopPropagation();
tree.setFocus(element);
let parent: ConnectionProfileGroup = undefined;
if (element instanceof ConnectionProfileGroup) {
parent = <ConnectionProfileGroup>element;
}
else if (element instanceof ConnectionProfile) {
parent = (<ConnectionProfile>element).parent;
}
var actionContext: any;
if (element instanceof TreeNode) {
actionContext = new ObjectExplorerActionsContext();
actionContext.container = event.target;
actionContext.treeNode = <TreeNode>element;
actionContext.tree = tree;
} else if (element instanceof ConnectionProfile) {
actionContext = new ObjectExplorerActionsContext();
actionContext.container = event.target;
actionContext.connectionProfile = <ConnectionProfile>element;
actionContext.tree = tree;
} else {
actionContext = element;
}
let anchor = { x: event.posx + 1, y: event.posy };
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => this.actionProvider.getActions(tree, element),
getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id),
onHide: (wasCancelled?: boolean) => {
if (wasCancelled) {
tree.DOMFocus();
}
},
getActionsContext: () => (actionContext)
});
return true;
}
}

View File

@@ -0,0 +1,110 @@
/*---------------------------------------------------------------------------------------------
* 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 { ConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { ITree, IDataSource } from 'vs/base/parts/tree/browser/tree';
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { TPromise } from 'vs/base/common/winjs.base';
import { TreeUpdateUtils } from 'sql/parts/objectExplorer/viewlet/treeUpdateUtils';
import { IConnectionManagementService, IErrorMessageService } from 'sql/parts/connection/common/connectionManagement';
import Severity from 'vs/base/common/severity';
/**
* Implements the DataSource(that returns a parent/children of an element) for the server tree
*/
export class ServerTreeDataSource implements IDataSource {
constructor(
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IErrorMessageService private _errorMessageService: IErrorMessageService
) {
}
/**
* Returns the unique identifier of the given element.
* No more than one element may use a given identifier.
*/
public getId(tree: ITree, element: any): string {
if (element instanceof ConnectionProfile) {
return (<ConnectionProfile>element).id;
} else if (element instanceof ConnectionProfileGroup) {
return (<ConnectionProfileGroup>element).id;
} else if (element instanceof TreeNode) {
return (<TreeNode>element).id;
} else {
return undefined;
}
}
/**
* Returns a boolean value indicating whether the element has children.
*/
public hasChildren(tree: ITree, element: any): boolean {
if (element instanceof ConnectionProfile) {
return true;
} else if (element instanceof ConnectionProfileGroup) {
return (<ConnectionProfileGroup>element).hasChildren();
} else if (element instanceof TreeNode) {
return !(<TreeNode>element).isAlwaysLeaf;
}
return false;
}
/**
* Returns the element's children as an array in a promise.
*/
public getChildren(tree: ITree, element: any): TPromise<any> {
return new TPromise<any>((resolve) => {
if (element instanceof ConnectionProfile) {
TreeUpdateUtils.getObjectExplorerNode(<ConnectionProfile>element, this._connectionManagementService, this._objectExplorerService).then(nodes => {
resolve(nodes);
}, error => {
resolve([]);
});
} else if (element instanceof ConnectionProfileGroup) {
resolve((<ConnectionProfileGroup>element).getChildren());
} else if (element instanceof TreeNode) {
var node = <TreeNode>element;
if (node.children) {
resolve(node.children);
} else {
this._objectExplorerService.resolveTreeNodeChildren(node.getSession(), node).then(() => {
resolve(node.children);
}, expandError => {
this.showError(expandError);
resolve([]);
});
}
} else {
resolve([]);
}
});
}
/**
* Returns the element's parent in a promise.
*/
public getParent(tree: ITree, element: any): TPromise<any> {
if (element instanceof ConnectionProfile) {
return TPromise.as((<ConnectionProfile>element).getParent());
} else if (element instanceof ConnectionProfileGroup) {
return TPromise.as((<ConnectionProfileGroup>element).getParent());
} else if (element instanceof TreeNode) {
return TPromise.as(TreeUpdateUtils.getObjectExplorerParent(<TreeNode>element, this._connectionManagementService));
} else {
return TPromise.as(null);
}
}
private showError(errorMessage: string) {
if (this._errorMessageService) {
this._errorMessageService.showDialog(Severity.Error, '', errorMessage);
}
}
}

View File

@@ -0,0 +1,198 @@
/*---------------------------------------------------------------------------------------------
* 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!sql/media/objectTypes/objecttypes';
import 'vs/css!sql/media/icons/common-icons';
import * as dom from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
import { ConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
import { IConnectionProfileGroupTemplateData, IConnectionTemplateData, IObjectExplorerTemplateData } from 'sql/parts/objectExplorer/viewlet/templateData';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
/**
* Renders the tree items.
* Uses the dom template to render connection groups and connections.
*/
export class ServerTreeRenderer implements IRenderer {
public static CONNECTION_HEIGHT = 25;
public static CONNECTION_GROUP_HEIGHT = 38;
private static CONNECTION_TEMPLATE_ID = 'connectionProfile';
private static CONNECTION_GROUP_TEMPLATE_ID = 'connectionProfileGroup';
public static OBJECTEXPLORER_HEIGHT = 25;
private static OBJECTEXPLORER_TEMPLATE_ID = 'objectExplorer';
/**
* _isCompact is used to render connections tiles with and without the action buttons.
* When set to true, like in the connection dialog recent connections tree, the connection
* tile is rendered without the action buttons( such as connect, new query).
*/
private _isCompact: boolean = false;
constructor(isCompact: boolean,
@IInstantiationService private _instantiationService: IInstantiationService,
@IContextViewService private _contextViewService: IContextViewService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IThemeService private _themeService: IThemeService
) {
// isCompact defaults to false unless explicitly set by instantiation call.
if (isCompact) {
this._isCompact = isCompact;
}
}
/**
* Returns the element's height in the tree, in pixels.
*/
public getHeight(tree: ITree, element: any): number {
if (element instanceof ConnectionProfileGroup) {
return ServerTreeRenderer.CONNECTION_GROUP_HEIGHT;
} else if (element instanceof ConnectionProfile) {
return ServerTreeRenderer.CONNECTION_HEIGHT;
}
return ServerTreeRenderer.OBJECTEXPLORER_HEIGHT;
}
/**
* Returns a template ID for a given element.
*/
public getTemplateId(tree: ITree, element: any): string {
if (element instanceof ConnectionProfileGroup) {
return ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID;
} else if (element instanceof ConnectionProfile) {
return ServerTreeRenderer.CONNECTION_TEMPLATE_ID;
}
return ServerTreeRenderer.OBJECTEXPLORER_TEMPLATE_ID;
}
/**
* Render template in a dom element based on template id
*/
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
if (templateId === ServerTreeRenderer.CONNECTION_TEMPLATE_ID) {
const connectionTemplate: IObjectExplorerTemplateData = Object.create(null);
connectionTemplate.root = dom.append(container, dom.$('.connection-tile'));
connectionTemplate.icon = dom.append(connectionTemplate.root, dom.$('div.icon server-page'));
connectionTemplate.label = dom.append(connectionTemplate.root, dom.$('div.label'));
return connectionTemplate;
} else if (templateId === ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID) {
container.classList.add('server-group');
const groupTemplate: IConnectionProfileGroupTemplateData = Object.create(null);
groupTemplate.root = dom.append(container, dom.$('.server-group'));
groupTemplate.name = dom.append(groupTemplate.root, dom.$('span.name'));
return groupTemplate;
} else {
const objectExplorerTemplate: IObjectExplorerTemplateData = Object.create(null);
objectExplorerTemplate.root = dom.append(container, dom.$('.object-element-group'));
objectExplorerTemplate.icon = dom.append(objectExplorerTemplate.root, dom.$('div.object-icon'));
objectExplorerTemplate.label = dom.append(objectExplorerTemplate.root, dom.$('div.label'));
return objectExplorerTemplate;
}
}
/**
* Render a element, given an object bag returned by the template
*/
public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
if (templateId === ServerTreeRenderer.CONNECTION_TEMPLATE_ID) {
this.renderConnection(tree, element, templateData);
} else if (templateId === ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID) {
this.renderConnectionProfileGroup(tree, element, templateData);
} else {
this.renderObjectExplorer(tree, element, templateData);
}
}
private renderObjectExplorer(tree: ITree, treeNode: TreeNode, templateData: IObjectExplorerTemplateData): void {
var iconName = treeNode.nodeTypeId;
if (treeNode.nodeStatus) {
iconName = treeNode.nodeTypeId + '_' + treeNode.nodeStatus;
}
if (treeNode.nodeSubType) {
iconName = treeNode.nodeTypeId + '_' + treeNode.nodeSubType;
}
let tokens: string[] = [];
for (var index = 1; index < templateData.icon.classList.length; index++) {
tokens.push(templateData.icon.classList.item(index));
}
templateData.icon.classList.remove(...tokens);
templateData.icon.classList.add('icon');
let iconLoweCaseName = iconName.toLocaleLowerCase();
templateData.icon.classList.add(iconLoweCaseName);
templateData.label.textContent = treeNode.label;
templateData.root.title = treeNode.label;
}
private renderConnection(tree: ITree, connection: ConnectionProfile, templateData: IConnectionTemplateData): void {
if (!this._isCompact) {
if (this._connectionManagementService.isConnected(undefined, connection)) {
templateData.icon.classList.remove('disconnected');
templateData.icon.classList.add('connected');
} else {
templateData.icon.classList.remove('connected');
templateData.icon.classList.add('disconnected');
}
}
let label = connection.title;
if (!connection.isConnectionOptionsValid) {
label = localize('loading', 'Loading...');
}
templateData.label.textContent = label;
templateData.root.title = label;
templateData.connectionProfile = connection;
}
private renderConnectionProfileGroup(tree: ITree, connectionProfileGroup: ConnectionProfileGroup, templateData: IConnectionProfileGroupTemplateData): void {
var rowElement = this.findParentElement(templateData.root, 'monaco-tree-row');
if (rowElement) {
if (connectionProfileGroup.color) {
rowElement.style.background = connectionProfileGroup.color;
} else {
// If the group doesn't contain specific color, assign the default color
rowElement.style.background = '#515151';
}
}
if (connectionProfileGroup.description && (connectionProfileGroup.description !== '')) {
templateData.root.title = connectionProfileGroup.description;
}
templateData.name.hidden = false;
templateData.name.textContent = connectionProfileGroup.name;
}
/**
* Returns the first parent which contains the className
*/
private findParentElement(container: HTMLElement, className: string): HTMLElement {
var currentElement = container;
while (currentElement) {
if (currentElement.className.includes(className)) {
break;
}
currentElement = currentElement.parentElement;
}
return currentElement;
}
public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
// no op
// InputBox disposed in wrapUp
}
}

View File

@@ -0,0 +1,473 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/serverTreeActions';
import * as errors from 'vs/base/common/errors';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import * as builder from 'vs/base/browser/builder';
import Severity from 'vs/base/common/severity';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { ConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import * as ConnectionUtils from 'sql/parts/connection/common/utils';
import { ActiveConnectionsFilterAction } from 'sql/parts/objectExplorer/viewlet/connectionTreeAction';
import { IConnectionManagementService, IErrorMessageService } from 'sql/parts/connection/common/connectionManagement';
import { TreeCreationUtils } from 'sql/parts/objectExplorer/viewlet/treeCreationUtils';
import { TreeUpdateUtils } from 'sql/parts/objectExplorer/viewlet/treeUpdateUtils';
import { TreeSelectionHandler } from 'sql/parts/objectExplorer/viewlet/treeSelectionHandler';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { Button } from 'sql/base/browser/ui/button/button';
import { attachButtonStyler } from 'sql/common/theme/styler';
import Event, { Emitter } from 'vs/base/common/event';
import { TreeNode, TreeItemCollapsibleState } from 'sql/parts/objectExplorer/common/treeNode';
const $ = builder.$;
/**
* ServerTreeview implements the dynamic tree view.
*/
export class ServerTreeView {
public messages: builder.Builder;
private _buttonSection: builder.Builder;
private _treeSelectionHandler: TreeSelectionHandler;
private _activeConnectionsFilterAction: ActiveConnectionsFilterAction;
private _tree: ITree;
private _toDispose: IDisposable[] = [];
private _onSelectionOrFocusChange: Emitter<void>;
constructor(
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IThemeService private _themeService: IThemeService,
@IErrorMessageService private _errorMessageService: IErrorMessageService,
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
) {
this._activeConnectionsFilterAction = this._instantiationService.createInstance(
ActiveConnectionsFilterAction,
ActiveConnectionsFilterAction.ID,
ActiveConnectionsFilterAction.LABEL,
this);
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
this._onSelectionOrFocusChange = new Emitter();
}
/**
* Get active connections filter action
*/
public get activeConnectionsFilterAction(): ActiveConnectionsFilterAction {
return this._activeConnectionsFilterAction;
}
/**
* Event fired when the tree's selection or focus changes
*/
public get onSelectionOrFocusChange(): Event<void> {
return this._onSelectionOrFocusChange.event;
}
/**
* Render the view body
*/
public renderBody(container: HTMLElement): Thenable<void> {
// Add div to display no connections found message and hide it by default
this.messages = $('div.title').appendTo(container);
$('span').style('padding-left', '10px').text('No connections found.').appendTo(this.messages);
this.messages.hide();
if (!this._connectionManagementService.hasRegisteredServers()) {
this._activeConnectionsFilterAction.enabled = false;
this._buttonSection = $('div.button-section').appendTo(container);
var connectButton = new Button(this._buttonSection);
connectButton.label = localize('serverTree.addConnection', 'Add Connection');
this._toDispose.push(attachButtonStyler(connectButton, this._themeService));
this._toDispose.push(connectButton.onDidClick(() => {
this._connectionManagementService.showConnectionDialog();
}));
}
this._tree = TreeCreationUtils.createRegisteredServersTree(container, this._instantiationService);
//this._tree.setInput(undefined);
this._toDispose.push(this._tree.onDidChangeSelection((event) => this.onSelected(event)));
this._toDispose.push(this._tree.onDidBlur(() => this._onSelectionOrFocusChange.fire()));
this._toDispose.push(this._tree.onDidChangeFocus(() => this._onSelectionOrFocusChange.fire()));
// Theme styler
this._toDispose.push(attachListStyler(this._tree, this._themeService));
const self = this;
// Refresh Tree when these events are emitted
this._toDispose.push(this._connectionManagementService.onAddConnectionProfile((newProfile: IConnectionProfile) => {
self.handleAddConnectionProfile(newProfile);
})
);
this._toDispose.push(this._connectionManagementService.onDeleteConnectionProfile(() => {
self.refreshTree();
})
);
this._toDispose.push(this._connectionManagementService.onDisconnect((connectionParams) => {
if (self.isObjectExplorerConnectionUri(connectionParams.connectionUri)) {
self.deleteObjectExplorerNodeAndRefreshTree(connectionParams.connectionProfile);
}
}));
if (this._objectExplorerService && this._objectExplorerService.onUpdateObjectExplorerNodes) {
this._toDispose.push(this._objectExplorerService.onUpdateObjectExplorerNodes(args => {
if (args.errorMessage) {
this.showError(args.errorMessage);
}
if (args.connection) {
self.onObjectExplorerSessionCreated(args.connection);
}
}));
}
return new Promise<void>((resolve, reject) => {
self.refreshTree();
let root = <ConnectionProfileGroup>this._tree.getInput();
if (root && !root.hasValidConnections) {
this._treeSelectionHandler.onTreeActionStateChange(true);
resolve();
} else {
resolve();
}
});
}
private isObjectExplorerConnectionUri(uri: string): boolean {
let isBackupRestoreUri: boolean = uri.indexOf(ConnectionUtils.ConnectionUriBackupIdAttributeName) >= 0 ||
uri.indexOf(ConnectionUtils.ConnectionUriRestoreIdAttributeName) >= 0;
return uri && uri.startsWith(ConnectionUtils.uriPrefixes.default) && !isBackupRestoreUri;
}
private handleAddConnectionProfile(newProfile: IConnectionProfile) {
if (newProfile) {
let groups = this._connectionManagementService.getConnectionGroups();
let profile = ConnectionUtils.findProfileInGroup(newProfile, groups);
if (profile) {
newProfile = profile;
}
}
if (this._buttonSection) {
this._buttonSection.getHTMLElement().style.display = 'none';
this._activeConnectionsFilterAction.enabled = true;
}
let currentSelections = this._tree.getSelection();
let currentSelectedElement = currentSelections && currentSelections.length >= 1 ? currentSelections[0] : undefined;
let newProfileIsSelected = currentSelectedElement && newProfile ? currentSelectedElement.id === newProfile.id : false;
if (newProfile && currentSelectedElement && !newProfileIsSelected) {
this._tree.clearSelection();
}
this.refreshTree();
if (newProfile && !newProfileIsSelected) {
this._tree.reveal(newProfile);
this._tree.select(newProfile);
}
}
private showError(errorMessage: string) {
if (this._errorMessageService) {
this._errorMessageService.showDialog(Severity.Error, '', errorMessage);
}
}
private getConnectionInTreeInput(connectionId: string): ConnectionProfile {
let root = TreeUpdateUtils.getTreeInput(this._connectionManagementService);
let connections = ConnectionProfileGroup.getConnectionsInGroup(root);
let results = connections.filter(con => {
if (connectionId === con.id) {
return true;
} else {
return false;
}
});
if (results && results.length > 0) {
return results[0];
}
return null;
}
private onObjectExplorerSessionCreated(connection: IConnectionProfile) {
var conn = this.getConnectionInTreeInput(connection.id);
if (conn) {
this._tree.refresh(conn).then(() => {
return this._tree.expand(conn).then(() => {
return this._tree.reveal(conn, 0.5).then(() => {
this._treeSelectionHandler.onTreeActionStateChange(false);
});
});
}).done(null, errors.onUnexpectedError);
}
}
public addObjectExplorerNodeAndRefreshTree(connection: IConnectionProfile): void {
this.messages.hide();
if (!this._objectExplorerService.getObjectExplorerNode(connection)) {
this._objectExplorerService.updateObjectExplorerNodes(connection).then(() => {
// The oe request is sent. an event will be raised when the session is created
}, error => {
});
}
}
public deleteObjectExplorerNodeAndRefreshTree(connection: IConnectionProfile): void {
if (connection) {
var conn = this.getConnectionInTreeInput(connection.id);
if (conn) {
this._objectExplorerService.deleteObjectExplorerNode(conn);
this._tree.collapse(conn);
this._tree.refresh(conn);
}
}
}
public refreshTree(): void {
this.messages.hide();
this.clearOtherActions();
TreeUpdateUtils.registeredServerUpdate(this._tree, this._connectionManagementService);
}
/**
* Filter connections based on view (recent/active)
*/
private filterConnections(treeInput: ConnectionProfileGroup[], view: string): ConnectionProfileGroup[] {
if (!treeInput || treeInput.length === 0) {
return undefined;
}
let result = treeInput.map(group => {
// Keep active/recent connections and remove the rest
if (group.connections) {
group.connections = group.connections.filter(con => {
if (view === 'active') {
return this._connectionManagementService.isConnected(undefined, con);
} else if (view === 'recent') {
return this._connectionManagementService.isRecent(con);
}
return false;
});
}
group.children = this.filterConnections(group.children, view);
// Remove subgroups that are undefined
if (group.children) {
group.children = group.children.filter(group => {
return (group) ? true : false;
});
}
// Return a group only if it has a filtered result or subgroup.
if ((group.connections && group.connections.length > 0) || (group.children && group.children.length > 0)) {
return group;
}
return undefined;
});
return result;
}
/**
* Set tree elements based on the view (recent/active)
*/
public showFilteredTree(view: string): void {
const self = this;
this.messages.hide();
// Clear other action views if user switched between two views
this.clearOtherActions(view);
let root = TreeUpdateUtils.getTreeInput(this._connectionManagementService);
let treeInput: ConnectionProfileGroup = null;
if (root) {
// Filter results based on view
let filteredResults = this.filterConnections([root], view);
if (!filteredResults || !filteredResults[0]) {
this.messages.show();
this.messages.domFocus();
} else {
treeInput = filteredResults[0];
}
this._tree.setInput(treeInput).done(() => {
if (this.messages.isHidden()) {
self._tree.getFocus();
self._tree.expandAll(ConnectionProfileGroup.getSubgroups(treeInput));
} else {
self._tree.clearFocus();
}
}, errors.onUnexpectedError);
} else {
//no op
}
}
/**
* Searches and sets the tree input to the results
*/
public searchTree(searchString: string): void {
if (!searchString) {
return;
}
const self = this;
this.messages.hide();
// Clear other actions if user searched during other views
this.clearOtherActions();
// Filter connections based on search
let filteredResults = this.searchConnections(searchString);
if (!filteredResults || filteredResults.length === 0) {
this.messages.show();
this.messages.domFocus();
}
// Add all connections to tree root and set tree input
let treeInput = new ConnectionProfileGroup('searchroot', undefined, 'searchroot', undefined, undefined);
treeInput.addConnections(filteredResults);
this._tree.setInput(treeInput).done(() => {
if (this.messages.isHidden()) {
self._tree.getFocus();
self._tree.expandAll(ConnectionProfileGroup.getSubgroups(treeInput));
} else {
self._tree.clearFocus();
}
}, errors.onUnexpectedError);
}
/**
* Searches through all the connections and returns a list of matching connections
*/
private searchConnections(searchString: string): ConnectionProfile[] {
let root = TreeUpdateUtils.getTreeInput(this._connectionManagementService);
let connections = ConnectionProfileGroup.getConnectionsInGroup(root);
let results = connections.filter(con => {
if (searchString && (searchString.length > 0)) {
return this.isMatch(con, searchString);
} else {
return false;
}
});
return results;
}
/**
* Returns true if the connection matches the search string.
* For now, the search criteria is true if the
* server name or database name contains the search string (ignores case).
*/
private isMatch(connection: ConnectionProfile, searchString: string): boolean {
searchString = searchString.trim().toLocaleUpperCase();
if (this.checkIncludes(searchString, connection.databaseName) || this.checkIncludes(searchString, connection.serverName)) {
return true;
}
return false;
}
private checkIncludes(searchString: string, candidate: string): boolean {
if (candidate && searchString) {
return candidate.toLocaleUpperCase().includes(searchString);
}
return false;
}
/**
* Clears the toggle icons for active and recent
*/
private clearOtherActions(view?: string) {
if (!view) {
this._activeConnectionsFilterAction.isSet = false;
}
if (view === 'recent') {
this._activeConnectionsFilterAction.isSet = false;
}
}
private onSelected(event: any): void {
this._treeSelectionHandler.onTreeSelect(event, this._tree, this._connectionManagementService, this._objectExplorerService, () => this._onSelectionOrFocusChange.fire());
this._onSelectionOrFocusChange.fire();
}
/**
* set the layout of the view
*/
public layout(height: number): void {
this._tree.layout(height);
}
/**
* set the visibility of the view
*/
public setVisible(visible: boolean): void {
if (visible) {
this._tree.onVisible();
} else {
this._tree.onHidden();
}
}
/**
* Get the list of selected nodes in the tree
*/
public getSelection(): any[] {
return this._tree.getSelection();
}
/**
* Get whether the tree view currently has focus
*/
public isFocused(): boolean {
return this._tree.isDOMFocused();
}
/**
* Set whether the given element is expanded or collapsed
*/
public setExpandedState(element: TreeNode | ConnectionProfile, expandedState: TreeItemCollapsibleState): Thenable<void> {
if (expandedState === TreeItemCollapsibleState.Collapsed) {
return this._tree.collapse(element);
} else if (expandedState === TreeItemCollapsibleState.Expanded) {
return this._tree.expand(element);
}
return Promise.resolve();
}
/**
* Reveal the given element in the tree
*/
public reveal(element: TreeNode | ConnectionProfile): Thenable<void> {
return this._tree.reveal(element);
}
/**
* Select the given element in the tree and clear any other selections
*/
public setSelected(element: TreeNode | ConnectionProfile, selected: boolean, clearOtherSelections: boolean): Thenable<void> {
if (clearOtherSelections || (selected && clearOtherSelections !== false)) {
this._tree.clearSelection();
}
if (selected) {
this._tree.select(element);
return this._tree.reveal(element);
} else {
this._tree.deselect(element);
return Promise.resolve();
}
}
/**
* Check if the given element in the tree is expanded
*/
public isExpanded(element: TreeNode | ConnectionProfile): boolean {
return this._tree.isExpanded(element);
}
/**
* dispose the server tree view
*/
public dispose(): void {
this._tree.dispose();
this._toDispose = dispose(this._toDispose);
}
}

View File

@@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
export interface IConnectionTemplateData {
root: HTMLElement;
label: HTMLSpanElement;
icon: HTMLElement;
connectionProfile: ConnectionProfile;
}
export interface IConnectionProfileGroupTemplateData {
root: HTMLElement;
name: HTMLSpanElement;
inputBox: InputBox;
}
export interface IObjectExplorerTemplateData {
root: HTMLElement;
label: HTMLSpanElement;
icon: HTMLElement;
treeNode: TreeNode;
}

View File

@@ -0,0 +1,61 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/serverTreeActions';
import nls = require('vs/nls');
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServerTreeRenderer } from 'sql/parts/objectExplorer/viewlet/serverTreeRenderer';
import { ServerTreeDataSource } from 'sql/parts/objectExplorer/viewlet/serverTreeDataSource';
import { ServerTreeController } from 'sql/parts/objectExplorer/viewlet/serverTreeController';
import { ServerTreeActionProvider } from 'sql/parts/objectExplorer/viewlet/serverTreeActionProvider';
import { DefaultFilter, DefaultAccessibilityProvider, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
import { IController } from 'vs/base/parts/tree/browser/tree';
import { ServerTreeDragAndDrop, RecentConnectionsDragAndDrop } from 'sql/parts/objectExplorer/viewlet/dragAndDropController';
import { RecentConnectionDataSource } from 'sql/parts/objectExplorer/viewlet/recentConnectionDataSource';
export class TreeCreationUtils {
/**
* Create a Recent Connections tree
*/
public static createConnectionTree(treeContainer: HTMLElement, instantiationService: IInstantiationService, useController?: IController): Tree {
const dataSource = instantiationService.createInstance(RecentConnectionDataSource);
const renderer = instantiationService.createInstance(ServerTreeRenderer, true);
const controller = useController ? useController : new DefaultController();
const dnd = instantiationService.createInstance(RecentConnectionsDragAndDrop);
const filter = new DefaultFilter();
const sorter = undefined;
const accessibilityProvider = new DefaultAccessibilityProvider();
return new Tree(treeContainer, { dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider },
{
indentPixels: 10,
twistiePixels: 20,
ariaLabel: nls.localize('treeAriaLabel', "Recent Connections")
});
}
/**
* Create a Servers viewlet tree
*/
public static createRegisteredServersTree(treeContainer: HTMLElement, instantiationService: IInstantiationService): Tree {
const dataSource = instantiationService.createInstance(ServerTreeDataSource);
const actionProvider = instantiationService.createInstance(ServerTreeActionProvider);
const renderer = instantiationService.createInstance(ServerTreeRenderer, false);
const controller = instantiationService.createInstance(ServerTreeController, actionProvider);
const dnd = instantiationService.createInstance(ServerTreeDragAndDrop);
const filter = new DefaultFilter();
const sorter = undefined;
const accessibilityProvider = new DefaultAccessibilityProvider();
return new Tree(treeContainer, { dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider },
{
indentPixels: 10,
twistiePixels: 20,
ariaLabel: nls.localize('treeCreation.regTreeAriaLabel', "Servers")
});
}
}

View File

@@ -0,0 +1,120 @@
/*---------------------------------------------------------------------------------------------
* 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 } from 'sql/parts/connection/common/connectionManagement';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress';
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
import { TreeUpdateUtils } from 'sql/parts/objectExplorer/viewlet/treeUpdateUtils';
export class TreeSelectionHandler {
progressRunner: IProgressRunner;
private _clicks: number = 0;
private _doubleClickTimeoutId: number = -1;
constructor( @IProgressService private _progressService: IProgressService) {
}
public onTreeActionStateChange(started: boolean): void {
if (this.progressRunner) {
this.progressRunner.done();
}
if (started) {
this.progressRunner = this._progressService.show(true);
} else {
this.progressRunner = null;
}
}
private isMouseEvent(event: any): boolean {
return event && event.payload && event.payload.origin === 'mouse';
}
/**
* Handle selection of tree element
*/
public onTreeSelect(event: any, tree: ITree, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, connectionCompleteCallback: () => void) {
if (this.isMouseEvent(event)) {
this._clicks++;
}
// clear pending click timeouts to avoid sending multiple events on double-click
if (this._doubleClickTimeoutId !== -1) {
clearTimeout(this._doubleClickTimeoutId);
}
let isKeyboard = event && event.payload && event.payload.origin === 'keyboard';
// grab the current selection for use later
let selection = tree.getSelection();
this._doubleClickTimeoutId = setTimeout(() => {
// don't send tree update events while dragging
if (!TreeUpdateUtils.isInDragAndDrop) {
let isDoubleClick = this._clicks > 1;
this.handleTreeItemSelected(connectionManagementService, objectExplorerService, isDoubleClick, isKeyboard, selection, tree, connectionCompleteCallback);
}
this._clicks = 0;
this._doubleClickTimeoutId = -1;
}, 300);
}
/**
*
* @param connectionManagementService
* @param objectExplorerService
* @param isDoubleClick
* @param isKeyboard
* @param selection
* @param tree
* @param connectionCompleteCallback A function that gets called after a connection is established due to the selection, if needed
*/
private handleTreeItemSelected(connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, isDoubleClick: boolean, isKeyboard: boolean, selection: any[], tree: ITree, connectionCompleteCallback: () => void): void {
let connectionProfile: ConnectionProfile = undefined;
let options: IConnectionCompletionOptions = {
params: undefined,
saveTheConnection: false,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true,
showDashboard: isDoubleClick // only show the dashboard if the action is double click
};
if (selection && selection.length > 0 && (selection[0] instanceof ConnectionProfile)) {
connectionProfile = <ConnectionProfile>selection[0];
if (connectionProfile) {
this.onTreeActionStateChange(true);
TreeUpdateUtils.connectAndCreateOeSession(connectionProfile, options, connectionManagementService, objectExplorerService, tree).then(sessionCreated => {
if (!sessionCreated) {
this.onTreeActionStateChange(false);
}
if (connectionCompleteCallback) {
connectionCompleteCallback();
}
}, error => {
this.onTreeActionStateChange(false);
});
}
} else if (isDoubleClick && selection && selection.length > 0 && (selection[0] instanceof TreeNode)) {
let treeNode = <TreeNode>selection[0];
if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
connectionProfile = TreeUpdateUtils.getConnectionProfile(treeNode);
if (connectionProfile) {
connectionManagementService.showDashboard(connectionProfile);
}
}
}
if (isKeyboard) {
tree.toggleExpansion(selection[0]);
}
}
}

View File

@@ -0,0 +1,278 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
import { IConnectionManagementService, IConnectionCompletionOptions, IConnectionCallbacks } from 'sql/parts/connection/common/connectionManagement';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { NodeType } from 'sql/parts/objectExplorer/common/nodeType';
import { TPromise } from 'vs/base/common/winjs.base';
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
import errors = require('vs/base/common/errors');
export class TreeUpdateUtils {
public static isInDragAndDrop: boolean = false;
/**
* Set input for the tree.
*/
public static structuralTreeUpdate(tree: ITree, viewKey: string, connectionManagementService: IConnectionManagementService): void {
let selectedElement: any;
let targetsToExpand: any[];
if (tree) {
let selection = tree.getSelection();
if (selection && selection.length === 1) {
selectedElement = <any>selection[0];
}
targetsToExpand = tree.getExpandedElements();
}
let groups;
let treeInput = new ConnectionProfileGroup('root', null, undefined, undefined, undefined);
if (viewKey === 'recent') {
groups = connectionManagementService.getRecentConnections();
treeInput.addConnections(groups);
} else if (viewKey === 'active') {
groups = connectionManagementService.getActiveConnections();
treeInput.addConnections(groups);
} else if (viewKey === 'saved') {
treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
}
tree.setInput(treeInput).done(() => {
// Make sure to expand all folders that where expanded in the previous session
if (targetsToExpand) {
tree.expandAll(targetsToExpand);
}
if (selectedElement) {
tree.select(selectedElement);
}
tree.getFocus();
});
}
/**
* Set input for the registered servers tree.
*/
public static registeredServerUpdate(tree: ITree, connectionManagementService: IConnectionManagementService, elementToSelect?: any): void {
let selectedElement: any = elementToSelect;
let targetsToExpand: any[];
// Focus
tree.DOMFocus();
if (tree) {
let selection = tree.getSelection();
if (!selectedElement) {
if (selection && selection.length === 1) {
selectedElement = <any>selection[0];
}
}
targetsToExpand = tree.getExpandedElements();
if (selectedElement && targetsToExpand.indexOf(selectedElement) === -1) {
targetsToExpand.push(selectedElement);
}
}
let treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
if (treeInput) {
if (treeInput !== tree.getInput()) {
tree.setInput(treeInput).then(() => {
// Make sure to expand all folders that where expanded in the previous session
if (targetsToExpand) {
tree.expandAll(targetsToExpand);
}
if (selectedElement) {
tree.select(selectedElement);
}
tree.getFocus();
}, errors.onUnexpectedError);
}
}
}
public static getTreeInput(connectionManagementService: IConnectionManagementService): ConnectionProfileGroup {
let groups = connectionManagementService.getConnectionGroups();
if (groups && groups.length > 0) {
let treeInput = groups[0];
treeInput.name = 'root';
return treeInput;
}
// Should never get to this case.
return undefined;
}
public static hasObjectExplorerNode(connection: ConnectionProfile, connectionManagementService: IConnectionManagementService): boolean {
let isConnected = connectionManagementService.isConnected(undefined, connection);
return isConnected;
}
public static connectIfNotConnected(
connection: ConnectionProfile,
options: IConnectionCompletionOptions,
connectionManagementService: IConnectionManagementService,
tree: ITree): TPromise<ConnectionProfile> {
return new TPromise<ConnectionProfile>((resolve, reject) => {
if (!connectionManagementService.isProfileConnected(connection)) {
// don't try to reconnect if currently connecting
if (connectionManagementService.isProfileConnecting(connection)) {
resolve(undefined);
// else if we aren't connected or connecting then try to connect
} else {
let callbacks: IConnectionCallbacks = undefined;
if (tree) {
// Show the spinner in OE by adding the 'loading' trait to the connection, and set up callbacks to hide the spinner
tree.addTraits('loading', [connection]);
callbacks = {
onConnectStart: undefined,
onConnectReject: () => {
tree.collapse(connection);
tree.removeTraits('loading', [connection]);
},
onConnectSuccess: () => tree.removeTraits('loading', [connection]),
onDisconnect: undefined
};
}
connectionManagementService.connect(connection, undefined, options, callbacks).then(result => {
if (result.connected) {
let existingConnection = connectionManagementService.findExistingConnection(connection);
resolve(existingConnection);
} else {
reject('connection failed');
}
}, connectionError => {
reject(connectionError);
});
}
} else {
let existingConnection = connectionManagementService.findExistingConnection(connection);
if (options && options.showDashboard) {
connectionManagementService.showDashboard(connection).then((value) => {
resolve(existingConnection);
});
} else {
resolve(existingConnection);
}
}
});
}
/**
* Makes a connection if the not already connected and try to create new object explorer session
* I the profile is already connected, tries to do the action requested in the options (e.g. open dashboard)
* Returns true if new object explorer session created for the connection, otherwise returns false
* @param connection Connection Profile
* @param options Includes the actions to happened after connection is made
* @param connectionManagementService Connection management service instance
* @param objectExplorerService Object explorer service instance
*/
public static connectAndCreateOeSession(connection: ConnectionProfile, options: IConnectionCompletionOptions,
connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, tree: ITree): TPromise<boolean> {
return new TPromise<boolean>((resolve, reject) => {
TreeUpdateUtils.connectIfNotConnected(connection, options, connectionManagementService, tree).then(connectedConnection => {
if (connectedConnection) {
// append group ID and original display name to build unique OE session ID
connectedConnection.options['groupId'] = connection.groupId;
connectedConnection.options['databaseDisplayName'] = connection.databaseName;
var rootNode: TreeNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
if (!rootNode) {
objectExplorerService.updateObjectExplorerNodes(connectedConnection).then(() => {
rootNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
resolve(true);
// The oe request is sent. an event will be raised when the session is created
}, error => {
reject('session failed');
});
} else {
resolve(false);
}
} else {
resolve(false);
}
}, connectionError => {
reject(connectionError);
});
});
}
public static getObjectExplorerNode(connection: ConnectionProfile, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService): TPromise<TreeNode[]> {
return new TPromise<TreeNode[]>((resolve, reject) => {
if (connection.isDisconnecting) {
resolve([]);
} else {
var rootNode = objectExplorerService.getObjectExplorerNode(connection);
if (rootNode) {
objectExplorerService.resolveTreeNodeChildren(rootNode.getSession(), rootNode).then(() => {
resolve(rootNode.children);
}, expandError => {
resolve([]);
});
} else {
resolve([]);
}
}
});
}
public static getObjectExplorerParent(objectExplorerNode: TreeNode, connectionManagementService: IConnectionManagementService): any {
if (objectExplorerNode && objectExplorerNode.parent) {
// if object explorer node's parent is root, return connection profile
if (!objectExplorerNode.parent.parent) {
var connectionId = objectExplorerNode.getConnectionProfile().id;
// get connection profile from connection profile groups
let root = TreeUpdateUtils.getTreeInput(connectionManagementService);
let connections = ConnectionProfileGroup.getConnectionsInGroup(root);
let results = connections.filter(con => {
if (connectionId === con.id) {
return true;
} else {
return false;
}
});
if (results && results.length > 0) {
return results[0];
}
} else {
return objectExplorerNode.parent;
}
}
return null;
}
/**
*
* @param treeNode Returns true if the tree node is a database node
*/
public static isDatabaseNode(treeNode: TreeNode): boolean {
return treeNode && treeNode.nodeTypeId === NodeType.Database;
}
/**
*
* @param treeNode Returns true if the tree node is an available database node
*/
public static isAvailableDatabaseNode(treeNode: TreeNode): boolean {
return treeNode && treeNode.nodeTypeId === NodeType.Database && treeNode.nodeStatus !== 'Unavailable';
}
/**
* Get connection profile with the current database
*/
public static getConnectionProfile(treeNode: TreeNode): ConnectionProfile {
var connectionProfile = treeNode.getConnectionProfile();
var databaseName = treeNode.getDatabaseName();
if (databaseName !== undefined && connectionProfile.databaseName !== databaseName) {
connectionProfile = connectionProfile.cloneWithDatabase(databaseName);
}
return connectionProfile;
}
}