mirror of
https://github.com/grafana/grafana.git
synced 2024-11-28 19:54:10 -06:00
Scopes: Persist selected scopes when searching (#89758)
This commit is contained in:
parent
c0058f9c7e
commit
4c9fef6183
@ -17,9 +17,9 @@ import { t, Trans } from 'app/core/internationalization';
|
|||||||
|
|
||||||
import { ScopesInput } from './ScopesInput';
|
import { ScopesInput } from './ScopesInput';
|
||||||
import { ScopesScene } from './ScopesScene';
|
import { ScopesScene } from './ScopesScene';
|
||||||
import { ScopesTreeLevel } from './ScopesTreeLevel';
|
import { ScopesTree } from './ScopesTree';
|
||||||
import { fetchNodes, fetchScope, fetchSelectedScopes } from './api';
|
import { fetchNodes, fetchScope, fetchSelectedScopes } from './api';
|
||||||
import { NodesMap, SelectedScope, TreeScope } from './types';
|
import { NodeReason, NodesMap, SelectedScope, TreeScope } from './types';
|
||||||
import { getBasicScope } from './utils';
|
import { getBasicScope } from './utils';
|
||||||
|
|
||||||
export interface ScopesFiltersSceneState extends SceneObjectState {
|
export interface ScopesFiltersSceneState extends SceneObjectState {
|
||||||
@ -47,6 +47,7 @@ export class ScopesFiltersScene extends SceneObjectBase<ScopesFiltersSceneState>
|
|||||||
nodes: {
|
nodes: {
|
||||||
'': {
|
'': {
|
||||||
name: '',
|
name: '',
|
||||||
|
reason: NodeReason.Result,
|
||||||
nodeType: 'container',
|
nodeType: 'container',
|
||||||
title: '',
|
title: '',
|
||||||
isExpandable: true,
|
isExpandable: true,
|
||||||
@ -119,7 +120,19 @@ export class ScopesFiltersScene extends SceneObjectBase<ScopesFiltersSceneState>
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.subscribe((childNodes) => {
|
.subscribe((childNodes) => {
|
||||||
currentNode.nodes = childNodes;
|
const persistedNodes = this.state.treeScopes
|
||||||
|
.map(({ path }) => path[path.length - 1])
|
||||||
|
.filter((nodeName) => nodeName in currentNode.nodes && !(nodeName in childNodes))
|
||||||
|
.reduce<NodesMap>((acc, nodeName) => {
|
||||||
|
acc[nodeName] = {
|
||||||
|
...currentNode.nodes[nodeName],
|
||||||
|
reason: NodeReason.Persisted,
|
||||||
|
};
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
currentNode.nodes = { ...persistedNodes, ...childNodes };
|
||||||
|
|
||||||
this.setState({ nodes });
|
this.setState({ nodes });
|
||||||
|
|
||||||
@ -284,7 +297,7 @@ export function ScopesFiltersSceneRenderer({ model }: SceneComponentProps<Scopes
|
|||||||
{isLoadingScopes ? (
|
{isLoadingScopes ? (
|
||||||
<Spinner data-testid="scopes-filters-loading" />
|
<Spinner data-testid="scopes-filters-loading" />
|
||||||
) : (
|
) : (
|
||||||
<ScopesTreeLevel
|
<ScopesTree
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
nodePath={['']}
|
nodePath={['']}
|
||||||
loadingNodeName={loadingNodeName}
|
loadingNodeName={loadingNodeName}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { groupBy } from 'lodash';
|
import { groupBy } from 'lodash';
|
||||||
import { useMemo } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { IconButton, Input, Tooltip } from '@grafana/ui';
|
import { IconButton, Input, Tooltip, useStyles2 } from '@grafana/ui';
|
||||||
import { useStyles2 } from '@grafana/ui/';
|
|
||||||
import { t } from 'app/core/internationalization';
|
import { t } from 'app/core/internationalization';
|
||||||
|
|
||||||
import { NodesMap, SelectedScope } from './types';
|
import { NodesMap, SelectedScope } from './types';
|
||||||
@ -28,6 +27,12 @@ export function ScopesInput({
|
|||||||
}: ScopesInputProps) {
|
}: ScopesInputProps) {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
const [isTooltipVisible, setIsTooltipVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTooltipVisible(false);
|
||||||
|
}, [scopes]);
|
||||||
|
|
||||||
const scopesPaths = useMemo(() => {
|
const scopesPaths = useMemo(() => {
|
||||||
const pathsTitles = scopes.map(({ scope, path }) => {
|
const pathsTitles = scopes.map(({ scope, path }) => {
|
||||||
let currentLevel = nodes;
|
let currentLevel = nodes;
|
||||||
@ -64,7 +69,7 @@ export function ScopesInput({
|
|||||||
|
|
||||||
const groupedByPath = groupBy(pathsTitles, ([path]) => path);
|
const groupedByPath = groupBy(pathsTitles, ([path]) => path);
|
||||||
|
|
||||||
return Object.entries(groupedByPath)
|
const scopesPaths = Object.entries(groupedByPath)
|
||||||
.map(([path, pathScopes]) => {
|
.map(([path, pathScopes]) => {
|
||||||
const scopesTitles = pathScopes.map(([, scopeTitle]) => scopeTitle).join(', ');
|
const scopesTitles = pathScopes.map(([, scopeTitle]) => scopeTitle).join(', ');
|
||||||
|
|
||||||
@ -75,41 +80,44 @@ export function ScopesInput({
|
|||||||
{path}
|
{path}
|
||||||
</p>
|
</p>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
return <>{scopesPaths}</>;
|
||||||
}, [nodes, scopes, styles]);
|
}, [nodes, scopes, styles]);
|
||||||
|
|
||||||
const scopesTitles = useMemo(() => scopes.map(({ scope }) => scope.spec.title).join(', '), [scopes]);
|
const scopesTitles = useMemo(() => scopes.map(({ scope }) => scope.spec.title).join(', '), [scopes]);
|
||||||
|
|
||||||
const input = (
|
const input = useMemo(
|
||||||
<Input
|
() => (
|
||||||
readOnly
|
<Input
|
||||||
placeholder={t('scopes.filters.input.placeholder', 'Select scopes...')}
|
readOnly
|
||||||
loading={isLoading}
|
placeholder={t('scopes.filters.input.placeholder', 'Select scopes...')}
|
||||||
value={scopesTitles}
|
loading={isLoading}
|
||||||
aria-label={t('scopes.filters.input.placeholder', 'Select scopes...')}
|
value={scopesTitles}
|
||||||
data-testid="scopes-filters-input"
|
aria-label={t('scopes.filters.input.placeholder', 'Select scopes...')}
|
||||||
suffix={
|
data-testid="scopes-filters-input"
|
||||||
scopes.length > 0 && !isDisabled ? (
|
suffix={
|
||||||
<IconButton
|
scopes.length > 0 && !isDisabled ? (
|
||||||
aria-label={t('scopes.filters.input.removeAll', 'Remove all scopes')}
|
<IconButton
|
||||||
name="times"
|
aria-label={t('scopes.filters.input.removeAll', 'Remove all scopes')}
|
||||||
onClick={() => onRemoveAllClick()}
|
name="times"
|
||||||
/>
|
onClick={() => onRemoveAllClick()}
|
||||||
) : undefined
|
/>
|
||||||
}
|
) : undefined
|
||||||
onClick={() => {
|
|
||||||
if (!isDisabled) {
|
|
||||||
onInputClick();
|
|
||||||
}
|
}
|
||||||
}}
|
onMouseOver={() => setIsTooltipVisible(true)}
|
||||||
/>
|
onMouseOut={() => setIsTooltipVisible(false)}
|
||||||
|
onClick={() => {
|
||||||
|
if (!isDisabled) {
|
||||||
|
onInputClick();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[isDisabled, isLoading, onInputClick, onRemoveAllClick, scopes, scopesTitles]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (scopes.length === 0) {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content={<>{scopesPaths}</>} interactive={true}>
|
<Tooltip content={scopesPaths} show={scopes.length === 0 ? false : isTooltipVisible}>
|
||||||
{input}
|
{input}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
@ -10,47 +10,54 @@ import { ScopesFiltersScene } from './ScopesFiltersScene';
|
|||||||
import { ScopesScene } from './ScopesScene';
|
import { ScopesScene } from './ScopesScene';
|
||||||
import {
|
import {
|
||||||
buildTestScene,
|
buildTestScene,
|
||||||
fetchSuggestedDashboardsSpy,
|
|
||||||
fetchNodesSpy,
|
fetchNodesSpy,
|
||||||
fetchScopeSpy,
|
fetchScopeSpy,
|
||||||
fetchSelectedScopesSpy,
|
fetchSelectedScopesSpy,
|
||||||
getApplicationsClustersExpand,
|
fetchSuggestedDashboardsSpy,
|
||||||
getApplicationsClustersSelect,
|
|
||||||
getApplicationsClustersSlothClusterNorthSelect,
|
|
||||||
getApplicationsClustersSlothClusterSouthSelect,
|
|
||||||
getApplicationsExpand,
|
|
||||||
getApplicationsSearch,
|
|
||||||
getApplicationsSlothPictureFactorySelect,
|
|
||||||
getApplicationsSlothPictureFactoryTitle,
|
|
||||||
getApplicationsSlothVoteTrackerSelect,
|
|
||||||
getFiltersApply,
|
|
||||||
getFiltersCancel,
|
|
||||||
getFiltersInput,
|
|
||||||
getClustersExpand,
|
|
||||||
getClustersSelect,
|
|
||||||
getClustersSlothClusterNorthRadio,
|
|
||||||
getClustersSlothClusterSouthRadio,
|
|
||||||
getDashboard,
|
getDashboard,
|
||||||
getDashboardsContainer,
|
getDashboardsContainer,
|
||||||
getDashboardsExpand,
|
getDashboardsExpand,
|
||||||
getDashboardsSearch,
|
getDashboardsSearch,
|
||||||
|
getFiltersApply,
|
||||||
|
getFiltersCancel,
|
||||||
|
getFiltersInput,
|
||||||
getMock,
|
getMock,
|
||||||
|
getNotFoundForFilter,
|
||||||
|
getNotFoundForFilterClear,
|
||||||
|
getNotFoundForScope,
|
||||||
|
getNotFoundNoScopes,
|
||||||
|
getPersistedApplicationsSlothPictureFactorySelect,
|
||||||
|
getPersistedApplicationsSlothPictureFactoryTitle,
|
||||||
|
getPersistedApplicationsSlothVoteTrackerTitle,
|
||||||
|
getResultApplicationsClustersExpand,
|
||||||
|
getResultApplicationsClustersSelect,
|
||||||
|
getResultApplicationsClustersSlothClusterNorthSelect,
|
||||||
|
getResultApplicationsClustersSlothClusterSouthSelect,
|
||||||
|
getResultApplicationsExpand,
|
||||||
|
getResultApplicationsSlothPictureFactorySelect,
|
||||||
|
getResultApplicationsSlothPictureFactoryTitle,
|
||||||
|
getResultApplicationsSlothVoteTrackerSelect,
|
||||||
|
getResultApplicationsSlothVoteTrackerTitle,
|
||||||
|
getResultClustersExpand,
|
||||||
|
getResultClustersSelect,
|
||||||
|
getResultClustersSlothClusterEastRadio,
|
||||||
|
getResultClustersSlothClusterNorthRadio,
|
||||||
|
getResultClustersSlothClusterSouthRadio,
|
||||||
|
getTreeHeadline,
|
||||||
|
getTreeSearch,
|
||||||
mocksScopes,
|
mocksScopes,
|
||||||
queryAllDashboard,
|
queryAllDashboard,
|
||||||
queryFiltersApply,
|
|
||||||
queryApplicationsClustersTitle,
|
|
||||||
queryApplicationsSlothPictureFactoryTitle,
|
|
||||||
queryApplicationsSlothVoteTrackerTitle,
|
|
||||||
queryDashboard,
|
queryDashboard,
|
||||||
queryDashboardsContainer,
|
queryDashboardsContainer,
|
||||||
queryDashboardsExpand,
|
queryDashboardsExpand,
|
||||||
renderDashboard,
|
|
||||||
getNotFoundForScope,
|
|
||||||
queryDashboardsSearch,
|
queryDashboardsSearch,
|
||||||
getNotFoundForFilter,
|
queryFiltersApply,
|
||||||
getClustersSlothClusterEastRadio,
|
queryPersistedApplicationsSlothPictureFactoryTitle,
|
||||||
getNotFoundForFilterClear,
|
queryPersistedApplicationsSlothVoteTrackerTitle,
|
||||||
getNotFoundNoScopes,
|
queryResultApplicationsClustersTitle,
|
||||||
|
queryResultApplicationsSlothPictureFactoryTitle,
|
||||||
|
queryResultApplicationsSlothVoteTrackerTitle,
|
||||||
|
renderDashboard,
|
||||||
} from './testUtils';
|
} from './testUtils';
|
||||||
|
|
||||||
jest.mock('@grafana/runtime', () => ({
|
jest.mock('@grafana/runtime', () => ({
|
||||||
@ -106,15 +113,15 @@ describe('ScopesScene', () => {
|
|||||||
describe('Tree', () => {
|
describe('Tree', () => {
|
||||||
it('Navigates through scopes nodes', async () => {
|
it('Navigates through scopes nodes', async () => {
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getApplicationsClustersExpand());
|
await userEvents.click(getResultApplicationsClustersExpand());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Fetches scope details on select', async () => {
|
it('Fetches scope details on select', async () => {
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getApplicationsSlothVoteTrackerSelect());
|
await userEvents.click(getResultApplicationsSlothVoteTrackerSelect());
|
||||||
await waitFor(() => expect(fetchScopeSpy).toHaveBeenCalledTimes(1));
|
await waitFor(() => expect(fetchScopeSpy).toHaveBeenCalledTimes(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -126,77 +133,167 @@ describe('ScopesScene', () => {
|
|||||||
])
|
])
|
||||||
);
|
);
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
expect(getApplicationsSlothVoteTrackerSelect()).toBeChecked();
|
expect(getResultApplicationsSlothVoteTrackerSelect()).toBeChecked();
|
||||||
expect(getApplicationsSlothPictureFactorySelect()).toBeChecked();
|
expect(getResultApplicationsSlothPictureFactorySelect()).toBeChecked();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Can select scopes from same level', async () => {
|
it('Can select scopes from same level', async () => {
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getApplicationsSlothVoteTrackerSelect());
|
await userEvents.click(getResultApplicationsSlothVoteTrackerSelect());
|
||||||
await userEvents.click(getApplicationsSlothPictureFactorySelect());
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
await userEvents.click(getApplicationsClustersSelect());
|
await userEvents.click(getResultApplicationsClustersSelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
expect(getFiltersInput().value).toBe('slothVoteTracker, slothPictureFactory, Cluster Index Helper');
|
expect(getFiltersInput().value).toBe('slothVoteTracker, slothPictureFactory, Cluster Index Helper');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Can select a node from an inner level', async () => {
|
it('Can select a node from an inner level', async () => {
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getApplicationsSlothVoteTrackerSelect());
|
await userEvents.click(getResultApplicationsSlothVoteTrackerSelect());
|
||||||
await userEvents.click(getApplicationsClustersExpand());
|
await userEvents.click(getResultApplicationsClustersExpand());
|
||||||
await userEvents.click(getApplicationsClustersSlothClusterNorthSelect());
|
await userEvents.click(getResultApplicationsClustersSlothClusterNorthSelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
expect(getFiltersInput().value).toBe('slothClusterNorth');
|
expect(getFiltersInput().value).toBe('slothClusterNorth');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Can select a node from an upper level', async () => {
|
it('Can select a node from an upper level', async () => {
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getApplicationsSlothVoteTrackerSelect());
|
await userEvents.click(getResultApplicationsSlothVoteTrackerSelect());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getClustersSelect());
|
await userEvents.click(getResultClustersSelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
expect(getFiltersInput().value).toBe('Cluster Index Helper');
|
expect(getFiltersInput().value).toBe('Cluster Index Helper');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Respects only one select per container', async () => {
|
it('Respects only one select per container', async () => {
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getClustersExpand());
|
await userEvents.click(getResultClustersExpand());
|
||||||
await userEvents.click(getClustersSlothClusterNorthRadio());
|
await userEvents.click(getResultClustersSlothClusterNorthRadio());
|
||||||
expect(getClustersSlothClusterNorthRadio().checked).toBe(true);
|
expect(getResultClustersSlothClusterNorthRadio().checked).toBe(true);
|
||||||
expect(getClustersSlothClusterSouthRadio().checked).toBe(false);
|
expect(getResultClustersSlothClusterSouthRadio().checked).toBe(false);
|
||||||
await userEvents.click(getClustersSlothClusterSouthRadio());
|
await userEvents.click(getResultClustersSlothClusterSouthRadio());
|
||||||
expect(getClustersSlothClusterNorthRadio().checked).toBe(false);
|
expect(getResultClustersSlothClusterNorthRadio().checked).toBe(false);
|
||||||
expect(getClustersSlothClusterSouthRadio().checked).toBe(true);
|
expect(getResultClustersSlothClusterSouthRadio().checked).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Search works', async () => {
|
it('Search works', async () => {
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.type(getApplicationsSearch(), 'Clusters');
|
await userEvents.type(getTreeSearch(), 'Clusters');
|
||||||
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
|
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
|
||||||
expect(queryApplicationsSlothPictureFactoryTitle()).not.toBeInTheDocument();
|
expect(queryResultApplicationsSlothPictureFactoryTitle()).not.toBeInTheDocument();
|
||||||
expect(queryApplicationsSlothVoteTrackerTitle()).not.toBeInTheDocument();
|
expect(queryResultApplicationsSlothVoteTrackerTitle()).not.toBeInTheDocument();
|
||||||
expect(getApplicationsClustersSelect()).toBeInTheDocument();
|
expect(getResultApplicationsClustersSelect()).toBeInTheDocument();
|
||||||
await userEvents.clear(getApplicationsSearch());
|
await userEvents.clear(getTreeSearch());
|
||||||
await userEvents.type(getApplicationsSearch(), 'sloth');
|
await userEvents.type(getTreeSearch(), 'sloth');
|
||||||
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(4));
|
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(4));
|
||||||
expect(getApplicationsSlothPictureFactoryTitle()).toBeInTheDocument();
|
expect(getResultApplicationsSlothPictureFactoryTitle()).toBeInTheDocument();
|
||||||
expect(getApplicationsSlothVoteTrackerSelect()).toBeInTheDocument();
|
expect(getResultApplicationsSlothVoteTrackerSelect()).toBeInTheDocument();
|
||||||
expect(queryApplicationsClustersTitle()).not.toBeInTheDocument();
|
expect(queryResultApplicationsClustersTitle()).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Opens to a selected scope', async () => {
|
it('Opens to a selected scope', async () => {
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getApplicationsSlothPictureFactorySelect());
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getClustersExpand());
|
await userEvents.click(getResultClustersExpand());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
expect(queryApplicationsSlothPictureFactoryTitle()).toBeInTheDocument();
|
expect(queryResultApplicationsSlothPictureFactoryTitle()).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Persists a scope', async () => {
|
||||||
|
await userEvents.click(getFiltersInput());
|
||||||
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
|
await userEvents.type(getTreeSearch(), 'slothVoteTracker');
|
||||||
|
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
|
||||||
|
expect(getPersistedApplicationsSlothPictureFactoryTitle()).toBeInTheDocument();
|
||||||
|
expect(queryPersistedApplicationsSlothVoteTrackerTitle()).not.toBeInTheDocument();
|
||||||
|
expect(queryResultApplicationsSlothPictureFactoryTitle()).not.toBeInTheDocument();
|
||||||
|
expect(getResultApplicationsSlothVoteTrackerTitle()).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Does not persist a retrieved scope', async () => {
|
||||||
|
await userEvents.click(getFiltersInput());
|
||||||
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
|
await userEvents.type(getTreeSearch(), 'slothPictureFactory');
|
||||||
|
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
|
||||||
|
expect(queryPersistedApplicationsSlothPictureFactoryTitle()).not.toBeInTheDocument();
|
||||||
|
expect(getResultApplicationsSlothPictureFactoryTitle()).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Removes persisted nodes', async () => {
|
||||||
|
await userEvents.click(getFiltersInput());
|
||||||
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
|
await userEvents.type(getTreeSearch(), 'slothVoteTracker');
|
||||||
|
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
|
||||||
|
await userEvents.clear(getTreeSearch());
|
||||||
|
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(4));
|
||||||
|
expect(queryPersistedApplicationsSlothPictureFactoryTitle()).not.toBeInTheDocument();
|
||||||
|
expect(queryPersistedApplicationsSlothVoteTrackerTitle()).not.toBeInTheDocument();
|
||||||
|
expect(getResultApplicationsSlothPictureFactoryTitle()).toBeInTheDocument();
|
||||||
|
expect(getResultApplicationsSlothVoteTrackerTitle()).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Persists nodes from search', async () => {
|
||||||
|
await userEvents.click(getFiltersInput());
|
||||||
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
|
await userEvents.type(getTreeSearch(), 'sloth');
|
||||||
|
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
|
||||||
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
|
await userEvents.click(getResultApplicationsSlothVoteTrackerSelect());
|
||||||
|
await userEvents.type(getTreeSearch(), 'slothunknown');
|
||||||
|
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(4));
|
||||||
|
expect(getPersistedApplicationsSlothPictureFactoryTitle()).toBeInTheDocument();
|
||||||
|
expect(getPersistedApplicationsSlothVoteTrackerTitle()).toBeInTheDocument();
|
||||||
|
await userEvents.clear(getTreeSearch());
|
||||||
|
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(5));
|
||||||
|
expect(getResultApplicationsSlothPictureFactoryTitle()).toBeInTheDocument();
|
||||||
|
expect(getResultApplicationsSlothVoteTrackerTitle()).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Selects a persisted scope', async () => {
|
||||||
|
await userEvents.click(getFiltersInput());
|
||||||
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
|
await userEvents.type(getTreeSearch(), 'slothVoteTracker');
|
||||||
|
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
|
||||||
|
await userEvents.click(getResultApplicationsSlothVoteTrackerSelect());
|
||||||
|
await userEvents.click(getFiltersApply());
|
||||||
|
expect(getFiltersInput().value).toBe('slothPictureFactory, slothVoteTracker');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Deselects a persisted scope', async () => {
|
||||||
|
await userEvents.click(getFiltersInput());
|
||||||
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
|
await userEvents.type(getTreeSearch(), 'slothVoteTracker');
|
||||||
|
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
|
||||||
|
await userEvents.click(getResultApplicationsSlothVoteTrackerSelect());
|
||||||
|
await userEvents.click(getFiltersApply());
|
||||||
|
expect(getFiltersInput().value).toBe('slothPictureFactory, slothVoteTracker');
|
||||||
|
await userEvents.click(getFiltersInput());
|
||||||
|
await userEvents.click(getPersistedApplicationsSlothPictureFactorySelect());
|
||||||
|
await userEvents.click(getFiltersApply());
|
||||||
|
expect(getFiltersInput().value).toBe('slothVoteTracker');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Shows the proper headline', async () => {
|
||||||
|
await userEvents.click(getFiltersInput());
|
||||||
|
expect(getTreeHeadline()).toHaveTextContent('Recommended');
|
||||||
|
await userEvents.type(getTreeSearch(), 'Applications');
|
||||||
|
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(2));
|
||||||
|
expect(getTreeHeadline()).toHaveTextContent('Results');
|
||||||
|
await userEvents.type(getTreeSearch(), 'unknown');
|
||||||
|
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
|
||||||
|
expect(getTreeHeadline()).toHaveTextContent('No results found for your query');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -208,7 +305,7 @@ describe('ScopesScene', () => {
|
|||||||
|
|
||||||
it('Fetches scope details on save', async () => {
|
it('Fetches scope details on save', async () => {
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getClustersSelect());
|
await userEvents.click(getResultClustersSelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
await waitFor(() => expect(fetchSelectedScopesSpy).toHaveBeenCalled());
|
await waitFor(() => expect(fetchSelectedScopesSpy).toHaveBeenCalled());
|
||||||
expect(filtersScene.getSelectedScopes()).toEqual(
|
expect(filtersScene.getSelectedScopes()).toEqual(
|
||||||
@ -218,7 +315,7 @@ describe('ScopesScene', () => {
|
|||||||
|
|
||||||
it("Doesn't save the scopes on close", async () => {
|
it("Doesn't save the scopes on close", async () => {
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getClustersSelect());
|
await userEvents.click(getResultClustersSelect());
|
||||||
await userEvents.click(getFiltersCancel());
|
await userEvents.click(getFiltersCancel());
|
||||||
await waitFor(() => expect(fetchSelectedScopesSpy).not.toHaveBeenCalled());
|
await waitFor(() => expect(fetchSelectedScopesSpy).not.toHaveBeenCalled());
|
||||||
expect(filtersScene.getSelectedScopes()).toEqual([]);
|
expect(filtersScene.getSelectedScopes()).toEqual([]);
|
||||||
@ -226,7 +323,7 @@ describe('ScopesScene', () => {
|
|||||||
|
|
||||||
it('Shows selected scopes', async () => {
|
it('Shows selected scopes', async () => {
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getClustersSelect());
|
await userEvents.click(getResultClustersSelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
expect(getFiltersInput().value).toEqual('Cluster Index Helper');
|
expect(getFiltersInput().value).toEqual('Cluster Index Helper');
|
||||||
});
|
});
|
||||||
@ -240,8 +337,8 @@ describe('ScopesScene', () => {
|
|||||||
|
|
||||||
it('Does not fetch dashboards list when the list is not expanded', async () => {
|
it('Does not fetch dashboards list when the list is not expanded', async () => {
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getApplicationsSlothPictureFactorySelect());
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
await waitFor(() => expect(fetchSuggestedDashboardsSpy).not.toHaveBeenCalled());
|
await waitFor(() => expect(fetchSuggestedDashboardsSpy).not.toHaveBeenCalled());
|
||||||
});
|
});
|
||||||
@ -249,16 +346,16 @@ describe('ScopesScene', () => {
|
|||||||
it('Fetches dashboards list when the list is expanded', async () => {
|
it('Fetches dashboards list when the list is expanded', async () => {
|
||||||
await userEvents.click(getDashboardsExpand());
|
await userEvents.click(getDashboardsExpand());
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getApplicationsSlothPictureFactorySelect());
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
await waitFor(() => expect(fetchSuggestedDashboardsSpy).toHaveBeenCalled());
|
await waitFor(() => expect(fetchSuggestedDashboardsSpy).toHaveBeenCalled());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Fetches dashboards list when the list is expanded after scope selection', async () => {
|
it('Fetches dashboards list when the list is expanded after scope selection', async () => {
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getApplicationsSlothPictureFactorySelect());
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
await userEvents.click(getDashboardsExpand());
|
await userEvents.click(getDashboardsExpand());
|
||||||
await waitFor(() => expect(fetchSuggestedDashboardsSpy).toHaveBeenCalled());
|
await waitFor(() => expect(fetchSuggestedDashboardsSpy).toHaveBeenCalled());
|
||||||
@ -267,22 +364,22 @@ describe('ScopesScene', () => {
|
|||||||
it('Shows dashboards for multiple scopes', async () => {
|
it('Shows dashboards for multiple scopes', async () => {
|
||||||
await userEvents.click(getDashboardsExpand());
|
await userEvents.click(getDashboardsExpand());
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getApplicationsSlothPictureFactorySelect());
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
expect(getDashboard('1')).toBeInTheDocument();
|
expect(getDashboard('1')).toBeInTheDocument();
|
||||||
expect(getDashboard('2')).toBeInTheDocument();
|
expect(getDashboard('2')).toBeInTheDocument();
|
||||||
expect(queryDashboard('3')).not.toBeInTheDocument();
|
expect(queryDashboard('3')).not.toBeInTheDocument();
|
||||||
expect(queryDashboard('4')).not.toBeInTheDocument();
|
expect(queryDashboard('4')).not.toBeInTheDocument();
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsSlothVoteTrackerSelect());
|
await userEvents.click(getResultApplicationsSlothVoteTrackerSelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
expect(getDashboard('1')).toBeInTheDocument();
|
expect(getDashboard('1')).toBeInTheDocument();
|
||||||
expect(getDashboard('2')).toBeInTheDocument();
|
expect(getDashboard('2')).toBeInTheDocument();
|
||||||
expect(getDashboard('3')).toBeInTheDocument();
|
expect(getDashboard('3')).toBeInTheDocument();
|
||||||
expect(getDashboard('4')).toBeInTheDocument();
|
expect(getDashboard('4')).toBeInTheDocument();
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsSlothPictureFactorySelect());
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
expect(queryDashboard('1')).not.toBeInTheDocument();
|
expect(queryDashboard('1')).not.toBeInTheDocument();
|
||||||
expect(queryDashboard('2')).not.toBeInTheDocument();
|
expect(queryDashboard('2')).not.toBeInTheDocument();
|
||||||
@ -293,8 +390,8 @@ describe('ScopesScene', () => {
|
|||||||
it('Filters the dashboards list', async () => {
|
it('Filters the dashboards list', async () => {
|
||||||
await userEvents.click(getDashboardsExpand());
|
await userEvents.click(getDashboardsExpand());
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getApplicationsSlothPictureFactorySelect());
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
expect(getDashboard('1')).toBeInTheDocument();
|
expect(getDashboard('1')).toBeInTheDocument();
|
||||||
expect(getDashboard('2')).toBeInTheDocument();
|
expect(getDashboard('2')).toBeInTheDocument();
|
||||||
@ -305,10 +402,10 @@ describe('ScopesScene', () => {
|
|||||||
it('Deduplicates the dashboards list', async () => {
|
it('Deduplicates the dashboards list', async () => {
|
||||||
await userEvents.click(getDashboardsExpand());
|
await userEvents.click(getDashboardsExpand());
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getApplicationsClustersExpand());
|
await userEvents.click(getResultApplicationsClustersExpand());
|
||||||
await userEvents.click(getApplicationsClustersSlothClusterNorthSelect());
|
await userEvents.click(getResultApplicationsClustersSlothClusterNorthSelect());
|
||||||
await userEvents.click(getApplicationsClustersSlothClusterSouthSelect());
|
await userEvents.click(getResultApplicationsClustersSlothClusterSouthSelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
expect(queryAllDashboard('5')).toHaveLength(1);
|
expect(queryAllDashboard('5')).toHaveLength(1);
|
||||||
expect(queryAllDashboard('6')).toHaveLength(1);
|
expect(queryAllDashboard('6')).toHaveLength(1);
|
||||||
@ -325,8 +422,8 @@ describe('ScopesScene', () => {
|
|||||||
it('Does not show the input when there are no dashboards found for scope', async () => {
|
it('Does not show the input when there are no dashboards found for scope', async () => {
|
||||||
await userEvents.click(getDashboardsExpand());
|
await userEvents.click(getDashboardsExpand());
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getClustersExpand());
|
await userEvents.click(getResultClustersExpand());
|
||||||
await userEvents.click(getClustersSlothClusterEastRadio());
|
await userEvents.click(getResultClustersSlothClusterEastRadio());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
expect(getNotFoundForScope()).toBeInTheDocument();
|
expect(getNotFoundForScope()).toBeInTheDocument();
|
||||||
expect(queryDashboardsSearch()).not.toBeInTheDocument();
|
expect(queryDashboardsSearch()).not.toBeInTheDocument();
|
||||||
@ -335,8 +432,8 @@ describe('ScopesScene', () => {
|
|||||||
it('Does show the input and a message when there are no dashboards found for filter', async () => {
|
it('Does show the input and a message when there are no dashboards found for filter', async () => {
|
||||||
await userEvents.click(getDashboardsExpand());
|
await userEvents.click(getDashboardsExpand());
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getApplicationsSlothPictureFactorySelect());
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
await userEvents.type(getDashboardsSearch(), 'unknown');
|
await userEvents.type(getDashboardsSearch(), 'unknown');
|
||||||
expect(queryDashboardsSearch()).toBeInTheDocument();
|
expect(queryDashboardsSearch()).toBeInTheDocument();
|
||||||
@ -380,8 +477,8 @@ describe('ScopesScene', () => {
|
|||||||
describe('Enrichers', () => {
|
describe('Enrichers', () => {
|
||||||
it('Data requests', async () => {
|
it('Data requests', async () => {
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getApplicationsSlothPictureFactorySelect());
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!;
|
const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!;
|
||||||
@ -391,7 +488,7 @@ describe('ScopesScene', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsSlothVoteTrackerSelect());
|
await userEvents.click(getResultApplicationsSlothVoteTrackerSelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!;
|
const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!;
|
||||||
@ -403,7 +500,7 @@ describe('ScopesScene', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsSlothPictureFactorySelect());
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!;
|
const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!;
|
||||||
@ -415,8 +512,8 @@ describe('ScopesScene', () => {
|
|||||||
|
|
||||||
it('Filters requests', async () => {
|
it('Filters requests', async () => {
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsExpand());
|
await userEvents.click(getResultApplicationsExpand());
|
||||||
await userEvents.click(getApplicationsSlothPictureFactorySelect());
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(dashboardScene.enrichFiltersRequest().scopes).toEqual(
|
expect(dashboardScene.enrichFiltersRequest().scopes).toEqual(
|
||||||
@ -425,7 +522,7 @@ describe('ScopesScene', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsSlothVoteTrackerSelect());
|
await userEvents.click(getResultApplicationsSlothVoteTrackerSelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(dashboardScene.enrichFiltersRequest().scopes).toEqual(
|
expect(dashboardScene.enrichFiltersRequest().scopes).toEqual(
|
||||||
@ -436,7 +533,7 @@ describe('ScopesScene', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await userEvents.click(getFiltersInput());
|
await userEvents.click(getFiltersInput());
|
||||||
await userEvents.click(getApplicationsSlothPictureFactorySelect());
|
await userEvents.click(getResultApplicationsSlothPictureFactorySelect());
|
||||||
await userEvents.click(getFiltersApply());
|
await userEvents.click(getFiltersApply());
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(dashboardScene.enrichFiltersRequest().scopes).toEqual(
|
expect(dashboardScene.enrichFiltersRequest().scopes).toEqual(
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
import { groupBy } from 'lodash';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { ScopesTreeHeadline } from './ScopesTreeHeadline';
|
||||||
|
import { ScopesTreeItem } from './ScopesTreeItem';
|
||||||
|
import { ScopesTreeLoading } from './ScopesTreeLoading';
|
||||||
|
import { ScopesTreeSearch } from './ScopesTreeSearch';
|
||||||
|
import { NodeReason, NodesMap, OnNodeSelectToggle, OnNodeUpdate, TreeScope } from './types';
|
||||||
|
|
||||||
|
export interface ScopesTreeProps {
|
||||||
|
nodes: NodesMap;
|
||||||
|
nodePath: string[];
|
||||||
|
loadingNodeName: string | undefined;
|
||||||
|
scopes: TreeScope[];
|
||||||
|
onNodeUpdate: OnNodeUpdate;
|
||||||
|
onNodeSelectToggle: OnNodeSelectToggle;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ScopesTree({
|
||||||
|
nodes,
|
||||||
|
nodePath,
|
||||||
|
loadingNodeName,
|
||||||
|
scopes,
|
||||||
|
onNodeUpdate,
|
||||||
|
onNodeSelectToggle,
|
||||||
|
}: ScopesTreeProps) {
|
||||||
|
const nodeId = nodePath[nodePath.length - 1];
|
||||||
|
const node = nodes[nodeId];
|
||||||
|
const childNodes = Object.values(node.nodes);
|
||||||
|
const isNodeLoading = loadingNodeName === nodeId;
|
||||||
|
const scopeNames = scopes.map(({ scopeName }) => scopeName);
|
||||||
|
const anyChildExpanded = childNodes.some(({ isExpanded }) => isExpanded);
|
||||||
|
const groupedNodes = useMemo(() => groupBy(childNodes, 'reason'), [childNodes]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ScopesTreeSearch
|
||||||
|
anyChildExpanded={anyChildExpanded}
|
||||||
|
nodePath={nodePath}
|
||||||
|
query={node.query}
|
||||||
|
onNodeUpdate={onNodeUpdate}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ScopesTreeLoading isNodeLoading={isNodeLoading}>
|
||||||
|
<ScopesTreeItem
|
||||||
|
anyChildExpanded={anyChildExpanded}
|
||||||
|
isNodeLoading={isNodeLoading}
|
||||||
|
loadingNodeName={loadingNodeName}
|
||||||
|
node={node}
|
||||||
|
nodePath={nodePath}
|
||||||
|
nodes={groupedNodes[NodeReason.Persisted] ?? []}
|
||||||
|
scopes={scopes}
|
||||||
|
scopeNames={scopeNames}
|
||||||
|
type="persisted"
|
||||||
|
onNodeSelectToggle={onNodeSelectToggle}
|
||||||
|
onNodeUpdate={onNodeUpdate}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ScopesTreeHeadline
|
||||||
|
anyChildExpanded={anyChildExpanded}
|
||||||
|
query={node.query}
|
||||||
|
resultsNodes={groupedNodes[NodeReason.Result] ?? []}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ScopesTreeItem
|
||||||
|
anyChildExpanded={anyChildExpanded}
|
||||||
|
isNodeLoading={isNodeLoading}
|
||||||
|
loadingNodeName={loadingNodeName}
|
||||||
|
node={node}
|
||||||
|
nodePath={nodePath}
|
||||||
|
nodes={groupedNodes[NodeReason.Result] ?? []}
|
||||||
|
scopes={scopes}
|
||||||
|
scopeNames={scopeNames}
|
||||||
|
type="result"
|
||||||
|
onNodeSelectToggle={onNodeSelectToggle}
|
||||||
|
onNodeUpdate={onNodeUpdate}
|
||||||
|
/>
|
||||||
|
</ScopesTreeLoading>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { useStyles2 } from '@grafana/ui';
|
||||||
|
import { Trans } from 'app/core/internationalization';
|
||||||
|
|
||||||
|
import { Node } from './types';
|
||||||
|
|
||||||
|
export interface ScopesTreeHeadlineProps {
|
||||||
|
anyChildExpanded: boolean;
|
||||||
|
query: string;
|
||||||
|
resultsNodes: Node[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ScopesTreeHeadline({ anyChildExpanded, query, resultsNodes }: ScopesTreeHeadlineProps) {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
if (anyChildExpanded) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<h6 className={styles.container} data-testid="scopes-tree-headline">
|
||||||
|
{!query ? (
|
||||||
|
<Trans i18nKey="scopes.tree.headline.recommended">Recommended</Trans>
|
||||||
|
) : resultsNodes.length === 0 ? (
|
||||||
|
<Trans i18nKey="scopes.tree.headline.noResults">No results found for your query</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans i18nKey="scopes.tree.headline.results">Results</Trans>
|
||||||
|
)}
|
||||||
|
</h6>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
container: css({
|
||||||
|
color: theme.colors.text.secondary,
|
||||||
|
margin: theme.spacing(1, 0),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,143 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { Checkbox, Icon, RadioButtonDot, useStyles2 } from '@grafana/ui';
|
||||||
|
import { t } from 'app/core/internationalization';
|
||||||
|
|
||||||
|
import { ScopesTree } from './ScopesTree';
|
||||||
|
import { Node, OnNodeSelectToggle, OnNodeUpdate, TreeScope } from './types';
|
||||||
|
|
||||||
|
export interface ScopesTreeItemProps {
|
||||||
|
anyChildExpanded: boolean;
|
||||||
|
isNodeLoading: boolean;
|
||||||
|
loadingNodeName: string | undefined;
|
||||||
|
node: Node;
|
||||||
|
nodePath: string[];
|
||||||
|
nodes: Node[];
|
||||||
|
scopeNames: string[];
|
||||||
|
scopes: TreeScope[];
|
||||||
|
type: 'persisted' | 'result';
|
||||||
|
onNodeUpdate: OnNodeUpdate;
|
||||||
|
onNodeSelectToggle: OnNodeSelectToggle;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ScopesTreeItem({
|
||||||
|
anyChildExpanded,
|
||||||
|
loadingNodeName,
|
||||||
|
node,
|
||||||
|
nodePath,
|
||||||
|
nodes,
|
||||||
|
scopeNames,
|
||||||
|
scopes,
|
||||||
|
type,
|
||||||
|
onNodeSelectToggle,
|
||||||
|
onNodeUpdate,
|
||||||
|
}: ScopesTreeItemProps) {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div role="tree">
|
||||||
|
{nodes.map((childNode) => {
|
||||||
|
const isSelected = childNode.isSelectable && scopeNames.includes(childNode.linkId!);
|
||||||
|
|
||||||
|
if (anyChildExpanded && !childNode.isExpanded) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const childNodePath = [...nodePath, childNode.name];
|
||||||
|
|
||||||
|
const radioName = childNodePath.join('.');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={childNode.name} role="treeitem" aria-selected={childNode.isExpanded}>
|
||||||
|
<div className={styles.title}>
|
||||||
|
{childNode.isSelectable && !childNode.isExpanded ? (
|
||||||
|
node.disableMultiSelect ? (
|
||||||
|
<RadioButtonDot
|
||||||
|
id={radioName}
|
||||||
|
name={radioName}
|
||||||
|
checked={isSelected}
|
||||||
|
label=""
|
||||||
|
data-testid={`scopes-tree-${type}-${childNode.name}-radio`}
|
||||||
|
onClick={() => {
|
||||||
|
onNodeSelectToggle(childNodePath);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Checkbox
|
||||||
|
checked={isSelected}
|
||||||
|
data-testid={`scopes-tree-${type}-${childNode.name}-checkbox`}
|
||||||
|
onChange={() => {
|
||||||
|
onNodeSelectToggle(childNodePath);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{childNode.isExpandable ? (
|
||||||
|
<button
|
||||||
|
className={styles.expand}
|
||||||
|
data-testid={`scopes-tree-${type}-${childNode.name}-expand`}
|
||||||
|
aria-label={
|
||||||
|
childNode.isExpanded ? t('scopes.tree.collapse', 'Collapse') : t('scopes.tree.expand', 'Expand')
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
onNodeUpdate(childNodePath, !childNode.isExpanded, childNode.query);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon name={!childNode.isExpanded ? 'angle-right' : 'angle-down'} />
|
||||||
|
|
||||||
|
{childNode.title}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<span data-testid={`scopes-tree-${type}-${childNode.name}-title`}>{childNode.title}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.children}>
|
||||||
|
{childNode.isExpanded && (
|
||||||
|
<ScopesTree
|
||||||
|
nodes={node.nodes}
|
||||||
|
nodePath={childNodePath}
|
||||||
|
loadingNodeName={loadingNodeName}
|
||||||
|
scopes={scopes}
|
||||||
|
onNodeUpdate={onNodeUpdate}
|
||||||
|
onNodeSelectToggle={onNodeSelectToggle}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
title: css({
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
fontSize: theme.typography.pxToRem(14),
|
||||||
|
lineHeight: theme.typography.pxToRem(22),
|
||||||
|
padding: theme.spacing(0.5, 0),
|
||||||
|
|
||||||
|
'& > label': css({
|
||||||
|
gap: 0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
expand: css({
|
||||||
|
alignItems: 'center',
|
||||||
|
background: 'none',
|
||||||
|
border: 0,
|
||||||
|
display: 'flex',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
}),
|
||||||
|
children: css({
|
||||||
|
paddingLeft: theme.spacing(4),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
@ -1,185 +0,0 @@
|
|||||||
import { css } from '@emotion/css';
|
|
||||||
import { debounce } from 'lodash';
|
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
|
||||||
import Skeleton from 'react-loading-skeleton';
|
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
|
||||||
import { Checkbox, FilterInput, Icon, RadioButtonDot, useStyles2 } from '@grafana/ui';
|
|
||||||
import { t, Trans } from 'app/core/internationalization';
|
|
||||||
|
|
||||||
import { NodesMap, TreeScope } from './types';
|
|
||||||
|
|
||||||
export interface ScopesTreeLevelProps {
|
|
||||||
nodes: NodesMap;
|
|
||||||
nodePath: string[];
|
|
||||||
loadingNodeName: string | undefined;
|
|
||||||
scopes: TreeScope[];
|
|
||||||
onNodeUpdate: (path: string[], isExpanded: boolean, query: string) => void;
|
|
||||||
onNodeSelectToggle: (path: string[]) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ScopesTreeLevel({
|
|
||||||
nodes,
|
|
||||||
nodePath,
|
|
||||||
loadingNodeName,
|
|
||||||
scopes,
|
|
||||||
onNodeUpdate,
|
|
||||||
onNodeSelectToggle,
|
|
||||||
}: ScopesTreeLevelProps) {
|
|
||||||
const styles = useStyles2(getStyles);
|
|
||||||
|
|
||||||
const nodeId = nodePath[nodePath.length - 1];
|
|
||||||
const node = nodes[nodeId];
|
|
||||||
const childNodes = node.nodes;
|
|
||||||
const childNodesArr = Object.values(childNodes);
|
|
||||||
const isNodeLoading = loadingNodeName === nodeId;
|
|
||||||
|
|
||||||
const scopeNames = scopes.map(({ scopeName }) => scopeName);
|
|
||||||
const anyChildExpanded = childNodesArr.some(({ isExpanded }) => isExpanded);
|
|
||||||
|
|
||||||
const [queryValue, setQueryValue] = useState(node.query);
|
|
||||||
useEffect(() => {
|
|
||||||
setQueryValue(node.query);
|
|
||||||
}, [node.query]);
|
|
||||||
const onQueryUpdate = useMemo(() => debounce(onNodeUpdate, 500), [onNodeUpdate]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!anyChildExpanded && (
|
|
||||||
<FilterInput
|
|
||||||
placeholder={t('scopes.tree.search', 'Search')}
|
|
||||||
value={queryValue}
|
|
||||||
className={styles.searchInput}
|
|
||||||
data-testid={`scopes-tree-${nodeId}-search`}
|
|
||||||
onChange={(value) => {
|
|
||||||
setQueryValue(value);
|
|
||||||
onQueryUpdate(nodePath, true, value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!anyChildExpanded && !node.query && (
|
|
||||||
<h6 className={styles.headline}>
|
|
||||||
<Trans i18nKey="scopes.tree.headline">Recommended</Trans>
|
|
||||||
</h6>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div role="tree">
|
|
||||||
{isNodeLoading && <Skeleton count={5} className={styles.loader} />}
|
|
||||||
|
|
||||||
{!isNodeLoading &&
|
|
||||||
childNodesArr.map((childNode) => {
|
|
||||||
const isSelected = childNode.isSelectable && scopeNames.includes(childNode.linkId!);
|
|
||||||
|
|
||||||
if (anyChildExpanded && !childNode.isExpanded && !isSelected) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const childNodePath = [...nodePath, childNode.name];
|
|
||||||
|
|
||||||
const radioName = childNodePath.join('.');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={childNode.name} role="treeitem" aria-selected={childNode.isExpanded}>
|
|
||||||
<div className={styles.itemTitle}>
|
|
||||||
{childNode.isSelectable && !childNode.isExpanded ? (
|
|
||||||
node.disableMultiSelect ? (
|
|
||||||
<RadioButtonDot
|
|
||||||
id={radioName}
|
|
||||||
name={radioName}
|
|
||||||
checked={isSelected}
|
|
||||||
label=""
|
|
||||||
data-testid={`scopes-tree-${childNode.name}-radio`}
|
|
||||||
onClick={() => {
|
|
||||||
onNodeSelectToggle(childNodePath);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Checkbox
|
|
||||||
checked={isSelected}
|
|
||||||
data-testid={`scopes-tree-${childNode.name}-checkbox`}
|
|
||||||
onChange={() => {
|
|
||||||
onNodeSelectToggle(childNodePath);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{childNode.isExpandable ? (
|
|
||||||
<button
|
|
||||||
className={styles.itemExpand}
|
|
||||||
data-testid={`scopes-tree-${childNode.name}-expand`}
|
|
||||||
aria-label={
|
|
||||||
childNode.isExpanded ? t('scopes.tree.collapse', 'Collapse') : t('scopes.tree.expand', 'Expand')
|
|
||||||
}
|
|
||||||
onClick={() => {
|
|
||||||
onNodeUpdate(childNodePath, !childNode.isExpanded, childNode.query);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon name={!childNode.isExpanded ? 'angle-right' : 'angle-down'} />
|
|
||||||
|
|
||||||
{childNode.title}
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<span data-testid={`scopes-tree-${childNode.name}-title`}>{childNode.title}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.itemChildren}>
|
|
||||||
{childNode.isExpanded && (
|
|
||||||
<ScopesTreeLevel
|
|
||||||
nodes={node.nodes}
|
|
||||||
nodePath={childNodePath}
|
|
||||||
loadingNodeName={loadingNodeName}
|
|
||||||
scopes={scopes}
|
|
||||||
onNodeUpdate={onNodeUpdate}
|
|
||||||
onNodeSelectToggle={onNodeSelectToggle}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => {
|
|
||||||
return {
|
|
||||||
searchInput: css({
|
|
||||||
margin: theme.spacing(1, 0),
|
|
||||||
}),
|
|
||||||
headline: css({
|
|
||||||
color: theme.colors.text.secondary,
|
|
||||||
margin: theme.spacing(1, 0),
|
|
||||||
}),
|
|
||||||
loader: css({
|
|
||||||
margin: theme.spacing(0.5, 0),
|
|
||||||
}),
|
|
||||||
itemTitle: css({
|
|
||||||
alignItems: 'center',
|
|
||||||
display: 'flex',
|
|
||||||
gap: theme.spacing(1),
|
|
||||||
fontSize: theme.typography.pxToRem(14),
|
|
||||||
lineHeight: theme.typography.pxToRem(22),
|
|
||||||
padding: theme.spacing(0.5, 0),
|
|
||||||
|
|
||||||
'& > label': css({
|
|
||||||
gap: 0,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
itemExpand: css({
|
|
||||||
alignItems: 'center',
|
|
||||||
background: 'none',
|
|
||||||
border: 0,
|
|
||||||
display: 'flex',
|
|
||||||
gap: theme.spacing(1),
|
|
||||||
margin: 0,
|
|
||||||
padding: 0,
|
|
||||||
}),
|
|
||||||
itemChildren: css({
|
|
||||||
paddingLeft: theme.spacing(4),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
};
|
|
@ -0,0 +1,29 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
import Skeleton from 'react-loading-skeleton';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
|
export interface ScopesTreeLoadingProps {
|
||||||
|
children: ReactNode;
|
||||||
|
isNodeLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ScopesTreeLoading({ children, isNodeLoading }: ScopesTreeLoadingProps) {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
if (isNodeLoading) {
|
||||||
|
return <Skeleton count={5} className={styles.loader} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
loader: css({
|
||||||
|
margin: theme.spacing(0.5, 0),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,53 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { FilterInput, useStyles2 } from '@grafana/ui';
|
||||||
|
import { t } from 'app/core/internationalization';
|
||||||
|
|
||||||
|
import { OnNodeUpdate } from './types';
|
||||||
|
|
||||||
|
export interface ScopesTreeSearchProps {
|
||||||
|
anyChildExpanded: boolean;
|
||||||
|
nodePath: string[];
|
||||||
|
query: string;
|
||||||
|
onNodeUpdate: OnNodeUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ScopesTreeSearch({ anyChildExpanded, nodePath, query, onNodeUpdate }: ScopesTreeSearchProps) {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
const [queryValue, setQueryValue] = useState(query);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setQueryValue(query);
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
const onQueryUpdate = useMemo(() => debounce(onNodeUpdate, 500), [onNodeUpdate]);
|
||||||
|
|
||||||
|
if (anyChildExpanded) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FilterInput
|
||||||
|
placeholder={t('scopes.tree.search', 'Search')}
|
||||||
|
value={queryValue}
|
||||||
|
className={styles.input}
|
||||||
|
data-testid="scopes-tree-search"
|
||||||
|
onChange={(value) => {
|
||||||
|
setQueryValue(value);
|
||||||
|
onQueryUpdate(nodePath, true, value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
input: css({
|
||||||
|
margin: theme.spacing(1, 0),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
@ -1,8 +1,8 @@
|
|||||||
import { Scope, ScopeSpec, ScopeNode, ScopeDashboardBinding } from '@grafana/data';
|
import { Scope, ScopeDashboardBinding, ScopeNode, ScopeSpec } from '@grafana/data';
|
||||||
import { config, getBackendSrv } from '@grafana/runtime';
|
import { config, getBackendSrv } from '@grafana/runtime';
|
||||||
import { ScopedResourceClient } from 'app/features/apiserver/client';
|
import { ScopedResourceClient } from 'app/features/apiserver/client';
|
||||||
|
|
||||||
import { NodesMap, SelectedScope, SuggestedDashboard, TreeScope } from './types';
|
import { NodeReason, NodesMap, SelectedScope, SuggestedDashboard, TreeScope } from './types';
|
||||||
import { getBasicScope, mergeScopes } from './utils';
|
import { getBasicScope, mergeScopes } from './utils';
|
||||||
|
|
||||||
const group = 'scope.grafana.app';
|
const group = 'scope.grafana.app';
|
||||||
@ -37,6 +37,7 @@ export async function fetchNodes(parent: string, query: string): Promise<NodesMa
|
|||||||
isSelectable: spec.linkType === 'scope',
|
isSelectable: spec.linkType === 'scope',
|
||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
query: '',
|
query: '',
|
||||||
|
reason: NodeReason.Result,
|
||||||
nodes: {},
|
nodes: {},
|
||||||
};
|
};
|
||||||
return acc;
|
return acc;
|
||||||
|
@ -314,11 +314,12 @@ export const getMock = jest
|
|||||||
|
|
||||||
const selectors = {
|
const selectors = {
|
||||||
tree: {
|
tree: {
|
||||||
search: (nodeId: string) => `scopes-tree-${nodeId}-search`,
|
search: 'scopes-tree-search',
|
||||||
select: (nodeId: string) => `scopes-tree-${nodeId}-checkbox`,
|
headline: 'scopes-tree-headline',
|
||||||
radio: (nodeId: string) => `scopes-tree-${nodeId}-radio`,
|
select: (nodeId: string, type: 'result' | 'persisted') => `scopes-tree-${type}-${nodeId}-checkbox`,
|
||||||
expand: (nodeId: string) => `scopes-tree-${nodeId}-expand`,
|
radio: (nodeId: string, type: 'result' | 'persisted') => `scopes-tree-${type}-${nodeId}-radio`,
|
||||||
title: (nodeId: string) => `scopes-tree-${nodeId}-title`,
|
expand: (nodeId: string, type: 'result' | 'persisted') => `scopes-tree-${type}-${nodeId}-expand`,
|
||||||
|
title: (nodeId: string, type: 'result' | 'persisted') => `scopes-tree-${type}-${nodeId}-title`,
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
input: 'scopes-filters-input',
|
input: 'scopes-filters-input',
|
||||||
@ -359,36 +360,50 @@ export const getNotFoundForScope = () => screen.getByTestId(selectors.dashboards
|
|||||||
export const getNotFoundForFilter = () => screen.getByTestId(selectors.dashboards.notFoundForFilter);
|
export const getNotFoundForFilter = () => screen.getByTestId(selectors.dashboards.notFoundForFilter);
|
||||||
export const getNotFoundForFilterClear = () => screen.getByTestId(selectors.dashboards.notFoundForFilterClear);
|
export const getNotFoundForFilterClear = () => screen.getByTestId(selectors.dashboards.notFoundForFilterClear);
|
||||||
|
|
||||||
export const getApplicationsExpand = () => screen.getByTestId(selectors.tree.expand('applications'));
|
export const getTreeSearch = () => screen.getByTestId<HTMLInputElement>(selectors.tree.search);
|
||||||
export const getApplicationsSearch = () => screen.getByTestId<HTMLInputElement>(selectors.tree.search('applications'));
|
export const getTreeHeadline = () => screen.getByTestId(selectors.tree.headline);
|
||||||
export const queryApplicationsSlothPictureFactoryTitle = () =>
|
export const getResultApplicationsExpand = () => screen.getByTestId(selectors.tree.expand('applications', 'result'));
|
||||||
screen.queryByTestId(selectors.tree.title('applications-slothPictureFactory'));
|
export const queryResultApplicationsSlothPictureFactoryTitle = () =>
|
||||||
export const getApplicationsSlothPictureFactoryTitle = () =>
|
screen.queryByTestId(selectors.tree.title('applications-slothPictureFactory', 'result'));
|
||||||
screen.getByTestId(selectors.tree.title('applications-slothPictureFactory'));
|
export const getResultApplicationsSlothPictureFactoryTitle = () =>
|
||||||
export const getApplicationsSlothPictureFactorySelect = () =>
|
screen.getByTestId(selectors.tree.title('applications-slothPictureFactory', 'result'));
|
||||||
screen.getByTestId(selectors.tree.select('applications-slothPictureFactory'));
|
export const getResultApplicationsSlothPictureFactorySelect = () =>
|
||||||
export const queryApplicationsSlothVoteTrackerTitle = () =>
|
screen.getByTestId(selectors.tree.select('applications-slothPictureFactory', 'result'));
|
||||||
screen.queryByTestId(selectors.tree.title('applications-slothVoteTracker'));
|
export const queryPersistedApplicationsSlothPictureFactoryTitle = () =>
|
||||||
export const getApplicationsSlothVoteTrackerSelect = () =>
|
screen.queryByTestId(selectors.tree.title('applications-slothPictureFactory', 'persisted'));
|
||||||
screen.getByTestId(selectors.tree.select('applications-slothVoteTracker'));
|
export const getPersistedApplicationsSlothPictureFactoryTitle = () =>
|
||||||
export const queryApplicationsClustersTitle = () => screen.queryByTestId(selectors.tree.title('applications.clusters'));
|
screen.getByTestId(selectors.tree.title('applications-slothPictureFactory', 'persisted'));
|
||||||
export const getApplicationsClustersSelect = () => screen.getByTestId(selectors.tree.select('applications.clusters'));
|
export const getPersistedApplicationsSlothPictureFactorySelect = () =>
|
||||||
export const getApplicationsClustersExpand = () => screen.getByTestId(selectors.tree.expand('applications.clusters'));
|
screen.getByTestId(selectors.tree.select('applications-slothPictureFactory', 'persisted'));
|
||||||
export const queryApplicationsClustersSlothClusterNorthTitle = () =>
|
export const queryResultApplicationsSlothVoteTrackerTitle = () =>
|
||||||
screen.queryByTestId(selectors.tree.title('applications.clusters-slothClusterNorth'));
|
screen.queryByTestId(selectors.tree.title('applications-slothVoteTracker', 'result'));
|
||||||
export const getApplicationsClustersSlothClusterNorthSelect = () =>
|
export const getResultApplicationsSlothVoteTrackerTitle = () =>
|
||||||
screen.getByTestId(selectors.tree.select('applications.clusters-slothClusterNorth'));
|
screen.getByTestId(selectors.tree.title('applications-slothVoteTracker', 'result'));
|
||||||
export const getApplicationsClustersSlothClusterSouthSelect = () =>
|
export const getResultApplicationsSlothVoteTrackerSelect = () =>
|
||||||
screen.getByTestId(selectors.tree.select('applications.clusters-slothClusterSouth'));
|
screen.getByTestId(selectors.tree.select('applications-slothVoteTracker', 'result'));
|
||||||
|
export const queryPersistedApplicationsSlothVoteTrackerTitle = () =>
|
||||||
|
screen.queryByTestId(selectors.tree.title('applications-slothVoteTracker', 'persisted'));
|
||||||
|
export const getPersistedApplicationsSlothVoteTrackerTitle = () =>
|
||||||
|
screen.getByTestId(selectors.tree.title('applications-slothVoteTracker', 'persisted'));
|
||||||
|
export const queryResultApplicationsClustersTitle = () =>
|
||||||
|
screen.queryByTestId(selectors.tree.title('applications.clusters', 'result'));
|
||||||
|
export const getResultApplicationsClustersSelect = () =>
|
||||||
|
screen.getByTestId(selectors.tree.select('applications.clusters', 'result'));
|
||||||
|
export const getResultApplicationsClustersExpand = () =>
|
||||||
|
screen.getByTestId(selectors.tree.expand('applications.clusters', 'result'));
|
||||||
|
export const getResultApplicationsClustersSlothClusterNorthSelect = () =>
|
||||||
|
screen.getByTestId(selectors.tree.select('applications.clusters-slothClusterNorth', 'result'));
|
||||||
|
export const getResultApplicationsClustersSlothClusterSouthSelect = () =>
|
||||||
|
screen.getByTestId(selectors.tree.select('applications.clusters-slothClusterSouth', 'result'));
|
||||||
|
|
||||||
export const getClustersSelect = () => screen.getByTestId(selectors.tree.select('clusters'));
|
export const getResultClustersSelect = () => screen.getByTestId(selectors.tree.select('clusters', 'result'));
|
||||||
export const getClustersExpand = () => screen.getByTestId(selectors.tree.expand('clusters'));
|
export const getResultClustersExpand = () => screen.getByTestId(selectors.tree.expand('clusters', 'result'));
|
||||||
export const getClustersSlothClusterNorthRadio = () =>
|
export const getResultClustersSlothClusterNorthRadio = () =>
|
||||||
screen.getByTestId<HTMLInputElement>(selectors.tree.radio('clusters-slothClusterNorth'));
|
screen.getByTestId<HTMLInputElement>(selectors.tree.radio('clusters-slothClusterNorth', 'result'));
|
||||||
export const getClustersSlothClusterSouthRadio = () =>
|
export const getResultClustersSlothClusterSouthRadio = () =>
|
||||||
screen.getByTestId<HTMLInputElement>(selectors.tree.radio('clusters-slothClusterSouth'));
|
screen.getByTestId<HTMLInputElement>(selectors.tree.radio('clusters-slothClusterSouth', 'result'));
|
||||||
export const getClustersSlothClusterEastRadio = () =>
|
export const getResultClustersSlothClusterEastRadio = () =>
|
||||||
screen.getByTestId<HTMLInputElement>(selectors.tree.radio('clusters-slothClusterEast'));
|
screen.getByTestId<HTMLInputElement>(selectors.tree.radio('clusters-slothClusterEast', 'result'));
|
||||||
|
|
||||||
export function buildTestScene(overrides: Partial<DashboardScene> = {}) {
|
export function buildTestScene(overrides: Partial<DashboardScene> = {}) {
|
||||||
return new DashboardScene({
|
return new DashboardScene({
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { Scope, ScopeDashboardBinding, ScopeNodeSpec } from '@grafana/data';
|
import { Scope, ScopeDashboardBinding, ScopeNodeSpec } from '@grafana/data';
|
||||||
|
|
||||||
|
export enum NodeReason {
|
||||||
|
Persisted,
|
||||||
|
Result,
|
||||||
|
}
|
||||||
|
|
||||||
export interface Node extends ScopeNodeSpec {
|
export interface Node extends ScopeNodeSpec {
|
||||||
name: string;
|
name: string;
|
||||||
|
reason: NodeReason;
|
||||||
isExpandable: boolean;
|
isExpandable: boolean;
|
||||||
isSelectable: boolean;
|
isSelectable: boolean;
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
@ -26,3 +32,6 @@ export interface SuggestedDashboard {
|
|||||||
dashboardTitle: string;
|
dashboardTitle: string;
|
||||||
items: ScopeDashboardBinding[];
|
items: ScopeDashboardBinding[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type OnNodeUpdate = (path: string[], isExpanded: boolean, query: string) => void;
|
||||||
|
export type OnNodeSelectToggle = (path: string[]) => void;
|
||||||
|
@ -1698,7 +1698,11 @@
|
|||||||
"tree": {
|
"tree": {
|
||||||
"collapse": "Collapse",
|
"collapse": "Collapse",
|
||||||
"expand": "Expand",
|
"expand": "Expand",
|
||||||
"headline": "Recommended",
|
"headline": {
|
||||||
|
"noResults": "No results found for your query",
|
||||||
|
"recommended": "Recommended",
|
||||||
|
"results": "Results"
|
||||||
|
},
|
||||||
"search": "Search"
|
"search": "Search"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1698,7 +1698,11 @@
|
|||||||
"tree": {
|
"tree": {
|
||||||
"collapse": "Cőľľäpşę",
|
"collapse": "Cőľľäpşę",
|
||||||
"expand": "Ēχpäʼnđ",
|
"expand": "Ēχpäʼnđ",
|
||||||
"headline": "Ŗęčőmmęʼnđęđ",
|
"headline": {
|
||||||
|
"noResults": "Ńő řęşūľŧş ƒőūʼnđ ƒőř yőūř qūęřy",
|
||||||
|
"recommended": "Ŗęčőmmęʼnđęđ",
|
||||||
|
"results": "Ŗęşūľŧş"
|
||||||
|
},
|
||||||
"search": "Ŝęäřčĥ"
|
"search": "Ŝęäřčĥ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user