From 60fed679c4d8e061e4e8b4b9ccfd88dcd5be1451 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Fri, 26 Sep 2025 11:52:28 +0200 Subject: [PATCH] Scopes: Add filterNode and toggleExpandNode to ScopesSelectorService (#111553) Add filterNode and toggleExpandNode to ScopesSelectorService --- .../commandPalette/actions/scopesUtils.ts | 3 +- .../scopes/selector/ScopesSelector.tsx | 6 +- .../scopes/selector/ScopesSelectorService.ts | 74 ++++++++++++++++--- .../features/scopes/selector/ScopesTree.tsx | 17 +++-- .../scopes/selector/ScopesTreeItem.tsx | 11 ++- .../scopes/selector/ScopesTreeItemList.tsx | 9 ++- .../scopes/selector/ScopesTreeSearch.tsx | 6 +- .../scopes/selector/useScopesHighlighting.tsx | 6 +- 8 files changed, 101 insertions(+), 31 deletions(-) diff --git a/public/app/features/commandPalette/actions/scopesUtils.ts b/public/app/features/commandPalette/actions/scopesUtils.ts index 1a24cb47620..65c27a0c0c1 100644 --- a/public/app/features/commandPalette/actions/scopesUtils.ts +++ b/public/app/features/commandPalette/actions/scopesUtils.ts @@ -31,7 +31,7 @@ export function useScopeServicesState() { }, }; } - const { updateNode, selectScope, resetSelection, searchAllNodes, deselectScope, apply } = + const { updateNode, filterNode, selectScope, resetSelection, searchAllNodes, deselectScope, apply } = services.scopesSelectorService; const selectorServiceState: ScopesSelectorServiceState | undefined = useObservable( services.scopesSelectorService.stateObservable ?? new Observable(), @@ -39,6 +39,7 @@ export function useScopeServicesState() { ); return { + filterNode, updateNode, selectScope, resetSelection, diff --git a/public/app/features/scopes/selector/ScopesSelector.tsx b/public/app/features/scopes/selector/ScopesSelector.tsx index 1e06753bcd7..60444eac90a 100644 --- a/public/app/features/scopes/selector/ScopesSelector.tsx +++ b/public/app/features/scopes/selector/ScopesSelector.tsx @@ -65,10 +65,11 @@ export const ScopesSelector = () => { removeAllScopes, closeAndApply, closeAndReset, - updateNode, + filterNode, selectScope, deselectScope, getRecentScopes, + toggleExpandedNode, } = scopesSelectorService; const recentScopes = getRecentScopes(); @@ -128,12 +129,13 @@ export const ScopesSelector = () => { { scopesSelectorService.changeScopes(scopeIds, parentNodeId); scopesSelectorService.closeAndReset(); diff --git a/public/app/features/scopes/selector/ScopesSelectorService.ts b/public/app/features/scopes/selector/ScopesSelectorService.ts index 2a70dca5fde..e3a504faf56 100644 --- a/public/app/features/scopes/selector/ScopesSelectorService.ts +++ b/public/app/features/scopes/selector/ScopesSelectorService.ts @@ -99,6 +99,61 @@ export class ScopesSelectorService extends ScopesServiceBase { + const path = getPathOfNode(scopeNodeId, this.state.nodes); + const nodeToToggle = treeNodeAtPath(this.state.tree!, path); + + if (!nodeToToggle) { + throw new Error(`Node ${scopeNodeId} not found in tree`); + } + + if (nodeToToggle.scopeNodeId !== '' && !isNodeExpandable(this.state.nodes[nodeToToggle.scopeNodeId])) { + throw new Error(`Trying to expand node at id ${scopeNodeId} that is not expandable`); + } + + // Collapse if expanded + if (nodeToToggle.expanded) { + const newTree = modifyTreeNodeAtPath(this.state.tree!, path, (treeNode) => { + treeNode.expanded = false; + // Resets query when collapsing + treeNode.query = ''; + }); + this.updateState({ tree: newTree }); + return; + } + + // Expand if collapsed + const newTree = modifyTreeNodeAtPath(this.state.tree!, path, (treeNode) => { + treeNode.expanded = true; + treeNode.query = ''; + }); + this.updateState({ tree: newTree }); + + await this.loadNodeChildren(path, nodeToToggle); + }; + + public filterNode = async (scopeNodeId: string, query: string) => { + const path = getPathOfNode(scopeNodeId, this.state.nodes); + const nodeToFilter = treeNodeAtPath(this.state.tree!, path); + + if (!nodeToFilter) { + throw new Error(`Trying to filter node at path or id ${scopeNodeId} not found`); + } + + if (nodeToFilter.scopeNodeId !== '' && !isNodeExpandable(this.state.nodes[nodeToFilter.scopeNodeId])) { + throw new Error(`Trying to filter node at id ${scopeNodeId} that is not expandable`); + } + + const newTree = modifyTreeNodeAtPath(this.state.tree!, path, (treeNode) => { + treeNode.expanded = true; + treeNode.query = query; + }); + this.updateState({ tree: newTree }); + + await this.loadNodeChildren(path, nodeToFilter, query); + }; + private expandOrFilterNode = async (scopeNodeId: string, query?: string) => { this.interactionProfiler?.startInteraction('scopeNodeDiscovery'); @@ -135,15 +190,16 @@ export class ScopesSelectorService extends ScopesServiceBase { - treeNode.expanded = false; - treeNode.query = ''; - }); - this.updateState({ tree: newTree }); - } else { + + if (!nodeToCollapse) { throw new Error(`Trying to collapse node at path or id ${scopeNodeId} not found`); } + + const newTree = modifyTreeNodeAtPath(this.state.tree!, path, (treeNode) => { + treeNode.expanded = false; + treeNode.query = ''; + }); + this.updateState({ tree: newTree }); }; private loadNodeChildren = async (path: string[], treeNode: TreeNode, query?: string) => { @@ -165,7 +221,7 @@ export class ScopesSelectorService extends ScopesServiceBase { if (expanded) { diff --git a/public/app/features/scopes/selector/ScopesTree.tsx b/public/app/features/scopes/selector/ScopesTree.tsx index a38bc60f13a..928827b3d32 100644 --- a/public/app/features/scopes/selector/ScopesTree.tsx +++ b/public/app/features/scopes/selector/ScopesTree.tsx @@ -18,7 +18,7 @@ export interface ScopesTreeProps { selectedScopes: SelectedScope[]; scopeNodes: NodesMap; - onNodeUpdate: (scopeNodeId: string, expanded: boolean, query: string) => void; + filterNode: (scopeNodeId: string, query: string) => void; selectScope: (scopeNodeId: string) => void; deselectScope: (scopeNodeId: string) => void; @@ -26,6 +26,8 @@ export interface ScopesTreeProps { // Recent scopes are only shown at the root node recentScopes?: Scope[][]; onRecentScopesSelect?: (scopeIds: string[], parentNodeId?: string) => void; + + toggleExpandedNode: (scopeNodeId: string) => void; } export function ScopesTree({ @@ -34,10 +36,11 @@ export function ScopesTree({ selectedScopes, recentScopes, onRecentScopesSelect, - onNodeUpdate, + filterNode, scopeNodes, selectScope, deselectScope, + toggleExpandedNode, }: ScopesTreeProps) { const styles = useStyles2(getStyles); @@ -75,7 +78,7 @@ export function ScopesTree({ treeQuery: tree.query, scopeNodes, selectedScopes, - onNodeUpdate, + toggleExpandedNode, selectScope, deselectScope, }); @@ -91,7 +94,7 @@ export function ScopesTree({ void; + filterNode: (scopeNodeId: string, query: string) => void; selectScope: (scopeNodeId: string) => void; deselectScope: (scopeNodeId: string) => void; + toggleExpandedNode: (scopeNodeId: string) => void; } export function ScopesTreeItem({ anyChildExpanded, loadingNodeName, treeNode, - onNodeUpdate, + filterNode, scopeNodes, selected, selectedScopes, selectScope, deselectScope, highlighted, + toggleExpandedNode, }: ScopesTreeItemProps) { const styles = useStyles2(getStyles); @@ -126,7 +128,7 @@ export function ScopesTreeItem({ data-testid={`scopes-tree-${treeNode.scopeNodeId}-expand`} aria-label={treeNode.expanded ? t('scopes.tree.collapse', 'Collapse') : t('scopes.tree.expand', 'Expand')} onClick={() => { - onNodeUpdate(treeNode.scopeNodeId, !treeNode.expanded, treeNode.query); + toggleExpandedNode(treeNode.scopeNodeId); }} > @@ -145,11 +147,12 @@ export function ScopesTreeItem({ )} diff --git a/public/app/features/scopes/selector/ScopesTreeItemList.tsx b/public/app/features/scopes/selector/ScopesTreeItemList.tsx index 5560ad61803..33d3a4e2b53 100644 --- a/public/app/features/scopes/selector/ScopesTreeItemList.tsx +++ b/public/app/features/scopes/selector/ScopesTreeItemList.tsx @@ -15,11 +15,12 @@ type Props = { maxHeight: string; selectedScopes: SelectedScope[]; scopeNodes: NodesMap; - onNodeUpdate: (scopeNodeId: string, expanded: boolean, query: string) => void; + filterNode: (scopeNodeId: string, query: string) => void; selectScope: (scopeNodeId: string) => void; deselectScope: (scopeNodeId: string) => void; highlightedId: string | undefined; id: string; + toggleExpandedNode: (scopeNodeId: string) => void; }; export function ScopesTreeItemList({ @@ -30,11 +31,12 @@ export function ScopesTreeItemList({ selectedScopes, scopeNodes, loadingNodeName, - onNodeUpdate, + filterNode, selectScope, deselectScope, highlightedId, id, + toggleExpandedNode, }: Props) { const styles = useStyles2(getStyles); @@ -65,10 +67,11 @@ export function ScopesTreeItemList({ scopeNodes={scopeNodes} loadingNodeName={loadingNodeName} anyChildExpanded={anyChildExpanded} - onNodeUpdate={onNodeUpdate} + filterNode={filterNode} selectScope={selectScope} deselectScope={deselectScope} highlighted={childNode.scopeNodeId === highlightedId} + toggleExpandedNode={toggleExpandedNode} /> ); })} diff --git a/public/app/features/scopes/selector/ScopesTreeSearch.tsx b/public/app/features/scopes/selector/ScopesTreeSearch.tsx index 755d605c783..780165f6074 100644 --- a/public/app/features/scopes/selector/ScopesTreeSearch.tsx +++ b/public/app/features/scopes/selector/ScopesTreeSearch.tsx @@ -12,7 +12,7 @@ export interface ScopesTreeSearchProps { anyChildExpanded: boolean; searchArea: string; treeNode: TreeNode; - onNodeUpdate: (scopeNodeId: string, expanded: boolean, query: string) => void; + filterNode: (scopeNodeId: string, query: string) => void; onFocus: () => void; onBlur: () => void; 'aria-controls': string; @@ -22,7 +22,7 @@ export interface ScopesTreeSearchProps { export function ScopesTreeSearch({ anyChildExpanded, treeNode, - onNodeUpdate, + filterNode, searchArea, onFocus, onBlur, @@ -45,7 +45,7 @@ export function ScopesTreeSearch({ useDebounce( () => { if (inputState.dirty) { - onNodeUpdate(treeNode.scopeNodeId, true, inputState.value); + filterNode(treeNode.scopeNodeId, inputState.value); } }, 500, diff --git a/public/app/features/scopes/selector/useScopesHighlighting.tsx b/public/app/features/scopes/selector/useScopesHighlighting.tsx index 2cf727ade60..75f573a3422 100644 --- a/public/app/features/scopes/selector/useScopesHighlighting.tsx +++ b/public/app/features/scopes/selector/useScopesHighlighting.tsx @@ -11,7 +11,7 @@ interface UseScopesHighlightingParams { treeQuery: string; scopeNodes: NodesMap; selectedScopes: SelectedScope[]; - onNodeUpdate: (scopeNodeId: string, expanded: boolean, query: string) => void; + toggleExpandedNode: (scopeNodeId: string) => void; selectScope: (scopeNodeId: string) => void; deselectScope: (scopeNodeId: string) => void; } @@ -22,7 +22,7 @@ export function useScopesHighlighting({ treeQuery, scopeNodes, selectedScopes, - onNodeUpdate, + toggleExpandedNode, selectScope, deselectScope, }: UseScopesHighlightingParams) { @@ -47,7 +47,7 @@ export function useScopesHighlighting({ isNodeExpandable(scopeNodes[nodeId]); if (isExpanding || isSelectingAndExpandable) { - onNodeUpdate(nodeId, true, treeQuery); + toggleExpandedNode(nodeId); setHighlightEnabled(false); return; }