[Security Solution] Collapse KPI and Table queries on Explore pages (#127930)

This commit is contained in:
Steph Milovic 2022-03-23 12:41:27 -06:00 committed by GitHub
parent 98300c2364
commit 5e73ef5327
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
154 changed files with 3966 additions and 1145 deletions

View file

@ -18,19 +18,25 @@ exports[`HeaderSection it renders 1`] = `
responsive={false}
>
<EuiFlexItem>
<EuiTitle
size="m"
<EuiFlexGroup
gutterSize="none"
>
<h4
data-test-subj="header-section-title"
>
<span
className="eui-textBreakNormal"
<EuiFlexItem>
<EuiTitle
size="m"
>
Test title
</span>
</h4>
</EuiTitle>
<h4
data-test-subj="header-section-title"
>
<span
className="eui-textBreakNormal"
>
Test title
</span>
</h4>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
<Subtitle
data-test-subj="header-section-subtitle"
/>

View file

@ -180,4 +180,94 @@ describe('HeaderSection', () => {
expect(wrapper.find('[data-test-subj="inspect-icon-button"]').first().exists()).toBe(false);
});
test('it does not render query-toggle-header when no arguments provided', () => {
const wrapper = mount(
<TestProviders>
<HeaderSection id="an id" title="Test title">
<p>{'Test children'}</p>
</HeaderSection>
</TestProviders>
);
expect(wrapper.find('[data-test-subj="query-toggle-header"]').first().exists()).toBe(false);
});
test('it does render query-toggle-header when toggleQuery arguments provided', () => {
const wrapper = mount(
<TestProviders>
<HeaderSection id="an id" title="Test title" toggleQuery={jest.fn()} toggleStatus={true}>
<p>{'Test children'}</p>
</HeaderSection>
</TestProviders>
);
expect(wrapper.find('[data-test-subj="query-toggle-header"]').first().exists()).toBe(true);
});
test('it does render everything but title when toggleStatus = true', () => {
const wrapper = mount(
<TestProviders>
<HeaderSection
id="an id"
title="Test title"
subtitle="subtitle"
headerFilters="headerFilters"
toggleQuery={jest.fn()}
toggleStatus={true}
>
<p>{'Test children'}</p>
</HeaderSection>
</TestProviders>
);
expect(wrapper.find('[data-test-subj="query-toggle-header"]').first().prop('iconType')).toBe(
'arrowDown'
);
expect(wrapper.find('[data-test-subj="header-section-supplements"]').first().exists()).toBe(
true
);
expect(wrapper.find('[data-test-subj="header-section-subtitle"]').first().exists()).toBe(true);
expect(wrapper.find('[data-test-subj="header-section-filters"]').first().exists()).toBe(true);
expect(wrapper.find('[data-test-subj="inspect-icon-button"]').first().exists()).toBe(true);
});
test('it does not render anything but title when toggleStatus = false', () => {
const wrapper = mount(
<TestProviders>
<HeaderSection
id="an id"
title="Test title"
subtitle="subtitle"
headerFilters="headerFilters"
toggleQuery={jest.fn()}
toggleStatus={false}
>
<p>{'Test children'}</p>
</HeaderSection>
</TestProviders>
);
expect(wrapper.find('[data-test-subj="query-toggle-header"]').first().prop('iconType')).toBe(
'arrowRight'
);
expect(wrapper.find('[data-test-subj="header-section-supplements"]').first().exists()).toBe(
false
);
expect(wrapper.find('[data-test-subj="header-section-filters"]').first().exists()).toBe(false);
expect(wrapper.find('[data-test-subj="header-section-subtitle"]').first().exists()).toBe(false);
expect(wrapper.find('[data-test-subj="inspect-icon-button"]').first().exists()).toBe(false);
});
test('it toggles query when icon is clicked', () => {
const mockToggle = jest.fn();
const wrapper = mount(
<TestProviders>
<HeaderSection id="an id" title="Test title" toggleQuery={mockToggle} toggleStatus={true}>
<p>{'Test children'}</p>
</HeaderSection>
</TestProviders>
);
wrapper.find('[data-test-subj="query-toggle-header"]').first().simulate('click');
expect(mockToggle).toBeCalledWith(false);
});
});

View file

@ -5,13 +5,21 @@
* 2.0.
*/
import { EuiFlexGroup, EuiFlexItem, EuiIconTip, EuiTitle, EuiTitleSize } from '@elastic/eui';
import React from 'react';
import {
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiIconTip,
EuiTitle,
EuiTitleSize,
} from '@elastic/eui';
import React, { useCallback } from 'react';
import styled, { css } from 'styled-components';
import { InspectButton } from '../inspect';
import { Subtitle } from '../subtitle';
import * as i18n from '../../containers/query_toggle/translations';
interface HeaderProps {
border?: boolean;
@ -51,6 +59,8 @@ export interface HeaderSectionProps extends HeaderProps {
split?: boolean;
stackHeader?: boolean;
subtitle?: string | React.ReactNode;
toggleQuery?: (status: boolean) => void;
toggleStatus?: boolean;
title: string | React.ReactNode;
titleSize?: EuiTitleSize;
tooltip?: string;
@ -72,56 +82,87 @@ const HeaderSectionComponent: React.FC<HeaderSectionProps> = ({
subtitle,
title,
titleSize = 'm',
toggleQuery,
toggleStatus = true,
tooltip,
}) => (
<Header data-test-subj="header-section" border={border} height={height}>
<EuiFlexGroup
alignItems={stackHeader ? undefined : 'center'}
direction={stackHeader ? 'column' : 'row'}
gutterSize="s"
>
<EuiFlexItem grow={growLeftSplit}>
<EuiFlexGroup alignItems="center" responsive={false} gutterSize="s">
<EuiFlexItem>
<EuiTitle size={titleSize}>
<h4 data-test-subj="header-section-title">
<span className="eui-textBreakNormal">{title}</span>
{tooltip && (
<>
{' '}
<EuiIconTip color="subdued" content={tooltip} size="l" type="iInCircle" />
</>
}) => {
const toggle = useCallback(() => {
if (toggleQuery) {
toggleQuery(!toggleStatus);
}
}, [toggleQuery, toggleStatus]);
return (
<Header data-test-subj="header-section" border={border} height={height}>
<EuiFlexGroup
alignItems={stackHeader ? undefined : 'center'}
direction={stackHeader ? 'column' : 'row'}
gutterSize="s"
>
<EuiFlexItem grow={growLeftSplit}>
<EuiFlexGroup alignItems="center" responsive={false} gutterSize="s">
<EuiFlexItem>
<EuiFlexGroup gutterSize={'none'}>
{toggleQuery && (
<EuiFlexItem grow={false}>
<EuiButtonIcon
data-test-subj="query-toggle-header"
aria-label={i18n.QUERY_BUTTON_TITLE(toggleStatus)}
color="text"
display="empty"
iconType={toggleStatus ? 'arrowDown' : 'arrowRight'}
onClick={toggle}
size="s"
title={i18n.QUERY_BUTTON_TITLE(toggleStatus)}
/>
</EuiFlexItem>
)}
</h4>
</EuiTitle>
<EuiFlexItem>
<EuiTitle size={titleSize}>
<h4 data-test-subj="header-section-title">
<span className="eui-textBreakNormal">{title}</span>
{tooltip && (
<>
{' '}
<EuiIconTip color="subdued" content={tooltip} size="l" type="iInCircle" />
</>
)}
</h4>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
{!hideSubtitle && (
<Subtitle data-test-subj="header-section-subtitle" items={subtitle} />
)}
</EuiFlexItem>
{id && showInspectButton && (
<EuiFlexItem grow={false}>
<InspectButton
isDisabled={isInspectDisabled}
queryId={id}
multiple={inspectMultiple}
title={title}
/>
{!hideSubtitle && toggleStatus && (
<Subtitle data-test-subj="header-section-subtitle" items={subtitle} />
)}
</EuiFlexItem>
)}
{headerFilters && <EuiFlexItem grow={false}>{headerFilters}</EuiFlexItem>}
</EuiFlexGroup>
</EuiFlexItem>
{id && showInspectButton && toggleStatus && (
<EuiFlexItem grow={false}>
<InspectButton
isDisabled={isInspectDisabled}
queryId={id}
multiple={inspectMultiple}
title={title}
/>
</EuiFlexItem>
)}
{children && (
<EuiFlexItem data-test-subj="header-section-supplements" grow={split ? true : false}>
{children}
{headerFilters && toggleStatus && (
<EuiFlexItem data-test-subj="header-section-filters" grow={false}>
{headerFilters}
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
)}
</EuiFlexGroup>
</Header>
);
{children && toggleStatus && (
<EuiFlexItem data-test-subj="header-section-supplements" grow={split ? true : false}>
{children}
</EuiFlexItem>
)}
</EuiFlexGroup>
</Header>
);
};
export const HeaderSection = React.memo(HeaderSectionComponent);

View file

@ -15,6 +15,9 @@ import { TestProviders } from '../../mock';
import { mockRuntimeMappings } from '../../containers/source/mock';
import { dnsTopDomainsLensAttributes } from '../visualization_actions/lens_attributes/network/dns_top_domains';
import { useRouteSpy } from '../../utils/route/use_route_spy';
import { useQueryToggle } from '../../containers/query_toggle';
jest.mock('../../containers/query_toggle');
jest.mock('../../lib/kibana');
jest.mock('./matrix_loader', () => ({
@ -25,9 +28,7 @@ jest.mock('../charts/barchart', () => ({
BarChart: () => <div className="barchart" />,
}));
jest.mock('../../containers/matrix_histogram', () => ({
useMatrixHistogramCombined: jest.fn(),
}));
jest.mock('../../containers/matrix_histogram');
jest.mock('../visualization_actions', () => ({
VisualizationActions: jest.fn(({ className }: { className: string }) => (
@ -78,9 +79,13 @@ describe('Matrix Histogram Component', () => {
title: 'mockTitle',
runtimeMappings: mockRuntimeMappings,
};
beforeAll(() => {
(useMatrixHistogramCombined as jest.Mock).mockReturnValue([
const mockUseMatrix = useMatrixHistogramCombined as jest.Mock;
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const mockSetToggle = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle });
mockUseMatrix.mockReturnValue([
false,
{
data: null,
@ -88,14 +93,16 @@ describe('Matrix Histogram Component', () => {
totalCount: null,
},
]);
wrapper = mount(<MatrixHistogram {...mockMatrixOverTimeHistogramProps} />, {
wrappingComponent: TestProviders,
});
});
describe('on initial load', () => {
beforeEach(() => {
wrapper = mount(<MatrixHistogram {...mockMatrixOverTimeHistogramProps} />, {
wrappingComponent: TestProviders,
});
});
test('it requests Matrix Histogram', () => {
expect(useMatrixHistogramCombined).toHaveBeenCalledWith({
expect(mockUseMatrix).toHaveBeenCalledWith({
endDate: mockMatrixOverTimeHistogramProps.endDate,
errorMessage: mockMatrixOverTimeHistogramProps.errorMessage,
histogramType: mockMatrixOverTimeHistogramProps.histogramType,
@ -114,6 +121,9 @@ describe('Matrix Histogram Component', () => {
describe('spacer', () => {
test('it renders a spacer by default', () => {
wrapper = mount(<MatrixHistogram {...mockMatrixOverTimeHistogramProps} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('[data-test-subj="spacer"]').exists()).toBe(true);
});
@ -129,8 +139,11 @@ describe('Matrix Histogram Component', () => {
});
describe('not initial load', () => {
beforeAll(() => {
(useMatrixHistogramCombined as jest.Mock).mockReturnValue([
beforeEach(() => {
wrapper = mount(<MatrixHistogram {...mockMatrixOverTimeHistogramProps} />, {
wrappingComponent: TestProviders,
});
mockUseMatrix.mockReturnValue([
false,
{
data: [
@ -159,6 +172,9 @@ describe('Matrix Histogram Component', () => {
describe('select dropdown', () => {
test('should be hidden if only one option is provided', () => {
wrapper = mount(<MatrixHistogram {...mockMatrixOverTimeHistogramProps} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('EuiSelect').exists()).toBe(false);
});
});
@ -287,4 +303,53 @@ describe('Matrix Histogram Component', () => {
expect(wrapper.find('[data-test-subj="mock-viz-actions"]').exists()).toBe(false);
});
});
describe('toggle query', () => {
const testProps = {
...mockMatrixOverTimeHistogramProps,
lensAttributes: dnsTopDomainsLensAttributes,
};
test('toggleQuery updates toggleStatus', () => {
wrapper = mount(<MatrixHistogram {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(mockUseMatrix.mock.calls[0][0].skip).toEqual(false);
wrapper.find('[data-test-subj="query-toggle-header"]').first().simulate('click');
expect(mockSetToggle).toBeCalledWith(false);
expect(mockUseMatrix.mock.calls[1][0].skip).toEqual(true);
});
test('toggleStatus=true, do not skip', () => {
wrapper = mount(<MatrixHistogram {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(mockUseMatrix.mock.calls[0][0].skip).toEqual(false);
});
test('toggleStatus=true, render components', () => {
wrapper = mount(<MatrixHistogram {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('MatrixLoader').exists()).toBe(true);
});
test('toggleStatus=false, do not render components', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
wrapper = mount(<MatrixHistogram {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('MatrixLoader').exists()).toBe(false);
});
test('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
wrapper = mount(<MatrixHistogram {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(mockUseMatrix.mock.calls[0][0].skip).toEqual(true);
});
});
});

View file

@ -34,6 +34,7 @@ import { GetLensAttributes, LensAttributes } from '../visualization_actions/type
import { useKibana, useGetUserCasesPermissions } from '../../lib/kibana';
import { APP_ID, SecurityPageName } from '../../../../common/constants';
import { useRouteSpy } from '../../utils/route/use_route_spy';
import { useQueryToggle } from '../../containers/query_toggle';
export type MatrixHistogramComponentProps = MatrixHistogramProps &
Omit<MatrixHistogramQueryProps, 'stackByField'> & {
@ -148,6 +149,19 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
},
[defaultStackByOption, stackByOptions]
);
const { toggleStatus, setToggleStatus } = useQueryToggle(id);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(skip || !toggleStatus);
}, [skip, toggleStatus]);
const toggleQuery = useCallback(
(status: boolean) => {
setToggleStatus(status);
// toggle on = skipQuery false
setQuerySkip(!status);
},
[setQuerySkip, setToggleStatus]
);
const matrixHistogramRequest = {
endDate,
@ -161,9 +175,8 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
runtimeMappings,
isPtrIncluded,
docValueFields,
skip,
skip: querySkip,
};
const [loading, { data, inspect, totalCount, refetch }] =
useMatrixHistogramCombined(matrixHistogramRequest);
const [{ pageName }] = useRouteSpy();
@ -225,7 +238,7 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
>
<HistogramPanel
data-test-subj={`${id}Panel`}
height={panelHeight}
height={toggleStatus ? panelHeight : undefined}
paddingSize={paddingSize}
>
{loading && !isInitialLoading && (
@ -239,8 +252,11 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
<HeaderSection
id={id}
height={toggleStatus ? undefined : 0}
title={titleWithStackByField}
titleSize={titleSize}
toggleStatus={toggleStatus}
toggleQuery={toggleQuery}
subtitle={subtitleWithCounts}
inspectMultiple
showInspectButton={showInspectButton || !onHostOrNetworkPage}
@ -276,17 +292,18 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
<EuiFlexItem grow={false}>{headerChildren}</EuiFlexItem>
</EuiFlexGroup>
</HeaderSection>
{isInitialLoading ? (
<MatrixLoader />
) : (
<BarChart
barChart={barChartData}
configs={barchartConfigs}
stackByField={selectedStackByOption.value}
timelineId={timelineId}
/>
)}
{toggleStatus ? (
isInitialLoading ? (
<MatrixLoader />
) : (
<BarChart
barChart={barChartData}
configs={barchartConfigs}
stackByField={selectedStackByOption.value}
timelineId={timelineId}
/>
)
) : null}
</HistogramPanel>
</HoverVisibilityContainer>
{showSpacer && <EuiSpacer data-test-subj="spacer" size="l" />}

View file

@ -10,7 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
import styled from 'styled-components';
const StyledEuiFlexGroup = styled(EuiFlexGroup)`
flex 1;
flex: 1;
`;
const MatrixLoaderComponent = () => (

View file

@ -80,7 +80,9 @@ export const useAnomaliesTableData = ({
earliestMs: number,
latestMs: number
) {
if (isMlUser && !skip && jobIds.length > 0) {
if (skip) {
setLoading(false);
} else if (isMlUser && !skip && jobIds.length > 0) {
try {
const data = await anomaliesTableData(
{

View file

@ -0,0 +1,88 @@
/*
* 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 { mount } from 'enzyme';
import { AnomaliesHostTable } from './anomalies_host_table';
import { TestProviders } from '../../../mock';
import React from 'react';
import { useQueryToggle } from '../../../containers/query_toggle';
import { useAnomaliesTableData } from '../anomaly/use_anomalies_table_data';
import { HostsType } from '../../../../hosts/store/model';
import { hasMlUserPermissions } from '../../../../../common/machine_learning/has_ml_user_permissions';
jest.mock('../../../containers/query_toggle');
jest.mock('../anomaly/use_anomalies_table_data');
jest.mock('../../../../../common/machine_learning/has_ml_user_permissions');
describe('Anomalies host table', () => {
describe('toggle query', () => {
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const mockUseAnomaliesTableData = useAnomaliesTableData as jest.Mock;
const mockSetToggle = jest.fn();
const testProps = {
startDate: '2019-07-17T20:00:00.000Z',
endDate: '2019-07-18T20:00:00.000Z',
narrowDateRange: jest.fn(),
skip: false,
type: HostsType.page,
};
beforeEach(() => {
jest.clearAllMocks();
(hasMlUserPermissions as jest.Mock).mockReturnValue(true);
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle });
mockUseAnomaliesTableData.mockReturnValue([
false,
{
anomalies: [],
interval: '10',
},
]);
});
test('toggleQuery updates toggleStatus', () => {
const wrapper = mount(<AnomaliesHostTable {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(false);
wrapper.find('[data-test-subj="query-toggle-header"]').first().simulate('click');
expect(mockSetToggle).toBeCalledWith(false);
expect(mockUseAnomaliesTableData.mock.calls[1][0].skip).toEqual(true);
});
test('toggleStatus=true, do not skip', () => {
mount(<AnomaliesHostTable {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(false);
});
test('toggleStatus=true, render components', () => {
const wrapper = mount(<AnomaliesHostTable {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('[data-test-subj="host-anomalies-table"]').exists()).toBe(true);
});
test('toggleStatus=false, do not render components', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
const wrapper = mount(<AnomaliesHostTable {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('[data-test-subj="host-anomalies-table"]').exists()).toBe(false);
});
test('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
mount(<AnomaliesHostTable {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(true);
});
});
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { useAnomaliesTableData } from '../anomaly/use_anomalies_table_data';
import { HeaderSection } from '../../header_section';
@ -21,6 +21,7 @@ import { BasicTable } from './basic_table';
import { getCriteriaFromHostType } from '../criteria/get_criteria_from_host_type';
import { Panel } from '../../panel';
import { anomaliesTableDefaultEquality } from './default_equality';
import { useQueryToggle } from '../../../containers/query_toggle';
const sorting = {
sort: {
@ -37,10 +38,24 @@ const AnomaliesHostTableComponent: React.FC<AnomaliesHostTableProps> = ({
type,
}) => {
const capabilities = useMlCapabilities();
const { toggleStatus, setToggleStatus } = useQueryToggle(`AnomaliesHostTable`);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(skip || !toggleStatus);
}, [skip, toggleStatus]);
const toggleQuery = useCallback(
(status: boolean) => {
setToggleStatus(status);
// toggle on = skipQuery false
setQuerySkip(!status);
},
[setQuerySkip, setToggleStatus]
);
const [loading, tableData] = useAnomaliesTableData({
startDate,
endDate,
skip,
skip: querySkip,
criteriaFields: getCriteriaFromHostType(type, hostName),
filterQuery: {
exists: { field: 'host.name' },
@ -64,21 +79,26 @@ const AnomaliesHostTableComponent: React.FC<AnomaliesHostTableProps> = ({
return (
<Panel loading={loading}>
<HeaderSection
height={!toggleStatus ? 40 : undefined}
subtitle={`${i18n.SHOWING}: ${pagination.totalItemCount.toLocaleString()} ${i18n.UNIT(
pagination.totalItemCount
)}`}
title={i18n.ANOMALIES}
toggleQuery={toggleQuery}
toggleStatus={toggleStatus}
tooltip={i18n.TOOLTIP}
isInspectDisabled={skip}
/>
<BasicTable
// @ts-expect-error the Columns<T, U> type is not as specific as EUI's...
columns={columns}
items={hosts}
pagination={pagination}
sorting={sorting}
/>
{toggleStatus && (
<BasicTable
data-test-subj="host-anomalies-table"
// @ts-expect-error the Columns<T, U> type is not as specific as EUI's...
columns={columns}
items={hosts}
pagination={pagination}
sorting={sorting}
/>
)}
{loading && (
<Loader data-test-subj="anomalies-host-table-loading-panel" overlay size="xl" />

View file

@ -0,0 +1,90 @@
/*
* 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 { mount } from 'enzyme';
import { AnomaliesNetworkTable } from './anomalies_network_table';
import { TestProviders } from '../../../mock';
import React from 'react';
import { useQueryToggle } from '../../../containers/query_toggle';
import { useAnomaliesTableData } from '../anomaly/use_anomalies_table_data';
import { NetworkType } from '../../../../network/store/model';
import { hasMlUserPermissions } from '../../../../../common/machine_learning/has_ml_user_permissions';
import { FlowTarget } from '../../../../../common/search_strategy';
jest.mock('../../../containers/query_toggle');
jest.mock('../anomaly/use_anomalies_table_data');
jest.mock('../../../../../common/machine_learning/has_ml_user_permissions');
describe('Anomalies network table', () => {
describe('toggle query', () => {
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const mockUseAnomaliesTableData = useAnomaliesTableData as jest.Mock;
const mockSetToggle = jest.fn();
const testProps = {
startDate: '2019-07-17T20:00:00.000Z',
endDate: '2019-07-18T20:00:00.000Z',
flowTarget: FlowTarget.destination,
narrowDateRange: jest.fn(),
skip: false,
type: NetworkType.page,
};
beforeEach(() => {
jest.clearAllMocks();
(hasMlUserPermissions as jest.Mock).mockReturnValue(true);
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle });
mockUseAnomaliesTableData.mockReturnValue([
false,
{
anomalies: [],
interval: '10',
},
]);
});
test('toggleQuery updates toggleStatus', () => {
const wrapper = mount(<AnomaliesNetworkTable {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(false);
wrapper.find('[data-test-subj="query-toggle-header"]').first().simulate('click');
expect(mockSetToggle).toBeCalledWith(false);
expect(mockUseAnomaliesTableData.mock.calls[1][0].skip).toEqual(true);
});
test('toggleStatus=true, do not skip', () => {
mount(<AnomaliesNetworkTable {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(false);
});
test('toggleStatus=true, render components', () => {
const wrapper = mount(<AnomaliesNetworkTable {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('[data-test-subj="network-anomalies-table"]').exists()).toBe(true);
});
test('toggleStatus=false, do not render components', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
const wrapper = mount(<AnomaliesNetworkTable {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('[data-test-subj="network-anomalies-table"]').exists()).toBe(false);
});
test('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
mount(<AnomaliesNetworkTable {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(true);
});
});
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { useAnomaliesTableData } from '../anomaly/use_anomalies_table_data';
import { HeaderSection } from '../../header_section';
@ -20,6 +20,7 @@ import { BasicTable } from './basic_table';
import { networkEquality } from './network_equality';
import { getCriteriaFromNetworkType } from '../criteria/get_criteria_from_network_type';
import { Panel } from '../../panel';
import { useQueryToggle } from '../../../containers/query_toggle';
const sorting = {
sort: {
@ -37,10 +38,25 @@ const AnomaliesNetworkTableComponent: React.FC<AnomaliesNetworkTableProps> = ({
flowTarget,
}) => {
const capabilities = useMlCapabilities();
const { toggleStatus, setToggleStatus } = useQueryToggle(`AnomaliesNetwork-${flowTarget}`);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(skip || !toggleStatus);
}, [skip, toggleStatus]);
const toggleQuery = useCallback(
(status: boolean) => {
setToggleStatus(status);
// toggle on = skipQuery false
setQuerySkip(!status);
},
[setQuerySkip, setToggleStatus]
);
const [loading, tableData] = useAnomaliesTableData({
startDate,
endDate,
skip,
skip: querySkip,
criteriaFields: getCriteriaFromNetworkType(type, ip, flowTarget),
});
@ -63,18 +79,23 @@ const AnomaliesNetworkTableComponent: React.FC<AnomaliesNetworkTableProps> = ({
subtitle={`${i18n.SHOWING}: ${pagination.totalItemCount.toLocaleString()} ${i18n.UNIT(
pagination.totalItemCount
)}`}
height={!toggleStatus ? 40 : undefined}
title={i18n.ANOMALIES}
tooltip={i18n.TOOLTIP}
toggleQuery={toggleQuery}
toggleStatus={toggleStatus}
isInspectDisabled={skip}
/>
<BasicTable
// @ts-expect-error the Columns<T, U> type is not as specific as EUI's...
columns={columns}
items={networks}
pagination={pagination}
sorting={sorting}
/>
{toggleStatus && (
<BasicTable
data-test-subj="network-anomalies-table"
// @ts-expect-error the Columns<T, U> type is not as specific as EUI's...
columns={columns}
items={networks}
pagination={pagination}
sorting={sorting}
/>
)}
{loading && (
<Loader data-test-subj="anomalies-network-table-loading-panel" overlay size="xl" />

View file

@ -0,0 +1,89 @@
/*
* 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 { mount } from 'enzyme';
import { AnomaliesUserTable } from './anomalies_user_table';
import { TestProviders } from '../../../mock';
import React from 'react';
import { useQueryToggle } from '../../../containers/query_toggle';
import { useAnomaliesTableData } from '../anomaly/use_anomalies_table_data';
import { UsersType } from '../../../../users/store/model';
import { hasMlUserPermissions } from '../../../../../common/machine_learning/has_ml_user_permissions';
jest.mock('../../../containers/query_toggle');
jest.mock('../anomaly/use_anomalies_table_data');
jest.mock('../../../../../common/machine_learning/has_ml_user_permissions');
describe('Anomalies user table', () => {
describe('toggle query', () => {
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const mockUseAnomaliesTableData = useAnomaliesTableData as jest.Mock;
const mockSetToggle = jest.fn();
const testProps = {
startDate: '2019-07-17T20:00:00.000Z',
endDate: '2019-07-18T20:00:00.000Z',
narrowDateRange: jest.fn(),
userName: 'coolguy',
skip: false,
type: UsersType.page,
};
beforeEach(() => {
jest.clearAllMocks();
(hasMlUserPermissions as jest.Mock).mockReturnValue(true);
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle });
mockUseAnomaliesTableData.mockReturnValue([
false,
{
anomalies: [],
interval: '10',
},
]);
});
test('toggleQuery updates toggleStatus', () => {
const wrapper = mount(<AnomaliesUserTable {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(false);
wrapper.find('[data-test-subj="query-toggle-header"]').first().simulate('click');
expect(mockSetToggle).toBeCalledWith(false);
expect(mockUseAnomaliesTableData.mock.calls[1][0].skip).toEqual(true);
});
test('toggleStatus=true, do not skip', () => {
mount(<AnomaliesUserTable {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(false);
});
test('toggleStatus=true, render components', () => {
const wrapper = mount(<AnomaliesUserTable {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('[data-test-subj="user-anomalies-table"]').exists()).toBe(true);
});
test('toggleStatus=false, do not render components', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
const wrapper = mount(<AnomaliesUserTable {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('[data-test-subj="user-anomalies-table"]').exists()).toBe(false);
});
test('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
mount(<AnomaliesUserTable {...testProps} />, {
wrappingComponent: TestProviders,
});
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(true);
});
});
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { useAnomaliesTableData } from '../anomaly/use_anomalies_table_data';
import { HeaderSection } from '../../header_section';
@ -23,6 +23,7 @@ import { Panel } from '../../panel';
import { anomaliesTableDefaultEquality } from './default_equality';
import { convertAnomaliesToUsers } from './convert_anomalies_to_users';
import { getAnomaliesUserTableColumnsCurated } from './get_anomalies_user_table_columns';
import { useQueryToggle } from '../../../containers/query_toggle';
const sorting = {
sort: {
@ -40,10 +41,24 @@ const AnomaliesUserTableComponent: React.FC<AnomaliesUserTableProps> = ({
}) => {
const capabilities = useMlCapabilities();
const { toggleStatus, setToggleStatus } = useQueryToggle(`AnomaliesUserTable`);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(skip || !toggleStatus);
}, [skip, toggleStatus]);
const toggleQuery = useCallback(
(status: boolean) => {
setToggleStatus(status);
// toggle on = skipQuery false
setQuerySkip(!status);
},
[setQuerySkip, setToggleStatus]
);
const [loading, tableData] = useAnomaliesTableData({
startDate,
endDate,
skip,
skip: querySkip,
criteriaFields: getCriteriaFromUsersType(type, userName),
filterQuery: {
exists: { field: 'user.name' },
@ -67,21 +82,27 @@ const AnomaliesUserTableComponent: React.FC<AnomaliesUserTableProps> = ({
return (
<Panel loading={loading} data-test-subj="user-anomalies-tab">
<HeaderSection
height={!toggleStatus ? 40 : undefined}
subtitle={`${i18n.SHOWING}: ${pagination.totalItemCount.toLocaleString()} ${i18n.UNIT(
pagination.totalItemCount
)}`}
title={i18n.ANOMALIES}
toggleQuery={toggleQuery}
toggleStatus={toggleStatus}
tooltip={i18n.TOOLTIP}
isInspectDisabled={skip}
/>
<BasicTable
// @ts-expect-error the Columns<T, U> type is not as specific as EUI's...
columns={columns}
items={users}
pagination={pagination}
sorting={sorting}
/>
{toggleStatus && (
<BasicTable
data-test-subj="user-anomalies-table"
// @ts-expect-error the Columns<T, U> type is not as specific as EUI's...
columns={columns}
items={users}
pagination={pagination}
sorting={sorting}
/>
)}
{loading && (
<Loader data-test-subj="anomalies-host-table-loading-panel" overlay size="xl" />

View file

@ -11,6 +11,8 @@ exports[`Paginated Table Component rendering it renders the default load more ta
<HeaderSectionComponent
subtitle="Showing: 1 Test Unit"
title="Hosts"
toggleQuery={[Function]}
toggleStatus={true}
tooltip="My test tooltip"
>
<p>
@ -58,6 +60,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta
},
]
}
data-test-subj="paginated-basic-table"
items={
Array [
Object {

View file

@ -15,6 +15,8 @@ import { getHostsColumns, mockData, rowItems, sortedHosts } from './index.mock';
import { ThemeProvider } from 'styled-components';
import { getMockTheme } from '../../lib/kibana/kibana_react.mock';
import { Direction } from '../../../../common/search_strategy';
import { useQueryToggle } from '../../containers/query_toggle';
jest.mock('../../containers/query_toggle');
jest.mock('react', () => {
const r = jest.requireActual('react');
@ -36,37 +38,41 @@ const mockTheme = getMockTheme({
});
describe('Paginated Table Component', () => {
let loadPage: jest.Mock<number>;
let updateLimitPagination: jest.Mock<number>;
let updateActivePage: jest.Mock<number>;
const loadPage = jest.fn();
const updateLimitPagination = jest.fn();
const updateActivePage = jest.fn();
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const mockSetToggle = jest.fn();
const mockSetQuerySkip = jest.fn();
beforeEach(() => {
loadPage = jest.fn();
updateLimitPagination = jest.fn();
updateActivePage = jest.fn();
jest.clearAllMocks();
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle });
});
const testProps = {
activePage: 0,
columns: getHostsColumns(),
headerCount: 1,
headerSupplement: <p>{'My test supplement.'}</p>,
headerTitle: 'Hosts',
headerTooltip: 'My test tooltip',
headerUnit: 'Test Unit',
itemsPerRow: rowItems,
limit: 1,
loading: false,
loadPage,
pageOfItems: mockData.Hosts.edges,
setQuerySkip: jest.fn(),
showMorePagesIndicator: true,
totalCount: 10,
updateActivePage,
updateLimitPagination: (limit: number) => updateLimitPagination({ limit }),
};
describe('rendering', () => {
test('it renders the default load more table', () => {
const wrapper = shallow(
<PaginatedTable
activePage={0}
columns={getHostsColumns()}
headerCount={1}
headerSupplement={<p>{'My test supplement.'}</p>}
headerTitle="Hosts"
headerTooltip="My test tooltip"
headerUnit="Test Unit"
itemsPerRow={rowItems}
limit={1}
loading={false}
loadPage={loadPage}
pageOfItems={mockData.Hosts.edges}
showMorePagesIndicator={true}
totalCount={10}
updateActivePage={updateActivePage}
updateLimitPagination={(limit) => updateLimitPagination({ limit })}
/>
);
const wrapper = shallow(<PaginatedTable {...testProps} />);
expect(wrapper).toMatchSnapshot();
});
@ -74,24 +80,7 @@ describe('Paginated Table Component', () => {
test('it renders the loading panel at the beginning ', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable
activePage={0}
columns={getHostsColumns()}
headerCount={-1}
headerSupplement={<p>{'My test supplement.'}</p>}
headerTitle="Hosts"
headerTooltip="My test tooltip"
headerUnit="Test Unit"
itemsPerRow={rowItems}
limit={1}
loading={true}
loadPage={loadPage}
pageOfItems={[]}
showMorePagesIndicator={true}
totalCount={10}
updateActivePage={updateActivePage}
updateLimitPagination={(limit) => updateLimitPagination({ limit })}
/>
<PaginatedTable {...testProps} headerCount={-1} loading={true} pageOfItems={[]} />
</ThemeProvider>
);
@ -103,24 +92,7 @@ describe('Paginated Table Component', () => {
test('it renders the over loading panel after data has been in the table ', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable
activePage={0}
columns={getHostsColumns()}
headerCount={1}
headerSupplement={<p>{'My test supplement.'}</p>}
headerTitle="Hosts"
headerTooltip="My test tooltip"
headerUnit="Test Unit"
itemsPerRow={rowItems}
limit={1}
loading={true}
loadPage={loadPage}
pageOfItems={mockData.Hosts.edges}
showMorePagesIndicator={true}
totalCount={10}
updateActivePage={updateActivePage}
updateLimitPagination={(limit) => updateLimitPagination({ limit })}
/>
<PaginatedTable {...testProps} loading={true} />
</ThemeProvider>
);
@ -130,24 +102,7 @@ describe('Paginated Table Component', () => {
test('it renders the correct amount of pages and starts at activePage: 0', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable
activePage={0}
columns={getHostsColumns()}
headerCount={1}
headerSupplement={<p>{'My test supplement.'}</p>}
headerTitle="Hosts"
headerTooltip="My test tooltip"
headerUnit="Test Unit"
itemsPerRow={rowItems}
limit={1}
loading={false}
loadPage={loadPage}
pageOfItems={mockData.Hosts.edges}
showMorePagesIndicator={true}
totalCount={10}
updateActivePage={updateActivePage}
updateLimitPagination={(limit) => updateLimitPagination({ limit })}
/>
<PaginatedTable {...testProps} />
</ThemeProvider>
);
@ -167,24 +122,7 @@ describe('Paginated Table Component', () => {
test('it render popover to select new limit in table', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable
activePage={0}
columns={getHostsColumns()}
headerCount={1}
headerSupplement={<p>{'My test supplement.'}</p>}
headerTitle="Hosts"
headerTooltip="My test tooltip"
headerUnit="Test Unit"
itemsPerRow={rowItems}
limit={2}
loading={false}
loadPage={loadPage}
pageOfItems={mockData.Hosts.edges}
showMorePagesIndicator={true}
totalCount={10}
updateActivePage={updateActivePage}
updateLimitPagination={(limit) => updateLimitPagination({ limit })}
/>
<PaginatedTable {...testProps} limit={2} />
</ThemeProvider>
);
@ -195,24 +133,7 @@ describe('Paginated Table Component', () => {
test('it will NOT render popover to select new limit in table if props itemsPerRow is empty', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable
activePage={0}
columns={getHostsColumns()}
headerCount={1}
headerSupplement={<p>{'My test supplement.'}</p>}
headerTitle="Hosts"
headerTooltip="My test tooltip"
headerUnit="Test Unit"
itemsPerRow={[]}
limit={2}
loading={false}
loadPage={loadPage}
pageOfItems={mockData.Hosts.edges}
showMorePagesIndicator={true}
totalCount={10}
updateActivePage={updateActivePage}
updateLimitPagination={(limit) => updateLimitPagination({ limit })}
/>
<PaginatedTable {...testProps} itemsPerRow={[]} limit={2} />
</ThemeProvider>
);
@ -224,24 +145,11 @@ describe('Paginated Table Component', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable
activePage={0}
{...testProps}
columns={sortedHosts}
headerCount={1}
headerSupplement={<p>{'My test supplement.'}</p>}
headerTitle="Hosts"
headerTooltip="My test tooltip"
headerUnit="Test Unit"
itemsPerRow={rowItems}
limit={2}
loading={false}
loadPage={jest.fn()}
onChange={mockOnChange}
pageOfItems={mockData.Hosts.edges}
showMorePagesIndicator={true}
sorting={{ direction: Direction.asc, field: 'node.host.name' }}
totalCount={10}
updateActivePage={updateActivePage}
updateLimitPagination={(limit) => updateLimitPagination({ limit })}
/>
</ThemeProvider>
);
@ -253,22 +161,9 @@ describe('Paginated Table Component', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable
activePage={0}
columns={getHostsColumns()}
headerCount={1}
headerSupplement={<p>{'My test supplement.'}</p>}
headerTitle="Hosts"
headerTooltip="My test tooltip"
headerUnit="Test Unit"
itemsPerRow={rowItems}
{...testProps}
limit={DEFAULT_MAX_TABLE_QUERY_SIZE}
loading={false}
loadPage={loadPage}
pageOfItems={mockData.Hosts.edges}
showMorePagesIndicator={true}
totalCount={DEFAULT_MAX_TABLE_QUERY_SIZE * 3}
updateActivePage={updateActivePage}
updateLimitPagination={(limit) => updateLimitPagination({ limit })}
/>
</ThemeProvider>
);
@ -279,24 +174,7 @@ describe('Paginated Table Component', () => {
test('Should show items per row if totalCount is greater than items', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable
activePage={0}
columns={getHostsColumns()}
headerCount={1}
headerSupplement={<p>{'My test supplement.'}</p>}
headerTitle="Hosts"
headerTooltip="My test tooltip"
headerUnit="Test Unit"
itemsPerRow={rowItems}
limit={DEFAULT_MAX_TABLE_QUERY_SIZE}
loading={false}
loadPage={loadPage}
pageOfItems={mockData.Hosts.edges}
showMorePagesIndicator={true}
totalCount={30}
updateActivePage={updateActivePage}
updateLimitPagination={(limit) => updateLimitPagination({ limit })}
/>
<PaginatedTable {...testProps} limit={DEFAULT_MAX_TABLE_QUERY_SIZE} totalCount={30} />
</ThemeProvider>
);
expect(wrapper.find('[data-test-subj="loadingMoreSizeRowPopover"]').exists()).toBeTruthy();
@ -305,24 +183,7 @@ describe('Paginated Table Component', () => {
test('Should hide items per row if totalCount is less than items', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable
activePage={0}
columns={getHostsColumns()}
headerCount={1}
headerSupplement={<p>{'My test supplement.'}</p>}
headerTitle="Hosts"
headerTooltip="My test tooltip"
headerUnit="Test Unit"
itemsPerRow={rowItems}
limit={DEFAULT_MAX_TABLE_QUERY_SIZE}
loading={false}
loadPage={loadPage}
pageOfItems={mockData.Hosts.edges}
showMorePagesIndicator={true}
totalCount={1}
updateActivePage={updateActivePage}
updateLimitPagination={(limit) => updateLimitPagination({ limit })}
/>
<PaginatedTable {...testProps} limit={DEFAULT_MAX_TABLE_QUERY_SIZE} totalCount={1} />
</ThemeProvider>
);
expect(wrapper.find('[data-test-subj="loadingMoreSizeRowPopover"]').exists()).toBeFalsy();
@ -331,24 +192,7 @@ describe('Paginated Table Component', () => {
test('Should hide pagination if totalCount is zero', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable
activePage={0}
columns={getHostsColumns()}
headerCount={1}
headerSupplement={<p>{'My test supplement.'}</p>}
headerTitle="Hosts"
headerTooltip="My test tooltip"
headerUnit="Test Unit"
itemsPerRow={rowItems}
limit={DEFAULT_MAX_TABLE_QUERY_SIZE}
loading={false}
loadPage={loadPage}
pageOfItems={mockData.Hosts.edges}
showMorePagesIndicator={true}
totalCount={0}
updateActivePage={updateActivePage}
updateLimitPagination={(limit) => updateLimitPagination({ limit })}
/>
<PaginatedTable {...testProps} limit={DEFAULT_MAX_TABLE_QUERY_SIZE} totalCount={0} />
</ThemeProvider>
);
@ -360,24 +204,7 @@ describe('Paginated Table Component', () => {
test('should call updateActivePage with 1 when clicking to the first page', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable
activePage={0}
columns={getHostsColumns()}
headerCount={1}
headerSupplement={<p>{'My test supplement.'}</p>}
headerTitle="Hosts"
headerTooltip="My test tooltip"
headerUnit="Test Unit"
itemsPerRow={rowItems}
limit={1}
loading={false}
loadPage={loadPage}
pageOfItems={mockData.Hosts.edges}
showMorePagesIndicator={true}
totalCount={10}
updateActivePage={updateActivePage}
updateLimitPagination={(limit) => updateLimitPagination({ limit })}
/>
<PaginatedTable {...testProps} />
</ThemeProvider>
);
wrapper.find('[data-test-subj="pagination-button-next"]').first().simulate('click');
@ -387,24 +214,7 @@ describe('Paginated Table Component', () => {
test('Should call updateActivePage with 0 when you pick a new limit', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable
activePage={0}
columns={getHostsColumns()}
headerCount={1}
headerSupplement={<p>{'My test supplement.'}</p>}
headerTitle="Hosts"
headerTooltip="My test tooltip"
headerUnit="Test Unit"
itemsPerRow={rowItems}
limit={2}
loading={false}
loadPage={loadPage}
pageOfItems={mockData.Hosts.edges}
showMorePagesIndicator={true}
totalCount={10}
updateActivePage={updateActivePage}
updateLimitPagination={(limit) => updateLimitPagination({ limit })}
/>
<PaginatedTable {...testProps} limit={2} />
</ThemeProvider>
);
wrapper.find('[data-test-subj="pagination-button-next"]').first().simulate('click');
@ -417,22 +227,8 @@ describe('Paginated Table Component', () => {
test('should update the page when the activePage is changed from redux', () => {
const ourProps: BasicTableProps<unknown> = {
...testProps,
activePage: 3,
columns: getHostsColumns(),
headerCount: 1,
headerSupplement: <p>{'My test supplement.'}</p>,
headerTitle: 'Hosts',
headerTooltip: 'My test tooltip',
headerUnit: 'Test Unit',
itemsPerRow: rowItems,
limit: 1,
loading: false,
loadPage,
pageOfItems: mockData.Hosts.edges,
showMorePagesIndicator: true,
totalCount: 10,
updateActivePage,
updateLimitPagination: (limit) => updateLimitPagination({ limit }),
};
// enzyme does not allow us to pass props to child of HOC
@ -462,24 +258,7 @@ describe('Paginated Table Component', () => {
test('Should call updateLimitPagination when you pick a new limit', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable
activePage={0}
columns={getHostsColumns()}
headerCount={1}
headerSupplement={<p>{'My test supplement.'}</p>}
headerTitle="Hosts"
headerTooltip="My test tooltip"
headerUnit="Test Unit"
itemsPerRow={rowItems}
limit={2}
loading={false}
loadPage={loadPage}
pageOfItems={mockData.Hosts.edges}
showMorePagesIndicator={true}
totalCount={10}
updateActivePage={updateActivePage}
updateLimitPagination={(limit) => updateLimitPagination({ limit })}
/>
<PaginatedTable {...testProps} limit={2} />
</ThemeProvider>
);
@ -494,24 +273,11 @@ describe('Paginated Table Component', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable
activePage={0}
{...testProps}
columns={sortedHosts}
headerCount={1}
headerSupplement={<p>{'My test supplement.'}</p>}
headerTitle="Hosts"
headerTooltip="My test tooltip"
headerUnit="Test Unit"
itemsPerRow={rowItems}
limit={2}
loading={false}
loadPage={jest.fn()}
onChange={mockOnChange}
pageOfItems={mockData.Hosts.edges}
showMorePagesIndicator={true}
sorting={{ direction: Direction.asc, field: 'node.host.name' }}
totalCount={10}
updateActivePage={updateActivePage}
updateLimitPagination={(limit) => updateLimitPagination({ limit })}
/>
</ThemeProvider>
);
@ -524,4 +290,41 @@ describe('Paginated Table Component', () => {
]);
});
});
describe('Toggle query', () => {
test('toggleQuery updates toggleStatus', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable {...testProps} setQuerySkip={mockSetQuerySkip} />
</ThemeProvider>
);
wrapper.find('[data-test-subj="query-toggle-header"]').first().simulate('click');
expect(mockSetToggle).toBeCalledWith(false);
expect(mockSetQuerySkip).toBeCalledWith(true);
});
test('toggleStatus=true, render table', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable {...testProps} />
</ThemeProvider>
);
expect(wrapper.find('[data-test-subj="paginated-basic-table"]').first().exists()).toEqual(
true
);
});
test('toggleStatus=false, hide table', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<PaginatedTable {...testProps} />
</ThemeProvider>
);
expect(wrapper.find('[data-test-subj="paginated-basic-table"]').first().exists()).toEqual(
false
);
});
});
});

View file

@ -20,7 +20,7 @@ import {
EuiTableRowCellProps,
} from '@elastic/eui';
import { noop } from 'lodash/fp';
import React, { FC, memo, useState, useMemo, useEffect, ComponentType } from 'react';
import React, { FC, memo, useState, useMemo, useEffect, ComponentType, useCallback } from 'react';
import styled from 'styled-components';
import { Direction } from '../../../../common/search_strategy';
@ -49,6 +49,7 @@ import { useStateToaster } from '../toasters';
import * as i18n from './translations';
import { Panel } from '../panel';
import { InspectButtonContainer } from '../inspect';
import { useQueryToggle } from '../../containers/query_toggle';
const DEFAULT_DATA_TEST_SUBJ = 'paginated-table';
@ -113,6 +114,7 @@ export interface BasicTableProps<T> {
onChange?: (criteria: Criteria) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
pageOfItems: any[];
setQuerySkip: (skip: boolean) => void;
showMorePagesIndicator: boolean;
sorting?: SortingBasicTable;
split?: boolean;
@ -153,6 +155,7 @@ const PaginatedTableComponent: FC<SiemTables> = ({
loadPage,
onChange = noop,
pageOfItems,
setQuerySkip,
showMorePagesIndicator,
sorting = null,
split,
@ -253,10 +256,24 @@ const PaginatedTableComponent: FC<SiemTables> = ({
[sorting]
);
const { toggleStatus, setToggleStatus } = useQueryToggle(id);
const toggleQuery = useCallback(
(status: boolean) => {
setToggleStatus(status);
// toggle on = skipQuery false
setQuerySkip(!status);
},
[setQuerySkip, setToggleStatus]
);
return (
<InspectButtonContainer show={!loadingInitial}>
<Panel data-test-subj={`${dataTestSubj}-loading-${loading}`} loading={loading}>
<HeaderSection
height={!toggleStatus ? 40 : undefined}
toggleStatus={toggleStatus}
toggleQuery={toggleQuery}
headerFilters={headerFilters}
id={id}
split={split}
@ -274,50 +291,56 @@ const PaginatedTableComponent: FC<SiemTables> = ({
>
{!loadingInitial && headerSupplement}
</HeaderSection>
{toggleStatus &&
(loadingInitial ? (
<EuiLoadingContent data-test-subj="initialLoadingPanelPaginatedTable" lines={10} />
) : (
<>
<BasicTable
data-test-subj="paginated-basic-table"
columns={columns}
items={pageOfItems}
onChange={onChange}
sorting={tableSorting}
/>
<FooterAction>
<EuiFlexItem>
{itemsPerRow &&
itemsPerRow.length > 0 &&
totalCount >= itemsPerRow[0].numberOfRow && (
<EuiPopover
id="customizablePagination"
data-test-subj="loadingMoreSizeRowPopover"
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
repositionOnScroll
>
<EuiContextMenuPanel
items={rowItems}
data-test-subj="loadingMorePickSizeRow"
/>
</EuiPopover>
)}
</EuiFlexItem>
{loadingInitial ? (
<EuiLoadingContent data-test-subj="initialLoadingPanelPaginatedTable" lines={10} />
) : (
<>
<BasicTable
columns={columns}
items={pageOfItems}
onChange={onChange}
sorting={tableSorting}
/>
<FooterAction>
<EuiFlexItem>
{itemsPerRow && itemsPerRow.length > 0 && totalCount >= itemsPerRow[0].numberOfRow && (
<EuiPopover
id="customizablePagination"
data-test-subj="loadingMoreSizeRowPopover"
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
repositionOnScroll
>
<EuiContextMenuPanel items={rowItems} data-test-subj="loadingMorePickSizeRow" />
</EuiPopover>
)}
</EuiFlexItem>
<PaginationWrapper grow={false}>
{totalCount > 0 && (
<EuiPagination
data-test-subj="numberedPagination"
pageCount={pageCount}
activePage={myActivePage}
onPageClick={goToPage}
/>
)}
</PaginationWrapper>
</FooterAction>
{(isInspect || myLoading) && (
<Loader data-test-subj="loadingPanelPaginatedTable" overlay size="xl" />
)}
</>
)}
<PaginationWrapper grow={false}>
{totalCount > 0 && (
<EuiPagination
data-test-subj="numberedPagination"
pageCount={pageCount}
activePage={myActivePage}
onPageClick={goToPage}
/>
)}
</PaginationWrapper>
</FooterAction>
{(isInspect || myLoading) && (
<Loader data-test-subj="loadingPanelPaginatedTable" overlay size="xl" />
)}
</>
))}
</Panel>
</InspectButtonContainer>
);

View file

@ -41,6 +41,7 @@ import {
NetworkKpiStrategyResponse,
} from '../../../../common/search_strategy';
import { getMockTheme } from '../../lib/kibana/kibana_react.mock';
import * as module from '../../containers/query_toggle';
const from = '2019-06-15T06:00:00.000Z';
const to = '2019-06-18T06:00:00.000Z';
@ -53,26 +54,37 @@ jest.mock('../charts/barchart', () => {
return { BarChart: () => <div className="barchart" /> };
});
const mockSetToggle = jest.fn();
jest
.spyOn(module, 'useQueryToggle')
.mockImplementation(() => ({ toggleStatus: true, setToggleStatus: mockSetToggle }));
const mockSetQuerySkip = jest.fn();
describe('Stat Items Component', () => {
const mockTheme = getMockTheme({ eui: { euiColorMediumShade: '#ece' } });
const state: State = mockGlobalState;
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const testProps = {
description: 'HOSTS',
fields: [{ key: 'hosts', value: null, color: '#6092C0', icon: 'cross' }],
from,
id: 'statItems',
key: 'mock-keys',
loading: false,
setQuerySkip: mockSetQuerySkip,
to,
narrowDateRange: mockNarrowDateRange,
};
beforeEach(() => {
jest.clearAllMocks();
});
describe.each([
[
mount(
<ThemeProvider theme={mockTheme}>
<ReduxStoreProvider store={store}>
<StatItemsComponent
description="HOSTS"
fields={[{ key: 'hosts', value: null, color: '#6092C0', icon: 'cross' }]}
from={from}
id="statItems"
key="mock-keys"
to={to}
narrowDateRange={mockNarrowDateRange}
/>
<StatItemsComponent {...testProps} />
</ReduxStoreProvider>
</ThemeProvider>
),
@ -81,17 +93,7 @@ describe('Stat Items Component', () => {
mount(
<ThemeProvider theme={mockTheme}>
<ReduxStoreProvider store={store}>
<StatItemsComponent
areaChart={[]}
barChart={[]}
description="HOSTS"
fields={[{ key: 'hosts', value: null, color: '#6092C0', icon: 'cross' }]}
from={from}
id="statItems"
key="mock-keys"
to={to}
narrowDateRange={mockNarrowDateRange}
/>
<StatItemsComponent areaChart={[]} barChart={[]} {...testProps} />
</ReduxStoreProvider>
</ThemeProvider>
),
@ -118,62 +120,59 @@ describe('Stat Items Component', () => {
});
});
const mockStatItemsData: StatItemsProps = {
...testProps,
areaChart: [
{
key: 'uniqueSourceIpsHistogram',
value: [
{ x: new Date('2019-05-03T13:00:00.000Z').toISOString(), y: 565975 },
{ x: new Date('2019-05-04T01:00:00.000Z').toISOString(), y: 1084366 },
{ x: new Date('2019-05-04T13:00:00.000Z').toISOString(), y: 12280 },
],
color: '#D36086',
},
{
key: 'uniqueDestinationIpsHistogram',
value: [
{ x: new Date('2019-05-03T13:00:00.000Z').toISOString(), y: 565975 },
{ x: new Date('2019-05-04T01:00:00.000Z').toISOString(), y: 1084366 },
{ x: new Date('2019-05-04T13:00:00.000Z').toISOString(), y: 12280 },
],
color: '#9170B8',
},
],
barChart: [
{ key: 'uniqueSourceIps', value: [{ x: 'uniqueSourceIps', y: '1714' }], color: '#D36086' },
{
key: 'uniqueDestinationIps',
value: [{ x: 'uniqueDestinationIps', y: 2354 }],
color: '#9170B8',
},
],
description: 'UNIQUE_PRIVATE_IPS',
enableAreaChart: true,
enableBarChart: true,
fields: [
{
key: 'uniqueSourceIps',
description: 'Source',
value: 1714,
color: '#D36086',
icon: 'cross',
},
{
key: 'uniqueDestinationIps',
description: 'Dest.',
value: 2359,
color: '#9170B8',
icon: 'cross',
},
],
};
let wrapper: ReactWrapper;
describe('rendering kpis with charts', () => {
const mockStatItemsData: StatItemsProps = {
areaChart: [
{
key: 'uniqueSourceIpsHistogram',
value: [
{ x: new Date('2019-05-03T13:00:00.000Z').toISOString(), y: 565975 },
{ x: new Date('2019-05-04T01:00:00.000Z').toISOString(), y: 1084366 },
{ x: new Date('2019-05-04T13:00:00.000Z').toISOString(), y: 12280 },
],
color: '#D36086',
},
{
key: 'uniqueDestinationIpsHistogram',
value: [
{ x: new Date('2019-05-03T13:00:00.000Z').toISOString(), y: 565975 },
{ x: new Date('2019-05-04T01:00:00.000Z').toISOString(), y: 1084366 },
{ x: new Date('2019-05-04T13:00:00.000Z').toISOString(), y: 12280 },
],
color: '#9170B8',
},
],
barChart: [
{ key: 'uniqueSourceIps', value: [{ x: 'uniqueSourceIps', y: '1714' }], color: '#D36086' },
{
key: 'uniqueDestinationIps',
value: [{ x: 'uniqueDestinationIps', y: 2354 }],
color: '#9170B8',
},
],
description: 'UNIQUE_PRIVATE_IPS',
enableAreaChart: true,
enableBarChart: true,
fields: [
{
key: 'uniqueSourceIps',
description: 'Source',
value: 1714,
color: '#D36086',
icon: 'cross',
},
{
key: 'uniqueDestinationIps',
description: 'Dest.',
value: 2359,
color: '#9170B8',
icon: 'cross',
},
],
from,
id: 'statItems',
key: 'mock-keys',
to,
narrowDateRange: mockNarrowDateRange,
};
let wrapper: ReactWrapper;
beforeAll(() => {
wrapper = mount(
<ReduxStoreProvider store={store}>
@ -202,6 +201,43 @@ describe('Stat Items Component', () => {
expect(wrapper.find(EuiHorizontalRule)).toHaveLength(1);
});
});
describe('Toggle query', () => {
test('toggleQuery updates toggleStatus', () => {
wrapper = mount(
<ReduxStoreProvider store={store}>
<StatItemsComponent {...mockStatItemsData} />
</ReduxStoreProvider>
);
wrapper.find('[data-test-subj="query-toggle-stat"]').first().simulate('click');
expect(mockSetToggle).toBeCalledWith(false);
expect(mockSetQuerySkip).toBeCalledWith(true);
});
test('toggleStatus=true, render all', () => {
wrapper = mount(
<ReduxStoreProvider store={store}>
<StatItemsComponent {...mockStatItemsData} />
</ReduxStoreProvider>
);
expect(wrapper.find('[data-test-subj="inspect-icon-button"]').first().exists()).toEqual(true);
expect(wrapper.find('[data-test-subj="stat-title"]').first().exists()).toEqual(true);
});
test('toggleStatus=false, render none', () => {
jest
.spyOn(module, 'useQueryToggle')
.mockImplementation(() => ({ toggleStatus: false, setToggleStatus: mockSetToggle }));
wrapper = mount(
<ReduxStoreProvider store={store}>
<StatItemsComponent {...mockStatItemsData} />
</ReduxStoreProvider>
);
expect(wrapper.find('[data-test-subj="inspect-icon-button"]').first().exists()).toEqual(
false
);
expect(wrapper.find('[data-test-subj="stat-title"]').first().exists()).toEqual(false);
});
});
});
describe('addValueToFields', () => {
@ -244,7 +280,9 @@ describe('useKpiMatrixStatus', () => {
'statItem',
from,
to,
mockNarrowDateRange
mockNarrowDateRange,
mockSetQuerySkip,
false
);
return (
@ -262,8 +300,10 @@ describe('useKpiMatrixStatus', () => {
<MockHookWrapperComponent fieldsMapping={mockNetworkMappings} data={mockData} />
</>
);
expect(wrapper.find('MockChildComponent').get(0).props).toEqual(mockEnableChartsData);
const result = { ...wrapper.find('MockChildComponent').get(0).props };
const { setQuerySkip, ...restResult } = result;
const { setQuerySkip: a, ...restExpect } = mockEnableChartsData;
expect(restResult).toEqual(restExpect);
});
test('it should not append areaChart if enableAreaChart is off', () => {

View file

@ -12,13 +12,16 @@ import {
EuiPanel,
EuiHorizontalRule,
EuiIcon,
EuiButtonIcon,
EuiLoadingSpinner,
EuiTitle,
IconType,
} from '@elastic/eui';
import { get, getOr } from 'lodash/fp';
import React, { useState, useEffect, useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import styled from 'styled-components';
import deepEqual from 'fast-deep-equal';
import { useQueryToggle } from '../../containers/query_toggle';
import {
HostsKpiStrategyResponse,
@ -34,6 +37,7 @@ import { InspectButton } from '../inspect';
import { VisualizationActions, HISTOGRAM_ACTIONS_BUTTON_CLASS } from '../visualization_actions';
import { HoverVisibilityContainer } from '../hover_visibility_container';
import { LensAttributes } from '../visualization_actions/types';
import * as i18n from '../../containers/query_toggle/translations';
import { UserskKpiStrategyResponse } from '../../../../common/search_strategy/security_solution/users';
const FlexItem = styled(EuiFlexItem)`
@ -84,6 +88,8 @@ export interface StatItemsProps extends StatItems {
narrowDateRange: UpdateDateRange;
to: string;
showInspectButton?: boolean;
loading: boolean;
setQuerySkip: (skip: boolean) => void;
}
export const numberFormatter = (value: string | number): string => value.toLocaleString();
@ -176,33 +182,27 @@ export const useKpiMatrixStatus = (
id: string,
from: string,
to: string,
narrowDateRange: UpdateDateRange
): StatItemsProps[] => {
const [statItemsProps, setStatItemsProps] = useState(mappings as StatItemsProps[]);
useEffect(() => {
setStatItemsProps(
mappings.map((stat) => {
return {
...stat,
areaChart: stat.enableAreaChart ? addValueToAreaChart(stat.fields, data) : undefined,
barChart: stat.enableBarChart ? addValueToBarChart(stat.fields, data) : undefined,
fields: addValueToFields(stat.fields, data),
id,
key: `kpi-summary-${stat.key}`,
statKey: `${stat.key}`,
from,
to,
narrowDateRange,
};
})
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);
return statItemsProps;
};
narrowDateRange: UpdateDateRange,
setQuerySkip: (skip: boolean) => void,
loading: boolean
): StatItemsProps[] =>
mappings.map((stat) => ({
...stat,
areaChart: stat.enableAreaChart ? addValueToAreaChart(stat.fields, data) : undefined,
barChart: stat.enableBarChart ? addValueToBarChart(stat.fields, data) : undefined,
fields: addValueToFields(stat.fields, data),
id,
key: `kpi-summary-${stat.key}`,
statKey: `${stat.key}`,
from,
to,
narrowDateRange,
setQuerySkip,
loading,
}));
const StyledTitle = styled.h6`
line-height: 200%;
`;
export const StatItemsComponent = React.memo<StatItemsProps>(
({
areaChart,
@ -214,13 +214,15 @@ export const StatItemsComponent = React.memo<StatItemsProps>(
from,
grow,
id,
showInspectButton,
loading = false,
showInspectButton = true,
index,
narrowDateRange,
statKey = 'item',
to,
barChartLensAttributes,
areaChartLensAttributes,
setQuerySkip,
}) => {
const isBarChartDataAvailable =
barChart &&
@ -239,101 +241,143 @@ export const StatItemsComponent = React.memo<StatItemsProps>(
[from, to]
);
const { toggleStatus, setToggleStatus } = useQueryToggle(id);
const toggleQuery = useCallback(
(status: boolean) => {
setToggleStatus(status);
// toggle on = skipQuery false
setQuerySkip(!status);
},
[setQuerySkip, setToggleStatus]
);
const toggle = useCallback(() => toggleQuery(!toggleStatus), [toggleQuery, toggleStatus]);
return (
<FlexItem grow={grow} data-test-subj={`stat-${statKey}`}>
<EuiPanel hasBorder>
<EuiFlexGroup gutterSize={'none'}>
<EuiFlexItem>
<EuiTitle size="xxxs">
<h6>{description}</h6>
</EuiTitle>
<EuiFlexGroup gutterSize={'none'}>
<EuiFlexItem grow={false}>
<EuiButtonIcon
aria-label={i18n.QUERY_BUTTON_TITLE(toggleStatus)}
data-test-subj="query-toggle-stat"
color="text"
display="empty"
iconType={toggleStatus ? 'arrowDown' : 'arrowRight'}
onClick={toggle}
size="xs"
title={i18n.QUERY_BUTTON_TITLE(toggleStatus)}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle size="xxxs">
<StyledTitle>{description}</StyledTitle>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{showInspectButton && (
{showInspectButton && toggleStatus && !loading && (
<EuiFlexItem grow={false}>
<InspectButton queryId={id} title={description} inspectIndex={index} />
</EuiFlexItem>
)}
</EuiFlexGroup>
{loading && (
<EuiFlexGroup justifyContent="center" alignItems="center">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="l" />
</EuiFlexItem>
</EuiFlexGroup>
)}
{toggleStatus && !loading && (
<>
<EuiFlexGroup>
{fields.map((field) => (
<FlexItem key={`stat-items-field-${field.key}`}>
<EuiFlexGroup alignItems="center" gutterSize="m" responsive={false}>
{(isAreaChartDataAvailable || isBarChartDataAvailable) && field.icon && (
<FlexItem grow={false}>
<EuiIcon
type={field.icon}
color={field.color}
size="l"
data-test-subj="stat-icon"
/>
</FlexItem>
)}
<EuiFlexGroup>
{fields.map((field) => (
<FlexItem key={`stat-items-field-${field.key}`}>
<EuiFlexGroup alignItems="center" gutterSize="m" responsive={false}>
{(isAreaChartDataAvailable || isBarChartDataAvailable) && field.icon && (
<FlexItem grow={false}>
<EuiIcon
type={field.icon}
color={field.color}
size="l"
data-test-subj="stat-icon"
<FlexItem>
<HoverVisibilityContainer
targetClassNames={[HISTOGRAM_ACTIONS_BUTTON_CLASS]}
>
<StatValue>
<p data-test-subj="stat-title">
{field.value != null
? field.value.toLocaleString()
: getEmptyTagValue()}{' '}
{field.description}
</p>
</StatValue>
{field.lensAttributes && timerange && (
<VisualizationActions
lensAttributes={field.lensAttributes}
queryId={id}
inspectIndex={index}
timerange={timerange}
title={description}
className="viz-actions"
/>
)}
</HoverVisibilityContainer>
</FlexItem>
</EuiFlexGroup>
</FlexItem>
))}
</EuiFlexGroup>
{(enableAreaChart || enableBarChart) && <EuiHorizontalRule />}
<EuiFlexGroup>
{enableBarChart && (
<FlexItem>
<BarChart
barChart={barChart}
configs={barchartConfigs()}
visualizationActionsOptions={{
lensAttributes: barChartLensAttributes,
queryId: id,
inspectIndex: index,
timerange,
title: description,
}}
/>
</FlexItem>
)}
{enableAreaChart && from != null && to != null && (
<>
<FlexItem>
<AreaChart
areaChart={areaChart}
configs={areachartConfigs({
xTickFormatter: histogramDateTimeFormatter([from, to]),
onBrushEnd: narrowDateRange,
})}
visualizationActionsOptions={{
lensAttributes: areaChartLensAttributes,
queryId: id,
inspectIndex: index,
timerange,
title: description,
}}
/>
</FlexItem>
)}
<FlexItem>
<HoverVisibilityContainer targetClassNames={[HISTOGRAM_ACTIONS_BUTTON_CLASS]}>
<StatValue>
<p data-test-subj="stat-title">
{field.value != null ? field.value.toLocaleString() : getEmptyTagValue()}{' '}
{field.description}
</p>
</StatValue>
{field.lensAttributes && timerange && (
<VisualizationActions
lensAttributes={field.lensAttributes}
queryId={id}
inspectIndex={index}
timerange={timerange}
title={description}
className="viz-actions"
/>
)}
</HoverVisibilityContainer>
</FlexItem>
</EuiFlexGroup>
</FlexItem>
))}
</EuiFlexGroup>
{(enableAreaChart || enableBarChart) && <EuiHorizontalRule />}
<EuiFlexGroup>
{enableBarChart && (
<FlexItem>
<BarChart
barChart={barChart}
configs={barchartConfigs()}
visualizationActionsOptions={{
lensAttributes: barChartLensAttributes,
queryId: id,
inspectIndex: index,
timerange,
title: description,
}}
/>
</FlexItem>
)}
{enableAreaChart && from != null && to != null && (
<>
<FlexItem>
<AreaChart
areaChart={areaChart}
configs={areachartConfigs({
xTickFormatter: histogramDateTimeFormatter([from, to]),
onBrushEnd: narrowDateRange,
})}
visualizationActionsOptions={{
lensAttributes: areaChartLensAttributes,
queryId: id,
inspectIndex: index,
timerange,
title: description,
}}
/>
</FlexItem>
</>
)}
</EuiFlexGroup>
</>
)}
</EuiFlexGroup>
</>
)}
</EuiPanel>
</FlexItem>
);
@ -344,6 +388,8 @@ export const StatItemsComponent = React.memo<StatItemsProps>(
prevProps.enableBarChart === nextProps.enableBarChart &&
prevProps.from === nextProps.from &&
prevProps.grow === nextProps.grow &&
prevProps.loading === nextProps.loading &&
prevProps.setQuerySkip === nextProps.setQuerySkip &&
prevProps.id === nextProps.id &&
prevProps.index === nextProps.index &&
prevProps.narrowDateRange === nextProps.narrowDateRange &&

View file

@ -6,7 +6,6 @@
*/
import { renderHook, act } from '@testing-library/react-hooks';
import { useKibana } from '../../../common/lib/kibana';
import { useMatrixHistogram, useMatrixHistogramCombined } from '.';
import { MatrixHistogramType } from '../../../../common/search_strategy';
@ -39,6 +38,7 @@ describe('useMatrixHistogram', () => {
indexNames: [],
stackByField: 'event.module',
startDate: new Date(Date.now()).toISOString(),
skip: false,
};
afterEach(() => {
@ -145,6 +145,17 @@ describe('useMatrixHistogram', () => {
mockDnsSearchStrategyResponse.rawResponse.aggregations?.dns_name_query_count.buckets
);
});
it('skip = true will cancel any running request', () => {
const abortSpy = jest.spyOn(AbortController.prototype, 'abort');
const localProps = { ...props };
const { rerender } = renderHook(() => useMatrixHistogram(localProps), {
wrapper: TestProviders,
});
localProps.skip = true;
act(() => rerender());
expect(abortSpy).toHaveBeenCalledTimes(3);
});
});
describe('useMatrixHistogramCombined', () => {

View file

@ -229,6 +229,14 @@ export const useMatrixHistogram = ({
};
}, [matrixHistogramRequest, hostsSearch, skip]);
useEffect(() => {
if (skip) {
setLoading(false);
searchSubscription$.current.unsubscribe();
abortCtrl.current.abort();
}
}, [skip]);
const runMatrixHistogramSearch = useCallback(
(to: string, from: string) => {
hostsSearch({

View file

@ -0,0 +1,74 @@
/*
* 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 {
renderHook,
act,
RenderResult,
WaitForNextUpdate,
cleanup,
} from '@testing-library/react-hooks';
import { QueryToggle, useQueryToggle } from '.';
import { RouteSpyState } from '../../utils/route/types';
import { SecurityPageName } from '../../../../common/constants';
import { useKibana } from '../../lib/kibana';
const mockRouteSpy: RouteSpyState = {
pageName: SecurityPageName.overview,
detailName: undefined,
tabName: undefined,
search: '',
pathName: '/',
};
jest.mock('../../lib/kibana');
jest.mock('../../utils/route/use_route_spy', () => ({
useRouteSpy: () => [mockRouteSpy],
}));
describe('useQueryToggle', () => {
let result: RenderResult<QueryToggle>;
let waitForNextUpdate: WaitForNextUpdate;
const mockSet = jest.fn();
beforeAll(() => {
(useKibana as jest.Mock).mockReturnValue({
services: {
storage: {
get: () => true,
set: mockSet,
},
},
});
});
beforeEach(() => {
jest.clearAllMocks();
});
it('Toggles local storage', async () => {
await act(async () => {
({ result, waitForNextUpdate } = renderHook(() => useQueryToggle('queryId')));
await waitForNextUpdate();
expect(result.current.toggleStatus).toEqual(true);
});
act(() => {
result.current.setToggleStatus(false);
});
expect(result.current.toggleStatus).toEqual(false);
expect(mockSet).toBeCalledWith('kibana.siem:queryId.query.toggle:overview', false);
cleanup();
});
it('null storage key, do not set', async () => {
await act(async () => {
({ result, waitForNextUpdate } = renderHook(() => useQueryToggle()));
await waitForNextUpdate();
expect(result.current.toggleStatus).toEqual(true);
});
act(() => {
result.current.setToggleStatus(false);
});
expect(mockSet).not.toBeCalled();
cleanup();
});
});

View file

@ -0,0 +1,55 @@
/*
* 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 { useEffect, useCallback, useState } from 'react';
import { useKibana } from '../../lib/kibana';
import { useRouteSpy } from '../../utils/route/use_route_spy';
export const getUniqueStorageKey = (pageName: string, id?: string): string | null =>
id && pageName.length > 0 ? `kibana.siem:${id}.query.toggle:${pageName}` : null;
export interface QueryToggle {
toggleStatus: boolean;
setToggleStatus: (b: boolean) => void;
}
export const useQueryToggle = (id?: string): QueryToggle => {
const [{ pageName }] = useRouteSpy();
const {
services: { storage },
} = useKibana();
const storageKey = getUniqueStorageKey(pageName, id);
const [storageValue, setStorageValue] = useState(
storageKey != null ? storage.get(storageKey) ?? true : true
);
useEffect(() => {
if (storageKey != null) {
setStorageValue(storage.get(storageKey) ?? true);
}
}, [storage, storageKey]);
const setToggleStatus = useCallback(
(isOpen: boolean) => {
if (storageKey != null) {
storage.set(storageKey, isOpen);
setStorageValue(isOpen);
}
},
[storage, storageKey]
);
return id
? {
toggleStatus: storageValue,
setToggleStatus,
}
: {
toggleStatus: true,
setToggleStatus: () => {},
};
};

View file

@ -0,0 +1,17 @@
/*
* 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 { i18n } from '@kbn/i18n';
export const QUERY_BUTTON_TITLE = (buttonOn: boolean) =>
buttonOn
? i18n.translate('xpack.securitySolution.toggleQuery.on', {
defaultMessage: 'Open',
})
: i18n.translate('xpack.securitySolution.toggleQuery.off', {
defaultMessage: 'Closed',
});

View file

@ -6,7 +6,7 @@
*/
import { useSearchStrategy } from './index';
import { renderHook } from '@testing-library/react-hooks';
import { act, renderHook } from '@testing-library/react-hooks';
import { useObservable } from '@kbn/securitysolution-hook-utils';
import { FactoryQueryTypes } from '../../../../common/search_strategy';
@ -200,4 +200,19 @@ describe('useSearchStrategy', () => {
expect(start).toBeCalledWith(expect.objectContaining({ signal }));
});
it('skip = true will cancel any running request', () => {
const abortSpy = jest.fn();
const signal = new AbortController().signal;
jest.spyOn(window, 'AbortController').mockReturnValue({ abort: abortSpy, signal });
const factoryQueryType = 'fakeQueryType' as FactoryQueryTypes;
const localProps = {
...userSearchStrategyProps,
skip: false,
factoryQueryType,
};
const { rerender } = renderHook(() => useSearchStrategy<FactoryQueryTypes>(localProps));
localProps.skip = true;
act(() => rerender());
expect(abortSpy).toHaveBeenCalledTimes(1);
});
});

View file

@ -96,6 +96,7 @@ export const useSearchStrategy = <QueryType extends FactoryQueryTypes>({
factoryQueryType,
initialResult,
errorMessage,
skip = false,
}: {
factoryQueryType: QueryType;
/**
@ -106,6 +107,7 @@ export const useSearchStrategy = <QueryType extends FactoryQueryTypes>({
* Message displayed to the user on a Toast when an erro happens.
*/
errorMessage?: string;
skip?: boolean;
}) => {
const abortCtrl = useRef(new AbortController());
const { getTransformChangesIfTheyExist } = useTransforms();
@ -154,6 +156,12 @@ export const useSearchStrategy = <QueryType extends FactoryQueryTypes>({
};
}, []);
useEffect(() => {
if (skip) {
abortCtrl.current.abort();
}
}, [skip]);
const [formatedResult, inspect] = useMemo(
() => [
result

View file

@ -12,7 +12,9 @@ import { mount } from 'enzyme';
import { TestProviders } from '../../../../common/mock';
import { AlertsCountPanel } from './index';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
jest.mock('../../../../common/containers/query_toggle');
jest.mock('react-router-dom', () => {
const actual = jest.requireActual('react-router-dom');
return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) };
@ -22,6 +24,12 @@ describe('AlertsCountPanel', () => {
const defaultProps = {
signalIndexName: 'signalIndexName',
};
const mockSetToggle = jest.fn();
const mockUseQueryToggle = useQueryToggle as jest.Mock;
beforeEach(() => {
jest.clearAllMocks();
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle });
});
it('renders correctly', async () => {
await act(async () => {
@ -54,4 +62,38 @@ describe('AlertsCountPanel', () => {
});
});
});
describe('toggleQuery', () => {
it('toggles', async () => {
await act(async () => {
const wrapper = mount(
<TestProviders>
<AlertsCountPanel {...defaultProps} />
</TestProviders>
);
wrapper.find('[data-test-subj="query-toggle-header"]').first().simulate('click');
expect(mockSetToggle).toBeCalledWith(false);
});
});
it('toggleStatus=true, render', async () => {
await act(async () => {
const wrapper = mount(
<TestProviders>
<AlertsCountPanel {...defaultProps} />
</TestProviders>
);
expect(wrapper.find('[data-test-subj="alertsCountTable"]').exists()).toEqual(true);
});
});
it('toggleStatus=false, hide', async () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
await act(async () => {
const wrapper = mount(
<TestProviders>
<AlertsCountPanel {...defaultProps} />
</TestProviders>
);
expect(wrapper.find('[data-test-subj="alertsCountTable"]').exists()).toEqual(false);
});
});
});
});

View file

@ -6,7 +6,7 @@
*/
import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types';
import React, { memo, useMemo, useState, useEffect } from 'react';
import React, { memo, useMemo, useState, useEffect, useCallback } from 'react';
import uuid from 'uuid';
import type { Filter, Query } from '@kbn/es-query';
@ -24,6 +24,7 @@ import type { AlertsCountAggregation } from './types';
import { DEFAULT_STACK_BY_FIELD } from '../common/config';
import { KpiPanel, StackByComboBox } from '../common/components';
import { useInspectButton } from '../common/hooks';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
export const DETECTIONS_ALERTS_COUNT_ID = 'detections-alerts-count';
@ -64,6 +65,20 @@ export const AlertsCountPanel = memo<AlertsCountPanelProps>(
}
}, [query, filters]);
const { toggleStatus, setToggleStatus } = useQueryToggle(DETECTIONS_ALERTS_COUNT_ID);
const [querySkip, setQuerySkip] = useState(!toggleStatus);
useEffect(() => {
setQuerySkip(!toggleStatus);
}, [toggleStatus]);
const toggleQuery = useCallback(
(status: boolean) => {
setToggleStatus(status);
// toggle on = skipQuery false
setQuerySkip(!status);
},
[setQuerySkip, setToggleStatus]
);
const {
loading: isLoadingAlerts,
data: alertsData,
@ -80,6 +95,7 @@ export const AlertsCountPanel = memo<AlertsCountPanelProps>(
runtimeMappings
),
indexName: signalIndexName,
skip: querySkip,
});
useEffect(() => {
@ -99,21 +115,26 @@ export const AlertsCountPanel = memo<AlertsCountPanelProps>(
});
return (
<InspectButtonContainer>
<KpiPanel hasBorder data-test-subj="alertsCountPanel">
<InspectButtonContainer show={toggleStatus}>
<KpiPanel $toggleStatus={toggleStatus} hasBorder data-test-subj="alertsCountPanel">
<HeaderSection
height={!toggleStatus ? 30 : undefined}
id={uniqueQueryId}
title={i18n.COUNT_TABLE_TITLE}
titleSize="s"
hideSubtitle
toggleStatus={toggleStatus}
toggleQuery={toggleQuery}
>
<StackByComboBox selected={selectedStackByOption} onSelect={setSelectedStackByOption} />
</HeaderSection>
<AlertsCount
data={alertsData}
loading={isLoadingAlerts}
selectedStackByOption={selectedStackByOption}
/>
{toggleStatus && (
<AlertsCount
data={alertsData}
loading={isLoadingAlerts}
selectedStackByOption={selectedStackByOption}
/>
)}
</KpiPanel>
</InspectButtonContainer>
);

View file

@ -12,9 +12,13 @@ import { mount } from 'enzyme';
import type { Filter } from '@kbn/es-query';
import { TestProviders } from '../../../../common/mock';
import { SecurityPageName } from '../../../../app/types';
import { MatrixLoader } from '../../../../common/components/matrix_histogram/matrix_loader';
import { AlertsHistogramPanel } from './index';
import * as helpers from './helpers';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
jest.mock('../../../../common/containers/query_toggle');
jest.mock('react-router-dom', () => {
const originalModule = jest.requireActual('react-router-dom');
@ -91,6 +95,12 @@ describe('AlertsHistogramPanel', () => {
updateDateRange: jest.fn(),
};
const mockSetToggle = jest.fn();
const mockUseQueryToggle = useQueryToggle as jest.Mock;
beforeEach(() => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle });
});
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
@ -339,4 +349,40 @@ describe('AlertsHistogramPanel', () => {
`);
});
});
describe('toggleQuery', () => {
it('toggles', async () => {
await act(async () => {
const wrapper = mount(
<TestProviders>
<AlertsHistogramPanel {...defaultProps} />
</TestProviders>
);
wrapper.find('[data-test-subj="query-toggle-header"]').first().simulate('click');
expect(mockSetToggle).toBeCalledWith(false);
});
});
it('toggleStatus=true, render', async () => {
await act(async () => {
const wrapper = mount(
<TestProviders>
<AlertsHistogramPanel {...defaultProps} />
</TestProviders>
);
expect(wrapper.find(MatrixLoader).exists()).toEqual(true);
});
});
it('toggleStatus=false, hide', async () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
await act(async () => {
const wrapper = mount(
<TestProviders>
<AlertsHistogramPanel {...defaultProps} />
</TestProviders>
);
expect(wrapper.find(MatrixLoader).exists()).toEqual(false);
});
});
});
});

View file

@ -45,6 +45,7 @@ import type { AlertsStackByField } from '../common/types';
import { KpiPanel, StackByComboBox } from '../common/components';
import { useInspectButton } from '../common/hooks';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
const defaultTotalAlertsObj: AlertsTotal = {
value: 0,
@ -116,6 +117,19 @@ export const AlertsHistogramPanel = memo<AlertsHistogramPanelProps>(
onlyField == null ? defaultStackByOption : onlyField
);
const { toggleStatus, setToggleStatus } = useQueryToggle(DETECTIONS_HISTOGRAM_ID);
const [querySkip, setQuerySkip] = useState(!toggleStatus);
useEffect(() => {
setQuerySkip(!toggleStatus);
}, [toggleStatus]);
const toggleQuery = useCallback(
(status: boolean) => {
setToggleStatus(status);
// toggle on = skipQuery false
setQuerySkip(!status);
},
[setQuerySkip, setToggleStatus]
);
const {
loading: isLoadingAlerts,
data: alertsData,
@ -132,6 +146,7 @@ export const AlertsHistogramPanel = memo<AlertsHistogramPanelProps>(
runtimeMappings
),
indexName: signalIndexName,
skip: querySkip,
});
const kibana = useKibana();
@ -270,17 +285,21 @@ export const AlertsHistogramPanel = memo<AlertsHistogramPanelProps>(
);
return (
<InspectButtonContainer show={!isInitialLoading}>
<InspectButtonContainer show={!isInitialLoading && toggleStatus}>
<KpiPanel
height={PANEL_HEIGHT}
hasBorder
paddingSize={paddingSize}
data-test-subj="alerts-histogram-panel"
$toggleStatus={toggleStatus}
>
<HeaderSection
id={uniqueQueryId}
height={!toggleStatus ? 30 : undefined}
title={titleText}
titleSize={titleSize}
toggleStatus={toggleStatus}
toggleQuery={toggleQuery}
subtitle={!isInitialLoading && showTotalAlertsCount && totalAlerts}
isInspectDisabled={isInspectDisabled}
hideSubtitle
@ -301,21 +320,23 @@ export const AlertsHistogramPanel = memo<AlertsHistogramPanelProps>(
</EuiFlexGroup>
</HeaderSection>
{isInitialLoading ? (
<MatrixLoader />
) : (
<AlertsHistogram
chartHeight={chartHeight}
data={formattedAlertsData}
from={from}
legendItems={legendItems}
legendPosition={legendPosition}
loading={isLoadingAlerts}
to={to}
showLegend={showLegend}
updateDateRange={updateDateRange}
/>
)}
{toggleStatus ? (
isInitialLoading ? (
<MatrixLoader />
) : (
<AlertsHistogram
chartHeight={chartHeight}
data={formattedAlertsData}
from={from}
legendItems={legendItems}
legendPosition={legendPosition}
loading={isLoadingAlerts}
to={to}
showLegend={showLegend}
updateDateRange={updateDateRange}
/>
)
) : null}
</KpiPanel>
</InspectButtonContainer>
);

View file

@ -12,17 +12,23 @@ import { PANEL_HEIGHT, MOBILE_PANEL_HEIGHT } from './config';
import { useStackByFields } from './hooks';
import * as i18n from './translations';
export const KpiPanel = styled(EuiPanel)<{ height?: number }>`
export const KpiPanel = styled(EuiPanel)<{ height?: number; $toggleStatus: boolean }>`
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
height: ${MOBILE_PANEL_HEIGHT}px;
@media only screen and (min-width: ${(props) => props.theme.eui.euiBreakpoints.m}) {
${({ $toggleStatus }) =>
$toggleStatus &&
`
height: ${PANEL_HEIGHT}px;
`}
}
${({ $toggleStatus }) =>
$toggleStatus &&
`
height: ${MOBILE_PANEL_HEIGHT}px;
`}
`;
interface StackedBySelectProps {
selected: string;

View file

@ -129,4 +129,22 @@ describe('useQueryAlerts', () => {
});
});
});
test('skip', async () => {
const abortSpy = jest.spyOn(AbortController.prototype, 'abort');
await act(async () => {
const localProps = { query: mockAlertsQuery, indexName, skip: false };
const { rerender, waitForNextUpdate } = renderHook<
[object, string],
ReturnQueryAlerts<unknown, unknown>
>(() => useQueryAlerts<unknown, unknown>(localProps));
await waitForNextUpdate();
await waitForNextUpdate();
localProps.skip = true;
act(() => rerender());
act(() => rerender());
expect(abortSpy).toHaveBeenCalledTimes(2);
});
});
});

View file

@ -94,6 +94,12 @@ export const useQueryAlerts = <Hit, Aggs>({
if (!isEmpty(query) && !skip) {
fetchData();
}
if (skip) {
setLoading(false);
isSubscribed = false;
abortCtrl.abort();
}
return () => {
isSubscribed = false;
abortCtrl.abort();

View file

@ -105,6 +105,7 @@ exports[`Authentication Table Component rendering it renders the authentication
isInspect={false}
loadPage={[MockFunction]}
loading={false}
setQuerySkip={[MockFunction]}
showMorePagesIndicator={true}
totalCount={54}
type="page"

View file

@ -45,6 +45,7 @@ describe('Authentication Table Component', () => {
isInspect={false}
loading={false}
loadPage={loadPage}
setQuerySkip={jest.fn()}
showMorePagesIndicator={getOr(
false,
'showMorePagesIndicator',

View file

@ -43,6 +43,7 @@ interface AuthenticationTableProps {
loadPage: (newActivePage: number) => void;
id: string;
isInspect: boolean;
setQuerySkip: (skip: boolean) => void;
showMorePagesIndicator: boolean;
totalCount: number;
type: hostsModel.HostsType;
@ -78,6 +79,7 @@ const AuthenticationTableComponent: React.FC<AuthenticationTableProps> = ({
isInspect,
loading,
loadPage,
setQuerySkip,
showMorePagesIndicator,
totalCount,
type,
@ -133,6 +135,7 @@ const AuthenticationTableComponent: React.FC<AuthenticationTableProps> = ({
loading={loading}
loadPage={loadPage}
pageOfItems={data}
setQuerySkip={setQuerySkip}
showMorePagesIndicator={showMorePagesIndicator}
totalCount={fakeTotalCount}
updateLimitPagination={updateLimitPagination}

View file

@ -54,6 +54,7 @@ interface HostRiskScoreTableProps {
isInspect: boolean;
loading: boolean;
loadPage: (newActivePage: number) => void;
setQuerySkip: (skip: boolean) => void;
severityCount: SeverityCount;
totalCount: number;
type: hostsModel.HostsType;
@ -71,6 +72,7 @@ const HostRiskScoreTableComponent: React.FC<HostRiskScoreTableProps> = ({
isInspect,
loading,
loadPage,
setQuerySkip,
severityCount,
totalCount,
type,
@ -207,6 +209,7 @@ const HostRiskScoreTableComponent: React.FC<HostRiskScoreTableProps> = ({
loadPage={loadPage}
onChange={onSort}
pageOfItems={data}
setQuerySkip={setQuerySkip}
showMorePagesIndicator={false}
sorting={sort}
split={true}

View file

@ -36,6 +36,7 @@ exports[`Hosts Table rendering it renders the default Hosts table 1`] = `
isInspect={false}
loadPage={[MockFunction]}
loading={false}
setQuerySkip={[MockFunction]}
showMorePagesIndicator={false}
totalCount={-1}
type="page"

View file

@ -69,6 +69,7 @@ describe('Hosts Table', () => {
fakeTotalCount={0}
loading={false}
loadPage={loadPage}
setQuerySkip={jest.fn()}
showMorePagesIndicator={false}
totalCount={-1}
type={hostsModel.HostsType.page}
@ -91,6 +92,7 @@ describe('Hosts Table', () => {
data={mockData}
totalCount={0}
fakeTotalCount={-1}
setQuerySkip={jest.fn()}
showMorePagesIndicator={false}
loadPage={loadPage}
type={hostsModel.HostsType.page}
@ -113,6 +115,7 @@ describe('Hosts Table', () => {
data={mockData}
totalCount={0}
fakeTotalCount={-1}
setQuerySkip={jest.fn()}
showMorePagesIndicator={false}
loadPage={loadPage}
type={hostsModel.HostsType.page}
@ -136,6 +139,7 @@ describe('Hosts Table', () => {
data={mockData}
totalCount={0}
fakeTotalCount={-1}
setQuerySkip={jest.fn()}
showMorePagesIndicator={false}
loadPage={loadPage}
type={hostsModel.HostsType.page}

View file

@ -42,6 +42,7 @@ interface HostsTableProps {
isInspect: boolean;
loading: boolean;
loadPage: (newActivePage: number) => void;
setQuerySkip: (skip: boolean) => void;
showMorePagesIndicator: boolean;
totalCount: number;
type: hostsModel.HostsType;
@ -77,6 +78,7 @@ const HostsTableComponent: React.FC<HostsTableProps> = ({
isInspect,
loading,
loadPage,
setQuerySkip,
showMorePagesIndicator,
totalCount,
type,
@ -172,6 +174,7 @@ const HostsTableComponent: React.FC<HostsTableProps> = ({
loadPage={loadPage}
onChange={onChange}
pageOfItems={data}
setQuerySkip={setQuerySkip}
showMorePagesIndicator={showMorePagesIndicator}
sorting={sorting}
totalCount={fakeTotalCount}

View file

@ -0,0 +1,66 @@
/*
* 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 { useHostsKpiAuthentications } from '../../../containers/kpi_hosts/authentications';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
import { render } from '@testing-library/react';
import { TestProviders } from '../../../../common/mock';
import React from 'react';
import { HostsKpiAuthentications } from './index';
jest.mock('../../../../common/containers/query_toggle');
jest.mock('../../../containers/kpi_hosts/authentications');
jest.mock('../common', () => ({
KpiBaseComponentManage: () => <span data-test-subj="KpiBaseComponentManage" />,
}));
describe('Authentications KPI', () => {
const mockUseHostsKpiAuthentications = useHostsKpiAuthentications as jest.Mock;
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const defaultProps = {
from: '2019-06-25T04:31:59.345Z',
to: '2019-06-25T06:31:59.345Z',
indexNames: [],
narrowDateRange: jest.fn(),
setQuery: jest.fn(),
skip: false,
};
beforeEach(() => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
mockUseHostsKpiAuthentications.mockReturnValue([
false,
{
id: '123',
inspect: {
dsl: [],
response: [],
},
refetch: jest.fn(),
},
]);
});
afterEach(() => {
jest.clearAllMocks();
});
it('toggleStatus=true, do not skip', () => {
render(
<TestProviders>
<HostsKpiAuthentications {...defaultProps} />
</TestProviders>
);
expect(mockUseHostsKpiAuthentications.mock.calls[0][0].skip).toEqual(false);
});
it('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() });
render(
<TestProviders>
<HostsKpiAuthentications {...defaultProps} />
</TestProviders>
);
expect(mockUseHostsKpiAuthentications.mock.calls[0][0].skip).toEqual(true);
});
});

View file

@ -5,17 +5,18 @@
* 2.0.
*/
import React from 'react';
import React, { useEffect, useState } from 'react';
import { StatItems } from '../../../../common/components/stat_items';
import { kpiUserAuthenticationsAreaLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentications_area';
import { kpiUserAuthenticationsBarLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentications_bar';
import { kpiUserAuthenticationsMetricSuccessLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentications_metric_success';
import { kpiUserAuthenticationsMetricFailureLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentication_metric_failure';
import { useHostsKpiAuthentications } from '../../../containers/kpi_hosts/authentications';
import { useHostsKpiAuthentications, ID } from '../../../containers/kpi_hosts/authentications';
import { KpiBaseComponentManage } from '../common';
import { HostsKpiProps, HostsKpiChartColors } from '../types';
import * as i18n from './translations';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
export const fieldsMapping: Readonly<StatItems[]> = [
{
@ -57,12 +58,17 @@ const HostsKpiAuthenticationsComponent: React.FC<HostsKpiProps> = ({
setQuery,
skip,
}) => {
const { toggleStatus } = useQueryToggle(ID);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(skip || !toggleStatus);
}, [skip, toggleStatus]);
const [loading, { refetch, id, inspect, ...data }] = useHostsKpiAuthentications({
filterQuery,
endDate: to,
indexNames,
startDate: from,
skip,
skip: querySkip,
});
return (
@ -77,6 +83,7 @@ const HostsKpiAuthenticationsComponent: React.FC<HostsKpiProps> = ({
narrowDateRange={narrowDateRange}
refetch={refetch}
setQuery={setQuery}
setQuerySkip={setQuerySkip}
/>
);
};

View file

@ -6,7 +6,7 @@
*/
import React from 'react';
import { EuiFlexItem, EuiLoadingSpinner, EuiFlexGroup } from '@elastic/eui';
import { EuiFlexGroup } from '@elastic/eui';
import styled from 'styled-components';
import deepEqual from 'fast-deep-equal';
@ -42,10 +42,11 @@ interface KpiBaseComponentProps {
from: string;
to: string;
narrowDateRange: UpdateDateRange;
setQuerySkip: (skip: boolean) => void;
}
export const KpiBaseComponent = React.memo<KpiBaseComponentProps>(
({ fieldsMapping, data, id, loading = false, from, to, narrowDateRange }) => {
({ fieldsMapping, data, id, loading = false, from, to, narrowDateRange, setQuerySkip }) => {
const { cases } = useKibana().services;
const CasesContext = cases.ui.getCasesContext();
const userPermissions = useGetUserCasesPermissions();
@ -57,13 +58,11 @@ export const KpiBaseComponent = React.memo<KpiBaseComponentProps>(
id,
from,
to,
narrowDateRange
narrowDateRange,
setQuerySkip,
loading
);
if (loading) {
return <KpiBaseComponentLoader />;
}
return (
<EuiFlexGroup wrap>
<CasesContext owner={[APP_ID]} userCanCrud={userCanCrud ?? false}>
@ -87,11 +86,3 @@ export const KpiBaseComponent = React.memo<KpiBaseComponentProps>(
KpiBaseComponent.displayName = 'KpiBaseComponent';
export const KpiBaseComponentManage = manageQuery(KpiBaseComponent);
export const KpiBaseComponentLoader: React.FC = () => (
<FlexGroup justifyContent="center" alignItems="center" data-test-subj="KpiLoader">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="xl" />
</EuiFlexItem>
</FlexGroup>
);

View file

@ -0,0 +1,66 @@
/*
* 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 { useHostsKpiHosts } from '../../../containers/kpi_hosts/hosts';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
import { render } from '@testing-library/react';
import { TestProviders } from '../../../../common/mock';
import React from 'react';
import { HostsKpiHosts } from './index';
jest.mock('../../../../common/containers/query_toggle');
jest.mock('../../../containers/kpi_hosts/hosts');
jest.mock('../common', () => ({
KpiBaseComponentManage: () => <span data-test-subj="KpiBaseComponentManage" />,
}));
describe('Hosts KPI', () => {
const mockUseHostsKpiHosts = useHostsKpiHosts as jest.Mock;
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const defaultProps = {
from: '2019-06-25T04:31:59.345Z',
to: '2019-06-25T06:31:59.345Z',
indexNames: [],
narrowDateRange: jest.fn(),
setQuery: jest.fn(),
skip: false,
};
beforeEach(() => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
mockUseHostsKpiHosts.mockReturnValue([
false,
{
id: '123',
inspect: {
dsl: [],
response: [],
},
refetch: jest.fn(),
},
]);
});
afterEach(() => {
jest.clearAllMocks();
});
it('toggleStatus=true, do not skip', () => {
render(
<TestProviders>
<HostsKpiHosts {...defaultProps} />
</TestProviders>
);
expect(mockUseHostsKpiHosts.mock.calls[0][0].skip).toEqual(false);
});
it('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() });
render(
<TestProviders>
<HostsKpiHosts {...defaultProps} />
</TestProviders>
);
expect(mockUseHostsKpiHosts.mock.calls[0][0].skip).toEqual(true);
});
});

View file

@ -5,15 +5,16 @@
* 2.0.
*/
import React from 'react';
import React, { useEffect, useState } from 'react';
import { StatItems } from '../../../../common/components/stat_items';
import { kpiHostAreaLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_host_area';
import { kpiHostMetricLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric';
import { useHostsKpiHosts } from '../../../containers/kpi_hosts/hosts';
import { useHostsKpiHosts, ID } from '../../../containers/kpi_hosts/hosts';
import { KpiBaseComponentManage } from '../common';
import { HostsKpiProps, HostsKpiChartColors } from '../types';
import * as i18n from './translations';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
export const fieldsMapping: Readonly<StatItems[]> = [
{
@ -42,12 +43,17 @@ const HostsKpiHostsComponent: React.FC<HostsKpiProps> = ({
setQuery,
skip,
}) => {
const { toggleStatus } = useQueryToggle(ID);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(skip || !toggleStatus);
}, [skip, toggleStatus]);
const [loading, { refetch, id, inspect, ...data }] = useHostsKpiHosts({
filterQuery,
endDate: to,
indexNames,
startDate: from,
skip,
skip: querySkip,
});
return (
@ -62,6 +68,7 @@ const HostsKpiHostsComponent: React.FC<HostsKpiProps> = ({
narrowDateRange={narrowDateRange}
refetch={refetch}
setQuery={setQuery}
setQuerySkip={setQuerySkip}
/>
);
};

View file

@ -11,6 +11,7 @@ import {
EuiHorizontalRule,
EuiIcon,
EuiPanel,
EuiLoadingSpinner,
EuiTitle,
EuiText,
} from '@elastic/eui';
@ -22,7 +23,6 @@ import {
BUTTON_CLASS as INPECT_BUTTON_CLASS,
} from '../../../../common/components/inspect';
import { KpiBaseComponentLoader } from '../common';
import * as i18n from './translations';
import { useInspectQuery } from '../../../../common/hooks/use_inspect_query';
@ -36,6 +36,13 @@ import { HoverVisibilityContainer } from '../../../../common/components/hover_vi
import { KpiRiskScoreStrategyResponse, RiskSeverity } from '../../../../../common/search_strategy';
import { RiskScore } from '../../../../common/components/severity/common';
const KpiBaseComponentLoader: React.FC = () => (
<EuiFlexGroup justifyContent="center" alignItems="center" data-test-subj="KpiLoader">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="xl" />
</EuiFlexItem>
</EuiFlexGroup>
);
const QUERY_ID = 'hostsKpiRiskyHostsQuery';
const HostCount = styled(EuiText)`

View file

@ -0,0 +1,66 @@
/*
* 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 { useHostsKpiUniqueIps } from '../../../containers/kpi_hosts/unique_ips';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
import { render } from '@testing-library/react';
import { TestProviders } from '../../../../common/mock';
import React from 'react';
import { HostsKpiUniqueIps } from './index';
jest.mock('../../../../common/containers/query_toggle');
jest.mock('../../../containers/kpi_hosts/unique_ips');
jest.mock('../common', () => ({
KpiBaseComponentManage: () => <span data-test-subj="KpiBaseComponentManage" />,
}));
describe('Unique IPs KPI', () => {
const mockUseHostsKpiUniqueIps = useHostsKpiUniqueIps as jest.Mock;
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const defaultProps = {
from: '2019-06-25T04:31:59.345Z',
to: '2019-06-25T06:31:59.345Z',
indexNames: [],
narrowDateRange: jest.fn(),
setQuery: jest.fn(),
skip: false,
};
beforeEach(() => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
mockUseHostsKpiUniqueIps.mockReturnValue([
false,
{
id: '123',
inspect: {
dsl: [],
response: [],
},
refetch: jest.fn(),
},
]);
});
afterEach(() => {
jest.clearAllMocks();
});
it('toggleStatus=true, do not skip', () => {
render(
<TestProviders>
<HostsKpiUniqueIps {...defaultProps} />
</TestProviders>
);
expect(mockUseHostsKpiUniqueIps.mock.calls[0][0].skip).toEqual(false);
});
it('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() });
render(
<TestProviders>
<HostsKpiUniqueIps {...defaultProps} />
</TestProviders>
);
expect(mockUseHostsKpiUniqueIps.mock.calls[0][0].skip).toEqual(true);
});
});

View file

@ -5,17 +5,18 @@
* 2.0.
*/
import React from 'react';
import React, { useEffect, useState } from 'react';
import { StatItems } from '../../../../common/components/stat_items';
import { kpiUniqueIpsAreaLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area';
import { kpiUniqueIpsBarLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar';
import { kpiUniqueIpsDestinationMetricLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric';
import { kpiUniqueIpsSourceMetricLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric';
import { useHostsKpiUniqueIps } from '../../../containers/kpi_hosts/unique_ips';
import { useHostsKpiUniqueIps, ID } from '../../../containers/kpi_hosts/unique_ips';
import { KpiBaseComponentManage } from '../common';
import { HostsKpiProps, HostsKpiChartColors } from '../types';
import * as i18n from './translations';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
export const fieldsMapping: Readonly<StatItems[]> = [
{
@ -57,12 +58,17 @@ const HostsKpiUniqueIpsComponent: React.FC<HostsKpiProps> = ({
setQuery,
skip,
}) => {
const { toggleStatus } = useQueryToggle(ID);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(skip || !toggleStatus);
}, [skip, toggleStatus]);
const [loading, { refetch, id, inspect, ...data }] = useHostsKpiUniqueIps({
filterQuery,
endDate: to,
indexNames,
startDate: from,
skip,
skip: querySkip,
});
return (
@ -77,6 +83,7 @@ const HostsKpiUniqueIpsComponent: React.FC<HostsKpiProps> = ({
narrowDateRange={narrowDateRange}
refetch={refetch}
setQuery={setQuery}
setQuerySkip={setQuerySkip}
/>
);
};

View file

@ -5,16 +5,30 @@
* 2.0.
*/
import { render } from '@testing-library/react';
import { render, fireEvent } from '@testing-library/react';
import React from 'react';
import { TopHostScoreContributors } from '.';
import { TestProviders } from '../../../common/mock';
import { useHostRiskScore } from '../../../risk_score/containers';
import { useQueryToggle } from '../../../common/containers/query_toggle';
jest.mock('../../../common/containers/query_toggle');
jest.mock('../../../risk_score/containers');
const useHostRiskScoreMock = useHostRiskScore as jest.Mock;
const testProps = {
setQuery: jest.fn(),
deleteQuery: jest.fn(),
hostName: 'test-host-name',
from: '2020-07-07T08:20:18.966Z',
to: '2020-07-08T08:20:18.966Z',
};
describe('Host Risk Flyout', () => {
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const mockSetToggle = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle });
});
it('renders', () => {
useHostRiskScoreMock.mockReturnValueOnce([
true,
@ -26,13 +40,7 @@ describe('Host Risk Flyout', () => {
const { queryByTestId } = render(
<TestProviders>
<TopHostScoreContributors
setQuery={jest.fn()}
deleteQuery={jest.fn()}
hostName={'test-host-name'}
from={'2020-07-07T08:20:18.966Z'}
to={'2020-07-08T08:20:18.966Z'}
/>
<TopHostScoreContributors {...testProps} />
</TestProviders>
);
@ -69,13 +77,7 @@ describe('Host Risk Flyout', () => {
const { queryAllByRole } = render(
<TestProviders>
<TopHostScoreContributors
setQuery={jest.fn()}
deleteQuery={jest.fn()}
hostName={'test-host-name'}
from={'2020-07-07T08:20:18.966Z'}
to={'2020-07-08T08:20:18.966Z'}
/>
<TopHostScoreContributors {...testProps} />
</TestProviders>
);
@ -83,4 +85,66 @@ describe('Host Risk Flyout', () => {
expect(queryAllByRole('row')[2]).toHaveTextContent('second');
expect(queryAllByRole('row')[3]).toHaveTextContent('third');
});
describe('toggleQuery', () => {
beforeEach(() => {
useHostRiskScoreMock.mockReturnValue([
true,
{
data: [],
isModuleEnabled: true,
},
]);
});
test('toggleQuery updates toggleStatus', () => {
const { getByTestId } = render(
<TestProviders>
<TopHostScoreContributors {...testProps} />
</TestProviders>
);
expect(useHostRiskScoreMock.mock.calls[0][0].skip).toEqual(false);
fireEvent.click(getByTestId('query-toggle-header'));
expect(mockSetToggle).toBeCalledWith(false);
expect(useHostRiskScoreMock.mock.calls[1][0].skip).toEqual(true);
});
test('toggleStatus=true, do not skip', () => {
render(
<TestProviders>
<TopHostScoreContributors {...testProps} />
</TestProviders>
);
expect(useHostRiskScoreMock.mock.calls[0][0].skip).toEqual(false);
});
test('toggleStatus=true, render components', () => {
const { queryByTestId } = render(
<TestProviders>
<TopHostScoreContributors {...testProps} />
</TestProviders>
);
expect(queryByTestId('topHostScoreContributors-table')).toBeTruthy();
});
test('toggleStatus=false, do not render components', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
const { queryByTestId } = render(
<TestProviders>
<TopHostScoreContributors {...testProps} />
</TestProviders>
);
expect(queryByTestId('topHostScoreContributors-table')).toBeFalsy();
});
test('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
render(
<TestProviders>
<TopHostScoreContributors {...testProps} />
</TestProviders>
);
expect(useHostRiskScoreMock.mock.calls[0][0].skip).toEqual(true);
});
});
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useMemo } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
EuiFlexGroup,
@ -27,6 +27,7 @@ import { HostsComponentsQueryProps } from '../../pages/navigation/types';
import { RuleLink } from '../../../detections/pages/detection_engine/rules/all/use_columns';
import { HostRiskScoreQueryId, useHostRiskScore } from '../../../risk_score/containers';
import { useQueryToggle } from '../../../common/containers/query_toggle';
export interface TopHostScoreContributorsProps
extends Pick<HostsComponentsQueryProps, 'setQuery' | 'deleteQuery'> {
@ -77,11 +78,27 @@ const TopHostScoreContributorsComponent: React.FC<TopHostScoreContributorsProps>
const sort = useMemo(() => ({ field: RiskScoreFields.timestamp, direction: Direction.desc }), []);
const { toggleStatus, setToggleStatus } = useQueryToggle(QUERY_ID);
const [querySkip, setQuerySkip] = useState(!toggleStatus);
useEffect(() => {
setQuerySkip(!toggleStatus);
}, [toggleStatus]);
const toggleQuery = useCallback(
(status: boolean) => {
setToggleStatus(status);
// toggle on = skipQuery false
setQuerySkip(!status);
},
[setQuerySkip, setToggleStatus]
);
const [loading, { data, refetch, inspect }] = useHostRiskScore({
filterQuery: hostName ? buildHostNamesFilter([hostName]) : undefined,
timerange,
onlyLatest: false,
sort,
skip: querySkip,
pagination: {
querySize: 1,
cursorStart: 0,
@ -119,24 +136,37 @@ const TopHostScoreContributorsComponent: React.FC<TopHostScoreContributorsProps>
<EuiPanel hasBorder data-test-subj="topHostScoreContributors">
<EuiFlexGroup gutterSize={'none'}>
<EuiFlexItem grow={1}>
<HeaderSection title={i18n.TOP_RISK_SCORE_CONTRIBUTORS} hideSubtitle />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<InspectButton queryId={QUERY_ID} title={i18n.TOP_RISK_SCORE_CONTRIBUTORS} />
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup gutterSize="none" direction="column">
<EuiFlexItem grow={1}>
<EuiInMemoryTable
items={items}
columns={columns}
pagination={tablePagination}
loading={loading}
<HeaderSection
height={!toggleStatus ? 40 : undefined}
title={i18n.TOP_RISK_SCORE_CONTRIBUTORS}
hideSubtitle
toggleQuery={toggleQuery}
toggleStatus={toggleStatus}
/>
</EuiFlexItem>
{toggleStatus && (
<EuiFlexItem grow={false}>
<InspectButton queryId={QUERY_ID} title={i18n.TOP_RISK_SCORE_CONTRIBUTORS} />
</EuiFlexItem>
)}
</EuiFlexGroup>
{toggleStatus && (
<EuiFlexGroup
data-test-subj="topHostScoreContributors-table"
gutterSize="none"
direction="column"
>
<EuiFlexItem grow={1}>
<EuiInMemoryTable
items={items}
columns={columns}
pagination={tablePagination}
loading={loading}
/>
</EuiFlexItem>
</EuiFlexGroup>
)}
</EuiPanel>
</InspectButtonContainer>
);

View file

@ -205,6 +205,7 @@ exports[`Uncommon Process Table Component rendering it renders the default Uncom
isInspect={false}
loadPage={[MockFunction]}
loading={false}
setQuerySkip={[MockFunction]}
showMorePagesIndicator={true}
totalCount={5}
type="page"

View file

@ -36,21 +36,24 @@ describe('Uncommon Process Table Component', () => {
const loadPage = jest.fn();
const mount = useMountAppended();
const defaultProps = {
data: mockData.edges,
fakeTotalCount: getOr(50, 'fakeTotalCount', mockData.pageInfo),
id: 'uncommonProcess',
isInspect: false,
loading: false,
loadPage,
setQuerySkip: jest.fn(),
showMorePagesIndicator: getOr(false, 'showMorePagesIndicator', mockData.pageInfo),
totalCount: mockData.totalCount,
type: hostsModel.HostsType.page,
};
describe('rendering', () => {
test('it renders the default Uncommon process table', () => {
const wrapper = shallow(
<TestProviders>
<UncommonProcessTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
id="uncommonProcess"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={hostsModel.HostsType.page}
/>
<UncommonProcessTable {...defaultProps} />
</TestProviders>
);
@ -60,17 +63,7 @@ describe('Uncommon Process Table Component', () => {
test('it has a double dash (empty value) without any hosts at all', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
id="uncommonProcess"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={hostsModel.HostsType.page}
/>
<UncommonProcessTable {...defaultProps} />
</TestProviders>
);
expect(wrapper.find('.euiTableRow').at(0).find('.euiTableRowCell').at(3).text()).toBe(
@ -81,17 +74,7 @@ describe('Uncommon Process Table Component', () => {
test('it has a single host without any extra comma when the number of hosts is exactly 1', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
id="uncommonProcess"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={hostsModel.HostsType.page}
/>
<UncommonProcessTable {...defaultProps} />
</TestProviders>
);
@ -103,17 +86,7 @@ describe('Uncommon Process Table Component', () => {
test('it has a single link when the number of hosts is exactly 1', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
id="uncommonProcess"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={hostsModel.HostsType.page}
/>
<UncommonProcessTable {...defaultProps} />
</TestProviders>
);
@ -125,17 +98,7 @@ describe('Uncommon Process Table Component', () => {
test('it has a comma separated list of hosts when the number of hosts is greater than 1', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
id="uncommonProcess"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={hostsModel.HostsType.page}
/>
<UncommonProcessTable {...defaultProps} />
</TestProviders>
);
@ -147,17 +110,7 @@ describe('Uncommon Process Table Component', () => {
test('it has 2 links when the number of hosts is equal to 2', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
id="uncommonProcess"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={hostsModel.HostsType.page}
/>
<UncommonProcessTable {...defaultProps} />
</TestProviders>
);
@ -169,17 +122,7 @@ describe('Uncommon Process Table Component', () => {
test('it is empty when all hosts are invalid because they do not contain an id and a name', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
id="uncommonProcess"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={hostsModel.HostsType.page}
/>
<UncommonProcessTable {...defaultProps} />
</TestProviders>
);
expect(wrapper.find('.euiTableRow').at(3).find('.euiTableRowCell').at(3).text()).toBe(
@ -190,17 +133,7 @@ describe('Uncommon Process Table Component', () => {
test('it has no link when all hosts are invalid because they do not contain an id and a name', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
id="uncommonProcess"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={hostsModel.HostsType.page}
/>
<UncommonProcessTable {...defaultProps} />
</TestProviders>
);
expect(
@ -211,17 +144,7 @@ describe('Uncommon Process Table Component', () => {
test('it is returns two hosts when others are invalid because they do not contain an id and a name', () => {
const wrapper = mount(
<TestProviders>
<UncommonProcessTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
id="uncommonProcess"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={hostsModel.HostsType.page}
/>
<UncommonProcessTable {...defaultProps} />
</TestProviders>
);
expect(wrapper.find('.euiTableRow').at(4).find('.euiTableRowCell').at(3).text()).toBe(

View file

@ -30,6 +30,7 @@ interface UncommonProcessTableProps {
isInspect: boolean;
loading: boolean;
loadPage: (newActivePage: number) => void;
setQuerySkip: (skip: boolean) => void;
showMorePagesIndicator: boolean;
totalCount: number;
type: hostsModel.HostsType;
@ -72,6 +73,7 @@ const UncommonProcessTableComponent = React.memo<UncommonProcessTableProps>(
loading,
loadPage,
totalCount,
setQuerySkip,
showMorePagesIndicator,
type,
}) => {
@ -125,6 +127,7 @@ const UncommonProcessTableComponent = React.memo<UncommonProcessTableProps>(
loading={loading}
loadPage={loadPage}
pageOfItems={data}
setQuerySkip={setQuerySkip}
showMorePagesIndicator={showMorePagesIndicator}
totalCount={fakeTotalCount}
updateLimitPagination={updateLimitPagination}

View file

@ -0,0 +1,30 @@
/*
* 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 { act, renderHook } from '@testing-library/react-hooks';
import { TestProviders } from '../../../common/mock';
import { useAuthentications } from './index';
import { HostsType } from '../../store/model';
describe('authentications', () => {
it('skip = true will cancel any running request', () => {
const abortSpy = jest.spyOn(AbortController.prototype, 'abort');
const localProps = {
startDate: '2020-07-07T08:20:18.966Z',
endDate: '2020-07-08T08:20:18.966Z',
indexNames: ['cool'],
type: HostsType.page,
skip: false,
};
const { rerender } = renderHook(() => useAuthentications(localProps), {
wrapper: TestProviders,
});
localProps.skip = true;
act(() => rerender());
expect(abortSpy).toHaveBeenCalledTimes(4);
});
});

View file

@ -36,7 +36,7 @@ import * as i18n from './translations';
import { useTransforms } from '../../../transforms/containers/use_transforms';
import { useAppToasts } from '../../../common/hooks/use_app_toasts';
const ID = 'hostsAuthenticationsQuery';
export const ID = 'hostsAuthenticationsQuery';
export interface AuthenticationArgs {
authentications: AuthenticationsEdges[];
@ -215,5 +215,13 @@ export const useAuthentications = ({
};
}, [authenticationsRequest, authenticationsSearch]);
useEffect(() => {
if (skip) {
setLoading(false);
searchSubscription$.current.unsubscribe();
abortCtrl.current.abort();
}
}, [skip]);
return [loading, authenticationsResponse];
};

View file

@ -0,0 +1,30 @@
/*
* 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 { act, renderHook } from '@testing-library/react-hooks';
import { TestProviders } from '../../../common/mock';
import { useAllHost } from './index';
import { HostsType } from '../../store/model';
describe('useAllHost', () => {
it('skip = true will cancel any running request', () => {
const abortSpy = jest.spyOn(AbortController.prototype, 'abort');
const localProps = {
startDate: '2020-07-07T08:20:18.966Z',
endDate: '2020-07-08T08:20:18.966Z',
indexNames: ['cool'],
type: HostsType.page,
skip: false,
};
const { rerender } = renderHook(() => useAllHost(localProps), {
wrapper: TestProviders,
});
localProps.skip = true;
act(() => rerender());
expect(abortSpy).toHaveBeenCalledTimes(4);
});
});

View file

@ -217,5 +217,13 @@ export const useAllHost = ({
};
}, [hostsRequest, hostsSearch]);
useEffect(() => {
if (skip) {
setLoading(false);
searchSubscription.current.unsubscribe();
abortCtrl.current.abort();
}
}, [skip]);
return [loading, hostsResponse];
};

View file

@ -0,0 +1,28 @@
/*
* 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 { act, renderHook } from '@testing-library/react-hooks';
import { TestProviders } from '../../../../common/mock';
import { useHostsKpiAuthentications } from './index';
describe('kpi hosts - authentications', () => {
it('skip = true will cancel any running request', () => {
const abortSpy = jest.spyOn(AbortController.prototype, 'abort');
const localProps = {
startDate: '2020-07-07T08:20:18.966Z',
endDate: '2020-07-08T08:20:18.966Z',
indexNames: ['cool'],
skip: false,
};
const { rerender } = renderHook(() => useHostsKpiAuthentications(localProps), {
wrapper: TestProviders,
});
localProps.skip = true;
act(() => rerender());
expect(abortSpy).toHaveBeenCalledTimes(4);
});
});

View file

@ -26,7 +26,7 @@ import * as i18n from './translations';
import { getInspectResponse } from '../../../../helpers';
import { InspectResponse } from '../../../../types';
const ID = 'hostsKpiAuthenticationsQuery';
export const ID = 'hostsKpiAuthenticationsQuery';
export interface HostsKpiAuthenticationsArgs
extends Omit<HostsKpiAuthenticationsStrategyResponse, 'rawResponse'> {
@ -165,5 +165,13 @@ export const useHostsKpiAuthentications = ({
};
}, [hostsKpiAuthenticationsRequest, hostsKpiAuthenticationsSearch]);
useEffect(() => {
if (skip) {
setLoading(false);
searchSubscription$.current.unsubscribe();
abortCtrl.current.abort();
}
}, [skip]);
return [loading, hostsKpiAuthenticationsResponse];
};

View file

@ -0,0 +1,28 @@
/*
* 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 { act, renderHook } from '@testing-library/react-hooks';
import { TestProviders } from '../../../../common/mock';
import { useHostsKpiHosts } from './index';
describe('kpi hosts - hosts', () => {
it('skip = true will cancel any running request', () => {
const abortSpy = jest.spyOn(AbortController.prototype, 'abort');
const localProps = {
startDate: '2020-07-07T08:20:18.966Z',
endDate: '2020-07-08T08:20:18.966Z',
indexNames: ['cool'],
skip: false,
};
const { rerender } = renderHook(() => useHostsKpiHosts(localProps), {
wrapper: TestProviders,
});
localProps.skip = true;
act(() => rerender());
expect(abortSpy).toHaveBeenCalledTimes(4);
});
});

View file

@ -26,7 +26,7 @@ import * as i18n from './translations';
import { getInspectResponse } from '../../../../helpers';
import { InspectResponse } from '../../../../types';
const ID = 'hostsKpiHostsQuery';
export const ID = 'hostsKpiHostsQuery';
export interface HostsKpiHostsArgs extends Omit<HostsKpiHostsStrategyResponse, 'rawResponse'> {
id: string;
@ -155,5 +155,13 @@ export const useHostsKpiHosts = ({
};
}, [hostsKpiHostsRequest, hostsKpiHostsSearch]);
useEffect(() => {
if (skip) {
setLoading(false);
searchSubscription$.current.unsubscribe();
abortCtrl.current.abort();
}
}, [skip]);
return [loading, hostsKpiHostsResponse];
};

View file

@ -1,10 +0,0 @@
/*
* 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.
*/
export * from './authentications';
export * from './hosts';
export * from './unique_ips';

View file

@ -0,0 +1,28 @@
/*
* 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 { act, renderHook } from '@testing-library/react-hooks';
import { TestProviders } from '../../../../common/mock';
import { useHostsKpiUniqueIps } from './index';
describe('kpi hosts - Unique Ips', () => {
it('skip = true will cancel any running request', () => {
const abortSpy = jest.spyOn(AbortController.prototype, 'abort');
const localProps = {
startDate: '2020-07-07T08:20:18.966Z',
endDate: '2020-07-08T08:20:18.966Z',
indexNames: ['cool'],
skip: false,
};
const { rerender } = renderHook(() => useHostsKpiUniqueIps(localProps), {
wrapper: TestProviders,
});
localProps.skip = true;
act(() => rerender());
expect(abortSpy).toHaveBeenCalledTimes(4);
});
});

View file

@ -26,7 +26,7 @@ import * as i18n from './translations';
import { getInspectResponse } from '../../../../helpers';
import { InspectResponse } from '../../../../types';
const ID = 'hostsKpiUniqueIpsQuery';
export const ID = 'hostsKpiUniqueIpsQuery';
export interface HostsKpiUniqueIpsArgs
extends Omit<HostsKpiUniqueIpsStrategyResponse, 'rawResponse'> {
@ -163,5 +163,13 @@ export const useHostsKpiUniqueIps = ({
};
}, [hostsKpiUniqueIpsRequest, hostsKpiUniqueIpsSearch]);
useEffect(() => {
if (skip) {
setLoading(false);
searchSubscription$.current.unsubscribe();
abortCtrl.current.abort();
}
}, [skip]);
return [loading, hostsKpiUniqueIpsResponse];
};

View file

@ -0,0 +1,30 @@
/*
* 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 { act, renderHook } from '@testing-library/react-hooks';
import { TestProviders } from '../../../common/mock';
import { useUncommonProcesses } from './index';
import { HostsType } from '../../store/model';
describe('useUncommonProcesses', () => {
it('skip = true will cancel any running request', () => {
const abortSpy = jest.spyOn(AbortController.prototype, 'abort');
const localProps = {
startDate: '2020-07-07T08:20:18.966Z',
endDate: '2020-07-08T08:20:18.966Z',
indexNames: ['cool'],
type: HostsType.page,
skip: false,
};
const { rerender } = renderHook(() => useUncommonProcesses(localProps), {
wrapper: TestProviders,
});
localProps.skip = true;
act(() => rerender());
expect(abortSpy).toHaveBeenCalledTimes(4);
});
});

View file

@ -34,7 +34,7 @@ import { InspectResponse } from '../../../types';
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
import { useAppToasts } from '../../../common/hooks/use_app_toasts';
const ID = 'hostsUncommonProcessesQuery';
export const ID = 'hostsUncommonProcessesQuery';
export interface UncommonProcessesArgs {
id: string;
@ -202,5 +202,13 @@ export const useUncommonProcesses = ({
};
}, [uncommonProcessesRequest, uncommonProcessesSearch]);
useEffect(() => {
if (skip) {
setLoading(false);
searchSubscription$.current.unsubscribe();
abortCtrl.current.abort();
}
}, [skip]);
return [loading, uncommonProcessesResponse];
};

View file

@ -0,0 +1,68 @@
/*
* 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 { render } from '@testing-library/react';
import { TestProviders } from '../../../common/mock';
import { useAuthentications } from '../../containers/authentications';
import { useQueryToggle } from '../../../common/containers/query_toggle';
import { AuthenticationsQueryTabBody } from './authentications_query_tab_body';
import { HostsType } from '../../store/model';
jest.mock('../../containers/authentications');
jest.mock('../../../common/containers/query_toggle');
jest.mock('../../../common/lib/kibana');
describe('Authentications query tab body', () => {
const mockUseAuthentications = useAuthentications as jest.Mock;
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const defaultProps = {
indexNames: [],
setQuery: jest.fn(),
skip: false,
startDate: '2019-06-25T04:31:59.345Z',
endDate: '2019-06-25T06:31:59.345Z',
type: HostsType.page,
};
beforeEach(() => {
jest.clearAllMocks();
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
mockUseAuthentications.mockReturnValue([
false,
{
authentications: [],
id: '123',
inspect: {
dsl: [],
response: [],
},
isInspected: false,
totalCount: 0,
pageInfo: { activePage: 1, fakeTotalCount: 100, showMorePagesIndicator: false },
loadPage: jest.fn(),
refetch: jest.fn(),
},
]);
});
it('toggleStatus=true, do not skip', () => {
render(
<TestProviders>
<AuthenticationsQueryTabBody {...defaultProps} />
</TestProviders>
);
expect(mockUseAuthentications.mock.calls[0][0].skip).toEqual(false);
});
it('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() });
render(
<TestProviders>
<AuthenticationsQueryTabBody {...defaultProps} />
</TestProviders>
);
expect(mockUseAuthentications.mock.calls[0][0].skip).toEqual(true);
});
});

View file

@ -6,7 +6,7 @@
*/
import { getOr } from 'lodash/fp';
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import { AuthenticationTable } from '../../components/authentications_table';
import { manageQuery } from '../../../common/components/page/manage_query';
import { useAuthentications } from '../../containers/authentications';
@ -22,6 +22,7 @@ import * as i18n from '../translations';
import { MatrixHistogramType } from '../../../../common/search_strategy/security_solution';
import { authenticationLensAttributes } from '../../../common/components/visualization_actions/lens_attributes/hosts/authentication';
import { LensAttributes } from '../../../common/components/visualization_actions/types';
import { useQueryToggle } from '../../../common/containers/query_toggle';
const AuthenticationTableManage = manageQuery(AuthenticationTable);
@ -76,6 +77,11 @@ const AuthenticationsQueryTabBodyComponent: React.FC<HostsComponentsQueryProps>
startDate,
type,
}) => {
const { toggleStatus } = useQueryToggle(ID);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(skip || !toggleStatus);
}, [skip, toggleStatus]);
const [
loading,
{ authentications, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch },
@ -84,7 +90,7 @@ const AuthenticationsQueryTabBodyComponent: React.FC<HostsComponentsQueryProps>
endDate,
filterQuery,
indexNames,
skip,
skip: querySkip,
startDate,
type,
});
@ -119,6 +125,7 @@ const AuthenticationsQueryTabBodyComponent: React.FC<HostsComponentsQueryProps>
loading={loading}
loadPage={loadPage}
refetch={refetch}
setQuerySkip={setQuerySkip}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', pageInfo)}
setQuery={setQuery}
totalCount={totalCount}

View file

@ -0,0 +1,81 @@
/*
* 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 { render } from '@testing-library/react';
import { TestProviders } from '../../../common/mock';
import { useHostRiskScore, useHostRiskScoreKpi } from '../../../risk_score/containers';
import { useQueryToggle } from '../../../common/containers/query_toggle';
import { HostRiskScoreQueryTabBody } from './host_risk_score_tab_body';
import { HostsType } from '../../store/model';
jest.mock('../../../risk_score/containers');
jest.mock('../../../common/containers/query_toggle');
jest.mock('../../../common/lib/kibana');
describe('Host risk score query tab body', () => {
const mockUseHostRiskScore = useHostRiskScore as jest.Mock;
const mockUseHostRiskScoreKpi = useHostRiskScoreKpi as jest.Mock;
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const defaultProps = {
indexNames: [],
setQuery: jest.fn(),
skip: false,
startDate: '2019-06-25T04:31:59.345Z',
endDate: '2019-06-25T06:31:59.345Z',
type: HostsType.page,
};
beforeEach(() => {
jest.clearAllMocks();
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
mockUseHostRiskScoreKpi.mockReturnValue({
loading: false,
severityCount: {
unknown: 12,
low: 12,
moderate: 12,
high: 12,
critical: 12,
},
});
mockUseHostRiskScore.mockReturnValue([
false,
{
hosts: [],
id: '123',
inspect: {
dsl: [],
response: [],
},
isInspected: false,
totalCount: 0,
pageInfo: { activePage: 1, fakeTotalCount: 100, showMorePagesIndicator: false },
loadPage: jest.fn(),
refetch: jest.fn(),
},
]);
});
it('toggleStatus=true, do not skip', () => {
render(
<TestProviders>
<HostRiskScoreQueryTabBody {...defaultProps} />
</TestProviders>
);
expect(mockUseHostRiskScore.mock.calls[0][0].skip).toEqual(false);
expect(mockUseHostRiskScoreKpi.mock.calls[0][0].skip).toEqual(false);
});
it('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() });
render(
<TestProviders>
<HostRiskScoreQueryTabBody {...defaultProps} />
</TestProviders>
);
expect(mockUseHostRiskScore.mock.calls[0][0].skip).toEqual(true);
expect(mockUseHostRiskScoreKpi.mock.calls[0][0].skip).toEqual(true);
});
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useMemo } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { noop } from 'lodash/fp';
import { HostsComponentsQueryProps } from './types';
import { manageQuery } from '../../../common/components/page/manage_query';
@ -18,6 +18,7 @@ import {
useHostRiskScore,
useHostRiskScoreKpi,
} from '../../../risk_score/containers';
import { useQueryToggle } from '../../../common/containers/query_toggle';
const HostRiskScoreTableManage = manageQuery(HostRiskScoreTable);
@ -43,15 +44,22 @@ export const HostRiskScoreQueryTabBody = ({
[activePage, limit]
);
const { toggleStatus } = useQueryToggle(HostRiskScoreQueryId.HOSTS_BY_RISK);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(!toggleStatus);
}, [toggleStatus]);
const [loading, { data, totalCount, inspect, isInspected, refetch }] = useHostRiskScore({
filterQuery,
skip,
skip: querySkip,
pagination,
sort,
});
const { severityCount, loading: isKpiLoading } = useHostRiskScoreKpi({
filterQuery,
skip: querySkip,
});
return (
@ -65,6 +73,7 @@ export const HostRiskScoreQueryTabBody = ({
loadPage={noop} // It isn't necessary because PaginatedTable updates redux store and we load the page when activePage updates on the store
refetch={refetch}
setQuery={setQuery}
setQuerySkip={setQuerySkip}
severityCount={severityCount}
totalCount={totalCount}
type={type}

View file

@ -0,0 +1,68 @@
/*
* 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 { render } from '@testing-library/react';
import { TestProviders } from '../../../common/mock';
import { useAllHost } from '../../containers/hosts';
import { useQueryToggle } from '../../../common/containers/query_toggle';
import { HostsQueryTabBody } from './hosts_query_tab_body';
import { HostsType } from '../../store/model';
jest.mock('../../containers/hosts');
jest.mock('../../../common/containers/query_toggle');
jest.mock('../../../common/lib/kibana');
describe('Hosts query tab body', () => {
const mockUseAllHost = useAllHost as jest.Mock;
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const defaultProps = {
indexNames: [],
setQuery: jest.fn(),
skip: false,
startDate: '2019-06-25T04:31:59.345Z',
endDate: '2019-06-25T06:31:59.345Z',
type: HostsType.page,
};
beforeEach(() => {
jest.clearAllMocks();
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
mockUseAllHost.mockReturnValue([
false,
{
hosts: [],
id: '123',
inspect: {
dsl: [],
response: [],
},
isInspected: false,
totalCount: 0,
pageInfo: { activePage: 1, fakeTotalCount: 100, showMorePagesIndicator: false },
loadPage: jest.fn(),
refetch: jest.fn(),
},
]);
});
it('toggleStatus=true, do not skip', () => {
render(
<TestProviders>
<HostsQueryTabBody {...defaultProps} />
</TestProviders>
);
expect(mockUseAllHost.mock.calls[0][0].skip).toEqual(false);
});
it('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() });
render(
<TestProviders>
<HostsQueryTabBody {...defaultProps} />
</TestProviders>
);
expect(mockUseAllHost.mock.calls[0][0].skip).toEqual(true);
});
});

View file

@ -6,11 +6,12 @@
*/
import { getOr } from 'lodash/fp';
import React from 'react';
import { useAllHost } from '../../containers/hosts';
import React, { useEffect, useState } from 'react';
import { useAllHost, ID } from '../../containers/hosts';
import { HostsComponentsQueryProps } from './types';
import { HostsTable } from '../../components/hosts_table';
import { manageQuery } from '../../../common/components/page/manage_query';
import { useQueryToggle } from '../../../common/containers/query_toggle';
const HostsTableManage = manageQuery(HostsTable);
@ -25,8 +26,21 @@ export const HostsQueryTabBody = ({
startDate,
type,
}: HostsComponentsQueryProps) => {
const { toggleStatus } = useQueryToggle(ID);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(skip || !toggleStatus);
}, [skip, toggleStatus]);
const [loading, { hosts, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch }] =
useAllHost({ docValueFields, endDate, filterQuery, indexNames, skip, startDate, type });
useAllHost({
docValueFields,
endDate,
filterQuery,
indexNames,
skip: querySkip,
startDate,
type,
});
return (
<HostsTableManage
@ -40,6 +54,7 @@ export const HostsQueryTabBody = ({
loadPage={loadPage}
refetch={refetch}
setQuery={setQuery}
setQuerySkip={setQuerySkip}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', pageInfo)}
totalCount={totalCount}
type={type}

View file

@ -0,0 +1,68 @@
/*
* 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 { render } from '@testing-library/react';
import { TestProviders } from '../../../common/mock';
import { useUncommonProcesses } from '../../containers/uncommon_processes';
import { useQueryToggle } from '../../../common/containers/query_toggle';
import { UncommonProcessQueryTabBody } from './uncommon_process_query_tab_body';
import { HostsType } from '../../store/model';
jest.mock('../../containers/uncommon_processes');
jest.mock('../../../common/containers/query_toggle');
jest.mock('../../../common/lib/kibana');
describe('Uncommon process query tab body', () => {
const mockUseUncommonProcesses = useUncommonProcesses as jest.Mock;
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const defaultProps = {
indexNames: [],
setQuery: jest.fn(),
skip: false,
startDate: '2019-06-25T04:31:59.345Z',
endDate: '2019-06-25T06:31:59.345Z',
type: HostsType.page,
};
beforeEach(() => {
jest.clearAllMocks();
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
mockUseUncommonProcesses.mockReturnValue([
false,
{
uncommonProcesses: [],
id: '123',
inspect: {
dsl: [],
response: [],
},
isInspected: false,
totalCount: 0,
pageInfo: { activePage: 1, fakeTotalCount: 100, showMorePagesIndicator: false },
loadPage: jest.fn(),
refetch: jest.fn(),
},
]);
});
it('toggleStatus=true, do not skip', () => {
render(
<TestProviders>
<UncommonProcessQueryTabBody {...defaultProps} />
</TestProviders>
);
expect(mockUseUncommonProcesses.mock.calls[0][0].skip).toEqual(false);
});
it('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() });
render(
<TestProviders>
<UncommonProcessQueryTabBody {...defaultProps} />
</TestProviders>
);
expect(mockUseUncommonProcesses.mock.calls[0][0].skip).toEqual(true);
});
});

View file

@ -6,11 +6,12 @@
*/
import { getOr } from 'lodash/fp';
import React from 'react';
import { useUncommonProcesses } from '../../containers/uncommon_processes';
import React, { useEffect, useState } from 'react';
import { useUncommonProcesses, ID } from '../../containers/uncommon_processes';
import { HostsComponentsQueryProps } from './types';
import { UncommonProcessTable } from '../../components/uncommon_process_table';
import { manageQuery } from '../../../common/components/page/manage_query';
import { useQueryToggle } from '../../../common/containers/query_toggle';
const UncommonProcessTableManage = manageQuery(UncommonProcessTable);
@ -25,6 +26,11 @@ export const UncommonProcessQueryTabBody = ({
startDate,
type,
}: HostsComponentsQueryProps) => {
const { toggleStatus } = useQueryToggle(ID);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(skip || !toggleStatus);
}, [skip, toggleStatus]);
const [
loading,
{ uncommonProcesses, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch },
@ -33,7 +39,7 @@ export const UncommonProcessQueryTabBody = ({
endDate,
filterQuery,
indexNames,
skip,
skip: querySkip,
startDate,
type,
});
@ -49,6 +55,7 @@ export const UncommonProcessQueryTabBody = ({
loadPage={loadPage}
refetch={refetch}
setQuery={setQuery}
setQuerySkip={setQuerySkip}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', pageInfo)}
totalCount={totalCount}
type={type}

View file

@ -3,6 +3,7 @@
exports[`Embeddable it renders 1`] = `
<section
className="siemEmbeddable"
data-test-subj="siemEmbeddable"
>
<Panel
hasBorder={true}

View file

@ -19,7 +19,7 @@ export interface EmbeddableProps {
}
export const Embeddable = React.memo<EmbeddableProps>(({ children }) => (
<section className="siemEmbeddable">
<section className="siemEmbeddable" data-test-subj="siemEmbeddable">
<Panel paddingSize="none" hasBorder>
{children}
</Panel>

View file

@ -109,7 +109,7 @@ describe('EmbeddedMapComponent', () => {
beforeEach(() => {
setQuery.mockClear();
mockGetStorage.mockReturnValue(false);
mockGetStorage.mockReturnValue(true);
});
afterEach(() => {
@ -190,36 +190,40 @@ describe('EmbeddedMapComponent', () => {
});
test('map hidden on close', async () => {
mockGetStorage.mockReturnValue(false);
const wrapper = mount(
<TestProviders>
<EmbeddedMapComponent {...testProps} />
</TestProviders>
);
expect(wrapper.find('[data-test-subj="siemEmbeddable"]').first().exists()).toEqual(false);
const container = wrapper.find('[data-test-subj="false-toggle-network-map"]').at(0);
container.simulate('click');
await waitFor(() => {
wrapper.update();
expect(mockSetStorage).toHaveBeenNthCalledWith(1, 'network_map_visbile', true);
expect(wrapper.find('[data-test-subj="siemEmbeddable"]').first().exists()).toEqual(true);
});
});
test('map visible on open', async () => {
mockGetStorage.mockReturnValue(true);
const wrapper = mount(
<TestProviders>
<EmbeddedMapComponent {...testProps} />
</TestProviders>
);
expect(wrapper.find('[data-test-subj="siemEmbeddable"]').first().exists()).toEqual(true);
const container = wrapper.find('[data-test-subj="true-toggle-network-map"]').at(0);
container.simulate('click');
await waitFor(() => {
wrapper.update();
expect(mockSetStorage).toHaveBeenNthCalledWith(1, 'network_map_visbile', false);
expect(wrapper.find('[data-test-subj="siemEmbeddable"]').first().exists()).toEqual(false);
});
});
});

View file

@ -245,6 +245,29 @@ export const EmbeddedMapComponent = ({
[storage]
);
const content = useMemo(() => {
if (!storageValue) {
return null;
}
return (
<Embeddable>
<InPortal node={portalNode}>
<MapToolTip />
</InPortal>
<EmbeddableMap maintainRatio={!isIndexError}>
{isIndexError ? (
<IndexPatternsMissingPrompt data-test-subj="missing-prompt" />
) : embeddable != null ? (
<services.embeddable.EmbeddablePanel embeddable={embeddable} />
) : (
<Loader data-test-subj="loading-panel" overlay size="xl" />
)}
</EmbeddableMap>
</Embeddable>
);
}, [embeddable, isIndexError, portalNode, services, storageValue]);
return isError ? null : (
<StyledEuiAccordion
onToggle={setDefaultMapVisibility}
@ -265,21 +288,7 @@ export const EmbeddedMapComponent = ({
paddingSize="none"
initialIsOpen={storageValue}
>
<Embeddable>
<InPortal node={portalNode}>
<MapToolTip />
</InPortal>
<EmbeddableMap maintainRatio={!isIndexError}>
{isIndexError ? (
<IndexPatternsMissingPrompt data-test-subj="missing-prompt" />
) : embeddable != null ? (
<services.embeddable.EmbeddablePanel embeddable={embeddable} />
) : (
<Loader data-test-subj="loading-panel" overlay size="xl" />
)}
</EmbeddableMap>
</Embeddable>
{content}
</StyledEuiAccordion>
);
};

View file

@ -0,0 +1,66 @@
/*
* 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 { useNetworkKpiDns } from '../../../containers/kpi_network/dns';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
import { render } from '@testing-library/react';
import { TestProviders } from '../../../../common/mock';
import React from 'react';
import { NetworkKpiDns } from './index';
jest.mock('../../../../common/containers/query_toggle');
jest.mock('../../../containers/kpi_network/dns');
jest.mock('../../../../hosts/components/kpi_hosts/common', () => ({
KpiBaseComponentManage: () => <span data-test-subj="KpiBaseComponentManage" />,
}));
describe('DNS KPI', () => {
const mockUseNetworkKpiDns = useNetworkKpiDns as jest.Mock;
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const defaultProps = {
from: '2019-06-25T04:31:59.345Z',
to: '2019-06-25T06:31:59.345Z',
indexNames: [],
narrowDateRange: jest.fn(),
setQuery: jest.fn(),
skip: false,
};
beforeEach(() => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
mockUseNetworkKpiDns.mockReturnValue([
false,
{
id: '123',
inspect: {
dsl: [],
response: [],
},
refetch: jest.fn(),
},
]);
});
afterEach(() => {
jest.clearAllMocks();
});
it('toggleStatus=true, do not skip', () => {
render(
<TestProviders>
<NetworkKpiDns {...defaultProps} />
</TestProviders>
);
expect(mockUseNetworkKpiDns.mock.calls[0][0].skip).toEqual(false);
});
it('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() });
render(
<TestProviders>
<NetworkKpiDns {...defaultProps} />
</TestProviders>
);
expect(mockUseNetworkKpiDns.mock.calls[0][0].skip).toEqual(true);
});
});

View file

@ -5,15 +5,16 @@
* 2.0.
*/
import React from 'react';
import React, { useEffect, useState } from 'react';
import { StatItems } from '../../../../common/components/stat_items';
import { kpiDnsQueriesLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/network/kpi_dns_queries';
import { useNetworkKpiDns, ID } from '../../../containers/kpi_network/dns';
import { KpiBaseComponentManage } from '../../../../hosts/components/kpi_hosts/common';
import { useNetworkKpiDns } from '../../../containers/kpi_network/dns';
import { NetworkKpiProps } from '../types';
import * as i18n from './translations';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
export const fieldsMapping: Readonly<StatItems[]> = [
{
@ -38,12 +39,17 @@ const NetworkKpiDnsComponent: React.FC<NetworkKpiProps> = ({
setQuery,
skip,
}) => {
const { toggleStatus } = useQueryToggle(ID);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(skip || !toggleStatus);
}, [skip, toggleStatus]);
const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiDns({
filterQuery,
endDate: to,
indexNames,
startDate: from,
skip,
skip: querySkip,
});
return (
@ -58,6 +64,7 @@ const NetworkKpiDnsComponent: React.FC<NetworkKpiProps> = ({
narrowDateRange={narrowDateRange}
refetch={refetch}
setQuery={setQuery}
setQuerySkip={setQuerySkip}
/>
);
};

View file

@ -227,7 +227,9 @@ export const mockEnableChartsData = {
],
from: '2019-06-15T06:00:00.000Z',
id: 'statItem',
loading: false,
statKey: 'UniqueIps',
setQuerySkip: jest.fn(),
to: '2019-06-18T06:00:00.000Z',
narrowDateRange: mockNarrowDateRange,
areaChartLensAttributes: kpiUniquePrivateIpsAreaLensAttributes,

View file

@ -0,0 +1,66 @@
/*
* 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 { useNetworkKpiNetworkEvents } from '../../../containers/kpi_network/network_events';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
import { render } from '@testing-library/react';
import { TestProviders } from '../../../../common/mock';
import React from 'react';
import { NetworkKpiNetworkEvents } from './index';
jest.mock('../../../../common/containers/query_toggle');
jest.mock('../../../containers/kpi_network/network_events');
jest.mock('../../../../hosts/components/kpi_hosts/common', () => ({
KpiBaseComponentManage: () => <span data-test-subj="KpiBaseComponentManage" />,
}));
describe('Network Events KPI', () => {
const mockUseNetworkKpiNetworkEvents = useNetworkKpiNetworkEvents as jest.Mock;
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const defaultProps = {
from: '2019-06-25T04:31:59.345Z',
to: '2019-06-25T06:31:59.345Z',
indexNames: [],
narrowDateRange: jest.fn(),
setQuery: jest.fn(),
skip: false,
};
beforeEach(() => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
mockUseNetworkKpiNetworkEvents.mockReturnValue([
false,
{
id: '123',
inspect: {
dsl: [],
response: [],
},
refetch: jest.fn(),
},
]);
});
afterEach(() => {
jest.clearAllMocks();
});
it('toggleStatus=true, do not skip', () => {
render(
<TestProviders>
<NetworkKpiNetworkEvents {...defaultProps} />
</TestProviders>
);
expect(mockUseNetworkKpiNetworkEvents.mock.calls[0][0].skip).toEqual(false);
});
it('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() });
render(
<TestProviders>
<NetworkKpiNetworkEvents {...defaultProps} />
</TestProviders>
);
expect(mockUseNetworkKpiNetworkEvents.mock.calls[0][0].skip).toEqual(true);
});
});

View file

@ -5,16 +5,16 @@
* 2.0.
*/
import React from 'react';
import React, { useEffect, useState } from 'react';
import { euiPaletteColorBlind } from '@elastic/eui';
import { StatItems } from '../../../../common/components/stat_items';
import { useNetworkKpiNetworkEvents } from '../../../containers/kpi_network/network_events';
import { ID, useNetworkKpiNetworkEvents } from '../../../containers/kpi_network/network_events';
import { NetworkKpiProps } from '../types';
import * as i18n from './translations';
import { kpiNetworkEventsLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/network/kpi_network_events';
import { KpiBaseComponentManage } from '../../../../hosts/components/kpi_hosts/common';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
const euiVisColorPalette = euiPaletteColorBlind();
const euiColorVis1 = euiVisColorPalette[1];
@ -43,12 +43,17 @@ const NetworkKpiNetworkEventsComponent: React.FC<NetworkKpiProps> = ({
setQuery,
skip,
}) => {
const { toggleStatus } = useQueryToggle(ID);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(skip || !toggleStatus);
}, [skip, toggleStatus]);
const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiNetworkEvents({
filterQuery,
endDate: to,
indexNames,
startDate: from,
skip,
skip: querySkip,
});
return (
@ -63,6 +68,7 @@ const NetworkKpiNetworkEventsComponent: React.FC<NetworkKpiProps> = ({
narrowDateRange={narrowDateRange}
refetch={refetch}
setQuery={setQuery}
setQuerySkip={setQuerySkip}
/>
);
};

View file

@ -0,0 +1,66 @@
/*
* 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 { useNetworkKpiTlsHandshakes } from '../../../containers/kpi_network/tls_handshakes';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
import { render } from '@testing-library/react';
import { TestProviders } from '../../../../common/mock';
import React from 'react';
import { NetworkKpiTlsHandshakes } from './index';
jest.mock('../../../../common/containers/query_toggle');
jest.mock('../../../containers/kpi_network/tls_handshakes');
jest.mock('../../../../hosts/components/kpi_hosts/common', () => ({
KpiBaseComponentManage: () => <span data-test-subj="KpiBaseComponentManage" />,
}));
describe('TLS Handshakes KPI', () => {
const mockUseNetworkKpiTlsHandshakes = useNetworkKpiTlsHandshakes as jest.Mock;
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const defaultProps = {
from: '2019-06-25T04:31:59.345Z',
to: '2019-06-25T06:31:59.345Z',
indexNames: [],
narrowDateRange: jest.fn(),
setQuery: jest.fn(),
skip: false,
};
beforeEach(() => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
mockUseNetworkKpiTlsHandshakes.mockReturnValue([
false,
{
id: '123',
inspect: {
dsl: [],
response: [],
},
refetch: jest.fn(),
},
]);
});
afterEach(() => {
jest.clearAllMocks();
});
it('toggleStatus=true, do not skip', () => {
render(
<TestProviders>
<NetworkKpiTlsHandshakes {...defaultProps} />
</TestProviders>
);
expect(mockUseNetworkKpiTlsHandshakes.mock.calls[0][0].skip).toEqual(false);
});
it('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() });
render(
<TestProviders>
<NetworkKpiTlsHandshakes {...defaultProps} />
</TestProviders>
);
expect(mockUseNetworkKpiTlsHandshakes.mock.calls[0][0].skip).toEqual(true);
});
});

View file

@ -5,14 +5,15 @@
* 2.0.
*/
import React from 'react';
import React, { useEffect, useState } from 'react';
import { StatItems } from '../../../../common/components/stat_items';
import { kpiTlsHandshakesLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes';
import { useNetworkKpiTlsHandshakes, ID } from '../../../containers/kpi_network/tls_handshakes';
import { KpiBaseComponentManage } from '../../../../hosts/components/kpi_hosts/common';
import { useNetworkKpiTlsHandshakes } from '../../../containers/kpi_network/tls_handshakes';
import { NetworkKpiProps } from '../types';
import * as i18n from './translations';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
export const fieldsMapping: Readonly<StatItems[]> = [
{
@ -37,12 +38,17 @@ const NetworkKpiTlsHandshakesComponent: React.FC<NetworkKpiProps> = ({
setQuery,
skip,
}) => {
const { toggleStatus } = useQueryToggle(ID);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(skip || !toggleStatus);
}, [skip, toggleStatus]);
const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiTlsHandshakes({
filterQuery,
endDate: to,
indexNames,
startDate: from,
skip,
skip: querySkip,
});
return (
@ -57,6 +63,7 @@ const NetworkKpiTlsHandshakesComponent: React.FC<NetworkKpiProps> = ({
narrowDateRange={narrowDateRange}
refetch={refetch}
setQuery={setQuery}
setQuerySkip={setQuerySkip}
/>
);
};

View file

@ -0,0 +1,66 @@
/*
* 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 { useNetworkKpiUniqueFlows } from '../../../containers/kpi_network/unique_flows';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
import { render } from '@testing-library/react';
import { TestProviders } from '../../../../common/mock';
import React from 'react';
import { NetworkKpiUniqueFlows } from './index';
jest.mock('../../../../common/containers/query_toggle');
jest.mock('../../../containers/kpi_network/unique_flows');
jest.mock('../../../../hosts/components/kpi_hosts/common', () => ({
KpiBaseComponentManage: () => <span data-test-subj="KpiBaseComponentManage" />,
}));
describe('Unique Flows KPI', () => {
const mockUseNetworkKpiUniqueFlows = useNetworkKpiUniqueFlows as jest.Mock;
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const defaultProps = {
from: '2019-06-25T04:31:59.345Z',
to: '2019-06-25T06:31:59.345Z',
indexNames: [],
narrowDateRange: jest.fn(),
setQuery: jest.fn(),
skip: false,
};
beforeEach(() => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
mockUseNetworkKpiUniqueFlows.mockReturnValue([
false,
{
id: '123',
inspect: {
dsl: [],
response: [],
},
refetch: jest.fn(),
},
]);
});
afterEach(() => {
jest.clearAllMocks();
});
it('toggleStatus=true, do not skip', () => {
render(
<TestProviders>
<NetworkKpiUniqueFlows {...defaultProps} />
</TestProviders>
);
expect(mockUseNetworkKpiUniqueFlows.mock.calls[0][0].skip).toEqual(false);
});
it('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() });
render(
<TestProviders>
<NetworkKpiUniqueFlows {...defaultProps} />
</TestProviders>
);
expect(mockUseNetworkKpiUniqueFlows.mock.calls[0][0].skip).toEqual(true);
});
});

View file

@ -5,14 +5,15 @@
* 2.0.
*/
import React from 'react';
import React, { useState, useEffect } from 'react';
import { StatItems } from '../../../../common/components/stat_items';
import { kpiUniqueFlowIdsLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids';
import { useNetworkKpiUniqueFlows, ID } from '../../../containers/kpi_network/unique_flows';
import { KpiBaseComponentManage } from '../../../../hosts/components/kpi_hosts/common';
import { useNetworkKpiUniqueFlows } from '../../../containers/kpi_network/unique_flows';
import { NetworkKpiProps } from '../types';
import * as i18n from './translations';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
export const fieldsMapping: Readonly<StatItems[]> = [
{
@ -37,12 +38,17 @@ const NetworkKpiUniqueFlowsComponent: React.FC<NetworkKpiProps> = ({
setQuery,
skip,
}) => {
const { toggleStatus } = useQueryToggle(ID);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(skip || !toggleStatus);
}, [skip, toggleStatus]);
const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiUniqueFlows({
filterQuery,
endDate: to,
indexNames,
startDate: from,
skip,
skip: querySkip,
});
return (
@ -57,6 +63,7 @@ const NetworkKpiUniqueFlowsComponent: React.FC<NetworkKpiProps> = ({
narrowDateRange={narrowDateRange}
refetch={refetch}
setQuery={setQuery}
setQuerySkip={setQuerySkip}
/>
);
};

View file

@ -0,0 +1,66 @@
/*
* 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 { useNetworkKpiUniquePrivateIps } from '../../../containers/kpi_network/unique_private_ips';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
import { render } from '@testing-library/react';
import { TestProviders } from '../../../../common/mock';
import React from 'react';
import { NetworkKpiUniquePrivateIps } from './index';
jest.mock('../../../../common/containers/query_toggle');
jest.mock('../../../containers/kpi_network/unique_private_ips');
jest.mock('../../../../hosts/components/kpi_hosts/common', () => ({
KpiBaseComponentManage: () => <span data-test-subj="KpiBaseComponentManage" />,
}));
describe('Unique Private IPs KPI', () => {
const mockUseNetworkKpiUniquePrivateIps = useNetworkKpiUniquePrivateIps as jest.Mock;
const mockUseQueryToggle = useQueryToggle as jest.Mock;
const defaultProps = {
from: '2019-06-25T04:31:59.345Z',
to: '2019-06-25T06:31:59.345Z',
indexNames: [],
narrowDateRange: jest.fn(),
setQuery: jest.fn(),
skip: false,
};
beforeEach(() => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
mockUseNetworkKpiUniquePrivateIps.mockReturnValue([
false,
{
id: '123',
inspect: {
dsl: [],
response: [],
},
refetch: jest.fn(),
},
]);
});
afterEach(() => {
jest.clearAllMocks();
});
it('toggleStatus=true, do not skip', () => {
render(
<TestProviders>
<NetworkKpiUniquePrivateIps {...defaultProps} />
</TestProviders>
);
expect(mockUseNetworkKpiUniquePrivateIps.mock.calls[0][0].skip).toEqual(false);
});
it('toggleStatus=false, skip', () => {
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() });
render(
<TestProviders>
<NetworkKpiUniquePrivateIps {...defaultProps} />
</TestProviders>
);
expect(mockUseNetworkKpiUniquePrivateIps.mock.calls[0][0].skip).toEqual(true);
});
});

View file

@ -5,11 +5,14 @@
* 2.0.
*/
import React from 'react';
import React, { useEffect, useState } from 'react';
import { euiPaletteColorBlind } from '@elastic/eui';
import { StatItems } from '../../../../common/components/stat_items';
import { useNetworkKpiUniquePrivateIps } from '../../../containers/kpi_network/unique_private_ips';
import {
useNetworkKpiUniquePrivateIps,
ID,
} from '../../../containers/kpi_network/unique_private_ips';
import { NetworkKpiProps } from '../types';
import * as i18n from './translations';
import { kpiUniquePrivateIpsSourceMetricLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric';
@ -17,6 +20,7 @@ import { kpiUniquePrivateIpsDestinationMetricLensAttributes } from '../../../../
import { kpiUniquePrivateIpsAreaLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area';
import { kpiUniquePrivateIpsBarLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar';
import { KpiBaseComponentManage } from '../../../../hosts/components/kpi_hosts/common';
import { useQueryToggle } from '../../../../common/containers/query_toggle';
const euiVisColorPalette = euiPaletteColorBlind();
const euiColorVis2 = euiVisColorPalette[2];
@ -62,12 +66,17 @@ const NetworkKpiUniquePrivateIpsComponent: React.FC<NetworkKpiProps> = ({
setQuery,
skip,
}) => {
const { toggleStatus } = useQueryToggle(ID);
const [querySkip, setQuerySkip] = useState(skip || !toggleStatus);
useEffect(() => {
setQuerySkip(skip || !toggleStatus);
}, [skip, toggleStatus]);
const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiUniquePrivateIps({
filterQuery,
endDate: to,
indexNames,
startDate: from,
skip,
skip: querySkip,
});
return (
@ -82,6 +91,7 @@ const NetworkKpiUniquePrivateIpsComponent: React.FC<NetworkKpiProps> = ({
narrowDateRange={narrowDateRange}
refetch={refetch}
setQuery={setQuery}
setQuerySkip={setQuerySkip}
/>
);
};

View file

@ -141,6 +141,7 @@ exports[`NetworkTopNFlow Table Component rendering it renders the default Networ
isInspect={false}
loadPage={[MockFunction]}
loading={false}
setQuerySkip={[MockFunction]}
showMorePagesIndicator={true}
totalCount={80}
type="page"

View file

@ -34,6 +34,19 @@ describe('NetworkTopNFlow Table Component', () => {
let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const mount = useMountAppended();
const defaultProps = {
data: mockData.edges,
fakeTotalCount: getOr(50, 'fakeTotalCount', mockData.pageInfo),
id: 'dns',
isInspect: false,
loading: false,
loadPage,
setQuerySkip: jest.fn(),
showMorePagesIndicator: getOr(false, 'showMorePagesIndicator', mockData.pageInfo),
totalCount: mockData.totalCount,
type: networkModel.NetworkType.page,
};
beforeEach(() => {
store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
});
@ -42,17 +55,7 @@ describe('NetworkTopNFlow Table Component', () => {
test('it renders the default NetworkTopNFlow table', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<NetworkDnsTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
id="dns"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={networkModel.NetworkType.page}
/>
<NetworkDnsTable {...defaultProps} />
</ReduxStoreProvider>
);
@ -64,17 +67,7 @@ describe('NetworkTopNFlow Table Component', () => {
test('when you click on the column header, you should show the sorting icon', () => {
const wrapper = mount(
<TestProviders store={store}>
<NetworkDnsTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
id="dns"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={networkModel.NetworkType.page}
/>
<NetworkDnsTable {...defaultProps} />
</TestProviders>
);

View file

@ -32,6 +32,7 @@ interface NetworkDnsTableProps {
isInspect: boolean;
loading: boolean;
loadPage: (newActivePage: number) => void;
setQuerySkip: (skip: boolean) => void;
showMorePagesIndicator: boolean;
totalCount: number;
type: networkModel.NetworkType;
@ -56,6 +57,7 @@ const NetworkDnsTableComponent: React.FC<NetworkDnsTableProps> = ({
loading,
loadPage,
showMorePagesIndicator,
setQuerySkip,
totalCount,
type,
}) => {
@ -153,6 +155,7 @@ const NetworkDnsTableComponent: React.FC<NetworkDnsTableProps> = ({
loadPage={loadPage}
onChange={onChange}
pageOfItems={data}
setQuerySkip={setQuerySkip}
showMorePagesIndicator={showMorePagesIndicator}
sorting={sorting}
totalCount={fakeTotalCount}

View file

@ -95,6 +95,7 @@ exports[`NetworkHttp Table Component rendering it renders the default NetworkHtt
isInspect={false}
loadPage={[MockFunction]}
loading={false}
setQuerySkip={[MockFunction]}
showMorePagesIndicator={false}
totalCount={4}
type="page"

View file

@ -31,6 +31,18 @@ jest.mock('../../../common/components/link_to');
describe('NetworkHttp Table Component', () => {
const loadPage = jest.fn();
const state: State = mockGlobalState;
const defaultProps = {
data: mockData.edges,
fakeTotalCount: getOr(50, 'fakeTotalCount', mockData.pageInfo),
id: 'http',
isInspect: false,
loading: false,
loadPage,
setQuerySkip: jest.fn(),
showMorePagesIndicator: getOr(false, 'showMorePagesIndicator', mockData.pageInfo),
totalCount: mockData.totalCount,
type: networkModel.NetworkType.page,
};
const { storage } = createSecuritySolutionStorageMock();
let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
@ -44,17 +56,7 @@ describe('NetworkHttp Table Component', () => {
test('it renders the default NetworkHttp table', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<NetworkHttpTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
id="http"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={networkModel.NetworkType.page}
/>
<NetworkHttpTable {...defaultProps} />
</ReduxStoreProvider>
);
@ -66,17 +68,7 @@ describe('NetworkHttp Table Component', () => {
test('when you click on the column header, you should show the sorting icon', () => {
const wrapper = mount(
<TestProviders store={store}>
<NetworkHttpTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
id="http"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={networkModel.NetworkType.page}
/>
<NetworkHttpTable {...defaultProps} />
</TestProviders>
);

View file

@ -23,6 +23,7 @@ interface NetworkHttpTableProps {
isInspect: boolean;
loading: boolean;
loadPage: (newActivePage: number) => void;
setQuerySkip: (skip: boolean) => void;
showMorePagesIndicator: boolean;
totalCount: number;
type: networkModel.NetworkType;
@ -46,6 +47,7 @@ const NetworkHttpTableComponent: React.FC<NetworkHttpTableProps> = ({
isInspect,
loading,
loadPage,
setQuerySkip,
showMorePagesIndicator,
totalCount,
type,
@ -123,6 +125,7 @@ const NetworkHttpTableComponent: React.FC<NetworkHttpTableProps> = ({
loadPage={loadPage}
onChange={onChange}
pageOfItems={data}
setQuerySkip={setQuerySkip}
showMorePagesIndicator={showMorePagesIndicator}
sorting={sorting}
totalCount={fakeTotalCount}

View file

@ -151,6 +151,7 @@ exports[`NetworkTopCountries Table Component rendering it renders the IP Details
isInspect={false}
loadPage={[MockFunction]}
loading={false}
setQuerySkip={[MockFunction]}
showMorePagesIndicator={true}
totalCount={524}
type="details"
@ -308,6 +309,7 @@ exports[`NetworkTopCountries Table Component rendering it renders the default Ne
isInspect={false}
loadPage={[MockFunction]}
loading={false}
setQuerySkip={[MockFunction]}
showMorePagesIndicator={true}
totalCount={524}
type="page"

View file

@ -33,6 +33,24 @@ describe('NetworkTopCountries Table Component', () => {
const loadPage = jest.fn();
const state: State = mockGlobalState;
const mount = useMountAppended();
const defaultProps = {
data: mockData.NetworkTopCountries.edges,
fakeTotalCount: getOr(50, 'fakeTotalCount', mockData.NetworkTopCountries.pageInfo),
flowTargeted: FlowTargetSourceDest.source,
id: 'topCountriesSource',
indexPattern: mockIndexPattern,
isInspect: false,
loading: false,
loadPage,
setQuerySkip: jest.fn(),
showMorePagesIndicator: getOr(
false,
'showMorePagesIndicator',
mockData.NetworkTopCountries.pageInfo
),
totalCount: mockData.NetworkTopCountries.totalCount,
type: networkModel.NetworkType.page,
};
const { storage } = createSecuritySolutionStorageMock();
let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
@ -45,23 +63,7 @@ describe('NetworkTopCountries Table Component', () => {
test('it renders the default NetworkTopCountries table', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<NetworkTopCountriesTable
data={mockData.NetworkTopCountries.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.NetworkTopCountries.pageInfo)}
flowTargeted={FlowTargetSourceDest.source}
id="topCountriesSource"
indexPattern={mockIndexPattern}
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(
false,
'showMorePagesIndicator',
mockData.NetworkTopCountries.pageInfo
)}
totalCount={mockData.NetworkTopCountries.totalCount}
type={networkModel.NetworkType.page}
/>
<NetworkTopCountriesTable {...defaultProps} />
</ReduxStoreProvider>
);
@ -70,23 +72,7 @@ describe('NetworkTopCountries Table Component', () => {
test('it renders the IP Details NetworkTopCountries table', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<NetworkTopCountriesTable
data={mockData.NetworkTopCountries.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.NetworkTopCountries.pageInfo)}
flowTargeted={FlowTargetSourceDest.source}
id="topCountriesSource"
indexPattern={mockIndexPattern}
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(
false,
'showMorePagesIndicator',
mockData.NetworkTopCountries.pageInfo
)}
totalCount={mockData.NetworkTopCountries.totalCount}
type={networkModel.NetworkType.details}
/>
<NetworkTopCountriesTable {...defaultProps} type={networkModel.NetworkType.details} />
</ReduxStoreProvider>
);
@ -98,23 +84,7 @@ describe('NetworkTopCountries Table Component', () => {
test('when you click on the column header, you should show the sorting icon', () => {
const wrapper = mount(
<TestProviders store={store}>
<NetworkTopCountriesTable
data={mockData.NetworkTopCountries.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.NetworkTopCountries.pageInfo)}
flowTargeted={FlowTargetSourceDest.source}
id="topCountriesSource"
isInspect={false}
indexPattern={mockIndexPattern}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(
false,
'showMorePagesIndicator',
mockData.NetworkTopCountries.pageInfo
)}
totalCount={mockData.NetworkTopCountries.totalCount}
type={networkModel.NetworkType.page}
/>
<NetworkTopCountriesTable {...defaultProps} />
</TestProviders>
);
expect(store.getState().network.page.queries.topCountriesSource.sort).toEqual({

View file

@ -35,6 +35,7 @@ interface NetworkTopCountriesTableProps {
isInspect: boolean;
loading: boolean;
loadPage: (newActivePage: number) => void;
setQuerySkip: (skip: boolean) => void;
showMorePagesIndicator: boolean;
totalCount: number;
type: networkModel.NetworkType;
@ -62,6 +63,7 @@ const NetworkTopCountriesTableComponent: React.FC<NetworkTopCountriesTableProps>
isInspect,
loading,
loadPage,
setQuerySkip,
showMorePagesIndicator,
totalCount,
type,
@ -170,6 +172,7 @@ const NetworkTopCountriesTableComponent: React.FC<NetworkTopCountriesTableProps>
loadPage={loadPage}
onChange={onChange}
pageOfItems={data}
setQuerySkip={setQuerySkip}
showMorePagesIndicator={showMorePagesIndicator}
sorting={{ field, direction: sort.direction }}
totalCount={fakeTotalCount}

View file

@ -99,6 +99,7 @@ exports[`NetworkTopNFlow Table Component rendering it renders the default Networ
isInspect={false}
loadPage={[MockFunction]}
loading={false}
setQuerySkip={[MockFunction]}
showMorePagesIndicator={true}
totalCount={524}
type="details"
@ -204,6 +205,7 @@ exports[`NetworkTopNFlow Table Component rendering it renders the default Networ
isInspect={false}
loadPage={[MockFunction]}
loading={false}
setQuerySkip={[MockFunction]}
showMorePagesIndicator={true}
totalCount={524}
type="page"

View file

@ -35,6 +35,19 @@ describe('NetworkTopNFlow Table Component', () => {
const { storage } = createSecuritySolutionStorageMock();
let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const mount = useMountAppended();
const defaultProps = {
data: mockData.edges,
fakeTotalCount: getOr(50, 'fakeTotalCount', mockData.pageInfo),
flowTargeted: FlowTargetSourceDest.source,
id: 'topNFlowSource',
isInspect: false,
loading: false,
loadPage,
setQuerySkip: jest.fn(),
showMorePagesIndicator: getOr(false, 'showMorePagesIndicator', mockData.pageInfo),
totalCount: mockData.totalCount,
type: networkModel.NetworkType.page,
};
beforeEach(() => {
store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
@ -44,18 +57,7 @@ describe('NetworkTopNFlow Table Component', () => {
test('it renders the default NetworkTopNFlow table on the Network page', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<NetworkTopNFlowTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
flowTargeted={FlowTargetSourceDest.source}
id="topNFlowSource"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={networkModel.NetworkType.page}
/>
<NetworkTopNFlowTable {...defaultProps} />
</ReduxStoreProvider>
);
@ -65,18 +67,7 @@ describe('NetworkTopNFlow Table Component', () => {
test('it renders the default NetworkTopNFlow table on the IP Details page', () => {
const wrapper = shallow(
<ReduxStoreProvider store={store}>
<NetworkTopNFlowTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
flowTargeted={FlowTargetSourceDest.source}
id="topNFlowSource"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={networkModel.NetworkType.details}
/>
<NetworkTopNFlowTable {...defaultProps} type={networkModel.NetworkType.details} />
</ReduxStoreProvider>
);
@ -88,18 +79,7 @@ describe('NetworkTopNFlow Table Component', () => {
test('when you click on the column header, you should show the sorting icon', () => {
const wrapper = mount(
<TestProviders store={store}>
<NetworkTopNFlowTable
data={mockData.edges}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.pageInfo)}
flowTargeted={FlowTargetSourceDest.source}
id="topNFlowSource"
isInspect={false}
loading={false}
loadPage={loadPage}
showMorePagesIndicator={getOr(false, 'showMorePagesIndicator', mockData.pageInfo)}
totalCount={mockData.totalCount}
type={networkModel.NetworkType.page}
/>
<NetworkTopNFlowTable {...defaultProps} />
</TestProviders>
);
expect(store.getState().network.page.queries.topNFlowSource.sort).toEqual({

View file

@ -31,6 +31,7 @@ interface NetworkTopNFlowTableProps {
isInspect: boolean;
loading: boolean;
loadPage: (newActivePage: number) => void;
setQuerySkip: (skip: boolean) => void;
showMorePagesIndicator: boolean;
totalCount: number;
type: networkModel.NetworkType;
@ -57,6 +58,7 @@ const NetworkTopNFlowTableComponent: React.FC<NetworkTopNFlowTableProps> = ({
isInspect,
loading,
loadPage,
setQuerySkip,
showMorePagesIndicator,
totalCount,
type,
@ -166,6 +168,7 @@ const NetworkTopNFlowTableComponent: React.FC<NetworkTopNFlowTableProps> = ({
loadPage={loadPage}
onChange={onChange}
pageOfItems={data}
setQuerySkip={setQuerySkip}
showMorePagesIndicator={showMorePagesIndicator}
sorting={sorting}
totalCount={fakeTotalCount}

Some files were not shown because too many files have changed in this diff Show more