diff --git a/x-pack/plugins/security_solution/common/search_strategy/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/common/index.ts index 87165a1277708..d8ef64fe1b5a4 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/common/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/common/index.ts @@ -55,6 +55,7 @@ export interface Hits { export interface GenericBuckets { key: string; + key_as_string?: string; // contains, for example, formatted dates doc_count: number; } diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/index.test.tsx index aeb96c3a7239d..8f53a3f226ce6 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/index.test.tsx @@ -6,12 +6,15 @@ */ import { render, screen } from '@testing-library/react'; +import { Settings } from '@elastic/charts'; import React from 'react'; import { TestProviders } from '../../mock'; import { mockAlertSearchResponse, mockNoDataAlertSearchResponse, + mockNoStackByField1Response, + mockOnlyStackByField0Response, } from './lib/mocks/mock_alert_search_response'; import * as i18n from './translations'; import type { Props } from '.'; @@ -25,9 +28,19 @@ const defaultProps: Props = { stackByField1: 'host.name', }; +jest.mock('@elastic/charts', () => { + const actual = jest.requireActual('@elastic/charts'); + return { + ...actual, + Settings: jest.fn().mockReturnValue(null), + }; +}); + describe('AlertsTreemap', () => { describe('when the response has data', () => { beforeEach(() => { + jest.clearAllMocks(); + render( @@ -42,6 +55,10 @@ describe('AlertsTreemap', () => { test('it renders the legend with the expected overflow-y style', () => { expect(screen.getByTestId('draggable-legend')).toHaveClass('eui-yScroll'); }); + + test('it uses a theme with the expected `minFontSize` to show more labels at various screen resolutions', () => { + expect((Settings as jest.Mock).mock.calls[0][0].theme[0].partition.minFontSize).toEqual(4); + }); }); describe('when the response does NOT have data', () => { @@ -65,4 +82,64 @@ describe('AlertsTreemap', () => { expect(screen.getByText(i18n.NO_DATA_LABEL)).toBeInTheDocument(); }); }); + + describe('when the user has specified a `stackByField1`, and the response has data for `stackByField0`, but `stackByField1` is not present in the response', () => { + const stackByField1 = 'Ransomware.version'; // this non-ECS field is requested + + beforeEach(() => { + render( + + + + ); + }); + + test('it renders the "no data" message', () => { + expect(screen.getByText(i18n.NO_DATA_LABEL)).toBeInTheDocument(); + }); + + test('it renders an additional reason label with the `stackByField1` field', () => { + expect(screen.getByText(i18n.NO_DATA_REASON_LABEL(stackByField1))).toBeInTheDocument(); + }); + + test('it still renders the legend, because we have values for stackByField0', () => { + expect(screen.getByTestId('draggable-legend')).toBeInTheDocument(); + }); + }); + + describe('when the user has NOT specified a `stackByField1`, and the response has data for `stackByField0`', () => { + const stackByField1 = ''; // the user has NOT specified a `stackByField1` + + beforeEach(() => { + render( + + + + ); + }); + + test('it renders the treemap', () => { + expect(screen.getByTestId('treemap').querySelector('.echChart')).toBeInTheDocument(); + }); + + test('it does NOT render the "no data" message', () => { + expect(screen.queryByText(i18n.NO_DATA_LABEL)).not.toBeInTheDocument(); + }); + + test('it does NOT render an additional reason label with the `stackByField1` field, which was not requested', () => { + expect(screen.queryByText(i18n.NO_DATA_REASON_LABEL(stackByField1))).not.toBeInTheDocument(); + }); + + test('it renders the legend, because we have values for stackByField0', () => { + expect(screen.getByTestId('draggable-legend')).toBeInTheDocument(); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/index.tsx index 143b702cfb71b..fc6edda0c90b9 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/index.tsx @@ -28,6 +28,7 @@ import { import { getLayersMultiDimensional, getLayersOneDimension } from './lib/layers'; import { getFirstGroupLegendItems } from './lib/legend'; import { NoData } from './no_data'; +import { NO_DATA_REASON_LABEL } from './translations'; import type { AlertsTreeMapAggregation, FlattenedBucket, RawBucket } from './types'; export const DEFAULT_MIN_CHART_HEIGHT = 370; // px @@ -68,7 +69,7 @@ const AlertsTreemapComponent: React.FC = ({ fillLabel: { valueFont: { fontWeight: 700 } }, idealFontSizeJump: 1.15, maxFontSize: 16, - minFontSize: 8, + minFontSize: 4, sectorLineStroke: fillColor, // draws the light or dark "lines" between partitions sectorLineWidth: 1.5, }, @@ -167,21 +168,25 @@ const AlertsTreemapComponent: React.FC = ({
- - - - + {stackByField1 != null && !isEmpty(stackByField1) && normalizedData.length === 0 ? ( + + ) : ( + + + + + )} diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/flatten/flatten_bucket.test.ts b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/flatten/flatten_bucket.test.ts index 141d73c923bb8..763ecc4e3df60 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/flatten/flatten_bucket.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/flatten/flatten_bucket.test.ts @@ -11,6 +11,7 @@ import { bucketsWithoutStackByField1, maxRiskSubAggregations, } from './mocks/mock_buckets'; +import type { RawBucket } from '../../types'; describe('flattenBucket', () => { it(`returns the expected flattened buckets when stackByField1 has buckets`, () => { @@ -46,6 +47,63 @@ describe('flattenBucket', () => { ]); }); + it(`it prefers to populate 'key' using the RawBucket's 'key_as_string' when available, because it contains formatted dates`, () => { + const bucketWithOptionalKeyAsString: RawBucket = { + key: '1658955590866', + key_as_string: '2022-07-27T20:59:50.866Z', // <-- should be preferred over `key` when present + doc_count: 1, + maxRiskSubAggregation: { value: 21 }, + stackByField1: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'Host-vmdx1cnu3m', doc_count: 1 }], + }, + }; + + expect( + flattenBucket({ bucket: bucketWithOptionalKeyAsString, maxRiskSubAggregations }) + ).toEqual([ + { + doc_count: 1, + key: bucketWithOptionalKeyAsString.key_as_string, // <-- uses the preferred `key_as_string` + maxRiskSubAggregation: { value: 21 }, + stackByField1DocCount: 1, + stackByField1Key: 'Host-vmdx1cnu3m', + }, + ]); + }); + + it(`it prefers to populate 'stackByField1Key' using the 'stackByField1.buckets[n].key_as_string' when available, because it contains formatted dates`, () => { + const keyAsString = '2022-07-27T09:33:19.329Z'; + + const bucketWithKeyAsString: RawBucket = { + key: 'Threshold rule', + doc_count: 1, + maxRiskSubAggregation: { value: 99 }, + stackByField1: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: '1658914399329', + key_as_string: keyAsString, // <-- should be preferred over `stackByField1.buckets[n].key` when present + doc_count: 1, + }, + ], + }, + }; + + expect(flattenBucket({ bucket: bucketWithKeyAsString, maxRiskSubAggregations })).toEqual([ + { + doc_count: 1, + key: 'Threshold rule', + maxRiskSubAggregation: { value: 99 }, + stackByField1DocCount: 1, + stackByField1Key: keyAsString, // <-- uses the preferred `stackByField1.buckets[n].key_as_string` + }, + ]); + }); + it(`returns an empty array when there's nothing to flatten, because stackByField1 is undefined`, () => { expect( flattenBucket({ bucket: bucketsWithoutStackByField1[0], maxRiskSubAggregations }) diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/flatten/flatten_bucket.ts b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/flatten/flatten_bucket.ts index aa7966e7f79cd..ef15178cbc256 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/flatten/flatten_bucket.ts +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/flatten/flatten_bucket.ts @@ -16,8 +16,8 @@ export const flattenBucket = ({ }): FlattenedBucket[] => bucket.stackByField1?.buckets?.map((x) => ({ doc_count: bucket.doc_count, - key: bucket.key, + key: bucket.key_as_string ?? bucket.key, // prefer key_as_string when available, because it contains a formatted date maxRiskSubAggregation: bucket.maxRiskSubAggregation, - stackByField1Key: x.key, + stackByField1Key: x.key_as_string ?? x.key, stackByField1DocCount: x.doc_count, })) ?? []; diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/legend/index.test.ts b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/legend/index.test.ts index 514b2743504d4..303cca251f417 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/legend/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/legend/index.test.ts @@ -16,12 +16,24 @@ import { getLegendItemFromFlattenedBucket, getLegendMap, } from '.'; -import type { FlattenedBucket } from '../../types'; +import type { FlattenedBucket, RawBucket } from '../../types'; describe('legend', () => { const colorPalette = getRiskScorePalette(RISK_SCORE_STEPS); describe('getLegendItemFromRawBucket', () => { + const bucket: RawBucket = { + key: '1658955590866', + key_as_string: '2022-07-27T20:59:50.866Z', // <-- should be preferred over `key` when present + doc_count: 1, + maxRiskSubAggregation: { value: 21 }, + stackByField1: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'Host-vmdx1cnu3m', doc_count: 1 }], + }, + }; + it('returns an undefined color when showColor is false', () => { expect( getLegendItemFromRawBucket({ @@ -70,6 +82,30 @@ describe('legend', () => { ).toContain('draggable-legend-item-treemap-kibana_alert_rule_name-matches everything-'); }); + it('renders the expected label', () => { + const item = getLegendItemFromRawBucket({ + bucket: bucketsWithStackByField1[0], + colorPalette, + maxRiskSubAggregations, + showColor: true, + stackByField0: 'kibana.alert.rule.name', + }); + + expect(item.render != null && item.render()).toEqual(`matches everything (Risk 21)`); + }); + + it('prefers `key_as_string` over `key` when rendering the label', () => { + const item = getLegendItemFromRawBucket({ + bucket, + colorPalette, + maxRiskSubAggregations, + showColor: true, + stackByField0: '@timestamp', + }); + + expect(item.render != null && item.render()).toEqual(`${bucket.key_as_string} (Risk 21)`); + }); + it('returns the expected field', () => { expect( getLegendItemFromRawBucket({ @@ -93,6 +129,18 @@ describe('legend', () => { }).value ).toEqual('matches everything'); }); + + it('prefers `key_as_string` over `key` when populating value', () => { + expect( + getLegendItemFromRawBucket({ + bucket, + colorPalette, + maxRiskSubAggregations, + showColor: true, + stackByField0: '@timestamp', + }).value + ).toEqual(bucket.key_as_string); + }); }); describe('getLegendItemFromFlattenedBucket', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/legend/index.ts b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/legend/index.ts index 77865b7d55013..5f705e4f0c9fd 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/legend/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/legend/index.ts @@ -38,11 +38,11 @@ export const getLegendItemFromRawBucket = ({ ), render: () => getLabel({ - baseLabel: bucket.key, + baseLabel: bucket.key_as_string ?? bucket.key, // prefer key_as_string when available, because it contains a formatted date riskScore: bucket.maxRiskSubAggregation?.value, }), field: stackByField0, - value: bucket.key, + value: bucket.key_as_string ?? bucket.key, }); export const getLegendItemFromFlattenedBucket = ({ diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/mocks/mock_alert_search_response.ts b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/mocks/mock_alert_search_response.ts index b28b55a396ad1..59f0de746dd50 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/mocks/mock_alert_search_response.ts +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/lib/mocks/mock_alert_search_response.ts @@ -138,3 +138,120 @@ export const mockNoDataAlertSearchResponse = { }, }, }; + +/** + * This response has multiple values for `stackByField0`, but no values for `stackByField1`, even though `stackByField1` was requested + */ +export const mockNoStackByField1Response = { + took: 3, + timeout: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 23, + relation: 'eq', + }, + max_score: null, + hits: [], + }, + aggregations: { + stackByField0: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'mimikatz process started', + doc_count: 9, + maxRiskSubAggregation: { + value: 99, + }, + stackByField1: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + }, + { + key: 'matches everything too', + doc_count: 8, + maxRiskSubAggregation: { + value: 21, + }, + stackByField1: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + }, + { + key: 'Threshold rule', + doc_count: 6, + maxRiskSubAggregation: { + value: 99, + }, + stackByField1: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + }, + ], + }, + }, +}; + +/** + * This response has multiple values, but ONLY for `stackByField0`, because `stackByField1` was NOT requested + */ +export const mockOnlyStackByField0Response = { + took: 1, + timeout: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 30, + relation: 'eq', + }, + max_score: null, + hits: [], + }, + aggregations: { + stackByField0: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'matches everything too', + doc_count: 14, + maxRiskSubAggregation: { + value: 21, + }, + }, + { + key: 'mimikatz process started', + doc_count: 9, + maxRiskSubAggregation: { + value: 99, + }, + }, + { + key: 'Threshold rule', + doc_count: 7, + maxRiskSubAggregation: { + value: 99, + }, + }, + ], + }, + }, +}; diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/no_data/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/no_data/index.tsx index 2dba94d3c12fa..d60b0bbba1852 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/no_data/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/no_data/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; @@ -15,12 +15,25 @@ const NoDataLabel = styled(EuiText)` text-align: center; `; -const NoDataComponent: React.FC = () => ( +interface Props { + reason?: string; +} + +const NoDataComponent: React.FC = ({ reason }) => ( {i18n.NO_DATA_LABEL} + + {reason != null && ( + <> + + + {reason} + + + )} ); diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/translations.ts b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/translations.ts index 886cebf434f67..8f2b627ebcbb8 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/translations.ts @@ -14,6 +14,14 @@ export const NO_DATA_LABEL = i18n.translate( } ); +export const NO_DATA_REASON_LABEL = (stackByField1: string) => + i18n.translate('xpack.securitySolution.components.alertsTreemap.noDataReasonLabel', { + values: { + stackByField1, + }, + defaultMessage: 'The {stackByField1} field was not present in any groups', + }); + export const RISK_LABEL = (riskScore: number) => i18n.translate('xpack.securitySolution.components.alertsTreemap.riskLabel', { values: { diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/types.ts b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/types.ts index b0316952487d3..62c832fd457bc 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap/types.ts @@ -25,7 +25,10 @@ export interface AlertsTreeMapAggregation { }; } -export type FlattenedBucket = Pick & { +export type FlattenedBucket = Pick< + RawBucket, + 'doc_count' | 'key' | 'key_as_string' | 'maxRiskSubAggregation' +> & { stackByField1Key?: string; stackByField1DocCount?: number; }; diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx index a14f7c9b23ff5..0db21ee27ea75 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx @@ -18,6 +18,7 @@ import { import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query'; import { ChartContextMenu } from '../../../detections/pages/detection_engine/chart_panels/chart_context_menu'; import { ChartSelect } from '../../../detections/pages/detection_engine/chart_panels/chart_select'; +import { TREEMAP } from '../../../detections/pages/detection_engine/chart_panels/chart_select/translations'; import { TestProviders } from '../../mock/test_providers'; import type { Props } from '.'; import { AlertsTreemapPanel } from '.'; @@ -64,7 +65,7 @@ const defaultProps: Props = { setStackByField1={jest.fn()} /> ), - + inspectTitle: TREEMAP, isPanelExpanded: true, filters: [ { diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx index e0b13cf2a00d3..74532bb7d7930 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx @@ -33,6 +33,7 @@ export interface Props { addFilter?: ({ field, value }: { field: string; value: string | number }) => void; alignHeader?: 'center' | 'baseline' | 'stretch' | 'flexStart' | 'flexEnd'; chartOptionsContextMenu?: (queryId: string) => React.ReactNode; + inspectTitle: string; isPanelExpanded: boolean; filters?: Filter[]; height?: number; @@ -53,6 +54,7 @@ const AlertsTreemapPanelComponent: React.FC = ({ addFilter, alignHeader, chartOptionsContextMenu, + inspectTitle, isPanelExpanded, filters, height = DEFAULT_HEIGHT, @@ -155,6 +157,7 @@ const AlertsTreemapPanelComponent: React.FC = ({ alignHeader={alignHeader} hideSubtitle id={uniqueQueryId} + inspectTitle={inspectTitle} outerDirection="row" showInspectButton={chartOptionsContextMenu == null} title={title} diff --git a/x-pack/plugins/security_solution/public/common/components/header_section/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_section/index.test.tsx index 9e89acc20b3f1..fe99e22c448e4 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_section/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_section/index.test.tsx @@ -11,8 +11,28 @@ import React from 'react'; import { TestProviders } from '../../mock'; import { getHeaderAlignment, HeaderSection } from '.'; +import { ModalInspectQuery } from '../inspect/modal'; + +jest.mock('../inspect/modal', () => { + const actual = jest.requireActual('../inspect/modal'); + return { + ...actual, + ModalInspectQuery: jest.fn().mockReturnValue(null), + }; +}); + +jest.mock('../inspect/use_inspect', () => ({ + useInspect: () => ({ + isShowingModal: true, + handleClick: jest.fn(), + request: 'fake request', + response: 'fake response', + }), +})); describe('HeaderSection', () => { + beforeEach(() => jest.clearAllMocks()); + test('it renders', () => { const wrapper = shallow(); @@ -181,6 +201,35 @@ describe('HeaderSection', () => { expect(wrapper.find('[data-test-subj="inspect-icon-button"]').first().exists()).toBe(false); }); + test('it defaults to using `title` for the inspect modal when `inspectTitle` is NOT provided', () => { + const title = 'Use this by default'; + + mount( + + +

{'Test children'}

+
+
+ ); + + expect((ModalInspectQuery as jest.Mock).mock.calls[0][0].title).toEqual(title); + }); + + test('it uses `inspectTitle` instead of `title` for the inspect modal when `inspectTitle` is provided', () => { + const title = `Don't use this`; + const inspectTitle = 'Use this instead'; + + mount( + + +

{'Test children'}

+
+
+ ); + + expect((ModalInspectQuery as jest.Mock).mock.calls[0][0].title).toEqual(inspectTitle); + }); + test('it does not render query-toggle-header when no arguments provided', () => { const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx index e8fe65e52d60c..153764e8ef13b 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx @@ -68,6 +68,7 @@ export interface HeaderSectionProps extends HeaderProps { toggleQuery?: (status: boolean) => void; toggleStatus?: boolean; title: string | React.ReactNode; + inspectTitle?: string; titleSize?: EuiTitleSize; tooltip?: string; } @@ -99,6 +100,7 @@ const HeaderSectionComponent: React.FC = ({ hideSubtitle = false, id, inspectMultiple = false, + inspectTitle, isInspectDisabled, showInspectButton = true, split, @@ -191,7 +193,7 @@ const HeaderSectionComponent: React.FC = ({ queryId={id} multiple={inspectMultiple} showInspectButton={showInspectButton} - title={typeof title === 'string' ? title : undefined} + title={inspectTitle != null ? inspectTitle : title} />
)} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx index b413c249abc53..4cb954f3b14e4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx @@ -15,6 +15,7 @@ import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { DEFAULT_STACK_BY_FIELD, DEFAULT_STACK_BY_FIELD1 } from '../common/config'; import { TestProviders } from '../../../../common/mock'; import { ChartContextMenu } from '../../../pages/detection_engine/chart_panels/chart_context_menu'; +import { TABLE } from '../../../pages/detection_engine/chart_panels/chart_select/translations'; const from = '2022-07-28T08:20:18.966Z'; const to = '2022-07-28T08:20:18.966Z'; @@ -51,6 +52,7 @@ jest.mock('../../../containers/detection_engine/alerts/use_query', () => { describe('AlertsCountPanel', () => { const defaultProps = { + inspectTitle: TABLE, signalIndexName: 'signalIndexName', stackByField0: DEFAULT_STACK_BY_FIELD, stackByField1: DEFAULT_STACK_BY_FIELD1, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx index 7d4eeafe00798..45b378e54a924 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx @@ -32,6 +32,7 @@ interface AlertsCountPanelProps { alignHeader?: 'center' | 'baseline' | 'stretch' | 'flexStart' | 'flexEnd'; chartOptionsContextMenu?: (queryId: string) => React.ReactNode; filters?: Filter[]; + inspectTitle: string; panelHeight?: number; query?: Query; setStackByField0: (stackBy: string) => void; @@ -49,6 +50,7 @@ export const AlertsCountPanel = memo( alignHeader, chartOptionsContextMenu, filters, + inspectTitle, panelHeight, query, runtimeMappings, @@ -158,6 +160,7 @@ export const AlertsCountPanel = memo( void; /** Override all defaults, and only display this field */ onlyField?: AlertsStackByField; @@ -109,6 +110,7 @@ export const AlertsHistogramPanel = memo( defaultStackByOption = DEFAULT_STACK_BY_FIELD, filters, headerChildren, + inspectTitle, onFieldSelected, onlyField, paddingSize = 'm', @@ -336,6 +338,7 @@ export const AlertsHistogramPanel = memo( theme.eui.euiSizeM}; -`; - const FullHeightFlexItem = styled(EuiFlexItem)` height: 100%; `; @@ -132,6 +129,7 @@ const ChartPanelsComponent: React.FC = ({ chartOptionsContextMenu={chartOptionsContextMenu} defaultStackByOption={trendChartStackBy} filters={alertsHistogramDefaultFilters} + inspectTitle={TREND} onFieldSelected={updateCommonStackBy0} panelHeight={TREND_CHART_PANEL_HEIGHT} query={query} @@ -150,7 +148,7 @@ const ChartPanelsComponent: React.FC = ({ )} {alertViewSelection === 'table' && ( - + {isLoadingIndexPattern ? ( ) : ( @@ -158,6 +156,7 @@ const ChartPanelsComponent: React.FC = ({ alignHeader="flexStart" chartOptionsContextMenu={chartOptionsContextMenu} filters={alertsHistogramDefaultFilters} + inspectTitle={TABLE} panelHeight={TABLE_PANEL_HEIGHT} query={query} runtimeMappings={runtimeMappings} @@ -169,7 +168,7 @@ const ChartPanelsComponent: React.FC = ({ title={title} /> )} - + )} {alertViewSelection === 'treemap' && ( @@ -181,6 +180,7 @@ const ChartPanelsComponent: React.FC = ({ addFilter={addFilter} alignHeader="flexStart" chartOptionsContextMenu={chartOptionsContextMenu} + inspectTitle={TREEMAP} isPanelExpanded={isTreemapPanelExpanded} filters={alertsHistogramDefaultFilters} query={query}