mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Scopes: Remove disabled flag on nodes search input (#89041)
This commit is contained in:
@@ -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<ScopesFiltersSceneState>
|
||||
|
||||
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<ScopesFiltersSceneState>
|
||||
|
||||
this.addActivationHandler(() => {
|
||||
this.fetchBaseNodes();
|
||||
|
||||
return () => {
|
||||
this.nodesFetchingSub?.unsubscribe();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,6 +87,8 @@ export class ScopesFiltersScene extends SceneObjectBase<ScopesFiltersSceneState>
|
||||
}
|
||||
|
||||
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<ScopesFiltersSceneState>
|
||||
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[]) {
|
||||
|
||||
@@ -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 && (
|
||||
<Input
|
||||
prefix={<Icon name="filter" />}
|
||||
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)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div role="tree">
|
||||
{childNodesArr.map((childNode) => {
|
||||
const isSelected = childNode.isSelectable && scopeNames.includes(childNode.linkId!);
|
||||
{isNodeLoading && <Skeleton count={5} className={styles.loader} />}
|
||||
|
||||
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 (
|
||||
<div key={childNode.name} role="treeitem" aria-selected={childNode.isExpanded}>
|
||||
<div className={styles.itemTitle}>
|
||||
{childNode.isSelectable && !childNode.isExpanded ? (
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={!!loadingNodeName || (anyChildSelected && !isSelected && node.disableMultiSelect)}
|
||||
data-testid={`scopes-tree-${childNode.name}-checkbox`}
|
||||
onChange={() => {
|
||||
onNodeSelectToggle(childNodePath);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
const childNodePath = [...nodePath, childNode.name];
|
||||
|
||||
{childNode.isExpandable && (
|
||||
<IconButton
|
||||
disabled={(anyChildSelected && !childNode.isExpanded) || !!loadingNodeName}
|
||||
name={
|
||||
!childNode.isExpanded
|
||||
? 'angle-right'
|
||||
: loadingNodeName === childNode.name
|
||||
? 'spinner'
|
||||
: 'angle-down'
|
||||
}
|
||||
aria-label={
|
||||
childNode.isExpanded ? t('scopes.tree.collapse', 'Collapse') : t('scopes.tree.expand', 'Expand')
|
||||
}
|
||||
data-testid={`scopes-tree-${childNode.name}-expand`}
|
||||
onClick={() => {
|
||||
onNodeUpdate(childNodePath, !childNode.isExpanded, childNode.query);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
return (
|
||||
<div key={childNode.name} role="treeitem" aria-selected={childNode.isExpanded}>
|
||||
<div className={styles.itemTitle}>
|
||||
{childNode.isSelectable && !childNode.isExpanded ? (
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={anyChildSelected && !isSelected && node.disableMultiSelect}
|
||||
data-testid={`scopes-tree-${childNode.name}-checkbox`}
|
||||
onChange={() => {
|
||||
onNodeSelectToggle(childNodePath);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<span data-testid={`scopes-tree-${childNode.name}-title`}>{childNode.title}</span>
|
||||
{childNode.isExpandable && (
|
||||
<IconButton
|
||||
disabled={anyChildSelected && !childNode.isExpanded}
|
||||
name={!childNode.isExpanded ? 'angle-right' : 'angle-down'}
|
||||
aria-label={
|
||||
childNode.isExpanded ? t('scopes.tree.collapse', 'Collapse') : t('scopes.tree.expand', 'Expand')
|
||||
}
|
||||
data-testid={`scopes-tree-${childNode.name}-expand`}
|
||||
onClick={() => {
|
||||
onNodeUpdate(childNodePath, !childNode.isExpanded, childNode.query);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<span data-testid={`scopes-tree-${childNode.name}-title`}>{childNode.title}</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.itemChildren}>
|
||||
{childNode.isExpanded && (
|
||||
<ScopesTreeLevel
|
||||
showQuery={showQuery}
|
||||
nodes={node.nodes}
|
||||
nodePath={childNodePath}
|
||||
loadingNodeName={loadingNodeName}
|
||||
scopeNames={scopeNames}
|
||||
onNodeUpdate={onNodeUpdate}
|
||||
onNodeSelectToggle={onNodeSelectToggle}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.itemChildren}>
|
||||
{childNode.isExpanded && (
|
||||
<ScopesTreeLevel
|
||||
showQuery={showQuery}
|
||||
nodes={node.nodes}
|
||||
nodePath={childNodePath}
|
||||
loadingNodeName={loadingNodeName}
|
||||
scopeNames={scopeNames}
|
||||
onNodeUpdate={onNodeUpdate}
|
||||
onNodeSelectToggle={onNodeSelectToggle}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user