Skip to content

Commit

Permalink
[SIEM] Restores the _External alert count_ widget's subtitle (#62094)
Browse files Browse the repository at this point in the history
## [SIEM] Restores the _External alert count_ widget's subtitle

Fixes an issue where the _External alert count_ widget's subtitle, (e.g. `Showing: 47,642,905 external alerts`), didn't render after data is loaded

### Before

![external-alerts-before](https://user-images.githubusercontent.com/4459398/78086038-f3fe7c80-7379-11ea-8291-2ef807349aea.png)

### After

![external-alerts-after](https://user-images.githubusercontent.com/4459398/78086045-fb258a80-7379-11ea-9bc6-338dc3aba482.png)
  • Loading branch information
andrew-goldstein authored Apr 1, 2020
1 parent b06eaf4 commit 2cff8b4
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
HistogramAggregation,
MatrixHistogramQueryProps,
} from './types';
import { ChartSeriesData } from '../charts/common';
import { InspectButtonContainer } from '../inspect';

import { State, inputsSelectors, hostsModel, networkModel } from '../../store';
Expand Down Expand Up @@ -120,11 +119,6 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramProps &
const [selectedStackByOption, setSelectedStackByOption] = useState<MatrixHistogramOption>(
defaultStackByOption
);

const [titleWithStackByField, setTitle] = useState<string>('');
const [subtitleWithCounts, setSubtitle] = useState<string>('');
const [hideHistogram, setHideHistogram] = useState<boolean>(hideHistogramIfEmpty);
const [barChartData, setBarChartData] = useState<ChartSeriesData[] | null>(null);
const setSelectedChartOptionCallback = useCallback(
(event: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedStackByOption(
Expand All @@ -146,40 +140,36 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramProps &
}
);

useEffect(() => {
if (title != null) setTitle(typeof title === 'function' ? title(selectedStackByOption) : title);

if (subtitle != null)
setSubtitle(typeof subtitle === 'function' ? subtitle(totalCount) : subtitle);

if (totalCount <= 0 && hideHistogramIfEmpty) {
setHideHistogram(true);
} else {
setHideHistogram(false);
}
setBarChartData(getCustomChartData(data, mapping));
const titleWithStackByField = useMemo(
() => (title != null && typeof title === 'function' ? title(selectedStackByOption) : title),
[title, selectedStackByOption]
);
const subtitleWithCounts = useMemo(
() => (subtitle != null && typeof subtitle === 'function' ? subtitle(totalCount) : subtitle),
[subtitle, totalCount]
);
const hideHistogram = useMemo(() => (totalCount <= 0 && hideHistogramIfEmpty ? true : false), [
totalCount,
hideHistogramIfEmpty,
]);
const barChartData = useMemo(() => getCustomChartData(data, mapping), [data, mapping]);

useEffect(() => {
setQuery({ id, inspect, loading, refetch });

if (isInitialLoading && !!barChartData && data) {
setIsInitialLoading(false);
}
}, [
subtitle,
setSubtitle,
setHideHistogram,
setBarChartData,
setQuery,
hideHistogramIfEmpty,
totalCount,
id,
inspect,
isInspected,
loading,
refetch,
data,
refetch,
isInitialLoading,
barChartData,
data,
setIsInitialLoading,
]);

if (hideHistogram) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

/* eslint-disable react/display-name */

import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
import { mount, ReactWrapper } from 'enzyme';
import React from 'react';
import { ThemeProvider } from 'styled-components';

import { useQuery } from '../../../containers/matrix_histogram';
import { wait } from '../../../lib/helpers';
import { mockIndexPattern, TestProviders } from '../../../mock';

import { AlertsByCategory } from '.';

jest.mock('../../../lib/kibana');

jest.mock('../../../containers/matrix_histogram', () => {
return {
useQuery: jest.fn(),
};
});

const theme = () => ({ eui: { ...euiDarkVars, euiSizeL: '24px' }, darkMode: true });
const from = new Date('2020-03-31T06:00:00.000Z').valueOf();
const to = new Date('2019-03-31T06:00:00.000Z').valueOf();

describe('Alerts by category', () => {
let wrapper: ReactWrapper;

describe('before loading data', () => {
beforeAll(async () => {
(useQuery as jest.Mock).mockReturnValue({
data: null,
loading: false,
inspect: false,
totalCount: null,
});

wrapper = mount(
<ThemeProvider theme={theme}>
<TestProviders>
<AlertsByCategory
deleteQuery={jest.fn()}
filters={[]}
from={from}
indexPattern={mockIndexPattern}
setQuery={jest.fn()}
to={to}
/>
</TestProviders>
</ThemeProvider>
);

await wait();
wrapper.update();
});

test('it renders the expected title', () => {
expect(wrapper.find('[data-test-subj="header-section-title"]').text()).toEqual(
'External alert count'
);
});

test('it does NOT render the subtitle', () => {
expect(wrapper.find('[data-test-subj="header-panel-subtitle"]').exists()).toBe(false);
});

test('it renders the expected filter fields', () => {
const expectedOptions = ['event.category', 'event.module'];

expectedOptions.forEach(option => {
expect(wrapper.find(`option[value="${option}"]`).text()).toEqual(option);
});
});

test('it renders the `View alerts` button', () => {
expect(wrapper.find('[data-test-subj="view-alerts"]').exists()).toBe(true);
});

test('it does NOT render the bar chart when data is not available', () => {
expect(wrapper.find(`.echChart`).exists()).toBe(false);
});
});

describe('after loading data', () => {
beforeAll(async () => {
(useQuery as jest.Mock).mockReturnValue({
data: [
{ x: 1, y: 2, g: 'g1' },
{ x: 2, y: 4, g: 'g1' },
{ x: 3, y: 6, g: 'g1' },
{ x: 1, y: 1, g: 'g2' },
{ x: 2, y: 3, g: 'g2' },
{ x: 3, y: 5, g: 'g2' },
],
loading: false,
inspect: false,
totalCount: 6,
});

wrapper = mount(
<ThemeProvider theme={theme}>
<TestProviders>
<AlertsByCategory
deleteQuery={jest.fn()}
filters={[]}
from={from}
indexPattern={mockIndexPattern}
setQuery={jest.fn()}
to={to}
/>
</TestProviders>
</ThemeProvider>
);

await wait();
wrapper.update();
});

test('it renders the expected subtitle', () => {
expect(wrapper.find('[data-test-subj="header-panel-subtitle"]').text()).toEqual(
'Showing: 6 external alerts'
);
});

test('it renders the bar chart when data is available', () => {
expect(wrapper.find(`.echChart`).exists()).toBe(true);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ const AlertsByCategoryComponent: React.FC<Props> = ({
const urlSearch = useGetUrlSearch(navTabs.detections);

const alertsCountViewAlertsButton = useMemo(
() => <EuiButton href={getDetectionEngineAlertUrl(urlSearch)}>{i18n.VIEW_ALERTS}</EuiButton>,
() => (
<EuiButton data-test-subj="view-alerts" href={getDetectionEngineAlertUrl(urlSearch)}>
{i18n.VIEW_ALERTS}
</EuiButton>
),
[urlSearch]
);

Expand All @@ -87,7 +91,7 @@ const AlertsByCategoryComponent: React.FC<Props> = ({
...histogramConfigs,
defaultStackByOption:
alertsStackByOptions.find(o => o.text === DEFAULT_STACK_BY) ?? alertsStackByOptions[0],
getSubtitle: (totalCount: number) =>
subtitle: (totalCount: number) =>
`${SHOWING}: ${numeral(totalCount).format(defaultNumberFormat)} ${UNIT(totalCount)}`,
legendPosition: Position.Right,
}),
Expand Down

0 comments on commit 2cff8b4

Please sign in to comment.