mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Object Explorer API (#783)
See https://github.com/Microsoft/sqlopsstudio/wiki/Extensibility-API#object-explorer for usage details
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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([]);
|
||||
|
||||
Reference in New Issue
Block a user