mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 11:01:37 -05:00
Adding filtering to OE Service (#22900)
* Adding filtering to OE Service * Update src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts Co-authored-by: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> * Update src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts Co-authored-by: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> * Update src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts Co-authored-by: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> * Update src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts Co-authored-by: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> * Fixing compile errors * Fixing circular dependency --------- Co-authored-by: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com>
This commit is contained in:
@@ -18,14 +18,14 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
|||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list';
|
import { IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list';
|
||||||
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||||
import { ServerTreeRenderer } from 'sql/workbench/services/objectExplorer/browser/serverTreeRenderer';
|
import { ServerTreeRenderer, getLabelWithFilteredSuffix } from 'sql/workbench/services/objectExplorer/browser/serverTreeRenderer';
|
||||||
import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
|
import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
|
||||||
import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel';
|
import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel';
|
||||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||||
import { instanceOfSqlThemeIcon } from 'sql/workbench/services/objectExplorer/common/nodeType';
|
import { instanceOfSqlThemeIcon } from 'sql/workbench/services/objectExplorer/common/nodeType';
|
||||||
|
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
|
||||||
|
|
||||||
const DefaultConnectionIconClass = 'server-page';
|
const DefaultConnectionIconClass = 'server-page';
|
||||||
|
|
||||||
export interface ConnectionProfileGroupDisplayOptions {
|
export interface ConnectionProfileGroupDisplayOptions {
|
||||||
showColor: boolean;
|
showColor: boolean;
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,8 @@ class ConnectionProfileTemplate extends Disposable {
|
|||||||
constructor(
|
constructor(
|
||||||
container: HTMLElement,
|
container: HTMLElement,
|
||||||
private _isCompact: boolean,
|
private _isCompact: boolean,
|
||||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
|
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||||
|
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
container.parentElement!.classList.add('connection-profile');
|
container.parentElement!.classList.add('connection-profile');
|
||||||
@@ -122,6 +123,11 @@ class ConnectionProfileTemplate extends Disposable {
|
|||||||
let label = element.title;
|
let label = element.title;
|
||||||
this._label.textContent = label;
|
this._label.textContent = label;
|
||||||
this._root.title = element.serverInfo;
|
this._root.title = element.serverInfo;
|
||||||
|
|
||||||
|
const treeNode = this._objectExplorerService.getObjectExplorerNode(element);
|
||||||
|
if (treeNode?.filters?.length > 0) {
|
||||||
|
this._label.textContent = getLabelWithFilteredSuffix(this._label.textContent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +198,8 @@ class TreeNodeTemplate extends Disposable {
|
|||||||
iconRenderer.putIcon(this._icon, element.icon);
|
iconRenderer.putIcon(this._icon, element.icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._label.textContent = element.label;
|
this._label.textContent = element.filters.length > 0 ? getLabelWithFilteredSuffix(element.label) :
|
||||||
|
element.label;
|
||||||
this._root.title = element.label;
|
this._root.title = element.label;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,6 +182,9 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
// Cache of tree nodes for each connection by session ids
|
// Cache of tree nodes for each connection by session ids
|
||||||
private _treeNodeCache: Map<string, Map<string, TreeNode>> = new Map<string, Map<string, TreeNode>>();
|
private _treeNodeCache: Map<string, Map<string, TreeNode>> = new Map<string, Map<string, TreeNode>>();
|
||||||
|
|
||||||
|
// Cache of node filters for each connection by session ids
|
||||||
|
private _nodeFilterCache: Map<string, Map<string, azdata.NodeFilter[]>> = new Map<string, Map<string, azdata.NodeFilter[]>>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||||
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService,
|
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService,
|
||||||
@@ -237,6 +240,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._treeNodeCache.delete(session.sessionId);
|
this._treeNodeCache.delete(session.sessionId);
|
||||||
|
this._nodeFilterCache.delete(session.sessionId);
|
||||||
await this.closeSession(connection.providerName, session);
|
await this.closeSession(connection.providerName, session);
|
||||||
delete this._activeObjectExplorerNodes[connectionUri];
|
delete this._activeObjectExplorerNodes[connectionUri];
|
||||||
delete this._sessions[session.sessionId!];
|
delete this._sessions[session.sessionId!];
|
||||||
@@ -346,6 +350,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
let server = this.toTreeNode(session.rootNode, undefined);
|
let server = this.toTreeNode(session.rootNode, undefined);
|
||||||
this._treeNodeCache.set(sessionId, new Map<string, TreeNode>());
|
this._treeNodeCache.set(sessionId, new Map<string, TreeNode>());
|
||||||
this._treeNodeCache.get(sessionId)!.set(this.getTreeNodeCacheKey(server.toNodeInfo()), server);
|
this._treeNodeCache.get(sessionId)!.set(this.getTreeNodeCacheKey(server.toNodeInfo()), server);
|
||||||
|
this._nodeFilterCache.set(sessionId, new Map<string, azdata.NodeFilter[]>());
|
||||||
server.connection = connection;
|
server.connection = connection;
|
||||||
server.session = session;
|
server.session = session;
|
||||||
this._activeObjectExplorerNodes[connection!.id] = server;
|
this._activeObjectExplorerNodes[connection!.id] = server;
|
||||||
@@ -475,6 +480,12 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
refresh: boolean = false): Promise<azdata.ObjectExplorerExpandInfo> {
|
refresh: boolean = false): Promise<azdata.ObjectExplorerExpandInfo> {
|
||||||
let self = this;
|
let self = this;
|
||||||
return new Promise<azdata.ObjectExplorerExpandInfo>((resolve, reject) => {
|
return new Promise<azdata.ObjectExplorerExpandInfo>((resolve, reject) => {
|
||||||
|
const sessionFilterCache = this._nodeFilterCache.get(session.sessionId!);
|
||||||
|
// If the node has filters we need to cache them so that we can reapply them when the node is refreshed.
|
||||||
|
if (node.filters && sessionFilterCache) {
|
||||||
|
sessionFilterCache.set(this.getTreeNodeCacheKey(node), node.filters);
|
||||||
|
}
|
||||||
|
|
||||||
if (session.sessionId! in self._sessions && self._sessions[session.sessionId!]) {
|
if (session.sessionId! in self._sessions && self._sessions[session.sessionId!]) {
|
||||||
let newRequest = false;
|
let newRequest = false;
|
||||||
if (!self._sessions[session.sessionId!].nodes[node.nodePath]) {
|
if (!self._sessions[session.sessionId!].nodes[node.nodePath]) {
|
||||||
@@ -561,7 +572,8 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
self.callExpandOrRefreshFromProvider(provider, {
|
self.callExpandOrRefreshFromProvider(provider, {
|
||||||
sessionId: session.sessionId!,
|
sessionId: session.sessionId!,
|
||||||
nodePath: node.nodePath,
|
nodePath: node.nodePath,
|
||||||
securityToken: session.securityToken
|
securityToken: session.securityToken,
|
||||||
|
filters: node.filters
|
||||||
}, refresh).then(isExpanding => {
|
}, refresh).then(isExpanding => {
|
||||||
if (!isExpanding) {
|
if (!isExpanding) {
|
||||||
// The provider stated it's not going to expand the node, therefore do not need to track when merging results
|
// The provider stated it's not going to expand the node, therefore do not need to track when merging results
|
||||||
@@ -699,8 +711,9 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public resolveTreeNodeChildren(session: azdata.ObjectExplorerSession, parentTree: TreeNode): Promise<TreeNode[]> {
|
public resolveTreeNodeChildren(session: azdata.ObjectExplorerSession, parentTree: TreeNode): Promise<TreeNode[]> {
|
||||||
// Always refresh the node if it has an error, otherwise expand it normally
|
// Always refresh the node if it has an error or forceRefresh is set to true, otherwise expand it normally
|
||||||
let needsRefresh = !!parentTree.errorStateMessage;
|
let needsRefresh = !!parentTree.errorStateMessage || parentTree.forceRefresh;
|
||||||
|
parentTree.forceRefresh = false;
|
||||||
return this.expandOrRefreshTreeNode(session, parentTree, needsRefresh);
|
return this.expandOrRefreshTreeNode(session, parentTree, needsRefresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -752,15 +765,25 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const children = expandResult.nodes.map(node => {
|
const children = expandResult.nodes.map(node => {
|
||||||
|
let treeNode;
|
||||||
const cacheKey = this.getTreeNodeCacheKey(node);
|
const cacheKey = this.getTreeNodeCacheKey(node);
|
||||||
// In case of refresh, we want to update the existing node in the cache
|
// In case of refresh, we want to update the existing node in the cache
|
||||||
if (!refresh && sessionTreeNodeCache.has(cacheKey)) {
|
if (!refresh && sessionTreeNodeCache.has(cacheKey)) {
|
||||||
return sessionTreeNodeCache.get(cacheKey);
|
treeNode = sessionTreeNodeCache.get(cacheKey);
|
||||||
} else {
|
} else {
|
||||||
const treeNode = this.toTreeNode(node, parentTree);
|
treeNode = this.toTreeNode(node, parentTree);
|
||||||
sessionTreeNodeCache.set(cacheKey, treeNode);
|
sessionTreeNodeCache.set(cacheKey, treeNode);
|
||||||
return treeNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filterCacheKey = this.getTreeNodeCacheKey(treeNode);
|
||||||
|
const sessionFilterCache = this._nodeFilterCache.get(session.sessionId!);
|
||||||
|
// Making sure we retain the filters for the node.
|
||||||
|
if (sessionFilterCache?.has(filterCacheKey)) {
|
||||||
|
treeNode.filters = sessionFilterCache.get(filterCacheKey) ?? [];
|
||||||
|
} else {
|
||||||
|
treeNode.filters = [];
|
||||||
|
}
|
||||||
|
return treeNode;
|
||||||
});
|
});
|
||||||
parentTree.children = children.filter(c => c !== undefined);
|
parentTree.children = children.filter(c => c !== undefined);
|
||||||
return children;
|
return children;
|
||||||
@@ -785,7 +808,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let node = new TreeNode(nodeInfo.nodeType, nodeInfo.objectType, nodeInfo.label, isLeaf, nodeInfo.nodePath, nodeInfo.parentNodePath,
|
let node = new TreeNode(nodeInfo.nodeType, nodeInfo.objectType, nodeInfo.label, isLeaf, nodeInfo.nodePath, nodeInfo.parentNodePath,
|
||||||
nodeInfo.nodeSubType!, nodeInfo.nodeStatus, parent, nodeInfo.metadata, nodeInfo.iconType, nodeInfo.icon, {
|
nodeInfo.nodeSubType!, nodeInfo.nodeStatus, parent, nodeInfo.metadata, nodeInfo.iconType, nodeInfo.icon, nodeInfo.filterableProperties, {
|
||||||
getChildren: (treeNode?: TreeNode) => this.getChildren(treeNode),
|
getChildren: (treeNode?: TreeNode) => this.getChildren(treeNode),
|
||||||
isExpanded: treeNode => this.isExpanded(treeNode),
|
isExpanded: treeNode => this.isExpanded(treeNode),
|
||||||
setNodeExpandedState: async (treeNode, expandedState) => await this.setNodeExpandedState(treeNode, expandedState),
|
setNodeExpandedState: async (treeNode, expandedState) => await this.setNodeExpandedState(treeNode, expandedState),
|
||||||
@@ -1033,7 +1056,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
return this._configurationService.getValue<number>(NODE_EXPANSION_CONFIG);
|
return this._configurationService.getValue<number>(NODE_EXPANSION_CONFIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTreeNodeCacheKey(node: azdata.NodeInfo): string {
|
private getTreeNodeCacheKey(node: azdata.NodeInfo | TreeNode): string {
|
||||||
return node.nodePath;
|
return node.nodePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import { URI } from 'vs/base/common/uri';
|
|||||||
import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel';
|
import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel';
|
||||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||||
import { instanceOfSqlThemeIcon } from 'sql/workbench/services/objectExplorer/common/nodeType';
|
import { instanceOfSqlThemeIcon } from 'sql/workbench/services/objectExplorer/common/nodeType';
|
||||||
|
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
|
||||||
export interface IConnectionTemplateData {
|
export interface IConnectionTemplateData {
|
||||||
root: HTMLElement;
|
root: HTMLElement;
|
||||||
@@ -38,6 +40,10 @@ export interface IObjectExplorerTemplateData {
|
|||||||
treeNode: TreeNode;
|
treeNode: TreeNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLabelWithFilteredSuffix(label: string): string {
|
||||||
|
return localize('filteredTreeElementName', "{0} (filtered)", label);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the tree items.
|
* Renders the tree items.
|
||||||
* Uses the dom template to render connection groups and connections.
|
* Uses the dom template to render connection groups and connections.
|
||||||
@@ -59,7 +65,8 @@ export class ServerTreeRenderer implements IRenderer {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
isCompact: boolean,
|
isCompact: boolean,
|
||||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
|
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||||
|
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService
|
||||||
) {
|
) {
|
||||||
// isCompact defaults to false unless explicitly set by instantiation call.
|
// isCompact defaults to false unless explicitly set by instantiation call.
|
||||||
if (isCompact) {
|
if (isCompact) {
|
||||||
@@ -163,9 +170,9 @@ export class ServerTreeRenderer implements IRenderer {
|
|||||||
if (treeNode.icon && !instanceOfSqlThemeIcon(treeNode.icon)) {
|
if (treeNode.icon && !instanceOfSqlThemeIcon(treeNode.icon)) {
|
||||||
iconRenderer.putIcon(templateData.icon, treeNode.icon);
|
iconRenderer.putIcon(templateData.icon, treeNode.icon);
|
||||||
}
|
}
|
||||||
|
const nodeLabel = treeNode.filters?.length > 0 ? getLabelWithFilteredSuffix(treeNode.label) : treeNode.label;
|
||||||
templateData.label.textContent = treeNode.label;
|
templateData.label.textContent = nodeLabel;
|
||||||
templateData.root.title = treeNode.label;
|
templateData.root.title = nodeLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getIconPath(connection: ConnectionProfile): IconPath | undefined {
|
private getIconPath(connection: ConnectionProfile): IconPath | undefined {
|
||||||
@@ -224,10 +231,10 @@ export class ServerTreeRenderer implements IRenderer {
|
|||||||
|
|
||||||
let iconPath = this.getIconPath(connection);
|
let iconPath = this.getIconPath(connection);
|
||||||
this.renderServerIcon(templateData.icon, iconPath, isConnected);
|
this.renderServerIcon(templateData.icon, iconPath, isConnected);
|
||||||
|
const treeNode = this._objectExplorerService.getObjectExplorerNode(connection);
|
||||||
let label = connection.title;
|
let label = treeNode?.filters?.length > 0 ? getLabelWithFilteredSuffix(connection.title) : connection.title;
|
||||||
templateData.label.textContent = label;
|
templateData.label.textContent = label;
|
||||||
templateData.root.title = connection.serverInfo;
|
templateData.root.title = treeNode?.filters?.length > 0 ? getLabelWithFilteredSuffix(connection.title) : connection.serverInfo;
|
||||||
templateData.connectionProfile = connection;
|
templateData.connectionProfile = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,15 @@ export class TreeNode {
|
|||||||
*/
|
*/
|
||||||
public children?: TreeNode[];
|
public children?: TreeNode[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filterable properties that this node supports
|
||||||
|
*/
|
||||||
|
public filterProperties?: azdata.NodeFilterProperty[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters that are currently applied to this node children.
|
||||||
|
*/
|
||||||
|
public filters?: azdata.NodeFilter[];
|
||||||
|
|
||||||
public connection?: ConnectionProfile;
|
public connection?: ConnectionProfile;
|
||||||
|
|
||||||
@@ -104,10 +113,13 @@ export class TreeNode {
|
|||||||
|
|
||||||
public icon?: IconPath | SqlThemeIcon;
|
public icon?: IconPath | SqlThemeIcon;
|
||||||
|
|
||||||
|
public forceRefresh: boolean = false;
|
||||||
|
|
||||||
constructor(nodeTypeId: string, objectType: string, label: string, isAlwaysLeaf: boolean, nodePath: string, parentNodePath: string,
|
constructor(nodeTypeId: string, objectType: string, label: string, isAlwaysLeaf: boolean, nodePath: string, parentNodePath: string,
|
||||||
nodeSubType: string, nodeStatus?: string, parent?: TreeNode, metadata?: azdata.ObjectMetadata,
|
nodeSubType: string, nodeStatus?: string, parent?: TreeNode, metadata?: azdata.ObjectMetadata,
|
||||||
iconType?: string | SqlThemeIcon,
|
iconType?: string | SqlThemeIcon,
|
||||||
icon?: IconPath | SqlThemeIcon,
|
icon?: IconPath | SqlThemeIcon,
|
||||||
|
filterProperties?: azdata.NodeFilterProperty[],
|
||||||
private _objectExplorerCallbacks?: ObjectExplorerCallbacks) {
|
private _objectExplorerCallbacks?: ObjectExplorerCallbacks) {
|
||||||
this.nodeTypeId = nodeTypeId;
|
this.nodeTypeId = nodeTypeId;
|
||||||
this.objectType = objectType;
|
this.objectType = objectType;
|
||||||
@@ -122,6 +134,7 @@ export class TreeNode {
|
|||||||
this.nodeSubType = nodeSubType;
|
this.nodeSubType = nodeSubType;
|
||||||
this.nodeStatus = nodeStatus;
|
this.nodeStatus = nodeStatus;
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
|
this.filterProperties = filterProperties;
|
||||||
}
|
}
|
||||||
public getConnectionProfile(): ConnectionProfile | undefined {
|
public getConnectionProfile(): ConnectionProfile | undefined {
|
||||||
let currentNode: TreeNode = this;
|
let currentNode: TreeNode = this;
|
||||||
|
|||||||
Reference in New Issue
Block a user