From b546bf84be2c9592885054ab6d2174d3c1157b5a Mon Sep 17 00:00:00 2001 From: Andrew Goldstein Date: Wed, 29 Jan 2020 01:59:40 -0700 Subject: [PATCH] [SIEM] Overview page feedback (#56261) (#56276) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [SIEM] Overview page feedback Implements feedback and fixes to the Overview page ### Overview (default theme) ![01-overview-default-theme](https://user-images.githubusercontent.com/4459398/73315509-899c5500-41ed-11ea-9949-82853dd4ba59.png) ### Overview (dark theme) ![02-overview-dark-theme](https://user-images.githubusercontent.com/4459398/73315527-902acc80-41ed-11ea-9701-6a2c5fa40cce.png) ## Highlights * The new order of widgets is Signals, Alerts, Events, Host Events, Network events, per https://github.com/elastic/siem-team/issues/494 * Changed the default `External alerts count` `Stack by` to `event.module` https://github.com/elastic/siem-team/issues/491 * Added `event.module` to the `Events count` histogram https://github.com/elastic/siem-team/issues/491 * Widget titles will no longer include the currently selected `Stack by option`. The widgets will use the same static title text that appears on the other pages (i.e.. `Signals count`, `External alerts count`, and `Events count`) https://github.com/elastic/siem-team/issues/491 * The `Signals count` includes a `Stack by` that defaults to `signal.rule.threat.tatic.name` * Standardized on a 300px widget height for all histograms in the app (thanks @MichaelMarcialis for paring on this!) * The `Open as duplicate timeline` action is `Recent timelines` is now only shown when hovering over a recent timeline ## Loading States * The `Recent timelines` and `Security news` widgets now use the horizontal bar loading indicator * The `Host events` and `Network events` widgets now use the horizontal bar loading indicator * The `Host events` and `Network events` Showing _n_ events subtitles are now hidden on initial load * The counts in the `Host events` and `Network events` Showing _n_ events subtitles are now hidden on initial load * We no longer hide some histogram subtitles after initial load, to prevent shifting of content when a user makes a `Stack by` selection ## News Feed Error State ![news-feed-error-state](https://user-images.githubusercontent.com/4459398/73316060-1e538280-41ef-11ea-83f5-b8d6e9fa3741.png) * Fixed an issue where the `Security news` header was hidden when an invalid URL is configured * Added a space between the word `via` and the `SIEM advanced settings` link * Removed the capital “N” from "News" in the error message ## Misc Visual Changes * Fixed text truncation of the `Severity` column in the `Detections` page's `Signals` table * Added the “showing” subtitle to the `Signals count` histogram on the Detections page * Increased the `Stack by` histogram selector and the `View signals | alerts | events' buttons from 8 to 24px * Tweaked the border rendering in the Overview `Host Events` and `Network events` widget headers * Added 8px of spacing between the Overview `Host Events` and `Network events` widget accordion headers and their contents * Fixed an issue where the `Host events` and `Networ events` widgets didn't render in ie11 https://github.com/elastic/siem-team/issues/499 ## Non-Visual Fixes * Removed an incorrect usage of `usememo` * Removed the placeholder client-side username query from `x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx` * Updated the query of the Overview `Host events` widget to filter by "host.name exists" * Updated the query of the Overview `Network events` widget to filter by "source.ip exists or destination.ip : exists" --- .../components/matrix_histogram/index.tsx | 44 +- .../matrix_histogram/matrix_loader.tsx | 2 +- .../components/matrix_histogram/types.ts | 2 + .../components/matrix_histogram/utils.ts | 6 +- .../public/components/news_feed/news_feed.tsx | 43 +- .../components/news_feed/no_news/index.tsx | 2 +- .../components/news_feed/translations.ts | 2 +- .../overview/loading_placeholders/index.tsx | 26 + .../overview/overview_host/index.test.tsx | 144 +++ .../page/overview/overview_host/index.tsx | 25 +- .../__snapshots__/index.test.tsx.snap | 979 +++++++++--------- .../overview/overview_host_stats/index.tsx | 77 +- .../overview/overview_network/index.test.tsx | 137 +++ .../page/overview/overview_network/index.tsx | 134 +-- .../__snapshots__/index.test.tsx.snap | 555 +++++----- .../overview/overview_network_stats/index.tsx | 75 +- .../components/page/overview/stat_value.tsx | 78 +- .../recent_timelines/counts/index.tsx | 4 +- .../recent_timelines/header/index.tsx | 56 +- .../components/recent_timelines/index.tsx | 47 +- .../recent_timelines/recent_timelines.tsx | 66 +- .../containers/matrix_histogram/index.tsx | 2 + .../overview/overview_host/index.tsx | 58 +- .../components/signals/default_config.tsx | 4 +- .../signals_histogram_panel/index.tsx | 53 +- .../signals_histogram.tsx | 15 +- .../detection_engine/detection_engine.tsx | 3 +- .../plugins/siem/public/pages/hosts/hosts.tsx | 4 +- .../navigation/alerts_query_tab_body.tsx | 4 +- .../navigation/events_query_tab_body.tsx | 4 + .../navigation/alerts_query_tab_body.tsx | 4 +- .../siem/public/pages/network/network.tsx | 4 +- .../overview/alerts_by_category/index.tsx | 151 +-- .../overview/event_counts/index.test.tsx | 51 + .../pages/overview/event_counts/index.tsx | 14 +- .../overview/events_by_dataset/index.tsx | 160 +-- .../siem/public/pages/overview/overview.tsx | 96 +- .../pages/overview/overview_empty/index.tsx | 8 +- .../overview/signals_by_category/index.tsx | 89 +- .../public/pages/overview/translations.ts | 18 +- 40 files changed, 1858 insertions(+), 1388 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/components/page/overview/loading_placeholders/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.test.tsx diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx index cdbac6a67b4ef..04b988f8270f3 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx @@ -6,6 +6,7 @@ import React, { useState, useEffect, useCallback } from 'react'; import { ScaleType } from '@elastic/charts'; +import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSelect, EuiSpacer } from '@elastic/eui'; import { noop } from 'lodash/fp'; @@ -25,8 +26,21 @@ import { import { ChartSeriesData } from '../charts/common'; import { InspectButtonContainer } from '../inspect'; +const DEFAULT_PANEL_HEIGHT = 300; + +const HeaderChildrenFlexItem = styled(EuiFlexItem)` + margin-left: 24px; +`; + +const HistogramPanel = styled(Panel)<{ height?: number }>` + display: flex; + flex-direction: column; + ${({ height }) => (height != null ? `height: ${height}px;` : '')} +`; + export const MatrixHistogramComponent: React.FC = ({ + chartHeight, dataKey, defaultStackByOption, endDate, @@ -43,6 +57,7 @@ export const MatrixHistogramComponent: React.FC { const barchartConfigs = getBarchartConfigs({ + chartHeight, from: startDate, legendPosition, to: endDate, @@ -143,7 +159,7 @@ export const MatrixHistogramComponent: React.FC - + {loading && !isInitialLoading && ( - + = 0 ? subtitleWithCounts : null)} + > + + + {stackByOptions?.length > 1 && ( + + )} + + {headerChildren} + + ) : ( @@ -163,7 +197,7 @@ export const MatrixHistogramComponent: React.FC= 0 ? subtitleWithCounts : null)} + subtitle={!isInitialLoading && (totalCount >= 0 ? subtitleWithCounts : null)} > @@ -176,13 +210,13 @@ export const MatrixHistogramComponent: React.FC )} - {headerChildren} + {headerChildren} )} - + diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/matrix_loader.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/matrix_loader.tsx index 769ef170898b0..036526a14f77d 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/matrix_loader.tsx +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/matrix_loader.tsx @@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import styled from 'styled-components'; const StyledEuiFlexGroup = styled(EuiFlexGroup)` - height: 350px; /* to avoid jump when histogram loads */ + flex 1; `; const MatrixLoaderComponent = () => ( diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/types.ts b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/types.ts index e2b5600d539af..88f8f1ff28fa9 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/types.ts @@ -31,6 +31,7 @@ export type GetSubTitle = (count: number) => string; export type GetTitle = (matrixHistogramOption: MatrixHistogramOption) => string; export interface MatrixHistogramBasicProps { + chartHeight?: number; defaultIndex: string[]; defaultStackByOption: MatrixHistogramOption; endDate: number; @@ -39,6 +40,7 @@ export interface MatrixHistogramBasicProps { id: string; legendPosition?: Position; mapping?: MatrixHistogramMappingTypes; + panelHeight?: number; setQuery: SetQuery; sourceId: string; startDate: number; diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts index 6e932f0c87347..95b1cd806cf6c 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts @@ -11,6 +11,7 @@ import { MatrixHistogramDataTypes, MatrixHistogramMappingTypes } from './types'; import { histogramDateTimeFormatter } from '../utils'; interface GetBarchartConfigsProps { + chartHeight?: number; from: number; legendPosition?: Position; to: number; @@ -20,7 +21,10 @@ interface GetBarchartConfigsProps { showLegend?: boolean; } +export const DEFAULT_CHART_HEIGHT = 174; + export const getBarchartConfigs = ({ + chartHeight, from, legendPosition, to, @@ -65,7 +69,7 @@ export const getBarchartConfigs = ({ }, }, }, - customHeight: 324, + customHeight: chartHeight ?? DEFAULT_CHART_HEIGHT, }); export const formatToChartDataItem = ([key, value]: [ diff --git a/x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx b/x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx index d41ce357d9b7b..98eea1eaa6454 100644 --- a/x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx +++ b/x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx @@ -4,39 +4,42 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; +import { EuiSpacer } from '@elastic/eui'; import React from 'react'; -import { NoNews } from './no_news'; +import { LoadingPlaceholders } from '../page/overview/loading_placeholders'; import { NEWS_FEED_TITLE } from '../../pages/overview/translations'; -import { Post } from './post'; import { SidebarHeader } from '../sidebar_header'; + +import { NoNews } from './no_news'; +import { Post } from './post'; import { NewsItem } from './types'; interface Props { news: NewsItem[] | null | undefined; } -export const NewsFeed = React.memo(({ news }) => { - if (news == null) { - return ; - } - - if (news.length === 0) { - return ; - } +const SHOW_PLACEHOLDERS = 5; +const LINES_PER_LOADING_PLACEHOLDER = 4; - return ( - <> - - {news.map((n: NewsItem) => ( +const NewsFeedComponent: React.FC = ({ news }) => ( + <> + + {news == null ? ( + + ) : news.length === 0 ? ( + + ) : ( + news.map((n: NewsItem) => ( - ))} - - ); -}); + )) + )} + +); + +NewsFeedComponent.displayName = 'NewsFeedComponent'; -NewsFeed.displayName = 'NewsFeed'; +export const NewsFeed = React.memo(NewsFeedComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/news_feed/no_news/index.tsx b/x-pack/legacy/plugins/siem/public/components/news_feed/no_news/index.tsx index bd6648025d2aa..c4e0482c6b30a 100644 --- a/x-pack/legacy/plugins/siem/public/components/news_feed/no_news/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/news_feed/no_news/index.tsx @@ -12,7 +12,7 @@ import * as i18n from '../translations'; export const NoNews = React.memo(() => ( <> - {i18n.NO_NEWS_MESSAGE} + {i18n.NO_NEWS_MESSAGE}{' '} {i18n.ADVANCED_SETTINGS_LINK_TITLE} diff --git a/x-pack/legacy/plugins/siem/public/components/news_feed/translations.ts b/x-pack/legacy/plugins/siem/public/components/news_feed/translations.ts index 71981723cc937..5d3b4171f501e 100644 --- a/x-pack/legacy/plugins/siem/public/components/news_feed/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/news_feed/translations.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; export const NO_NEWS_MESSAGE = i18n.translate('xpack.siem.newsFeed.noNewsMessage', { defaultMessage: - 'Your current News feed URL returned no recent news. You may update the URL or disable security news via', + 'Your current news feed URL returned no recent news. You may update the URL or disable security news via', }); export const ADVANCED_SETTINGS_LINK_TITLE = i18n.translate( diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/loading_placeholders/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/loading_placeholders/index.tsx new file mode 100644 index 0000000000000..1dcc6b75f32e5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/loading_placeholders/index.tsx @@ -0,0 +1,26 @@ +/* + * 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. + */ + +import { EuiLoadingContent, EuiSpacer } from '@elastic/eui'; +import React from 'react'; + +const LoadingPlaceholdersComponent: React.FC<{ + lines: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; + placeholders: number; +}> = ({ lines, placeholders }) => ( + <> + {[...Array(placeholders).keys()].map((_, i) => ( + + + {i !== placeholders - 1 && } + + ))} + +); + +LoadingPlaceholdersComponent.displayName = 'LoadingPlaceholdersComponent'; + +export const LoadingPlaceholders = React.memo(LoadingPlaceholdersComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.test.tsx new file mode 100644 index 0000000000000..568cf032fb01c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.test.tsx @@ -0,0 +1,144 @@ +/* + * 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. + */ + +import { cloneDeep } from 'lodash/fp'; +import { mount } from 'enzyme'; +import React from 'react'; + +import { apolloClientObservable, mockGlobalState, TestProviders } from '../../../../mock'; + +import { OverviewHost } from '.'; +import { createStore, State } from '../../../../store'; +import { overviewHostQuery } from '../../../../containers/overview/overview_host/index.gql_query'; +import { GetOverviewHostQuery } from '../../../../graphql/types'; +import { MockedProvider } from 'react-apollo/test-utils'; +import { wait } from '../../../../lib/helpers'; + +jest.mock('../../../../lib/kibana'); + +const startDate = 1579553397080; +const endDate = 1579639797080; + +interface MockedProvidedQuery { + request: { + query: GetOverviewHostQuery.Query; + fetchPolicy: string; + variables: GetOverviewHostQuery.Variables; + }; + result: { + data: { + source: unknown; + }; + }; +} + +const mockOpenTimelineQueryResults: MockedProvidedQuery[] = [ + { + request: { + query: overviewHostQuery, + fetchPolicy: 'cache-and-network', + variables: { + sourceId: 'default', + timerange: { interval: '12h', from: startDate, to: endDate }, + filterQuery: undefined, + defaultIndex: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + inspect: false, + }, + }, + result: { + data: { + source: { + id: 'default', + OverviewHost: { + auditbeatAuditd: 1, + auditbeatFIM: 1, + auditbeatLogin: 1, + auditbeatPackage: 1, + auditbeatProcess: 1, + auditbeatUser: 1, + endgameDns: 1, + endgameFile: 1, + endgameImageLoad: 1, + endgameNetwork: 1, + endgameProcess: 1, + endgameRegistry: 1, + endgameSecurity: 1, + filebeatSystemModule: 1, + winlogbeatSecurity: 1, + winlogbeatMWSysmonOperational: 1, + }, + }, + }, + }, + }, +]; + +describe('OverviewHost', () => { + const state: State = mockGlobalState; + + let store = createStore(state, apolloClientObservable); + + beforeEach(() => { + const myState = cloneDeep(state); + store = createStore(myState, apolloClientObservable); + }); + + test('it renders the expected widget title', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('[data-test-subj="header-section-title"]') + .first() + .text() + ).toEqual('Host events'); + }); + + test('it renders an empty subtitle while loading', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('[data-test-subj="header-panel-subtitle"]') + .first() + .text() + ).toEqual(''); + }); + + test('it renders the expected event count in the subtitle after loading events', async () => { + const wrapper = mount( + + + + + + ); + await wait(); + wrapper.update(); + + expect( + wrapper + .find('[data-test-subj="header-panel-subtitle"]') + .first() + .text() + ).toEqual('Showing: 16 events'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx index 31d8467025f96..3868885fa29ee 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { isEmpty } from 'lodash/fp'; import { EuiButton, EuiFlexItem, EuiPanel } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -41,7 +42,7 @@ export interface OwnProps { } const OverviewHostStatsManage = manageQuery(OverviewHostStats); -type OverviewHostProps = OwnProps; +export type OverviewHostProps = OwnProps; const OverviewHostComponent: React.FC = ({ endDate, @@ -56,6 +57,7 @@ const OverviewHostComponent: React.FC = ({ = ({ return ( <> + !isEmpty(overviewHost) ? ( + + ) : ( + <>{''} + ) } title={ + - - - + - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - + + + - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - + + + - - - - - - - + + + + + + + + + + - - - - - + + + + + + + + + + + - - - - - - - + + + + + + + + + + - - - + + + + + + + + + + + + - - - + - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - + + + - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - + + + - - - - - - - - - - - - + + + + + + + + + + - - - - - - - + + + + + + + + + + - - - - - + + + + + + + + + + + - - - - - - - + + + + + + + + + + - - - + + + + + + + + + + + + - - - + - - - - - - - - - - + + + + + + + + + + + - - - + - - - - - - - - - - - - - + + + + + + - - - - - - + + + - - - + + + + + + + + + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.tsx index 7dca259ca3db4..b811a3615b148 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.tsx @@ -6,7 +6,7 @@ import { EuiAccordion, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useMemo } from 'react'; +import React from 'react'; import styled from 'styled-components'; import { OverviewHostData } from '../../../../graphql/types'; @@ -203,7 +203,11 @@ const Title = styled.div` margin-left: 24px; `; -export const OverviewHostStats = React.memo(({ data, loading }) => { +const AccordionContent = styled.div` + margin-top: 8px; +`; + +const OverviewHostStatsComponent: React.FC = ({ data, loading }) => { const allHostStats = getOverviewHostStats(data); const allHostStatsCount = allHostStats.reduce((total, stat) => total + stat.count, 0); @@ -213,56 +217,55 @@ export const OverviewHostStats = React.memo(({ data, loading const statsForGroup = allHostStats.filter(s => statGroup.statIds.includes(s.id)); const statsForGroupCount = statsForGroup.reduce((total, stat) => total + stat.count, 0); - const accordionButton = useMemo( - () => ( - - - {statGroup.name} - - - - - - ), - [statGroup, statsForGroupCount, loading, allHostStatsCount] - ); - return ( + - {statsForGroup.map(stat => ( - + buttonContent={ + - - {stat.title} - + {statGroup.name} - + - ))} + } + buttonContentClassName="accordion-button" + > + + {statsForGroup.map(stat => ( + + + + {stat.title} + + + + + + + ))} + - {i !== hostStatGroups.length - 1 && } ); })} ); -}); +}; + +OverviewHostStatsComponent.displayName = 'OverviewHostStatsComponent'; -OverviewHostStats.displayName = 'OverviewHostStats'; +export const OverviewHostStats = React.memo(OverviewHostStatsComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.test.tsx new file mode 100644 index 0000000000000..151bb444cfe75 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.test.tsx @@ -0,0 +1,137 @@ +/* + * 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. + */ + +import { cloneDeep } from 'lodash/fp'; +import { mount } from 'enzyme'; +import React from 'react'; + +import { apolloClientObservable, mockGlobalState, TestProviders } from '../../../../mock'; + +import { OverviewNetwork } from '.'; +import { createStore, State } from '../../../../store'; +import { overviewNetworkQuery } from '../../../../containers/overview/overview_network/index.gql_query'; +import { GetOverviewHostQuery } from '../../../../graphql/types'; +import { MockedProvider } from 'react-apollo/test-utils'; +import { wait } from '../../../../lib/helpers'; + +jest.mock('../../../../lib/kibana'); + +const startDate = 1579553397080; +const endDate = 1579639797080; + +interface MockedProvidedQuery { + request: { + query: GetOverviewHostQuery.Query; + fetchPolicy: string; + variables: GetOverviewHostQuery.Variables; + }; + result: { + data: { + source: unknown; + }; + }; +} + +const mockOpenTimelineQueryResults: MockedProvidedQuery[] = [ + { + request: { + query: overviewNetworkQuery, + fetchPolicy: 'cache-and-network', + variables: { + sourceId: 'default', + timerange: { interval: '12h', from: startDate, to: endDate }, + filterQuery: undefined, + defaultIndex: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + inspect: false, + }, + }, + result: { + data: { + source: { + id: 'default', + OverviewNetwork: { + auditbeatSocket: 1, + filebeatCisco: 1, + filebeatNetflow: 1, + filebeatPanw: 1, + filebeatSuricata: 1, + filebeatZeek: 1, + packetbeatDNS: 1, + packetbeatFlow: 1, + packetbeatTLS: 1, + }, + }, + }, + }, + }, +]; + +describe('OverviewNetwork', () => { + const state: State = mockGlobalState; + + let store = createStore(state, apolloClientObservable); + + beforeEach(() => { + const myState = cloneDeep(state); + store = createStore(myState, apolloClientObservable); + }); + + test('it renders the expected widget title', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('[data-test-subj="header-section-title"]') + .first() + .text() + ).toEqual('Network events'); + }); + + test('it renders an empty subtitle while loading', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('[data-test-subj="header-panel-subtitle"]') + .first() + .text() + ).toEqual(''); + }); + + test('it renders the expected event count in the subtitle after loading events', async () => { + const wrapper = mount( + + + + + + ); + await wait(); + wrapper.update(); + + expect( + wrapper + .find('[data-test-subj="header-panel-subtitle"]') + .first() + .text() + ).toEqual('Showing: 9 events'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.tsx index 36af58c4879a7..100abd997ee6b 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { isEmpty } from 'lodash/fp'; import { EuiButton, EuiFlexItem, EuiPanel } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -23,7 +24,7 @@ import { getOverviewNetworkStats, OverviewNetworkStats } from '../overview_netwo import { getNetworkUrl } from '../../../link_to'; import { InspectButtonContainer } from '../../../inspect'; -export interface OwnProps { +export interface OverviewNetworkProps { startDate: number; endDate: number; filterQuery?: ESQuery | string; @@ -42,35 +43,40 @@ export interface OwnProps { const OverviewNetworkStatsManage = manageQuery(OverviewNetworkStats); -export const OverviewNetwork = React.memo( - ({ endDate, filterQuery, startDate, setQuery }) => { - const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); +const OverviewNetworkComponent: React.FC = ({ + endDate, + filterQuery, + startDate, + setQuery, +}) => { + const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); - return ( - - - - - {({ overviewNetwork, loading, id, inspect, refetch }) => { - const networkEventsCount = getOverviewNetworkStats(overviewNetwork).reduce( - (total, stat) => total + stat.count, - 0 - ); - const formattedNetworkEventsCount = numeral(networkEventsCount).format( - defaultNumberFormat - ); + return ( + + + + + {({ overviewNetwork, loading, id, inspect, refetch }) => { + const networkEventsCount = getOverviewNetworkStats(overviewNetwork).reduce( + (total, stat) => total + stat.count, + 0 + ); + const formattedNetworkEventsCount = numeral(networkEventsCount).format( + defaultNumberFormat + ); - return ( - <> - + ( networkEventsCount, }} /> - } - title={ - - } - > - - - - + ) : ( + <>{''} + ) + } + title={ + + } + > + + + + + + + + ); + }} + + + + + ); +}; - - - ); - }} - - - - - ); - } -); +OverviewNetworkComponent.displayName = 'OverviewNetworkComponent'; -OverviewNetwork.displayName = 'OverviewNetwork'; +export const OverviewNetwork = React.memo(OverviewNetworkComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/__snapshots__/index.test.tsx.snap index 4544c05f7b180..fb59ba382f489 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/__snapshots__/index.test.tsx.snap @@ -4,6 +4,9 @@ exports[`Overview Network Stat Data rendering it renders the default OverviewNet + - - - + - - - - - - - - - - + + + + + + + + + + + - - - + - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - + + + - - - - - - - - - - - - - + + + + + + - - - - - - + + + - - - - - + + + + + + + + + + + - - - - - - - + + + + + + + + + + - - - + + + + + + + + + + + + - - - + - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - + + + - - - - - - - + + + + + + + + + + - - - + + + + + + + + + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.tsx index 123f7f21a75fd..260b1d6895140 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.tsx @@ -6,7 +6,7 @@ import { EuiAccordion, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useMemo } from 'react'; +import React from 'react'; import styled from 'styled-components'; import { OverviewNetworkData } from '../../../../graphql/types'; @@ -126,6 +126,10 @@ const Title = styled.div` margin-left: 24px; `; +const AccordionContent = styled.div` + margin-top: 8px; +`; + export const OverviewNetworkStats = React.memo(({ data, loading }) => { const allNetworkStats = getOverviewNetworkStats(data); const allNetworkStatsCount = allNetworkStats.reduce((total, stat) => total + stat.count, 0); @@ -136,54 +140,51 @@ export const OverviewNetworkStats = React.memo(({ data, lo const statsForGroup = allNetworkStats.filter(s => statGroup.statIds.includes(s.id)); const statsForGroupCount = statsForGroup.reduce((total, stat) => total + stat.count, 0); - const accordionButton = useMemo( - () => ( - - - {statGroup.name} - - - - - - ), - [statGroup, statsForGroupCount, loading, allNetworkStatsCount] - ); - return ( + - {statsForGroup.map(stat => ( - + buttonContent={ + - - {stat.title} - + {statGroup.name} - + - ))} + } + buttonContentClassName="accordion-button" + > + + {statsForGroup.map(stat => ( + + + + {stat.title} + + + + + + + ))} + - {i !== networkStatGroups.length - 1 && } ); })} diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/stat_value.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/stat_value.tsx index 5a496ba78eb6c..7615001eec9da 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/stat_value.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/stat_value.tsx @@ -4,51 +4,67 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiProgress, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiLoadingContent, EuiProgress, EuiText } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants'; import { useUiSetting$ } from '../../../lib/kibana'; const ProgressContainer = styled.div` - width: 100px; + margin-left: 8px; + min-width: 100px; `; -export const StatValue = React.memo<{ +const LoadingContent = styled(EuiLoadingContent)` + .euiLoadingContent__singleLine { + margin-bottom: 0px; + } +`; + +const StatValueComponent: React.FC<{ count: number; - isLoading: boolean; isGroupStat: boolean; + isLoading: boolean; max: number; -}>(({ count, isGroupStat, isLoading, max }) => { +}> = ({ count, isGroupStat, isLoading, max }) => { const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + const [isInitialLoading, setIsInitialLoading] = useState(true); + + useEffect(() => { + if (isInitialLoading && !isLoading) { + setIsInitialLoading(false); + } + }, [isLoading, isInitialLoading, setIsInitialLoading]); return ( - <> - {isLoading ? ( - - ) : ( - - - - {numeral(count).format(defaultNumberFormat)} - - - - - - - - - )} - + + + {!isInitialLoading && ( + + {numeral(count).format(defaultNumberFormat)} + + )} + + + + {isLoading ? ( + + ) : ( + + )} + + + ); -}); +}; + +StatValueComponent.displayName = 'StatValueComponent'; -StatValue.displayName = 'StatValue'; +export const StatValue = React.memo(StatValueComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx index 42ac3c19ff792..e04b6319cfb24 100644 --- a/x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx @@ -45,14 +45,14 @@ export const RecentTimelineCounts = React.memo<{ timeline: OpenTimelineResult; }>(({ timeline }) => { return ( - <> +
- +
); }); diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/header/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_timelines/header/index.tsx index 886a2345248a2..89c7ae6f1eed9 100644 --- a/x-pack/legacy/plugins/siem/public/components/recent_timelines/header/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/header/index.tsx @@ -4,62 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiFlexGroup, - EuiFlexItem, - EuiText, - EuiLink, - EuiToolTip, - EuiButtonIcon, -} from '@elastic/eui'; -import React from 'react'; +import { EuiText, EuiLink } from '@elastic/eui'; +import React, { useCallback } from 'react'; import { isUntitled } from '../../open_timeline/helpers'; import { OnOpenTimeline, OpenTimelineResult } from '../../open_timeline/types'; - import * as i18n from '../translations'; -export interface MeApiResponse { - username: string; -} - export const RecentTimelineHeader = React.memo<{ onOpenTimeline: OnOpenTimeline; timeline: OpenTimelineResult; -}>(({ onOpenTimeline, timeline }) => { - const { title, savedObjectId } = timeline; +}>(({ onOpenTimeline, timeline, timeline: { title, savedObjectId } }) => { + const onClick = useCallback( + () => onOpenTimeline({ duplicate: false, timelineId: `${savedObjectId}` }), + [onOpenTimeline, savedObjectId] + ); return ( - - - - onOpenTimeline({ duplicate: false, timelineId: `${savedObjectId}` })} - > - {isUntitled(timeline) ? i18n.UNTITLED_TIMELINE : title} - - - - - - - - onOpenTimeline({ - duplicate: true, - timelineId: `${savedObjectId}`, - }) - } - size="s" - /> - - - + + {isUntitled(timeline) ? i18n.UNTITLED_TIMELINE : title} + ); }); diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx index f1e22d1901d47..d5157e81b0fc8 100644 --- a/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx @@ -5,23 +5,22 @@ */ import ApolloClient from 'apollo-client'; -import { EuiHorizontalRule, EuiLink, EuiLoadingSpinner, EuiText } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui'; +import React, { useCallback } from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { ActionCreator } from 'typescript-fsa'; -import chrome from 'ui/chrome'; import { AllTimelinesQuery } from '../../containers/timeline/all'; import { SortFieldTimeline, Direction } from '../../graphql/types'; -import { fetchUsername, getMeApiUrl } from './helpers'; import { queryTimelineById, dispatchUpdateTimeline } from '../open_timeline/helpers'; import { DispatchUpdateTimeline, OnOpenTimeline } from '../open_timeline/types'; -import { RecentTimelines } from './recent_timelines'; +import { LoadingPlaceholders } from '../page/overview/loading_placeholders'; import { updateIsLoading as dispatchUpdateIsLoading } from '../../store/timeline/actions'; -import { FilterMode } from './types'; +import { RecentTimelines } from './recent_timelines'; import * as i18n from './translations'; +import { FilterMode } from './types'; export interface MeApiResponse { username: string; @@ -42,8 +41,6 @@ export type Props = OwnProps & DispatchProps; const StatefulRecentTimelinesComponent = React.memo( ({ apolloClient, filterBy, updateIsLoading, updateTimeline }) => { const actionDispatcher = updateIsLoading as ActionCreator<{ id: string; isLoading: boolean }>; - const [username, setUsername] = useState(undefined); - const LoadingSpinner = useMemo(() => , []); const onOpenTimeline: OnOpenTimeline = useCallback( ({ duplicate, timelineId }: { duplicate: boolean; timelineId: string }) => { queryTimelineById({ @@ -57,38 +54,6 @@ const StatefulRecentTimelinesComponent = React.memo( [apolloClient, updateIsLoading, updateTimeline] ); - useEffect(() => { - let canceled = false; - - const fetchData = async () => { - try { - const loggedInUser = await fetchUsername(getMeApiUrl(chrome.getBasePath)); - - if (!canceled) { - setUsername(loggedInUser); - } - } catch (e) { - if (!canceled) { - setUsername(null); - } - } - }; - - fetchData(); - - return () => { - canceled = true; - }; - }, []); - - if (username === undefined) { - return LoadingSpinner; - } else if (username == null) { - return null; - } - - // TODO: why does `createdBy: ` specified as a `search` query does not match results? - const noTimelinesMessage = filterBy === 'favorites' ? i18n.NO_FAVORITE_TIMELINES : i18n.NO_TIMELINES; @@ -108,7 +73,7 @@ const StatefulRecentTimelinesComponent = React.memo( {({ timelines, loading }) => ( <> {loading ? ( - <>{LoadingSpinner} + ) : ( {timelines.map((t, i) => ( -
- - - {t.description && t.description.length && ( - <> - - - {t.description} - - - )} - {i !== timelines.length - 1 && } -
+ + ( + + + + + {t.description && t.description.length && ( + <> + + + {t.description} + + + )} + + + {showHoverContent && ( + + + + onOpenTimeline({ + duplicate: true, + timelineId: `${t.savedObjectId}`, + }) + } + size="s" + /> + + + )} + + )} + /> + <>{i !== timelines.length - 1 && } + ))} ); diff --git a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx index d5fd325bb9a26..9e0b1579a7b65 100644 --- a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx @@ -24,6 +24,7 @@ import { UpdateDateRange } from '../../components/charts/common'; import { SetQuery } from '../../pages/hosts/navigation/types'; export interface OwnProps extends QueryTemplateProps { + chartHeight?: number; dataKey: string | string[]; defaultStackByOption: MatrixHistogramOption; errorMessage: string; @@ -37,6 +38,7 @@ export interface OwnProps extends QueryTemplateProps { isEventsHistogram?: boolean; legendPosition?: Position; mapping?: MatrixHistogramMappingTypes; + panelHeight?: number; query: Maybe; setQuery: SetQuery; showLegend?: boolean; diff --git a/x-pack/legacy/plugins/siem/public/containers/overview/overview_host/index.tsx b/x-pack/legacy/plugins/siem/public/containers/overview/overview_host/index.tsx index 36cadd7872cc8..8c40c4044a746 100644 --- a/x-pack/legacy/plugins/siem/public/containers/overview/overview_host/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/overview/overview_host/index.tsx @@ -41,34 +41,36 @@ export interface OverviewHostProps extends QueryTemplateProps { } const OverviewHostComponentQuery = React.memo( - ({ id = ID, children, filterQuery, isInspected, sourceId, startDate, endDate }) => ( - - query={overviewHostQuery} - fetchPolicy={getDefaultFetchPolicy()} - variables={{ - sourceId, - timerange: { - interval: '12h', - from: startDate, - to: endDate, - }, - filterQuery: createFilter(filterQuery), - defaultIndex: useUiSetting(DEFAULT_INDEX_KEY), - inspect: isInspected, - }} - > - {({ data, loading, refetch }) => { - const overviewHost = getOr({}, `source.OverviewHost`, data); - return children({ - id, - inspect: getOr(null, 'source.OverviewHost.inspect', data), - overviewHost, - loading, - refetch, - }); - }} - - ) + ({ id = ID, children, filterQuery, isInspected, sourceId, startDate, endDate }) => { + return ( + + query={overviewHostQuery} + fetchPolicy={getDefaultFetchPolicy()} + variables={{ + sourceId, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + filterQuery: createFilter(filterQuery), + defaultIndex: useUiSetting(DEFAULT_INDEX_KEY), + inspect: isInspected, + }} + > + {({ data, loading, refetch }) => { + const overviewHost = getOr({}, `source.OverviewHost`, data); + return children({ + id, + inspect: getOr(null, 'source.OverviewHost.inspect', data), + overviewHost, + loading, + refetch, + }); + }} + + ); + } ); OverviewHostComponentQuery.displayName = 'OverviewHostComponentQuery'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx index e6bbffa4fd927..6cf515050a39f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx @@ -114,13 +114,13 @@ export const signalsHeaders: ColumnHeader[] = [ columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.severity', label: i18n.SIGNALS_HEADERS_SEVERITY, - width: 100, + width: 105, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.risk_score', label: i18n.SIGNALS_HEADERS_RISK_SCORE, - width: 120, + width: 115, }, { columnHeaderType: defaultColumnHeaderType, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx index 2cdafe38a7434..29aaa951ff71a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { Position } from '@elastic/charts'; -import { EuiButton, EuiSelect, EuiPanel } from '@elastic/eui'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSelect, EuiPanel } from '@elastic/eui'; import numeral from '@elastic/numeral'; import React, { memo, useCallback, useMemo, useState, useEffect } from 'react'; import styled from 'styled-components'; @@ -12,8 +12,6 @@ import { isEmpty } from 'lodash/fp'; import { HeaderSection } from '../../../../components/header_section'; import { SignalsHistogram } from './signals_histogram'; - -import * as i18n from './translations'; import { Query } from '../../../../../../../../../src/plugins/data/common/query'; import { esFilters, esQuery } from '../../../../../../../../../src/plugins/data/common/es_query'; import { RegisterQuery, SignalsHistogramOption, SignalsAggregation, SignalsTotal } from './types'; @@ -26,8 +24,14 @@ import { useQuerySignals } from '../../../../containers/detection_engine/signals import { MatrixLoader } from '../../../../components/matrix_histogram/matrix_loader'; import { formatSignalsData, getSignalsHistogramQuery } from './helpers'; +import * as i18n from './translations'; + +const DEFAULT_PANEL_HEIGHT = 300; -const StyledEuiPanel = styled(EuiPanel)` +const StyledEuiPanel = styled(EuiPanel)<{ height?: number }>` + display: flex; + flex-direction: column; + ${({ height }) => (height != null ? `height: ${height}px;` : '')} position: relative; `; @@ -38,7 +42,12 @@ const defaultTotalSignalsObj: SignalsTotal = { export const DETECTIONS_HISTOGRAM_ID = 'detections-histogram'; +const ViewSignalsFlexItem = styled(EuiFlexItem)` + margin-left: 24px; +`; + interface SignalsHistogramPanelProps { + chartHeight?: number; defaultStackByOption?: SignalsHistogramOption; deleteQuery?: ({ id }: { id: string }) => void; filters?: esFilters.Filter[]; @@ -46,6 +55,7 @@ interface SignalsHistogramPanelProps { query?: Query; legendPosition?: Position; loadingInitial?: boolean; + panelHeight?: number; signalIndexName: string | null; setQuery: (params: RegisterQuery) => void; showLinkToSignals?: boolean; @@ -58,6 +68,7 @@ interface SignalsHistogramPanelProps { export const SignalsHistogramPanel = memo( ({ + chartHeight, defaultStackByOption = signalsHistogramOptions[0], deleteQuery, filters, @@ -65,6 +76,7 @@ export const SignalsHistogramPanel = memo( from, legendPosition = 'right', loadingInitial = false, + panelHeight = DEFAULT_PANEL_HEIGHT, setQuery, signalIndexName, showLinkToSignals = false, @@ -171,7 +183,7 @@ export const SignalsHistogramPanel = memo( return ( - + {isInitialLoading ? ( <> @@ -184,26 +196,33 @@ export const SignalsHistogramPanel = memo( title={title} subtitle={showTotalSignalsCount && totalSignals} > - {stackByOptions && ( - - )} - {showLinkToSignals && ( - {i18n.VIEW_SIGNALS} - )} + + + {stackByOptions && ( + + )} + + {showLinkToSignals && ( + + {i18n.VIEW_SIGNALS} + + )} + )} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram.tsx index 9d2af1e78f285..92f6740e4d767 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram.tsx @@ -19,7 +19,10 @@ import { useTheme } from '../../../../components/charts/common'; import { histogramDateTimeFormatter } from '../../../../components/utils'; import { HistogramData } from './types'; +const DEFAULT_CHART_HEIGHT = 174; + interface HistogramSignalsProps { + chartHeight?: number; from: number; legendPosition?: Position; loading: boolean; @@ -29,7 +32,15 @@ interface HistogramSignalsProps { } export const SignalsHistogram = React.memo( - ({ to, from, legendPosition = 'right', data, updateDateRange, loading }) => { + ({ + chartHeight = DEFAULT_CHART_HEIGHT, + data, + from, + legendPosition = 'right', + loading, + to, + updateDateRange, + }) => { const theme = useTheme(); return ( @@ -43,7 +54,7 @@ export const SignalsHistogram = React.memo( /> )} - + from={from} loadingInitial={loading} query={query} - signalIndexName={signalIndexName} setQuery={setQuery} + showTotalSignalsCount={true} + signalIndexName={signalIndexName} stackByOptions={signalsHistogramOptions} to={to} updateDateRange={updateDateRangeCallback} diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx index f989f5a9ba6dd..2e2986fb632b1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx @@ -36,7 +36,7 @@ import { HostsTabs } from './hosts_tabs'; import { navTabsHosts } from './nav_tabs'; import * as i18n from './translations'; import { HostsComponentProps, HostsComponentReduxProps } from './types'; -import { filterAlertsHosts } from './navigation'; +import { filterHostData } from './navigation'; import { HostsTableType } from '../../store/hosts/model'; const KpiHostsComponentManage = manageQuery(KpiHostsComponent); @@ -58,7 +58,7 @@ export const HostsComponent = React.memo( const { tabName } = useParams(); const tabsFilters = React.useMemo(() => { if (tabName === HostsTableType.alerts) { - return filters.length > 0 ? [...filters, ...filterAlertsHosts] : filterAlertsHosts; + return filters.length > 0 ? [...filters, ...filterHostData] : filterHostData; } return filters; }, [tabName, filters]); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/alerts_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/alerts_query_tab_body.tsx index b893acd4dbb3b..e9809766dc01b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/alerts_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/alerts_query_tab_body.tsx @@ -10,7 +10,7 @@ import { esFilters } from '../../../../../../../../src/plugins/data/common/es_qu import { AlertsView } from '../../../components/alerts_viewer'; import { AlertsComponentQueryProps } from './types'; -export const filterAlertsHosts: esFilters.Filter[] = [ +export const filterHostData: esFilters.Filter[] = [ { query: { bool: { @@ -44,7 +44,7 @@ export const filterAlertsHosts: esFilters.Filter[] = [ export const HostAlertsQueryTabBody = React.memo((alertsProps: AlertsComponentQueryProps) => { const { pageFilters, ...rest } = alertsProps; const hostPageFilters = useMemo( - () => (pageFilters != null ? [...filterAlertsHosts, ...pageFilters] : filterAlertsHosts), + () => (pageFilters != null ? [...filterHostData, ...pageFilters] : filterHostData), [pageFilters] ); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx index e3d1f6397044c..9ee1f994704ea 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx @@ -26,6 +26,10 @@ export const eventsStackByOptions: MatrixHistogramOption[] = [ text: 'event.dataset', value: 'event.dataset', }, + { + text: 'event.module', + value: 'event.module', + }, ]; export const EventsQueryTabBody = ({ diff --git a/x-pack/legacy/plugins/siem/public/pages/network/navigation/alerts_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/network/navigation/alerts_query_tab_body.tsx index 3eeabd3007afa..88fadab1d3f0e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/navigation/alerts_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/navigation/alerts_query_tab_body.tsx @@ -10,7 +10,7 @@ import { esFilters } from '../../../../../../../../src/plugins/data/common/es_qu import { AlertsView } from '../../../components/alerts_viewer'; import { NetworkComponentQueryProps } from './types'; -export const filterAlertsNetwork: esFilters.Filter[] = [ +export const filterNetworkData: esFilters.Filter[] = [ { query: { bool: { @@ -62,7 +62,7 @@ export const filterAlertsNetwork: esFilters.Filter[] = [ ]; export const NetworkAlertsQueryTabBody = React.memo((alertsProps: NetworkComponentQueryProps) => ( - + )); NetworkAlertsQueryTabBody.displayName = 'NetworkAlertsQueryTabBody'; diff --git a/x-pack/legacy/plugins/siem/public/pages/network/network.tsx b/x-pack/legacy/plugins/siem/public/pages/network/network.tsx index bd8552aa608af..0f9eaaef48aa7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/network.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/network.tsx @@ -29,7 +29,7 @@ import { networkModel, State, inputsSelectors } from '../../store'; import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { SpyRoute } from '../../utils/route/spy_routes'; import { navTabsNetwork, NetworkRoutes, NetworkRoutesLoading } from './navigation'; -import { filterAlertsNetwork } from './navigation/alerts_query_tab_body'; +import { filterNetworkData } from './navigation/alerts_query_tab_body'; import { NetworkEmptyPage } from './network_empty_page'; import * as i18n from './translations'; import { NetworkComponentProps } from './types'; @@ -56,7 +56,7 @@ const NetworkComponent = React.memo( const tabsFilters = useMemo(() => { if (tabName === NetworkRouteType.alerts) { - return filters.length > 0 ? [...filters, ...filterAlertsNetwork] : filterAlertsNetwork; + return filters.length > 0 ? [...filters, ...filterNetworkData] : filterNetworkData; } return filters; }, [tabName, filters]); diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/alerts_by_category/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/alerts_by_category/index.tsx index 7d00fb0c18006..07b0176172401 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/alerts_by_category/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/alerts_by_category/index.tsx @@ -7,9 +7,8 @@ import { EuiButton } from '@elastic/eui'; import numeral from '@elastic/numeral'; import React, { useCallback, useEffect, useMemo } from 'react'; -import { esFilters, IIndexPattern, Query } from 'src/plugins/data/public'; -import styled from 'styled-components'; +import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants'; import { ERROR_FETCHING_ALERTS_DATA, SHOWING, @@ -22,10 +21,14 @@ import { MatrixHistogramGqlQuery } from '../../../containers/matrix_histogram/in import { useKibana, useUiSetting$ } from '../../../lib/kibana'; import { convertToBuildEsQuery } from '../../../lib/keury'; import { SetAbsoluteRangeDatePicker } from '../../network/types'; -import { esQuery } from '../../../../../../../../src/plugins/data/public'; +import { + esFilters, + esQuery, + IIndexPattern, + Query, +} from '../../../../../../../../src/plugins/data/public'; import { inputsModel } from '../../../store'; import { HostsType } from '../../../store/hosts/model'; -import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants'; import * as i18n from '../translations'; @@ -33,6 +36,7 @@ const ID = 'alertsByCategoryOverview'; const NO_FILTERS: esFilters.Filter[] = []; const DEFAULT_QUERY: Query = { query: '', language: 'kuery' }; +const DEFAULT_STACK_BY = 'event.module'; interface Props { deleteQuery?: ({ id }: { id: string }) => void; @@ -51,80 +55,77 @@ interface Props { to: number; } -const ViewAlertsButton = styled(EuiButton)` - margin-left: 8px; -`; +const AlertsByCategoryComponent: React.FC = ({ + deleteQuery, + filters = NO_FILTERS, + from, + hideHeaderChildren = false, + indexPattern, + query = DEFAULT_QUERY, + setAbsoluteRangeDatePicker, + setQuery, + to, +}) => { + useEffect(() => { + return () => { + if (deleteQuery) { + deleteQuery({ id: ID }); + } + }; + }, []); + + const kibana = useKibana(); + const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); -export const AlertsByCategory = React.memo( - ({ - deleteQuery, - filters = NO_FILTERS, - from, - hideHeaderChildren = false, - indexPattern, - query = DEFAULT_QUERY, - setAbsoluteRangeDatePicker, - setQuery, - to, - }) => { - useEffect(() => { - return () => { - if (deleteQuery) { - deleteQuery({ id: ID }); - } - }; - }, []); + const updateDateRangeCallback = useCallback( + (min: number, max: number) => { + setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max }); + }, + [setAbsoluteRangeDatePicker] + ); + const alertsCountViewAlertsButton = useMemo( + () => {i18n.VIEW_ALERTS}, + [] + ); - const kibana = useKibana(); - const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + const getSubtitle = useCallback( + (totalCount: number) => + `${SHOWING}: ${numeral(totalCount).format(defaultNumberFormat)} ${UNIT(totalCount)}`, + [] + ); - const updateDateRangeCallback = useCallback( - (min: number, max: number) => { - setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max }); - }, - [setAbsoluteRangeDatePicker] - ); - const alertsCountViewAlertsButton = useMemo( - () => ( - {i18n.VIEW_ALERTS} - ), - [] - ); + const defaultStackByOption = + alertsStackByOptions.find(o => o.text === DEFAULT_STACK_BY) ?? alertsStackByOptions[0]; - const getSubtitle = useCallback( - (totalCount: number) => - `${SHOWING}: ${numeral(totalCount).format(defaultNumberFormat)} ${UNIT(totalCount)}`, - [] - ); + return ( + + ); +}; - return ( - - ); - } -); +AlertsByCategoryComponent.displayName = 'AlertsByCategoryComponent'; -AlertsByCategory.displayName = 'AlertsByCategory'; +export const AlertsByCategory = React.memo(AlertsByCategoryComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.test.tsx new file mode 100644 index 0000000000000..f5419a3ff50e9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.test.tsx @@ -0,0 +1,51 @@ +/* + * 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. + */ + +import { mount } from 'enzyme'; +import React from 'react'; + +import { OverviewHostProps } from '../../../components/page/overview/overview_host'; +import { OverviewNetworkProps } from '../../../components/page/overview/overview_network'; +import { mockIndexPattern, TestProviders } from '../../../mock'; + +import { EventCounts } from '.'; + +describe('EventCounts', () => { + const from = 1579553397080; + const to = 1579639797080; + + test('it filters the `Host events` widget with a `host.name` `exists` filter', () => { + const wrapper = mount( + + + + ); + + expect( + (wrapper + .find('[data-test-subj="overview-host-query"]') + .first() + .props() as OverviewHostProps).filterQuery + ).toContain('[{"bool":{"should":[{"exists":{"field":"host.name"}}]'); + }); + + test('it filters the `Network events` widget with a `source.ip` or `destination.ip` `exists` filter', () => { + const wrapper = mount( + + + + ); + + expect( + (wrapper + .find('[data-test-subj="overview-network-query"]') + .first() + .props() as OverviewNetworkProps).filterQuery + ).toContain( + '{"bool":{"filter":[{"bool":{"should":[{"bool":{"should":[{"exists":{"field":"source.ip"}}],"minimum_should_match":1}},{"bool":{"should":[{"exists":{"field":"destination.ip"}}],"minimum_should_match":1}}],"minimum_should_match":1}}]}}]' + ); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.tsx index 2a35dbf96d6d7..b13f723772c95 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.tsx @@ -6,14 +6,20 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; -import { esFilters, IIndexPattern, Query } from 'src/plugins/data/public'; import styled from 'styled-components'; import { OverviewHost } from '../../../components/page/overview/overview_host'; import { OverviewNetwork } from '../../../components/page/overview/overview_network'; +import { filterHostData } from '../../hosts/navigation/alerts_query_tab_body'; import { useKibana } from '../../../lib/kibana'; import { convertToBuildEsQuery } from '../../../lib/keury'; -import { esQuery } from '../../../../../../../../src/plugins/data/public'; +import { filterNetworkData } from '../../network/navigation/alerts_query_tab_body'; +import { + esFilters, + esQuery, + IIndexPattern, + Query, +} from '../../../../../../../../src/plugins/data/public'; import { inputsModel } from '../../../store'; const HorizontalSpacer = styled(EuiFlexItem)` @@ -56,7 +62,7 @@ const EventCountsComponent: React.FC = ({ config: esQuery.getEsQueryConfig(kibana.services.uiSettings), indexPattern, queries: [query], - filters, + filters: [...filters, ...filterHostData], })} startDate={from} setQuery={setQuery} @@ -72,7 +78,7 @@ const EventCountsComponent: React.FC = ({ config: esQuery.getEsQueryConfig(kibana.services.uiSettings), indexPattern, queries: [query], - filters, + filters: [...filters, ...filterNetworkData], })} startDate={from} setQuery={setQuery} diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx index 191b4a2592695..3269c1e585f5a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx @@ -7,8 +7,6 @@ import { EuiButton } from '@elastic/eui'; import numeral from '@elastic/numeral'; import React, { useCallback, useEffect, useMemo } from 'react'; -import { esFilters, IIndexPattern, Query } from 'src/plugins/data/public'; -import styled from 'styled-components'; import { ERROR_FETCHING_EVENTS_DATA, @@ -20,10 +18,14 @@ import { SetAbsoluteRangeDatePicker } from '../../network/types'; import { getTabsOnHostsUrl } from '../../../components/link_to/redirect_to_hosts'; import { MatrixHistogramContainer } from '../../../containers/matrix_histogram'; import { MatrixHistogramGqlQuery } from '../../../containers/matrix_histogram/index.gql_query'; -import { MatrixHistogramOption } from '../../../components/matrix_histogram/types'; import { eventsStackByOptions } from '../../hosts/navigation'; import { useKibana, useUiSetting$ } from '../../../lib/kibana'; -import { esQuery } from '../../../../../../../../src/plugins/data/public'; +import { + esFilters, + esQuery, + IIndexPattern, + Query, +} from '../../../../../../../../src/plugins/data/public'; import { inputsModel } from '../../../store'; import { HostsTableType, HostsType } from '../../../store/hosts/model'; import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants'; @@ -32,6 +34,7 @@ import * as i18n from '../translations'; const NO_FILTERS: esFilters.Filter[] = []; const DEFAULT_QUERY: Query = { query: '', language: 'kuery' }; +const DEFAULT_STACK_BY = 'event.dataset'; const ID = 'eventsByDatasetOverview'; @@ -51,85 +54,82 @@ interface Props { to: number; } -const ViewEventsButton = styled(EuiButton)` - margin-left: 8px; -`; +const EventsByDatasetComponent: React.FC = ({ + deleteQuery, + filters = NO_FILTERS, + from, + indexPattern, + query = DEFAULT_QUERY, + setAbsoluteRangeDatePicker, + setQuery, + to, +}) => { + useEffect(() => { + return () => { + if (deleteQuery) { + deleteQuery({ id: ID }); + } + }; + }, []); + + const kibana = useKibana(); + const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + + const updateDateRangeCallback = useCallback( + (min: number, max: number) => { + setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max }); + }, + [setAbsoluteRangeDatePicker] + ); + const eventsCountViewEventsButton = useMemo( + () => {i18n.VIEW_EVENTS}, + [] + ); -export const EventsByDataset = React.memo( - ({ - deleteQuery, - filters = NO_FILTERS, - from, - indexPattern, - query = DEFAULT_QUERY, - setAbsoluteRangeDatePicker, - setQuery, - to, - }) => { - useEffect(() => { - return () => { - if (deleteQuery) { - deleteQuery({ id: ID }); - } - }; - }, []); + const getSubtitle = useCallback( + (totalCount: number) => + `${SHOWING}: ${numeral(totalCount).format(defaultNumberFormat)} ${UNIT(totalCount)}`, + [] + ); - const kibana = useKibana(); - const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + const defaultStackByOption = + eventsStackByOptions.find(o => o.text === DEFAULT_STACK_BY) ?? eventsStackByOptions[0]; - const updateDateRangeCallback = useCallback( - (min: number, max: number) => { - setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max }); - }, - [setAbsoluteRangeDatePicker] - ); - const eventsCountViewEventsButton = useMemo( - () => ( - - {i18n.VIEW_EVENTS} - - ), - [] - ); + const filterQuery = useMemo( + () => + convertToBuildEsQuery({ + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), + indexPattern, + queries: [query], + filters, + }), + [kibana, indexPattern, query, filters] + ); - const getTitle = useCallback( - (option: MatrixHistogramOption) => i18n.EVENTS_COUNT_BY(option.text), - [] - ); - const getSubtitle = useCallback( - (totalCount: number) => - `${SHOWING}: ${numeral(totalCount).format(defaultNumberFormat)} ${UNIT(totalCount)}`, - [] - ); + return ( + + ); +}; - return ( - - ); - } -); +EventsByDatasetComponent.displayName = 'EventsByDatasetComponent'; -EventsByDataset.displayName = 'EventsByDataset'; +export const EventsByDataset = React.memo(EventsByDatasetComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx index 0b588e31be879..2009878a51c61 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx @@ -64,51 +64,57 @@ const OverviewComponent: React.FC = ({ {({ from, deleteQuery, setQuery, to }) => ( - <> - - - - - - - - - - + + + + + + + + + + + + + + + + + + )} diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/overview_empty/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/overview_empty/index.tsx index 43883515574ac..9565b764b09e7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/overview_empty/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/overview_empty/index.tsx @@ -13,7 +13,7 @@ import { useKibana } from '../../../lib/kibana'; const basePath = chrome.getBasePath(); -export const OverviewEmpty = React.memo(() => { +const OverviewEmptyComponent: React.FC = () => { const docLinks = useKibana().services.docLinks; return ( @@ -30,6 +30,8 @@ export const OverviewEmpty = React.memo(() => { title={i18nCommon.EMPTY_TITLE} /> ); -}); +}; -OverviewEmpty.displayName = 'OverviewEmpty'; +OverviewEmptyComponent.displayName = 'OverviewEmptyComponent'; + +export const OverviewEmpty = React.memo(OverviewEmptyComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/signals_by_category/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/signals_by_category/index.tsx index fcf726723bdc1..7b25c6838a787 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/signals_by_category/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/signals_by_category/index.tsx @@ -5,16 +5,18 @@ */ import React, { useCallback } from 'react'; -import { esFilters, IIndexPattern, Query } from 'src/plugins/data/public'; -import { useSignalIndex } from '../../../containers/detection_engine/signals/use_signal_index'; import { SignalsHistogramPanel } from '../../detection_engine/components/signals_histogram_panel'; +import { signalsHistogramOptions } from '../../detection_engine/components/signals_histogram_panel/config'; +import { useSignalIndex } from '../../../containers/detection_engine/signals/use_signal_index'; import { SetAbsoluteRangeDatePicker } from '../../network/types'; +import { esFilters, IIndexPattern, Query } from '../../../../../../../../src/plugins/data/public'; import { inputsModel } from '../../../store'; import * as i18n from '../translations'; -const NO_FILTERS: esFilters.Filter[] = []; const DEFAULT_QUERY: Query = { query: '', language: 'kuery' }; +const DEFAULT_STACK_BY = 'signal.rule.threat.tactic.name'; +const NO_FILTERS: esFilters.Filter[] = []; interface Props { deleteQuery?: ({ id }: { id: string }) => void; @@ -32,47 +34,46 @@ interface Props { to: number; } -export const SignalsByCategory = React.memo( - ({ - deleteQuery, - filters = NO_FILTERS, - from, - query = DEFAULT_QUERY, - setAbsoluteRangeDatePicker, - setQuery, - to, - }) => { - const updateDateRangeCallback = useCallback( - (min: number, max: number) => { - setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max }); - }, - [setAbsoluteRangeDatePicker] - ); - const defaultStackByOption = { - text: `${i18n.SIGNALS_BY_CATEGORY}`, - value: 'signal.rule.threat', - }; +const SignalsByCategoryComponent: React.FC = ({ + deleteQuery, + filters = NO_FILTERS, + from, + query = DEFAULT_QUERY, + setAbsoluteRangeDatePicker, + setQuery, + to, +}) => { + const { signalIndexName } = useSignalIndex(); + const updateDateRangeCallback = useCallback( + (min: number, max: number) => { + setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max }); + }, + [setAbsoluteRangeDatePicker] + ); + + const defaultStackByOption = + signalsHistogramOptions.find(o => o.text === DEFAULT_STACK_BY) ?? signalsHistogramOptions[0]; - const { signalIndexName } = useSignalIndex(); + return ( + + ); +}; - return ( - - ); - } -); +SignalsByCategoryComponent.displayName = 'SignalsByCategoryComponent'; -SignalsByCategory.displayName = 'SignalsByCategory'; +export const SignalsByCategory = React.memo(SignalsByCategoryComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/translations.ts b/x-pack/legacy/plugins/siem/public/pages/overview/translations.ts index 656abd3dc0570..e20083bf51772 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/overview/translations.ts @@ -6,21 +6,13 @@ import { i18n } from '@kbn/i18n'; -export const ALERTS_COUNT_BY = (groupByField: string) => - i18n.translate('xpack.siem.overview.alertsCountByTitle', { - values: { groupByField }, - defaultMessage: 'Alerts count by {groupByField}', - }); - export const ALERTS_GRAPH_TITLE = i18n.translate('xpack.siem.overview.alertsGraphTitle', { defaultMessage: 'External alerts count', }); -export const EVENTS_COUNT_BY = (groupByField: string) => - i18n.translate('xpack.siem.overview.eventsCountByTitle', { - values: { groupByField }, - defaultMessage: 'Events count by {groupByField}', - }); +export const EVENTS = i18n.translate('xpack.siem.overview.eventsTitle', { + defaultMessage: 'Events count', +}); export const NEWS_FEED_TITLE = i18n.translate('xpack.siem.overview.newsFeedSidebarTitle', { defaultMessage: 'Security news', @@ -38,8 +30,8 @@ export const RECENT_TIMELINES = i18n.translate('xpack.siem.overview.recentTimeli defaultMessage: 'Recent timelines', }); -export const SIGNALS_BY_CATEGORY = i18n.translate('xpack.siem.overview.signalsByCategoryTitle', { - defaultMessage: 'Signals count by MITRE ATT&CK\\u2122 category', +export const SIGNAL_COUNT = i18n.translate('xpack.siem.overview.signalCountTitle', { + defaultMessage: 'Signals count', }); export const VIEW_ALERTS = i18n.translate('xpack.siem.overview.viewAlertsButtonLabel', {