Add AsyncServerTree (#11838)

* wip

* Fixes

* More fixes

* more fixes

* Disable when preview features disabled

* remove unused imports

* Handle promises

* PR feedback

* Single default ServerGroup color value
This commit is contained in:
Charles Gagnon
2020-08-19 14:01:10 -07:00
committed by GitHub
parent d2e4eeac88
commit 3c538d1c2d
37 changed files with 1654 additions and 506 deletions

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 { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
/**
* Implements the DataSource(that returns a parent/children of an element) for the recent connection tree
*/
export class AsyncRecentConnectionTreeDataSource implements IAsyncDataSource<ConnectionProfileGroup, ServerTreeElement> {
/**
* Returns a boolean value indicating whether the element has children.
*/
public hasChildren(element: ServerTreeElement): boolean {
if (element instanceof ConnectionProfileGroup) {
return element.hasChildren();
}
return false;
}
/**
* Returns the element's children as an array in a promise.
*/
public async getChildren(element: ServerTreeElement): Promise<Iterable<ServerTreeElement>> {
if (element instanceof ConnectionProfileGroup) {
return element.getChildren();
} else {
return [];
}
}
}

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* 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/platform/connection/common/connectionProfileGroup';
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
import { FuzzyScore } from 'vs/base/common/filters';
import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
export class AsyncServerTree extends WorkbenchAsyncDataTree<ConnectionProfileGroup, ServerTreeElement, FuzzyScore> { }
export type ServerTreeElement = ConnectionProfile | ConnectionProfileGroup | TreeNode;

View File

@@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* 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/platform/connection/common/connectionProfileGroup';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
import { TreeUpdateUtils } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import Severity from 'vs/base/common/severity';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
/**
* Implements the DataSource(that returns a parent/children of an element) for the server tree
*/
export class AsyncServerTreeDataSource implements IAsyncDataSource<ConnectionProfileGroup, ServerTreeElement> {
constructor(
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IErrorMessageService private _errorMessageService: IErrorMessageService
) {
}
/**
* Returns a boolean value indicating whether the element has children.
*/
public hasChildren(element: ServerTreeElement): boolean {
if (element instanceof ConnectionProfile) {
return true;
} else if (element instanceof ConnectionProfileGroup) {
return element.hasChildren();
} else if (element instanceof TreeNode) {
return !element.isAlwaysLeaf;
}
return false;
}
/**
* Returns the element's children as an array in a promise.
*/
public async getChildren(element: ServerTreeElement): Promise<ServerTreeElement[]> {
try {
if (element instanceof ConnectionProfile) {
return await TreeUpdateUtils.getAsyncConnectionNodeChildren(element, this._connectionManagementService, this._objectExplorerService);
} else if (element instanceof ConnectionProfileGroup) {
return (element as ConnectionProfileGroup).getChildren();
} else if (element instanceof TreeNode) {
if (element.children) {
return element.children;
} else {
return await this._objectExplorerService.resolveTreeNodeChildren(element.getSession(), element);
}
}
} catch (err) {
if (element instanceof TreeNode) {
element.errorStateMessage = err.message ?? err;
}
if (err.message) {
this.showError(err.message);
}
throw err;
}
return [];
}
private showError(errorMessage: string) {
if (this._errorMessageService) {
this._errorMessageService.showDialog(Severity.Error, '', errorMessage);
}
}
}

View File

@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ServerTreeRenderer } from 'sql/workbench/services/objectExplorer/browser/serverTreeRenderer';
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
export class AsyncServerTreeDelegate implements IListVirtualDelegate<ServerTreeElement> {
getHeight(element: ServerTreeElement): number {
if (element instanceof ConnectionProfileGroup) {
return ServerTreeRenderer.CONNECTION_GROUP_HEIGHT;
} else if (element instanceof ConnectionProfile) {
return ServerTreeRenderer.CONNECTION_HEIGHT;
} else {
return ServerTreeRenderer.OBJECTEXPLORER_HEIGHT;
}
}
getTemplateId(element: ServerTreeElement): string {
if (element instanceof ConnectionProfileGroup) {
return ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID;
} else if (element instanceof ConnectionProfile) {
return ServerTreeRenderer.CONNECTION_TEMPLATE_ID;
} else {
return ServerTreeRenderer.OBJECTEXPLORER_TEMPLATE_ID;
}
}
}

View File

@@ -0,0 +1,124 @@
/*---------------------------------------------------------------------------------------------
* 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/platform/connection/common/connectionProfileGroup';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { TreeUpdateUtils } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils';
import { IDragAndDropData } from 'vs/base/browser/dnd';
import { ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverReactions } from 'vs/base/browser/ui/tree/tree';
import { ServerTreeDragAndDrop } from 'sql/workbench/services/objectExplorer/browser/dragAndDropController';
import { IDragAndDrop } from 'vs/base/parts/tree/browser/tree';
import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
/**
* Implements drag and drop for the server tree
*/
export class AsyncServerTreeDragAndDrop implements ITreeDragAndDrop<ServerTreeElement> {
private _dragAndDrop: IDragAndDrop;
constructor(
@IConnectionManagementService connectionManagementService: IConnectionManagementService,
) {
this._dragAndDrop = new ServerTreeDragAndDrop(connectionManagementService);
}
/**
* Returns a uri if the given element should be allowed to drag.
* Returns null, otherwise.
*/
public getDragURI(element: ServerTreeElement): string {
return this._dragAndDrop.getDragURI(undefined, element);
}
/**
* Returns a label(name) to display when dragging the element.
*/
public getDragLabel(elements: ServerTreeElement[]): string {
return this._dragAndDrop.getDragLabel(undefined, elements);
}
/**
* Called when the drag operation starts.
*/
public onDragStart(dragAndDropData: IDragAndDropData, originalEvent: DragEvent): void {
// Force the event cast while in preview - we don't use any of the mouse properties on the
// implementation so this is fine for now
return this._dragAndDrop.onDragStart(undefined, dragAndDropData, <any>originalEvent);
}
public onDragOver(data: IDragAndDropData, targetElement: ServerTreeElement, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
// Force the event cast while in preview - we don't use any of the mouse properties on the
// implementation so this is fine for now
const canDragOver = this._dragAndDrop.onDragOver(undefined, data, targetElement, <any>originalEvent);
if (canDragOver.accept) {
return TreeDragOverReactions.acceptBubbleDown(true);
} else {
return { accept: false };
}
}
/**
* Handle a drop in the server tree.
*/
public drop(data: IDragAndDropData, targetElement: ServerTreeElement, targetIndex: number, originalEvent: DragEvent): void {
// Force the event cast while in preview - we don't use any of the mouse properties on the
// implementation so this is fine for now
// TODO: chgagnon Drop on root node
this._dragAndDrop.drop(undefined, data, targetElement, <any>originalEvent);
}
public onDragEnd(originalEvent: DragEvent): void {
TreeUpdateUtils.isInDragAndDrop = false;
}
}
export class AsyncRecentConnectionsDragAndDrop implements ITreeDragAndDrop<ServerTreeElement> {
/**
* Returns a uri if the given element should be allowed to drag.
* Returns null, otherwise.
*/
public getDragURI(element: ServerTreeElement): string | null {
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(elements: ServerTreeElement[]): string {
if (elements[0] instanceof ConnectionProfile) {
return elements[0].serverName;
}
else if (elements[0] instanceof ConnectionProfileGroup) {
return elements[0].name;
}
return undefined;
}
/**
* Returns a DragOverReaction indicating whether sources can be
* dropped into target or some parent of the target.
*/
public onDragOver(data: IDragAndDropData, targetElement: ServerTreeElement, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
return { accept: false };
}
/**
* Handle drop in the server tree.
*/
public drop(data: IDragAndDropData, targetElement: ServerTreeElement, targetIndex: number, originalEvent: DragEvent): void {
// No op
}
}

View File

@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
export class AsyncServerTreeIdentityProvider implements IIdentityProvider<ServerTreeElement> {
getId(element: ServerTreeElement): { toString(): string; } {
return element.id;
}
}

View File

@@ -0,0 +1,296 @@
/*---------------------------------------------------------------------------------------------
* 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/objectTypes/objecttypes';
import * as dom from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
import { iconRenderer } from 'sql/workbench/services/objectExplorer/browser/iconRenderer';
import { URI } from 'vs/base/common/uri';
import { ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { FuzzyScore } from 'vs/base/common/filters';
import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list';
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { ServerTreeRenderer } from 'sql/workbench/services/objectExplorer/browser/serverTreeRenderer';
import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel';
class ConnectionProfileGroupTemplate extends Disposable {
private _root: HTMLElement;
private _nameContainer: HTMLElement;
constructor(
container: HTMLElement
) {
super();
container.parentElement.classList.add('server-group');
container.classList.add('server-group');
this._root = dom.append(container, dom.$('.server-group'));
this._nameContainer = dom.append(this._root, dom.$('span.name'));
}
set(element: ConnectionProfileGroup) {
let rowElement = findParentElement(this._root, 'monaco-list-row');
if (rowElement) {
if (element.color) {
rowElement.style.background = element.color;
} else {
// If the group doesn't contain specific color, assign the default color
rowElement.style.background = DefaultServerGroupColor;
}
}
if (element.description && (element.description !== '')) {
this._root.title = element.description;
}
this._nameContainer.hidden = false;
this._nameContainer.textContent = element.name;
}
}
export class ConnectionProfileGroupRenderer implements ITreeRenderer<ConnectionProfileGroup, FuzzyScore, ConnectionProfileGroupTemplate> {
readonly templateId: string = ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID;
constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { }
renderTemplate(container: HTMLElement): ConnectionProfileGroupTemplate {
return this._instantiationService.createInstance(ConnectionProfileGroupTemplate, container);
}
renderElement(node: ITreeNode<ConnectionProfileGroup, FuzzyScore>, index: number, template: ConnectionProfileGroupTemplate): void {
template.set(node.element);
}
disposeTemplate(templateData: ConnectionProfileGroupTemplate): void {
templateData.dispose();
}
}
class ConnectionProfileTemplate extends Disposable {
private _root: HTMLElement;
private _icon: HTMLElement;
private _label: HTMLElement;
/**
* _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).
*/
constructor(
container: HTMLElement,
private _isCompact: boolean,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super();
container.parentElement.classList.add('connection-profile');
this._root = dom.append(container, dom.$('.connection-tile'));
this._icon = dom.append(this._root, dom.$('div.icon server-page'));
this._label = dom.append(this._root, dom.$('div.label'));
}
set(element: ConnectionProfile) {
if (!this._isCompact) {
let iconPath: IconPath = getIconPath(element, this._connectionManagementService);
if (this._connectionManagementService.isConnected(undefined, element)) {
this._icon.classList.remove('disconnected');
this._icon.classList.add('connected');
renderServerIcon(this._icon, iconPath, true);
} else {
this._icon.classList.remove('connected');
this._icon.classList.add('disconnected');
renderServerIcon(this._icon, iconPath, false);
}
}
let label = element.title;
if (!element.isConnectionOptionsValid) {
label = localize('loading', "Loading...");
}
this._label.textContent = label;
this._root.title = element.serverInfo;
}
}
export class ConnectionProfileRenderer implements ITreeRenderer<ConnectionProfile, FuzzyScore, ConnectionProfileTemplate> {
readonly templateId: string = ServerTreeRenderer.CONNECTION_TEMPLATE_ID;
constructor(
private _isCompact: boolean,
@IInstantiationService private readonly _instantiationService: IInstantiationService) { }
renderTemplate(container: HTMLElement): ConnectionProfileTemplate {
return this._instantiationService.createInstance(ConnectionProfileTemplate, container, this._isCompact);
}
renderElement(node: ITreeNode<ConnectionProfile, FuzzyScore>, index: number, template: ConnectionProfileTemplate): void {
template.set(node.element);
}
disposeTemplate(templateData: ConnectionProfileTemplate): void {
templateData.dispose();
}
}
class TreeNodeTemplate extends Disposable {
private _root: HTMLElement;
private _icon: HTMLElement;
private _label: HTMLElement;
constructor(
container: HTMLElement
) {
super();
this._root = dom.append(container, dom.$('.object-element-group'));
this._icon = dom.append(this._root, dom.$('div.object-icon'));
this._label = dom.append(this._root, dom.$('div.label'));
}
set(element: TreeNode) {
// Use an explicitly defined iconType first. If not defined, fall back to using nodeType and
// other compount indicators instead.
let iconName: string = undefined;
if (element.iconType) {
iconName = (typeof element.iconType === 'string') ? element.iconType : element.iconType.id;
} else {
iconName = element.nodeTypeId;
if (element.nodeStatus) {
iconName = element.nodeTypeId + '_' + element.nodeStatus;
}
if (element.nodeSubType) {
iconName = element.nodeTypeId + '_' + element.nodeSubType;
}
}
let tokens: string[] = [];
for (let index = 1; index < this._icon.classList.length; index++) {
tokens.push(this._icon.classList.item(index));
}
this._icon.classList.remove(...tokens);
this._icon.classList.add('icon');
let iconLowerCaseName = iconName.toLocaleLowerCase();
this._icon.classList.add(iconLowerCaseName);
if (element.iconPath) {
iconRenderer.putIcon(this._icon, element.iconPath);
}
this._label.textContent = element.label;
this._root.title = element.label;
}
}
export class TreeNodeRenderer implements ITreeRenderer<TreeNode, FuzzyScore, TreeNodeTemplate> {
readonly templateId: string = ServerTreeRenderer.OBJECTEXPLORER_TEMPLATE_ID;
constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { }
renderTemplate(container: HTMLElement): TreeNodeTemplate {
return this._instantiationService.createInstance(TreeNodeTemplate, container);
}
renderElement(node: ITreeNode<TreeNode, FuzzyScore>, index: number, template: TreeNodeTemplate): void {
template.set(node.element);
}
disposeTemplate(templateData: TreeNodeTemplate): void {
templateData.dispose();
}
}
export class ServerTreeKeyboardNavigationLabelProvider implements IKeyboardNavigationLabelProvider<ServerTreeElement> {
constructor() { }
getKeyboardNavigationLabel(element: ServerTreeElement): { toString(): string; } {
if (element instanceof ConnectionProfileGroup) {
return element.groupName;
} else if (element instanceof ConnectionProfile) {
return element.title;
} else {
return element.label;
}
}
}
export class ServerTreeAccessibilityProvider implements IListAccessibilityProvider<ServerTreeElement> {
constructor(private _widgetAriaLabel: string) { }
getWidgetAriaLabel(): string {
return this._widgetAriaLabel;
}
getAriaLabel(element: ServerTreeElement): string | null {
if (element instanceof ConnectionProfileGroup) {
return element.fullName;
} else if (element instanceof ConnectionProfile) {
return element.title;
}
return element.label;
}
}
/**
* Returns the first parent which contains the className
*/
function findParentElement(container: HTMLElement, className: string): HTMLElement {
let currentElement = container;
while (currentElement) {
if (currentElement.className.indexOf(className) > -1) {
break;
}
currentElement = currentElement.parentElement;
}
return currentElement;
}
function getIconPath(connection: ConnectionProfile, connectionManagementService: IConnectionManagementService): IconPath {
if (!connection) { return undefined; }
if (connection['iconPath']) {
return connection['iconPath'];
}
let iconId = connectionManagementService.getConnectionIconId(connection.id);
if (!iconId) { return undefined; }
let providerProperties = connectionManagementService.getProviderProperties(connection.providerName);
if (!providerProperties) { return undefined; }
let iconPath: IconPath = undefined;
let pathConfig: URI | IconPath | { id: string, path: IconPath }[] = providerProperties['iconPath'];
if (Array.isArray(pathConfig)) {
for (const e of pathConfig) {
if (!e.id || e.id === iconId) {
iconPath = e.path;
connection['iconPath'] = iconPath;
break;
}
}
} else if (pathConfig['light']) {
iconPath = pathConfig as IconPath;
connection['iconPath'] = iconPath;
} else {
let singlePath = pathConfig as URI;
iconPath = { light: singlePath, dark: singlePath };
connection['iconPath'] = iconPath;
}
return iconPath;
}
function renderServerIcon(element: HTMLElement, iconPath: IconPath, isConnected: boolean): void {
if (!element) { return; }
if (iconPath) {
iconRenderer.putIcon(element, iconPath);
}
}
interface IconPath {
light: URI;
dark: URI;
}

View File

@@ -18,6 +18,7 @@ import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMess
import { UNSAVED_GROUP_ID } from 'sql/platform/connection/common/constants';
import { IServerGroupController } from 'sql/platform/serverGroup/common/serverGroupController';
import { ILogService } from 'vs/platform/log/common/log';
import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
export interface IServerView {
showFilteredTree(filter: string): void;
@@ -28,20 +29,18 @@ 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: IConnectionProfile | TreeNode,
private _tree: AsyncServerTree | ITree,
private element: ServerTreeElement,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IErrorMessageService private _errorMessageService: IErrorMessageService,
@ILogService private _logService: ILogService
) {
super(id, label);
this._tree = tree;
}
public async run(): Promise<boolean> {
let treeNode: TreeNode;
@@ -66,7 +65,11 @@ export class RefreshAction extends Action {
this.showError(error);
return true;
}
await this._tree.refresh(this.element);
if (this._tree instanceof AsyncServerTree) {
await this._tree.updateChildren(this.element);
} else {
await this._tree.refresh(this.element);
}
} catch (ex) {
this._logService.error(ex);
return true;

View File

@@ -20,6 +20,17 @@ export function supportsNodeNameDrop(nodeId: string): boolean {
return false;
}
export function supportsFolderNodeNameDrop(nodeId: string, label: string): boolean {
if (nodeId === 'Folder' && label === 'Columns') {
return true;
}
return false;
}
function escapeString(input: string | undefined): string | undefined {
return input?.replace(/]/g, ']]');
}
/**
* Implements drag and drop for the server tree
*/
@@ -42,7 +53,7 @@ export class ServerTreeDragAndDrop implements IDragAndDrop {
return (<ConnectionProfileGroup>element).id;
} else if (supportsNodeNameDrop(element.nodeTypeId)) {
return (<TreeNode>element).id;
} else if (element.nodeTypeId === 'Folder' && element.label === 'Columns' && element.children) {
} else if (supportsFolderNodeNameDrop(element.nodeTypeId, element.label) && element.children) {
return (<TreeNode>element).id;
} else {
return undefined;
@@ -83,17 +94,17 @@ export class ServerTreeDragAndDrop implements IDragAndDrop {
const data = dragAndDropData.getData();
const element = data[0];
if (supportsNodeNameDrop(element.nodeTypeId)) {
escapedSchema = this.escapeString(element.metadata.schema);
escapedName = this.escapeString(element.metadata.name);
escapedSchema = escapeString(element.metadata.schema);
escapedName = escapeString(element.metadata.name);
finalString = escapedSchema ? `[${escapedSchema}].[${escapedName}]` : `[${escapedName}]`;
originalEvent.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify([`${element.nodeTypeId}:${element.id}?${finalString}`]));
}
if (element.nodeTypeId === 'Folder' && element.label === 'Columns') {
if (supportsFolderNodeNameDrop(element.nodeTypeId, element.label)) {
// get children
let returnString = '';
for (let child of element.children) {
escapedSchema = this.escapeString(child.metadata.schema);
escapedName = this.escapeString(child.metadata.name);
escapedSchema = escapeString(child.metadata.schema);
escapedName = escapeString(child.metadata.name);
finalString = escapedSchema ? `[${escapedSchema}].[${escapedName}]` : `[${escapedName}]`;
returnString = returnString ? `${returnString},${finalString}` : `${finalString}`;
}
@@ -103,14 +114,6 @@ export class ServerTreeDragAndDrop implements IDragAndDrop {
return;
}
private escapeString(input: string | undefined): string | undefined {
if (input) {
let output = input.replace(/]/g, ']]');
return output;
}
return undefined;
}
public canDragToConnectionProfileGroup(source: any, targetConnectionProfileGroup: ConnectionProfileGroup) {
let canDragOver: boolean = true;
@@ -177,12 +180,18 @@ export class ServerTreeDragAndDrop implements IDragAndDrop {
if (source instanceof ConnectionProfile) {
// Change group id of profile
this._connectionManagementService.changeGroupIdForConnection(source, targetConnectionProfileGroup.id).then(() => {
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService, targetConnectionProfileGroup);
if (tree) {
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);
if (tree) {
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService);
}
});
}
}

View File

@@ -21,7 +21,9 @@ import { values } from 'vs/base/common/collections';
import { startsWith } from 'vs/base/common/strings';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import { IAction } from 'vs/base/common/actions';
import { ServerTreeActionProvider } from 'sql/workbench/services/objectExplorer/browser/serverTreeActionProvider';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
export const SERVICE_ID = 'ObjectExplorerService';
@@ -32,22 +34,22 @@ export interface NodeExpandInfoWithProviderId extends azdata.ObjectExplorerExpan
}
export interface IServerTreeView {
readonly tree: ITree;
readonly tree: ITree | AsyncServerTree;
readonly onSelectionOrFocusChange: Event<void>;
isObjectExplorerConnectionUri(uri: string): boolean;
deleteObjectExplorerNodeAndRefreshTree(profile: ConnectionProfile): Promise<void>;
getSelection(): Array<ConnectionProfile | TreeNode>;
getSelection(): Array<ServerTreeElement>;
isFocused(): boolean;
refreshElement(node: TreeNode): Promise<void>;
readonly treeActionProvider: { getActions: (tree: ITree, node: TreeNode | ConnectionProfile) => IAction[] }
isExpanded(node: TreeNode | ConnectionProfile): boolean;
reveal(node: TreeNode | ConnectionProfile): Promise<void>;
setExpandedState(node: TreeNode | ConnectionProfile, state: TreeItemCollapsibleState): Promise<void>;
setSelected(node: TreeNode | ConnectionProfile, selected: boolean, clearOtherSelections: boolean): Promise<void>;
readonly treeActionProvider: ServerTreeActionProvider;
isExpanded(node: ServerTreeElement): boolean;
reveal(node: ServerTreeElement): Promise<void>;
setExpandedState(node: ServerTreeElement, state: TreeItemCollapsibleState): Promise<void>;
setSelected(node: ServerTreeElement, selected: boolean, clearOtherSelections: boolean): Promise<void>;
refreshTree(): Promise<void>;
readonly activeConnectionsFilterAction: IAction;
renderBody(container: HTMLElement): Promise<void>;
layout(size: number);
layout(size: number): void;
}
export interface IObjectExplorerService {
@@ -78,7 +80,7 @@ export interface IObjectExplorerService {
registerNodeProvider(expander: azdata.ObjectExplorerNodeProvider): void;
getObjectExplorerNode(connection: IConnectionProfile): TreeNode;
getObjectExplorerNode(connection: IConnectionProfile): TreeNode | undefined;
updateObjectExplorerNodes(connectionProfile: IConnectionProfile): Promise<void>;
@@ -203,7 +205,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
public async updateObjectExplorerNodes(connection: IConnectionProfile): Promise<void> {
const withPassword = await this._connectionManagementService.addSavedPassword(connection);
let connectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, withPassword);
const connectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, withPassword);
return this.updateNewObjectExplorerNode(connectionProfile);
}
@@ -330,7 +332,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
}
}
public getObjectExplorerNode(connection: IConnectionProfile): TreeNode {
public getObjectExplorerNode(connection: IConnectionProfile): TreeNode | undefined {
return this._activeObjectExplorerNodes[connection.id];
}
@@ -678,7 +680,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
*/
public async getNodeActions(connectionId: string, nodePath: string): Promise<string[]> {
const node = await this.getTreeNode(connectionId, nodePath);
let actions = this._serverTreeView.treeActionProvider.getActions(this._serverTreeView.tree, this.getTreeItem(node));
const actions = this._serverTreeView.treeActionProvider.getActions(this._serverTreeView.tree, this.getTreeItem(node));
return actions.filter(action => action.label).map(action => action.label);
}

View File

@@ -25,6 +25,7 @@ import { IQueryManagementService } from 'sql/workbench/services/query/common/que
import { ServerInfoContextKey } from 'sql/workbench/services/connection/common/serverInfoContextKey';
import { fillInActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { firstIndex, find } from 'vs/base/common/arrays';
import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
/**
* Provides actions for the server tree elements
@@ -40,19 +41,15 @@ export class ServerTreeActionProvider {
) {
}
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): IAction[] {
public getActions(tree: AsyncServerTree | ITree, element: ServerTreeElement): IAction[] {
if (element instanceof ConnectionProfile) {
return this.getConnectionActions(tree, element);
}
if (element instanceof ConnectionProfileGroup) {
return this.getConnectionProfileGroupActions(tree, element);
return this.getConnectionProfileGroupActions(element);
}
if (element instanceof TreeNode) {
return this.getObjectExplorerNodeActions({
@@ -61,18 +58,13 @@ export class ServerTreeActionProvider {
treeNode: element
});
}
return [];
}
public hasSecondaryActions(tree: ITree, element: any): boolean {
return false;
}
/**
* Return actions for connection elements
*/
public getConnectionActions(tree: ITree, profile: ConnectionProfile): IAction[] {
private getConnectionActions(tree: AsyncServerTree | ITree, profile: ConnectionProfile): IAction[] {
let node = new TreeNode(NodeType.Server, '', false, '', '', '', undefined, undefined, undefined, undefined);
return this.getAllActions({
tree: tree,
@@ -158,7 +150,7 @@ export class ServerTreeActionProvider {
/**
* Return actions for connection group elements
*/
public getConnectionProfileGroupActions(tree: ITree, element: ConnectionProfileGroup): IAction[] {
private getConnectionProfileGroupActions(element: ConnectionProfileGroup): IAction[] {
return [
this._instantiationService.createInstance(AddServerAction, AddServerAction.ID, AddServerAction.LABEL),
this._instantiationService.createInstance(EditServerGroupAction, EditServerGroupAction.ID, EditServerGroupAction.LABEL, element),
@@ -202,7 +194,7 @@ export class ServerTreeActionProvider {
}
interface ObjectExplorerContext {
tree: ITree;
tree: AsyncServerTree | ITree;
profile: ConnectionProfile;
treeNode?: TreeNode;
}

View File

@@ -58,7 +58,7 @@ export class ServerTreeDataSource implements IDataSource {
*/
public async getChildren(tree: ITree, element: any): Promise<(ConnectionProfile | ConnectionProfileGroup | TreeNode)[]> {
if (element instanceof ConnectionProfile) {
return TreeUpdateUtils.getObjectExplorerNode(<ConnectionProfile>element, this._connectionManagementService, this._objectExplorerService);
return TreeUpdateUtils.getConnectionNodeChildren(<ConnectionProfile>element, this._objectExplorerService);
} else if (element instanceof ConnectionProfileGroup) {
return (element as ConnectionProfileGroup).getChildren();
} else if (element instanceof TreeNode) {

View File

@@ -15,6 +15,7 @@ import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode'
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { badgeRenderer, iconRenderer } from 'sql/workbench/services/objectExplorer/browser/iconRenderer';
import { URI } from 'vs/base/common/uri';
import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel';
export interface IConnectionTemplateData {
root: HTMLElement;
@@ -44,10 +45,10 @@ export class ServerTreeRenderer implements IRenderer {
public static CONNECTION_HEIGHT = 23;
public static CONNECTION_GROUP_HEIGHT = 38;
private static CONNECTION_TEMPLATE_ID = 'connectionProfile';
private static CONNECTION_GROUP_TEMPLATE_ID = 'connectionProfileGroup';
public static CONNECTION_TEMPLATE_ID = 'connectionProfile';
public static CONNECTION_GROUP_TEMPLATE_ID = 'connectionProfileGroup';
public static OBJECTEXPLORER_HEIGHT = 23;
private static OBJECTEXPLORER_TEMPLATE_ID = 'objectExplorer';
public 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
@@ -238,7 +239,7 @@ export class ServerTreeRenderer implements IRenderer {
rowElement.style.background = connectionProfileGroup.color;
} else {
// If the group doesn't contain specific color, assign the default color
rowElement.style.background = '#515151';
rowElement.style.background = DefaultServerGroupColor;
}
}
if (connectionProfileGroup.description && (connectionProfileGroup.description !== '')) {

View File

@@ -15,48 +15,130 @@ import { IController } from 'vs/base/parts/tree/browser/tree';
import { ServerTreeDragAndDrop, RecentConnectionsDragAndDrop } from 'sql/workbench/services/objectExplorer/browser/dragAndDropController';
import { RecentConnectionDataSource } from 'sql/workbench/services/objectExplorer/browser/recentConnectionDataSource';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService';
import { ConnectionProfileGroupRenderer, ConnectionProfileRenderer, TreeNodeRenderer, ServerTreeAccessibilityProvider, ServerTreeKeyboardNavigationLabelProvider } from 'sql/workbench/services/objectExplorer/browser/asyncServerTreeRenderer';
import { AsyncServerTreeIdentityProvider } from 'sql/workbench/services/objectExplorer/browser/asyncServerTreeIdentityProvider';
import { FuzzyScore } from 'vs/base/common/filters';
import { AsyncServerTreeDelegate } from 'sql/workbench/services/objectExplorer/browser/asyncServerTreeDelegate';
import { AsyncRecentConnectionsDragAndDrop, AsyncServerTreeDragAndDrop } from 'sql/workbench/services/objectExplorer/browser/asyncServerTreeDragAndDrop';
import { AsyncRecentConnectionTreeDataSource } from 'sql/workbench/services/objectExplorer/browser/asyncRecentConnectionTreeDataSource';
import { AsyncServerTreeDataSource } from 'sql/workbench/services/objectExplorer/browser/asyncServerTreeDataSource';
import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
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();
public static createConnectionTree(treeContainer: HTMLElement, instantiationService: IInstantiationService, configurationService: IConfigurationService, ariaLabel: string, useController?: IController): Tree | AsyncServerTree {
if (useAsyncServerTree(configurationService)) {
const dataSource = instantiationService.createInstance(AsyncRecentConnectionTreeDataSource);
const connectionProfileGroupRender = instantiationService.createInstance(ConnectionProfileGroupRenderer);
const connectionProfileRenderer = instantiationService.createInstance(ConnectionProfileRenderer, true);
const treeNodeRenderer = instantiationService.createInstance(TreeNodeRenderer);
const dnd = instantiationService.createInstance(AsyncRecentConnectionsDragAndDrop);
const identityProvider = instantiationService.createInstance(AsyncServerTreeIdentityProvider);
return new Tree(treeContainer, { dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider },
{
indentPixels: 0,
twistiePixels: 0,
ariaLabel: nls.localize('treeAriaLabel', "Recent Connections")
});
const treeOptions: IWorkbenchAsyncDataTreeOptions<ServerTreeElement, FuzzyScore> = {
keyboardSupport: true,
accessibilityProvider: new ServerTreeAccessibilityProvider(ariaLabel),
keyboardNavigationLabelProvider: instantiationService.createInstance(ServerTreeKeyboardNavigationLabelProvider),
dnd: dnd,
identityProvider: identityProvider
};
return instantiationService.createInstance(
AsyncServerTree,
'ServerTreeView',
treeContainer,
new AsyncServerTreeDelegate(),
[
connectionProfileGroupRender,
connectionProfileRenderer,
treeNodeRenderer
],
dataSource,
treeOptions,
);
} else {
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: 0,
twistiePixels: 0,
ariaLabel: nls.localize('treeAriaLabel', "Recent Connections")
});
}
}
/**
* Create a Servers viewlet tree
*/
public static createRegisteredServersTree(treeContainer: HTMLElement, instantiationService: IInstantiationService, horizontalScrollMode: boolean = false): Tree {
public static createServersTree(treeContainer: HTMLElement,
instantiationService: IInstantiationService,
configurationService: IConfigurationService,
horizontalScrollMode: boolean = false): Tree | AsyncServerTree {
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();
if (useAsyncServerTree(configurationService)) {
const dataSource = instantiationService.createInstance(AsyncServerTreeDataSource);
const connectionProfileGroupRender = instantiationService.createInstance(ConnectionProfileGroupRenderer);
const connectionProfileRenderer = instantiationService.createInstance(ConnectionProfileRenderer, false);
const treeNodeRenderer = instantiationService.createInstance(TreeNodeRenderer);
const dnd = instantiationService.createInstance(AsyncServerTreeDragAndDrop);
const identityProvider = instantiationService.createInstance(AsyncServerTreeIdentityProvider);
const treeOptions: IWorkbenchAsyncDataTreeOptions<ServerTreeElement, FuzzyScore> = {
keyboardSupport: true,
accessibilityProvider: new ServerTreeAccessibilityProvider(nls.localize('serversAriaLabel', "Servers")),
keyboardNavigationLabelProvider: instantiationService.createInstance(ServerTreeKeyboardNavigationLabelProvider),
openOnSingleClick: true,
openOnFocus: true,
dnd: dnd,
identityProvider: identityProvider
};
return instantiationService.createInstance(
AsyncServerTree,
'ServerTreeView',
treeContainer,
new AsyncServerTreeDelegate(),
[
connectionProfileGroupRender,
connectionProfileRenderer,
treeNodeRenderer
],
dataSource,
treeOptions
);
} else {
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"),
horizontalScrollMode: horizontalScrollMode ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden
});
}
return new Tree(treeContainer, { dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider },
{
indentPixels: 10,
twistiePixels: 20,
ariaLabel: nls.localize('treeCreation.regTreeAriaLabel', "Servers"),
horizontalScrollMode: horizontalScrollMode ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden
});
}
}
function useAsyncServerTree(configurationService: IConfigurationService): boolean {
return configurationService.getValue('workbench.enablePreviewFeatures') && configurationService.getValue('serverTree.useAsyncServerTree');
}

View File

@@ -11,6 +11,7 @@ import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/br
// import { IProgressRunner, IProgressService } from 'vs/platform/progress/common/progress';
import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
import { TreeUpdateUtils } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils';
import { AsyncServerTree } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
export class TreeSelectionHandler {
// progressRunner: IProgressRunner;
@@ -44,9 +45,9 @@ export class TreeSelectionHandler {
}
/**
* Handle select ion of tree element
* Handle selection of tree element
*/
public onTreeSelect(event: any, tree: ITree, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, connectionCompleteCallback: () => void) {
public onTreeSelect(event: any, tree: AsyncServerTree | ITree, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, connectionCompleteCallback: () => void) {
let sendSelectionEvent = ((event: any, selection: any, isDoubleClick: boolean, userInteraction: boolean) => {
// userInteraction: defensive - don't touch this something else is handling it.
if (userInteraction === true && this._lastClicked && this._lastClicked[0] === selection[0]) {
@@ -93,44 +94,50 @@ export class TreeSelectionHandler {
*
* @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: true,
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) {
private handleTreeItemSelected(connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, isDoubleClick: boolean, isKeyboard: boolean, selection: any[], tree: AsyncServerTree | ITree, connectionCompleteCallback: () => void): void {
if (tree instanceof AsyncServerTree) {
if (selection && selection.length > 0 && (selection[0] instanceof 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 = selection[0];
if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
connectionProfile = TreeUpdateUtils.getConnectionProfile(treeNode);
} else {
let connectionProfile: ConnectionProfile = undefined;
let options: IConnectionCompletionOptions = {
params: undefined,
saveTheConnection: true,
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) {
connectionManagementService.showDashboard(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 = selection[0];
if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
connectionProfile = TreeUpdateUtils.getConnectionProfile(treeNode);
if (connectionProfile) {
connectionManagementService.showDashboard(connectionProfile);
}
}
}
}
if (isKeyboard) {
tree.toggleExpansion(selection[0]);
if (isKeyboard) {
tree.toggleExpansion(selection[0]);
}
}
}
}

View File

@@ -11,9 +11,9 @@ import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/br
import { NodeType } from 'sql/workbench/services/objectExplorer/common/nodeType';
import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
import * as errors from 'vs/base/common/errors';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { Disposable } from 'vs/base/common/lifecycle';
import { onUnexpectedError } from 'vs/base/common/errors';
import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
export interface IExpandableTree extends ITree {
/**
@@ -47,13 +47,13 @@ export class TreeUpdateUtils {
/**
* Set input for the tree.
*/
public static structuralTreeUpdate(tree: ITree, viewKey: 'recent' | 'active' | 'saved', connectionManagementService: IConnectionManagementService, providers?: string[]): Promise<void> {
public static async structuralTreeUpdate(tree: AsyncServerTree | ITree, viewKey: 'recent' | 'active' | 'saved', connectionManagementService: IConnectionManagementService, providers?: string[]): Promise<void> {
// convert to old VS Code tree interface with expandable methods
let expandableTree: IExpandableTree = <IExpandableTree>tree;
let selectedElement: any;
let targetsToExpand: any[];
if (tree) {
if (tree && !(tree instanceof AsyncServerTree)) {
let selection = tree.getSelection();
if (selection && selection.length === 1) {
selectedElement = <any>selection[0];
@@ -72,10 +72,13 @@ export class TreeUpdateUtils {
treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService, providers);
}
const previousTreeInput = tree.getInput();
return tree.setInput(treeInput).then(async () => {
if (previousTreeInput instanceof Disposable) {
previousTreeInput.dispose();
}
await tree.setInput(treeInput);
if (previousTreeInput instanceof Disposable) {
previousTreeInput.dispose();
}
if (tree && !(tree instanceof AsyncServerTree)) {
// Make sure to expand all folders that where expanded in the previous session
if (targetsToExpand) {
await tree.expandAll(targetsToExpand);
@@ -83,64 +86,64 @@ export class TreeUpdateUtils {
if (selectedElement) {
tree.select(selectedElement);
}
});
}
}
/**
* Set input for the registered servers tree.
*/
public static registeredServerUpdate(tree: ITree, connectionManagementService: IConnectionManagementService, elementToSelect?: any): Promise<void> {
// convert to old VS Code tree interface with expandable methods
let expandableTree: IExpandableTree = <IExpandableTree>tree;
public static async registeredServerUpdate(tree: ITree | AsyncServerTree, connectionManagementService: IConnectionManagementService, elementToSelect?: any): Promise<void> {
if (tree instanceof AsyncServerTree) {
const treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
await tree.setInput(treeInput);
tree.rerender();
} else {
// convert to old VS Code tree interface with expandable methods
let expandableTree: IExpandableTree = <IExpandableTree>tree;
let selectedElement: any = elementToSelect;
let targetsToExpand: any[];
let selectedElement: any = elementToSelect;
let targetsToExpand: any[];
// Focus
tree.domFocus();
// Focus
tree.domFocus();
if (tree) {
let selection = tree.getSelection();
if (!selectedElement) {
if (selection && selection.length === 1) {
selectedElement = <any>selection[0];
if (tree) {
let selection = tree.getSelection();
if (!selectedElement) {
if (selection && selection.length === 1) {
selectedElement = <any>selection[0];
}
}
targetsToExpand = expandableTree.getExpandedElements();
if (selectedElement && targetsToExpand.indexOf(selectedElement) === -1) {
targetsToExpand.push(selectedElement);
}
}
targetsToExpand = expandableTree.getExpandedElements();
if (selectedElement && targetsToExpand.indexOf(selectedElement) === -1) {
targetsToExpand.push(selectedElement);
}
}
let treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
if (treeInput) {
if (treeInput !== tree.getInput()) {
return tree.setInput(treeInput).then(async () => {
// Make sure to expand all folders that where expanded in the previous session
if (targetsToExpand) {
await tree.expandAll(targetsToExpand);
}
if (selectedElement) {
tree.select(selectedElement);
}
tree.getFocus();
}, errors.onUnexpectedError);
let treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
if (treeInput) {
if (treeInput !== tree.getInput()) {
return tree.setInput(treeInput).then(async () => {
// Make sure to expand all folders that where expanded in the previous session
if (targetsToExpand) {
await tree.expandAll(targetsToExpand);
}
if (selectedElement) {
tree.select(selectedElement);
}
tree.getFocus();
}, onUnexpectedError);
}
}
}
return Promise.resolve();
}
public static getTreeInput(connectionManagementService: IConnectionManagementService, providers?: string[]): ConnectionProfileGroup {
let groups = connectionManagementService.getConnectionGroups(providers);
if (groups && groups.length > 0) {
let treeInput = groups[0];
treeInput.name = 'root';
groups.forEach(cpg => cpg.dispose());
return treeInput;
}
// Should never get to this case.
return undefined;
public static getTreeInput(connectionManagementService: IConnectionManagementService, providers?: string[]): ConnectionProfileGroup | undefined {
const groups = connectionManagementService.getConnectionGroups(providers);
const input = groups.find(group => group.isRoot);
// Dispose of the unused groups to clean up their handlers
groups.filter(g => g !== input).forEach(g => g.dispose());
return input;
}
public static hasObjectExplorerNode(connection: ConnectionProfile, connectionManagementService: IConnectionManagementService): boolean {
@@ -149,10 +152,10 @@ export class TreeUpdateUtils {
}
public static async connectIfNotConnected(
connection: IConnectionProfile,
connection: ConnectionProfile,
options: IConnectionCompletionOptions,
connectionManagementService: IConnectionManagementService,
tree: ITree): Promise<ConnectionProfile | undefined> {
tree: AsyncServerTree | ITree): Promise<ConnectionProfile | undefined> {
if (!connectionManagementService.isProfileConnected(connection)) {
// don't try to reconnect if currently connecting
if (connectionManagementService.isProfileConnecting(connection)) {
@@ -161,7 +164,15 @@ export class TreeUpdateUtils {
// else if we aren't connected or connecting then try to connect
} else {
let callbacks: IConnectionCallbacks = undefined;
if (tree) {
if (tree instanceof AsyncServerTree) {
callbacks = {
onConnectStart: undefined,
onConnectReject: undefined,
onConnectSuccess: undefined,
onDisconnect: undefined,
onConnectCanceled: undefined,
};
} else 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]);
let rejectOrCancelCallback = () => {
@@ -176,12 +187,13 @@ export class TreeUpdateUtils {
onConnectCanceled: rejectOrCancelCallback,
};
}
const result = await connectionManagementService.connect(connection, undefined, options, callbacks);
if (result.connected) {
let existingConnection = connectionManagementService.findExistingConnection(connection);
return existingConnection;
} else {
throw new Error('connection failed');
throw new Error(result.errorMessage);
}
}
} else {
@@ -202,8 +214,8 @@ export class TreeUpdateUtils {
* @param connectionManagementService Connection management service instance
* @param objectExplorerService Object explorer service instance
*/
public static async connectAndCreateOeSession(connection: IConnectionProfile, options: IConnectionCompletionOptions,
connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, tree: ITree): Promise<boolean> {
public static async connectAndCreateOeSession(connection: ConnectionProfile, options: IConnectionCompletionOptions,
connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, tree: AsyncServerTree | ITree): Promise<boolean> {
const connectedConnection = await TreeUpdateUtils.connectIfNotConnected(connection, options, connectionManagementService, tree);
if (connectedConnection) {
// append group ID and original display name to build unique OE session ID
@@ -213,7 +225,6 @@ export class TreeUpdateUtils {
let rootNode: TreeNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
if (!rootNode) {
await objectExplorerService.updateObjectExplorerNodes(connectedConnection);
rootNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
return true;
// The oe request is sent. an event will be raised when the session is created
} else {
@@ -224,50 +235,75 @@ export class TreeUpdateUtils {
}
}
public static getObjectExplorerNode(connection: ConnectionProfile, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService): Promise<TreeNode[]> {
return new Promise<TreeNode[]>((resolve, reject) => {
if (connection.isDisconnecting) {
resolve([]);
} else {
let rootNode = objectExplorerService.getObjectExplorerNode(connection);
if (rootNode) {
objectExplorerService.resolveTreeNodeChildren(rootNode.getSession(), rootNode).then(() => {
resolve(rootNode.children);
}, expandError => {
resolve([]);
});
} else {
resolve([]);
public static async getConnectionNodeChildren(connection: ConnectionProfile, objectExplorerService: IObjectExplorerService): Promise<TreeNode[]> {
if (connection.isDisconnecting) {
return [];
} else {
let rootNode = objectExplorerService.getObjectExplorerNode(connection);
if (rootNode) {
try {
await objectExplorerService.resolveTreeNodeChildren(rootNode.getSession(), rootNode);
return rootNode.children;
} catch (err) {
onUnexpectedError(err);
return [];
}
} else {
return [];
}
});
}
}
public static getObjectExplorerParent(objectExplorerNode: TreeNode, connectionManagementService: IConnectionManagementService): any {
public static async getAsyncConnectionNodeChildren(connection: ConnectionProfile, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService): Promise<TreeNode[]> {
if (connection.isDisconnecting) {
return [];
} else {
let rootNode = objectExplorerService.getObjectExplorerNode(connection);
if (rootNode) {
await objectExplorerService.resolveTreeNodeChildren(rootNode.getSession(), rootNode);
return rootNode.children;
} else {
const options: IConnectionCompletionOptions = {
params: undefined,
saveTheConnection: true,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true,
showDashboard: false
};
// Need to wait for the OE service to update its nodes in order to resolve the children
const nodesUpdatedPromise = new Promise((resolve, reject) => {
objectExplorerService.onUpdateObjectExplorerNodes(e => {
if (e.errorMessage) {
reject(new Error(e.errorMessage));
}
if (e.connection.id === connection.id) {
resolve();
}
});
});
await TreeUpdateUtils.connectAndCreateOeSession(connection, options, connectionManagementService, objectExplorerService, undefined);
await nodesUpdatedPromise;
let rootNode = objectExplorerService.getObjectExplorerNode(connection);
await objectExplorerService.resolveTreeNodeChildren(rootNode.getSession(), rootNode);
return rootNode.children;
}
}
}
public static getObjectExplorerParent(objectExplorerNode: TreeNode, connectionManagementService: IConnectionManagementService): ServerTreeElement | undefined {
if (objectExplorerNode && objectExplorerNode.parent) {
// if object explorer node's parent is root, return connection profile
if (!objectExplorerNode.parent.parent) {
let connectionId = objectExplorerNode.getConnectionProfile().id;
const 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];
}
const root = TreeUpdateUtils.getTreeInput(connectionManagementService);
return ConnectionProfileGroup.getConnectionsInGroup(root).find(conn => connectionId === conn.id);
} else {
return objectExplorerNode.parent;
}
}
return null;
return undefined;
}
/**