Skip to content

Commit

Permalink
Refactor alerts_histogram_panel to reuse logic on alerts_count
Browse files Browse the repository at this point in the history
  • Loading branch information
machadoum committed Jul 20, 2021
1 parent ef49be6 commit 5b21bc7
Show file tree
Hide file tree
Showing 30 changed files with 519 additions and 815 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { combineQueries } from '../../../timelines/components/timeline/helpers';
import { getOptions } from './helpers';
import { TopN } from './top_n';
import { TimelineId, TimelineTabs } from '../../../../common/types/timeline';
import { AlertsStackByField } from '../../../detections/components/kpis/common/types';
import { AlertsStackByField } from '../../../detections/components/alerts_kpis/common/types';

const EMPTY_FILTERS: Filter[] = [];
const EMPTY_QUERY: Query = { query: '', language: 'kuery' };
Expand Down Expand Up @@ -76,7 +76,7 @@ const connector = connect(makeMapStateToProps);
// to the index pattern.
export interface OwnProps {
browserFields: BrowserFields;
field: AlertsStackByField;
field: string;
indexPattern: IIndexPattern;
timelineId?: string;
toggleTopN: () => void;
Expand Down Expand Up @@ -154,7 +154,7 @@ const StatefulTopNComponent: React.FC<Props> = ({
data-test-subj="top-n"
defaultView={defaultView}
deleteQuery={timelineId === TimelineId.active ? undefined : deleteQuery}
field={field}
field={field as AlertsStackByField}
filters={timelineId === TimelineId.active ? EMPTY_FILTERS : globalFilters}
from={timelineId === TimelineId.active ? activeTimelineFrom : from}
indexPattern={indexPattern}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { TopNOption } from './helpers';
import * as i18n from './translations';
import { getIndicesSelector, IndicesSelector } from './selectors';
import { State } from '../../store';
import { AlertsStackByField } from '../../../detections/components/kpis/common/types';
import { AlertsStackByField } from '../../../detections/components/alerts_kpis/common/types';

const TopNContainer = styled.div`
width: 600px;
Expand Down Expand Up @@ -138,14 +138,11 @@ const TopNComponent: React.FC<Props> = ({
<SignalsByCategory
combinedQueries={combinedQueries}
filters={filters}
from={from}
headerChildren={headerChildren}
onlyField={field}
query={query}
setAbsoluteRangeDatePickerTarget={setAbsoluteRangeDatePickerTarget}
setQuery={setQuery}
timelineId={timelineId}
to={to}
/>
)}
</TopNContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@
import React from 'react';
import { shallow, mount } from 'enzyme';

import '../../../common/mock/match_media';
import { AlertsCount } from './alerts_count';
import { AlertSearchResponse } from '../../../containers/detection_engine/alerts/types';
import { TestProviders } from '../../../../common/mock';
import { DragDropContextWrapper } from '../../../../common/components/drag_and_drop/drag_drop_context_wrapper';
import { mockBrowserFields } from '../../../../common/containers/source/mock';
import { AlertsCountAggregation } from './types';

jest.mock('../../../common/lib/kibana');
jest.mock('../../../../common/lib/kibana');
const mockDispatch = jest.fn();

jest.mock('react-redux', () => {
Expand All @@ -29,10 +28,9 @@ jest.mock('react-redux', () => {

describe('AlertsCount', () => {
it('renders correctly', () => {
const alertsMock = {};
const wrapper = shallow(
<AlertsCount
data={alertsMock as AlertSearchResponse<{}, AlertsCountAggregation>}
data={{} as AlertSearchResponse<{}, AlertsCountAggregation>}
loading={false}
selectedStackByOption={'test_selected_field'}
/>
Expand All @@ -45,7 +43,6 @@ describe('AlertsCount', () => {
const alertFiedlKey = 'test_stack_by_test_key';
const alertFiedlCount = 999;
const alertData = {
...alertsMock,
aggregations: {
alertsByGroupingCount: {
buckets: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { DefaultDraggable } from '../../../../common/components/draggables';
import { GenericBuckets } from '../../../../../common';
import { AlertSearchResponse } from '../../../containers/detection_engine/alerts/types';
import { AlertsCountAggregation } from './types';
import { MISSING_IP } from '../common/helpers';

interface AlertsCountProps {
from: string;
Expand All @@ -32,6 +33,10 @@ const Wrapper = styled.div<{ height?: number }>`
${({ height }) => (height != null ? `height: ${height}px;` : '')};
`;

const StyledSpan = styled.span`
padding-left: 8px;
`;

const getAlertsCountTableColumns = (
selectedStackByOption: string,
defaultNumberFormat: string
Expand All @@ -41,15 +46,15 @@ const getAlertsCountTableColumns = (
field: 'key',
name: selectedStackByOption,
truncateText: true,
render: function DraggableStackOptionField(item: string) {
return (
render: function DraggableStackOptionField(value: string) {
return value === i18n.ALL_OTHERS || value === MISSING_IP ? (
<StyledSpan>{value}</StyledSpan>
) : (
<DefaultDraggable
field={selectedStackByOption}
id={`alert-count-draggable-${selectedStackByOption}-${item}`}
value={item}
>
{item}
</DefaultDraggable>
id={`alert-count-draggable-${selectedStackByOption}-${value}`}
value={value}
/>
);
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,19 @@
* 2.0.
*/

import { DEFAULT_MAX_TABLE_QUERY_SIZE, showAllOthersBucket } from '../../../../../common/constants';
import * as i18n from './translations';
import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../common/constants';
import { getMissingFields } from '../common/helpers';
import { AlertsStackByField } from '../common/types';

export const getAlertsCountQuery = (
stackByField: string,
stackByField: AlertsStackByField,
from: string,
to: string,
additionalFilters: Array<{
bool: { filter: unknown[]; should: unknown[]; must_not: unknown[]; must: unknown[] };
}>
}> = []
) => {
const missing = showAllOthersBucket.includes(stackByField)
? {
missing: stackByField.endsWith('.ip') ? '0.0.0.0' : i18n.ALL_OTHERS,
}
: {};
const missing = getMissingFields(stackByField);

return {
size: 0,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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 { waitFor, act } from '@testing-library/react';

import { mount } from 'enzyme';
import { esQuery } from '../../../../../../../../src/plugins/data/public';

import { TestProviders } from '../../../../common/mock';

import { AlertsCountPanel } from './index';

describe('AlertsCountPanel', () => {
const defaultProps = {
signalIndexName: 'signalIndexName',
};

it('renders correctly', async () => {
await act(async () => {
const wrapper = mount(
<TestProviders>
<AlertsCountPanel {...defaultProps} />
</TestProviders>
);

expect(wrapper.find('[data-test-subj="alertsCountPanel"]').exists()).toBeTruthy();
});
});

describe('Query', () => {
it('it render with a illegal KQL', async () => {
const spyOnBuildEsQuery = jest.spyOn(esQuery, 'buildEsQuery');
spyOnBuildEsQuery.mockImplementation(() => {
throw new Error('Something went wrong');
});
const props = { ...defaultProps, query: { query: 'host.name: "', language: 'kql' } };
const wrapper = mount(
<TestProviders>
<AlertsCountPanel {...props} />
</TestProviders>
);

await waitFor(() => {
expect(wrapper.find('[data-test-subj="alertsCountPanel"]').exists()).toBeTruthy();
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* 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, { memo, useMemo, useState, useEffect } from 'react';
import uuid from 'uuid';

import { useGlobalTime } from '../../../../common/containers/use_global_time';
import { HeaderSection } from '../../../../common/components/header_section';

import { useQueryAlerts } from '../../../containers/detection_engine/alerts/use_query';
import { InspectButtonContainer } from '../../../../common/components/inspect';

import { getAlertsCountQuery } from './helpers';
import * as i18n from './translations';
import { AlertsCount } from './alerts_count';
import { AlertsCountAggregation } from './types';
import { DATA_HEIGHT, DEFAULT_STACK_BY_FIELD } from '../common/config';
import { AlertsStackByField, AlertsStackByOption } from '../common/types';
import { Filter, esQuery, Query } from '../../../../../../../../src/plugins/data/public';
import { KpiPanel, StackBySelect } from '../common/components';
import { useInspectButton } from '../common/hooks';

export const DETECTIONS_ALERTS_COUNT_ID = 'detections-alerts-count';

interface AlertsCountPanelProps {
defaultStackByOption?: AlertsStackByOption;
filters?: Filter[];
query?: Query;
signalIndexName: string | null;
}

export const AlertsCountPanel = memo<AlertsCountPanelProps>(
({ filters, query, signalIndexName }) => {
const { to, from, deleteQuery, setQuery } = useGlobalTime();

// create a unique, but stable (across re-renders) query id
const uniqueQueryId = useMemo(() => `${DETECTIONS_ALERTS_COUNT_ID}-${uuid.v4()}`, []);
const [selectedStackByOption, setSelectedStackByOption] = useState<AlertsStackByField>(
DEFAULT_STACK_BY_FIELD
);

const additionalFilters = useMemo(() => {
try {
return [
esQuery.buildEsQuery(
undefined,
query != null ? [query] : [],
filters?.filter((f) => f.meta.disabled === false) ?? []
),
];
} catch (e) {
return [];
}
}, [query, filters]);

const {
loading: isLoadingAlerts,
data: alertsData,
setQuery: setAlertsQuery,
response,
request,
refetch,
} = useQueryAlerts<{}, AlertsCountAggregation>({
query: getAlertsCountQuery(selectedStackByOption, from, to, additionalFilters),
indexName: signalIndexName,
});

useEffect(() => {
setAlertsQuery(getAlertsCountQuery(selectedStackByOption, from, to, additionalFilters));
}, [setAlertsQuery, selectedStackByOption, from, to, additionalFilters]);

useInspectButton({
setQuery,
response,
request,
refetch,
uniqueQueryId,
deleteQuery,
loading: isLoadingAlerts,
});

return (
<InspectButtonContainer>
<KpiPanel hasBorder data-test-subj="alertsCountPanel">
<HeaderSection id={uniqueQueryId} title={i18n.COUNT_TABLE_TITLE} titleSize="s">
<StackBySelect selected={selectedStackByOption} onSelect={setSelectedStackByOption} />
</HeaderSection>
<AlertsCount
data={alertsData}
from={from}
loading={isLoadingAlerts}
to={to}
selectedStackByOption={selectedStackByOption}
height={DATA_HEIGHT}
/>
</KpiPanel>
</InspectButtonContainer>
);
}
);

AlertsCountPanel.displayName = 'AlertsCountPanel';
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,6 @@

import { i18n } from '@kbn/i18n';

export const STACK_BY_LABEL = i18n.translate(
'xpack.securitySolution.detectionEngine.alerts.count.stackByOptions.stackByLabel',
{
defaultMessage: 'Stack by',
}
);

export const ALL_OTHERS = i18n.translate(
'xpack.securitySolution.detectionEngine.alerts.count.allOthersGroupingLabel',
{
defaultMessage: 'All others',
}
);

export const COUNT_TABLE_COLUMN_TITLE = i18n.translate(
'xpack.securitySolution.detectionEngine.alerts.count.countTableColumnTitle',
{
Expand All @@ -34,3 +20,5 @@ export const COUNT_TABLE_TITLE = i18n.translate(
defaultMessage: 'Count',
}
);

export * from '../common/translations';
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { shallow } from 'enzyme';
import '../../../../common/mock/match_media';
import { AlertsHistogram } from './alerts_histogram';

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

describe('AlertsHistogram', () => {
it('renders correctly', () => {
Expand All @@ -26,6 +26,6 @@ describe('AlertsHistogram', () => {
/>
);

expect(wrapper.find('Chart')).toBeTruthy();
expect(wrapper.find('Chart').exists()).toBeTruthy();
});
});
Loading

0 comments on commit 5b21bc7

Please sign in to comment.