mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[8.4] [Kubernetes Security] - Tree Navigation Empty State (#137133)
This commit is contained in:
parent
5124d6c94d
commit
e0280ea2f1
12 changed files with 297 additions and 85 deletions
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 126 KiB |
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { createContext, useContext } from 'react';
|
||||
|
||||
import { useTreeView, UseTreeViewProps } from './hooks';
|
||||
|
||||
type TreeViewContextType = ReturnType<typeof useTreeView>;
|
||||
|
||||
const TreeViewContext = createContext<TreeViewContextType | null>(null);
|
||||
|
||||
export const useTreeViewContext = () => {
|
||||
const context = useContext(TreeViewContext);
|
||||
if (!context) {
|
||||
throw new Error('useTreeViewContext must be called within an TreeViewContextProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
type TreeViewContextProviderProps = {
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
export const TreeViewContextProvider = ({
|
||||
children,
|
||||
...useTreeViewProps
|
||||
}: TreeViewContextProviderProps & UseTreeViewProps) => {
|
||||
return (
|
||||
<TreeViewContext.Provider value={useTreeView(useTreeViewProps)}>
|
||||
{children}
|
||||
</TreeViewContext.Provider>
|
||||
);
|
||||
};
|
|
@ -10,6 +10,7 @@ import { waitFor } from '@testing-library/react';
|
|||
import { AppContextTestRender, createAppRootMockRenderer } from '../../../test';
|
||||
import { DynamicTreeView } from '.';
|
||||
import { clusterResponseMock, nodeResponseMock } from '../mocks';
|
||||
import { TreeViewContextProvider } from '../contexts';
|
||||
|
||||
describe('DynamicTreeView component', () => {
|
||||
let render: (props?: any) => ReturnType<AppContextTestRender['render']>;
|
||||
|
@ -19,37 +20,48 @@ describe('DynamicTreeView component', () => {
|
|||
|
||||
const waitForApiCall = () => waitFor(() => expect(mockedApi).toHaveBeenCalled());
|
||||
|
||||
const defaultProps = {
|
||||
globalFilter: {
|
||||
startDate: Date.now().toString(),
|
||||
endDate: (Date.now() + 1).toString(),
|
||||
},
|
||||
indexPattern: {
|
||||
title: '*-logs',
|
||||
},
|
||||
} as any;
|
||||
|
||||
beforeEach(() => {
|
||||
mockedContext = createAppRootMockRenderer();
|
||||
mockedApi = mockedContext.coreStart.http.get;
|
||||
mockedApi.mockResolvedValue(clusterResponseMock);
|
||||
render = (props) =>
|
||||
(renderResult = mockedContext.render(
|
||||
<DynamicTreeView
|
||||
query={{
|
||||
bool: {
|
||||
filter: [],
|
||||
must: [],
|
||||
must_not: [],
|
||||
should: [],
|
||||
},
|
||||
}}
|
||||
indexPattern={'*-logs'}
|
||||
tree={[
|
||||
{
|
||||
key: 'cluster',
|
||||
name: 'cluster',
|
||||
namePlural: 'clusters',
|
||||
type: 'cluster',
|
||||
iconProps: {
|
||||
type: 'cluster',
|
||||
<TreeViewContextProvider {...defaultProps}>
|
||||
<DynamicTreeView
|
||||
query={{
|
||||
bool: {
|
||||
filter: [],
|
||||
must: [],
|
||||
must_not: [],
|
||||
should: [],
|
||||
},
|
||||
},
|
||||
]}
|
||||
aria-label="Logical Tree View"
|
||||
onSelect={(selectionDepth, key, type) => {}}
|
||||
{...props}
|
||||
/>
|
||||
}}
|
||||
tree={[
|
||||
{
|
||||
key: 'cluster',
|
||||
name: 'cluster',
|
||||
namePlural: 'clusters',
|
||||
type: 'cluster',
|
||||
iconProps: {
|
||||
type: 'cluster',
|
||||
},
|
||||
},
|
||||
]}
|
||||
aria-label="Logical Tree View"
|
||||
onSelect={(selectionDepth, key, type) => {}}
|
||||
{...props}
|
||||
/>
|
||||
</TreeViewContextProvider>
|
||||
));
|
||||
});
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
import { useFetchDynamicTreeView } from './hooks';
|
||||
import { useStyles } from './styles';
|
||||
import { disableEventDefaults, focusNextElement } from './helpers';
|
||||
import { useTreeViewContext } from '../contexts';
|
||||
import type { DynamicTreeViewProps, DynamicTreeViewItemProps } from './types';
|
||||
|
||||
const BUTTON_TEST_ID = 'kubernetesSecurity:dynamicTreeViewButton';
|
||||
|
@ -55,15 +56,15 @@ export const DynamicTreeView = ({
|
|||
depth = 0,
|
||||
selectionDepth = {},
|
||||
query,
|
||||
indexPattern = '',
|
||||
onSelect,
|
||||
hasSelection,
|
||||
selected = '',
|
||||
expanded = true,
|
||||
...props
|
||||
}: DynamicTreeViewProps) => {
|
||||
const styles = useStyles(depth);
|
||||
|
||||
const { indexPattern, hasSelection, setNoResults } = useTreeViewContext();
|
||||
|
||||
const { data, fetchNextPage, isFetchingNextPage, hasNextPage, isLoading } =
|
||||
useFetchDynamicTreeView(query, tree[depth].key, indexPattern, expanded);
|
||||
|
||||
|
@ -86,6 +87,12 @@ export const DynamicTreeView = ({
|
|||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (depth === 0 && data && data.pages?.[0].buckets.length === 0) {
|
||||
setNoResults(true);
|
||||
}
|
||||
}, [data, depth, setNoResults]);
|
||||
|
||||
useEffect(() => {
|
||||
if (expanded) {
|
||||
fetchNextPage();
|
||||
|
@ -158,7 +165,6 @@ export const DynamicTreeView = ({
|
|||
aria-label={ariaLabel}
|
||||
depth={depth}
|
||||
expanded={expanded}
|
||||
indexPattern={indexPattern}
|
||||
isExpanded={isExpanded}
|
||||
onSelect={onSelect}
|
||||
onToggleExpand={onToggleExpand}
|
||||
|
@ -209,7 +215,6 @@ const DynamicTreeViewItem = ({
|
|||
selected,
|
||||
expanded,
|
||||
query,
|
||||
indexPattern,
|
||||
...props
|
||||
}: DynamicTreeViewItemProps) => {
|
||||
const isLastNode = depth === tree.length - 1;
|
||||
|
@ -319,7 +324,6 @@ const DynamicTreeViewItem = ({
|
|||
[tree[depth].type]: aggData.key,
|
||||
}}
|
||||
tree={tree}
|
||||
indexPattern={indexPattern}
|
||||
onSelect={onSelect}
|
||||
selected={selected}
|
||||
aria-label={`${aggData.key} child of ${props['aria-label']}`}
|
||||
|
|
|
@ -12,7 +12,6 @@ export type DynamicTreeViewProps = {
|
|||
depth?: number;
|
||||
selectionDepth?: TreeNavSelection;
|
||||
query: QueryDslQueryContainerBool;
|
||||
indexPattern?: string;
|
||||
onSelect: (selectionDepth: TreeNavSelection, key: string | number, type: string) => void;
|
||||
hasSelection?: boolean;
|
||||
'aria-label': string;
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiPanel, EuiFlexGroup, EuiFlexItem, EuiImage, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { CSSObject } from '@emotion/serialize';
|
||||
import icon from './assets/illustration_product_no_results_magnifying_glass.svg';
|
||||
|
||||
export const TREE_EMPTY_STATE = 'kubernetesSecurity:treeEmptyState';
|
||||
|
||||
const panelStyle: CSSObject = {
|
||||
maxWidth: 500,
|
||||
};
|
||||
|
||||
const wrapperStyle: CSSObject = {
|
||||
height: 262,
|
||||
};
|
||||
|
||||
export const EmptyState: React.FC = () => {
|
||||
return (
|
||||
<EuiPanel color="subdued" data-test-subj={TREE_EMPTY_STATE}>
|
||||
<EuiFlexGroup css={wrapperStyle} alignItems="center" justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPanel hasBorder={true} css={panelStyle}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s">
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.kubernetesSecurity.treeView.empty.title"
|
||||
defaultMessage="No results match your search criteria"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.kubernetesSecurity.treeView.empty.description"
|
||||
defaultMessage="Try searching over a longer period of time or modifying your search"
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiImage size="200" alt="" url={icon} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { KubernetesCollection, TreeNavSelection } from '../../types';
|
||||
import { addTimerangeAndDefaultFilterToQuery } from '../../utils/add_timerange_and_default_filter_to_query';
|
||||
import { addTreeNavSelectionToFilterQuery } from './helpers';
|
||||
import { IndexPattern, GlobalFilter } from '../../types';
|
||||
|
||||
export type UseTreeViewProps = {
|
||||
globalFilter: GlobalFilter;
|
||||
indexPattern?: IndexPattern;
|
||||
};
|
||||
|
||||
export const useTreeView = ({ globalFilter, indexPattern }: UseTreeViewProps) => {
|
||||
const [noResults, setNoResults] = useState(false);
|
||||
const [treeNavSelection, setTreeNavSelection] = useState<TreeNavSelection>({});
|
||||
|
||||
const filterQueryWithTimeRange = useMemo(() => {
|
||||
return JSON.parse(
|
||||
addTimerangeAndDefaultFilterToQuery(
|
||||
globalFilter.filterQuery,
|
||||
globalFilter.startDate,
|
||||
globalFilter.endDate
|
||||
)
|
||||
);
|
||||
}, [globalFilter.filterQuery, globalFilter.startDate, globalFilter.endDate]);
|
||||
|
||||
const onTreeNavSelect = useCallback((selection: TreeNavSelection) => {
|
||||
setTreeNavSelection(selection);
|
||||
}, []);
|
||||
|
||||
const hasSelection = useMemo(
|
||||
() => !!treeNavSelection[KubernetesCollection.cluster],
|
||||
[treeNavSelection]
|
||||
);
|
||||
|
||||
const sessionViewFilter = useMemo(
|
||||
() => addTreeNavSelectionToFilterQuery(globalFilter.filterQuery, treeNavSelection),
|
||||
[globalFilter.filterQuery, treeNavSelection]
|
||||
);
|
||||
|
||||
// Resetting defaults whenever filter changes
|
||||
useEffect(() => {
|
||||
setNoResults(false);
|
||||
setTreeNavSelection({});
|
||||
}, [filterQueryWithTimeRange]);
|
||||
|
||||
return {
|
||||
noResults,
|
||||
setNoResults,
|
||||
filterQueryWithTimeRange,
|
||||
indexPattern: indexPattern?.title || '',
|
||||
onTreeNavSelect,
|
||||
hasSelection,
|
||||
treeNavSelection,
|
||||
sessionViewFilter,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { TreeViewContainer } from '.';
|
||||
import { AppContextTestRender, createAppRootMockRenderer } from '../../test';
|
||||
import * as context from './contexts';
|
||||
|
||||
describe('TreeNav component', () => {
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
let mockedContext: AppContextTestRender;
|
||||
const spy = jest.spyOn(context, 'useTreeViewContext');
|
||||
|
||||
const defaultProps = {
|
||||
globalFilter: {
|
||||
startDate: Date.now().toString(),
|
||||
endDate: (Date.now() + 1).toString(),
|
||||
},
|
||||
renderSessionsView: <div>Session View</div>,
|
||||
} as any;
|
||||
|
||||
beforeEach(() => {
|
||||
mockedContext = createAppRootMockRenderer();
|
||||
});
|
||||
afterEach(() => {
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('shows empty message when there is no results', async () => {
|
||||
spy.mockImplementation(() => ({
|
||||
...jest.requireActual('./contexts').useTreeViewContext,
|
||||
noResults: true,
|
||||
}));
|
||||
|
||||
renderResult = mockedContext.render(<TreeViewContainer {...defaultProps} />);
|
||||
expect(await renderResult.getByText(/no results/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -5,13 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { EuiSplitPanel } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { EuiSplitPanel, EuiText } from '@elastic/eui';
|
||||
import { useStyles } from './styles';
|
||||
import { IndexPattern, GlobalFilter, TreeNavSelection, KubernetesCollection } from '../../types';
|
||||
import { IndexPattern, GlobalFilter } from '../../types';
|
||||
import { TreeNav } from './tree_nav';
|
||||
import { addTreeNavSelectionToFilterQuery } from './helpers';
|
||||
import { Breadcrumb } from './breadcrumb';
|
||||
import { TreeViewContextProvider, useTreeViewContext } from './contexts';
|
||||
import { EmptyState } from './empty_state';
|
||||
|
||||
export interface TreeViewContainerDeps {
|
||||
globalFilter: GlobalFilter;
|
||||
|
@ -24,35 +25,38 @@ export const TreeViewContainer = ({
|
|||
renderSessionsView,
|
||||
indexPattern,
|
||||
}: TreeViewContainerDeps) => {
|
||||
const styles = useStyles();
|
||||
const [treeNavSelection, setTreeNavSelection] = useState<TreeNavSelection>({});
|
||||
|
||||
const onTreeNavSelect = useCallback((selection: TreeNavSelection) => {
|
||||
setTreeNavSelection(selection);
|
||||
}, []);
|
||||
|
||||
const hasSelection = useMemo(
|
||||
() => !!treeNavSelection[KubernetesCollection.cluster],
|
||||
[treeNavSelection]
|
||||
return (
|
||||
<TreeViewContextProvider indexPattern={indexPattern} globalFilter={globalFilter}>
|
||||
<TreeViewContainerComponent renderSessionsView={renderSessionsView} />
|
||||
</TreeViewContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const TreeViewContainerComponent = ({
|
||||
renderSessionsView,
|
||||
}: Pick<TreeViewContainerDeps, 'renderSessionsView'>) => {
|
||||
const styles = useStyles();
|
||||
|
||||
const { hasSelection, treeNavSelection, sessionViewFilter, onTreeNavSelect, noResults } =
|
||||
useTreeViewContext();
|
||||
|
||||
return (
|
||||
<EuiSplitPanel.Outer direction="row" hasBorder borderRadius="m" css={styles.outerPanel}>
|
||||
<EuiSplitPanel.Inner color="subdued" grow={false} css={styles.navPanel}>
|
||||
<TreeNav
|
||||
indexPattern={indexPattern}
|
||||
globalFilter={globalFilter}
|
||||
onSelect={onTreeNavSelect}
|
||||
hasSelection={hasSelection}
|
||||
/>
|
||||
</EuiSplitPanel.Inner>
|
||||
<EuiSplitPanel.Inner css={styles.sessionsPanel}>
|
||||
<Breadcrumb treeNavSelection={treeNavSelection} onSelect={onTreeNavSelect} />
|
||||
{hasSelection &&
|
||||
renderSessionsView(
|
||||
addTreeNavSelectionToFilterQuery(globalFilter.filterQuery, treeNavSelection)
|
||||
)}
|
||||
</EuiSplitPanel.Inner>
|
||||
{noResults ? (
|
||||
<EmptyState />
|
||||
) : (
|
||||
<>
|
||||
<EuiSplitPanel.Inner color="subdued" grow={false} css={styles.navPanel}>
|
||||
<EuiText>
|
||||
<TreeNav />
|
||||
</EuiText>
|
||||
</EuiSplitPanel.Inner>
|
||||
<EuiSplitPanel.Inner css={styles.sessionsPanel}>
|
||||
<Breadcrumb treeNavSelection={treeNavSelection} onSelect={onTreeNavSelect} />
|
||||
{hasSelection && renderSessionsView(sessionViewFilter)}
|
||||
</EuiSplitPanel.Inner>
|
||||
</>
|
||||
)}
|
||||
</EuiSplitPanel.Outer>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ export const useStyles = () => {
|
|||
const { border } = euiTheme;
|
||||
|
||||
const outerPanel: CSSObject = {
|
||||
minHeight: '500px',
|
||||
minHeight: '262px',
|
||||
};
|
||||
|
||||
const navPanel: CSSObject = {
|
||||
|
|
|
@ -9,6 +9,7 @@ import React from 'react';
|
|||
import { AppContextTestRender, createAppRootMockRenderer } from '../../../test';
|
||||
import { clusterResponseMock } from '../mocks';
|
||||
import { TreeNav } from '.';
|
||||
import { TreeViewContextProvider } from '../contexts';
|
||||
|
||||
describe('TreeNav component', () => {
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
|
@ -25,6 +26,12 @@ describe('TreeNav component', () => {
|
|||
hasSelection: false,
|
||||
};
|
||||
|
||||
const TreeNavContainer = () => (
|
||||
<TreeViewContextProvider {...defaultProps}>
|
||||
<TreeNav />
|
||||
</TreeViewContextProvider>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
mockedContext = createAppRootMockRenderer();
|
||||
mockedApi = mockedContext.coreStart.http.get;
|
||||
|
@ -32,13 +39,13 @@ describe('TreeNav component', () => {
|
|||
});
|
||||
|
||||
it('mount with Logical View selected by default', async () => {
|
||||
renderResult = mockedContext.render(<TreeNav {...defaultProps} />);
|
||||
renderResult = mockedContext.render(<TreeNavContainer />);
|
||||
const elemLabel = await renderResult.getByDisplayValue(/logical/i);
|
||||
expect(elemLabel).toBeChecked();
|
||||
});
|
||||
|
||||
it('shows the tree path according with the selected view type', async () => {
|
||||
renderResult = mockedContext.render(<TreeNav {...defaultProps} />);
|
||||
renderResult = mockedContext.render(<TreeNavContainer />);
|
||||
|
||||
const logicalViewPath = 'cluster / namespace / pod / container image';
|
||||
const logicViewRadio = await renderResult.getByDisplayValue(/logical/i);
|
||||
|
@ -55,7 +62,7 @@ describe('TreeNav component', () => {
|
|||
});
|
||||
|
||||
it('collapses / expands the tree nav when clicking on collapse button', async () => {
|
||||
renderResult = mockedContext.render(<TreeNav {...defaultProps} />);
|
||||
renderResult = mockedContext.render(<TreeNavContainer />);
|
||||
|
||||
expect(renderResult.getByText(/cluster/i)).toBeVisible();
|
||||
|
||||
|
|
|
@ -23,20 +23,12 @@ import {
|
|||
TREE_NAVIGATION_EXPAND,
|
||||
} from '../../../../common/translations';
|
||||
import { useStyles } from './styles';
|
||||
import { IndexPattern, GlobalFilter, TreeNavSelection } from '../../../types';
|
||||
import { DynamicTreeView } from '../dynamic_tree_view';
|
||||
import { addTimerangeAndDefaultFilterToQuery } from '../../../utils/add_timerange_and_default_filter_to_query';
|
||||
import { INFRASTRUCTURE, LOGICAL, TREE_VIEW } from './constants';
|
||||
import { TreeViewKind, TreeViewOptionsGroup } from './types';
|
||||
import { useTreeViewContext } from '../contexts';
|
||||
|
||||
interface TreeNavProps {
|
||||
indexPattern?: IndexPattern;
|
||||
globalFilter: GlobalFilter;
|
||||
onSelect: (selection: TreeNavSelection) => void;
|
||||
hasSelection: boolean;
|
||||
}
|
||||
|
||||
export const TreeNav = ({ indexPattern, globalFilter, onSelect, hasSelection }: TreeNavProps) => {
|
||||
export const TreeNav = () => {
|
||||
const styles = useStyles();
|
||||
const [tree, setTree] = useState(TREE_VIEW.logical);
|
||||
const [selected, setSelected] = useState('');
|
||||
|
@ -47,18 +39,12 @@ export const TreeNav = ({ indexPattern, globalFilter, onSelect, hasSelection }:
|
|||
const logicalTreeViewPrefix = `${treeNavTypePrefix}${LOGICAL}`;
|
||||
const [toggleIdSelected, setToggleIdSelected] = useState(logicalTreeViewPrefix);
|
||||
|
||||
const { filterQueryWithTimeRange, onTreeNavSelect } = useTreeViewContext();
|
||||
|
||||
const handleToggleCollapse = () => {
|
||||
setIsCollapsed(!isCollapsed);
|
||||
};
|
||||
|
||||
const filterQueryWithTimeRange = useMemo(() => {
|
||||
return addTimerangeAndDefaultFilterToQuery(
|
||||
globalFilter.filterQuery,
|
||||
globalFilter.startDate,
|
||||
globalFilter.endDate
|
||||
);
|
||||
}, [globalFilter.filterQuery, globalFilter.startDate, globalFilter.endDate]);
|
||||
|
||||
const options: TreeViewOptionsGroup[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
|
@ -127,8 +113,7 @@ export const TreeNav = ({ indexPattern, globalFilter, onSelect, hasSelection }:
|
|||
<EuiSpacer size="s" />
|
||||
<div css={styles.treeViewContainer} className="eui-scrollBar">
|
||||
<DynamicTreeView
|
||||
query={JSON.parse(filterQueryWithTimeRange)}
|
||||
indexPattern={indexPattern?.title}
|
||||
query={filterQueryWithTimeRange}
|
||||
tree={tree}
|
||||
aria-label={selectedLabel}
|
||||
selected={selected}
|
||||
|
@ -142,9 +127,8 @@ export const TreeNav = ({ indexPattern, globalFilter, onSelect, hasSelection }:
|
|||
.map(([k, v]) => `${k}.${v}`)
|
||||
.join()
|
||||
);
|
||||
onSelect(newSelectionDepth);
|
||||
onTreeNavSelect(newSelectionDepth);
|
||||
}}
|
||||
hasSelection={hasSelection}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue