Object Explorer API (#783)

See https://github.com/Microsoft/sqlopsstudio/wiki/Extensibility-API#object-explorer for usage details
This commit is contained in:
Matt Irvine
2018-03-09 15:02:20 -08:00
committed by GitHub
parent fab4185c1f
commit c06ab27d08
13 changed files with 707 additions and 63 deletions

View File

@@ -5,7 +5,7 @@
'use strict';
import { NodeType } from 'sql/parts/registeredServer/common/nodeType';
import { TreeNode } from 'sql/parts/registeredServer/common/treeNode';
import { TreeNode, TreeItemCollapsibleState, ObjectExplorerCallbacks } from 'sql/parts/registeredServer/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';
@@ -20,6 +20,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { warn, error } from 'sql/base/common/log';
import { ServerTreeView } from 'sql/parts/registeredServer/viewlet/serverTreeView';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import * as vscode from 'vscode';
export const SERVICE_ID = 'ObjectExplorerService';
@@ -36,7 +37,7 @@ export interface IObjectExplorerService {
refreshNode(providerId: string, session: sqlops.ObjectExplorerSession, nodePath: string): Thenable<sqlops.ObjectExplorerExpandInfo>;
expandTreeNode(session: sqlops.ObjectExplorerSession, parentTree: TreeNode): Thenable<TreeNode[]>;
resolveTreeNodeChildren(session: sqlops.ObjectExplorerSession, parentTree: TreeNode): Thenable<TreeNode[]>;
refreshTreeNode(session: sqlops.ObjectExplorerSession, parentTree: TreeNode): Thenable<TreeNode[]>;
@@ -66,16 +67,19 @@ export interface IObjectExplorerService {
onSelectionOrFocusChange: Event<void>;
getServerTreeView(): ServerTreeView;
getActiveConnectionNodes(): TreeNode[];
getTreeNode(connectionId: string, nodePath: string): Thenable<TreeNode>;
}
interface SessionStatus {
nodes: { [nodePath: string]: NodeStatus };
connection: ConnectionProfile;
}
interface NodeStatus {
expandHandler: (result: sqlops.ObjectExplorerExpandInfo) => void;
expandEmitter: Emitter<sqlops.ObjectExplorerExpandInfo>;
}
export interface ObjectExplorerNodeEventArgs {
@@ -83,6 +87,10 @@ export interface ObjectExplorerNodeEventArgs {
errorMessage: string;
}
export interface NodeInfoWithConnection {
connectionId: string;
nodeInfo: sqlops.NodeInfo;
}
export class ObjectExplorerService implements IObjectExplorerService {
@@ -153,8 +161,8 @@ export class ObjectExplorerService implements IObjectExplorerService {
}
let nodeStatus = this._sessions[expandResponse.sessionId].nodes[expandResponse.nodePath];
if (nodeStatus && nodeStatus.expandHandler) {
nodeStatus.expandHandler(expandResponse);
if (nodeStatus && nodeStatus.expandEmitter) {
nodeStatus.expandEmitter.fire(expandResponse);
} else {
warn(`Cannot find node status for session: ${expandResponse.sessionId} and node path: ${expandResponse.nodePath}`);
}
@@ -268,24 +276,33 @@ export class ObjectExplorerService implements IObjectExplorerService {
let self = this;
return new Promise<sqlops.ObjectExplorerExpandInfo>((resolve, reject) => {
if (session.sessionId in self._sessions && self._sessions[session.sessionId]) {
self._sessions[session.sessionId].nodes[nodePath] = {
expandHandler: ((expandResult) => {
if (expandResult && !expandResult.errorMessage) {
resolve(expandResult);
}
else {
reject(expandResult ? expandResult.errorMessage : undefined);
}
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];
})
};
self.callExpandOrRefreshFromProvider(provider, {
sessionId: session ? session.sessionId : undefined,
nodePath: nodePath
}, refresh).then(result => {
}, error => {
reject(error);
});
}
}));
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}`);
}
@@ -323,7 +340,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
this._disposables = dispose(this._disposables);
}
public expandTreeNode(session: sqlops.ObjectExplorerSession, parentTree: TreeNode): Thenable<TreeNode[]> {
public resolveTreeNodeChildren(session: sqlops.ObjectExplorerSession, parentTree: TreeNode): Thenable<TreeNode[]> {
return this.expandOrRefreshTreeNode(session, parentTree);
}
@@ -377,7 +394,12 @@ export class ObjectExplorerService implements IObjectExplorerService {
}
return new TreeNode(nodeInfo.nodeType, nodeInfo.label, isLeaf, nodeInfo.nodePath,
nodeInfo.nodeSubType, nodeInfo.nodeStatus, parent, nodeInfo.metadata);
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 {
@@ -424,4 +446,96 @@ export class ObjectExplorerService implements IObjectExplorerService {
public getServerTreeView() {
return this._serverTreeView;
}
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

@@ -11,6 +11,19 @@ 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
@@ -59,8 +72,8 @@ export class TreeNode {
public nodeStatus: string;
/**
* Children of this node
*/
* Children of this node
*/
public children: TreeNode[];
@@ -108,8 +121,38 @@ export class TreeNode {
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) {
nodeSubType: string, nodeStatus: string, parent: TreeNode, metadata: sqlops.ObjectMetadata,
private _objectExplorerCallbacks: ObjectExplorerCallbacks) {
this.nodeTypeId = nodeTypeId;
this.label = label;
this.isAlwaysLeaf = isAlwaysLeaf;

View File

@@ -74,7 +74,7 @@ export class ServerTreeDataSource implements IDataSource {
if (node.children) {
resolve(node.children);
} else {
this._objectExplorerService.expandTreeNode(node.getSession(), node).then(() => {
this._objectExplorerService.resolveTreeNodeChildren(node.getSession(), node).then(() => {
resolve(node.children);
}, expandError => {
this.showError(expandError);

View File

@@ -28,6 +28,7 @@ import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesServ
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/registeredServer/common/treeNode';
const $ = builder.$;
@@ -428,6 +429,48 @@ export class ServerTreeView {
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
*/

View File

@@ -209,7 +209,7 @@ export class TreeUpdateUtils {
} else {
var rootNode = objectExplorerService.getObjectExplorerNode(connection);
if (rootNode) {
objectExplorerService.expandTreeNode(rootNode.getSession(), rootNode).then(() => {
objectExplorerService.resolveTreeNodeChildren(rootNode.getSession(), rootNode).then(() => {
resolve(rootNode.children);
}, expandError => {
resolve([]);