From 423c03912b72b98aa84687ca22c245852f737df5 Mon Sep 17 00:00:00 2001 From: Bogdan Matei Date: Wed, 12 Jun 2024 16:18:09 +0300 Subject: [PATCH] Scopes: Remove disabled flag on nodes search input (#89041) --- .../scene/Scopes/ScopesFiltersScene.tsx | 33 ++++- .../scene/Scopes/ScopesTreeLevel.tsx | 123 +++++++++--------- 2 files changed, 90 insertions(+), 66 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx b/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx index 3c048a0ae47..84c7ba88f83 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx +++ b/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx @@ -1,5 +1,6 @@ import { isEqual } from 'lodash'; import React from 'react'; +import { finalize, from, Subscription } from 'rxjs'; import { Scope } from '@grafana/data'; import { @@ -33,6 +34,8 @@ export class ScopesFiltersScene extends SceneObjectBase protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['scopes'] }); + private nodesFetchingSub: Subscription | undefined; + get scopesParent(): ScopesScene { return sceneGraph.getAncestor(this, ScopesScene); } @@ -61,6 +64,10 @@ export class ScopesFiltersScene extends SceneObjectBase this.addActivationHandler(() => { this.fetchBaseNodes(); + + return () => { + this.nodesFetchingSub?.unsubscribe(); + }; }); } @@ -80,6 +87,8 @@ export class ScopesFiltersScene extends SceneObjectBase } public async updateNode(path: string[], isExpanded: boolean, query: string) { + this.nodesFetchingSub?.unsubscribe(); + let nodes = { ...this.state.nodes }; let currentLevel: NodesMap = nodes; @@ -90,16 +99,30 @@ export class ScopesFiltersScene extends SceneObjectBase const name = path[path.length - 1]; const currentNode = currentLevel[name]; - if (isExpanded || currentNode.query !== query) { - this.setState({ loadingNodeName: name }); - - currentNode.nodes = await fetchNodes(name, query); - } + const isDifferentQuery = currentNode.query !== query; currentNode.isExpanded = isExpanded; currentNode.query = query; this.setState({ nodes, loadingNodeName: undefined }); + + if (isExpanded || isDifferentQuery) { + this.setState({ loadingNodeName: name }); + + this.nodesFetchingSub = from(fetchNodes(name, query)) + .pipe( + finalize(() => { + this.setState({ loadingNodeName: undefined }); + }) + ) + .subscribe((childNodes) => { + currentNode.nodes = childNodes; + + this.setState({ nodes }); + + this.nodesFetchingSub?.unsubscribe(); + }); + } } public toggleNodeSelect(path: string[]) { diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeLevel.tsx b/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeLevel.tsx index 51b7d882e02..1749f5790f0 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeLevel.tsx +++ b/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeLevel.tsx @@ -1,6 +1,7 @@ import { css } from '@emotion/css'; import { debounce } from 'lodash'; -import React from 'react'; +import React, { useMemo } from 'react'; +import Skeleton from 'react-loading-skeleton'; import { GrafanaTheme2 } from '@grafana/data'; import { Checkbox, Icon, IconButton, Input, useStyles2 } from '@grafana/ui'; @@ -33,89 +34,86 @@ export function ScopesTreeLevel({ const node = nodes[nodeId]; const childNodes = node.nodes; const childNodesArr = Object.values(childNodes); + const isNodeLoading = loadingNodeName === nodeId; const anyChildExpanded = childNodesArr.some(({ isExpanded }) => isExpanded); const anyChildSelected = childNodesArr.some(({ linkId }) => linkId && scopeNames.includes(linkId!)); + const onQueryUpdate = useMemo(() => debounce(onNodeUpdate, 500), [onNodeUpdate]); + return ( <> {showQuery && !anyChildExpanded && ( } className={styles.searchInput} - disabled={!!loadingNodeName} placeholder={t('scopes.tree.search', 'Filter')} defaultValue={node.query} data-testid={`scopes-tree-${nodeId}-search`} - onChange={debounce((evt) => { - onNodeUpdate(nodePath, true, evt.target.value); - }, 500)} + onInput={(evt) => onQueryUpdate(nodePath, true, evt.currentTarget.value)} /> )}
- {childNodesArr.map((childNode) => { - const isSelected = childNode.isSelectable && scopeNames.includes(childNode.linkId!); + {isNodeLoading && } - if (anyChildExpanded && !childNode.isExpanded && !isSelected) { - return null; - } + {!isNodeLoading && + childNodesArr.map((childNode) => { + const isSelected = childNode.isSelectable && scopeNames.includes(childNode.linkId!); - const childNodePath = [...nodePath, childNode.name]; + if (anyChildExpanded && !childNode.isExpanded && !isSelected) { + return null; + } - return ( -
-
- {childNode.isSelectable && !childNode.isExpanded ? ( - { - onNodeSelectToggle(childNodePath); - }} - /> - ) : null} + const childNodePath = [...nodePath, childNode.name]; - {childNode.isExpandable && ( - { - onNodeUpdate(childNodePath, !childNode.isExpanded, childNode.query); - }} - /> - )} + return ( +
+
+ {childNode.isSelectable && !childNode.isExpanded ? ( + { + onNodeSelectToggle(childNodePath); + }} + /> + ) : null} - {childNode.title} + {childNode.isExpandable && ( + { + onNodeUpdate(childNodePath, !childNode.isExpanded, childNode.query); + }} + /> + )} + + {childNode.title} +
+ +
+ {childNode.isExpanded && ( + + )} +
- -
- {childNode.isExpanded && ( - - )} -
-
- ); - })} + ); + })}
); @@ -126,6 +124,9 @@ const getStyles = (theme: GrafanaTheme2) => { searchInput: css({ margin: theme.spacing(1, 0), }), + loader: css({ + margin: theme.spacing(0.5, 0), + }), itemTitle: css({ alignItems: 'center', display: 'flex',