From a612673b96ba5414a461540d6df8205141e55415 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Tue, 12 Nov 2019 20:54:31 -0500 Subject: [PATCH] [SIEM] Detection engine placeholders (#50220) * attempt at getting nav working * fix detection-engine href redirect issue * rough out basic page routing * kql placeholder * update page and panel headers * rough out card table poc styles * change HeaderPanel to HeaderSection * cleanup and unit test updates * rough out utilityBar poc * clean up UtilityBar naming and styles * support popovers in utility bar * refactor icon side * UtilityBar unit tests * remove page tests for now * adjust routes * add comment * cleanup chart * open/closed signals content toggle * remove EuiFilterButton icons * fix misaligned popover button * add split prop for HeaderSection * fleshing out activity monitor table * update global header to include logo * fix tests * correct table types; thanks Garrett! * LinkIcon comp poc * fix bugs, errors, tests * rm import * table cleanup * correct merge errors * switch All Rules to EuiBasicTable * expand table types and values * fleshing out all rules table * rough out rule details * move chart to separate comp * update supplement layout * example rule fail * switch to new discover-style search * add ProgressInline comp * update unit tests and snapshots * cleanup * correct merge weirdness * move text styles to all subtitle items * correct invalid nav markup; update tests; cleanup * fix console errors * add empty page * change to EuiButtonEmpty in HeaderGlobal * overflow popover * rough out edit layout * new WrapperPage comp POC * cleanup * var for timeline gutter * tests and snapshots update * fix type + review + re-arrange code * adding feature flag + fix route issue * fix type with unit test * Removing unused translation --- .../integration/lib/navigation/selectors.ts | 8 +- .../__snapshots__/utility_bar.test.tsx.snap | 36 + .../utility_bar_action.test.tsx.snap | 11 + .../utility_bar_group.test.tsx.snap | 11 + .../utility_bar_section.test.tsx.snap | 13 + .../utility_bar_text.test.tsx.snap | 9 + .../detection_engine/utility_bar/index.ts | 11 + .../detection_engine/utility_bar/styles.tsx | 118 ++ .../utility_bar/utility_bar.test.tsx | 113 ++ .../utility_bar/utility_bar.tsx | 18 + .../utility_bar/utility_bar_action.test.tsx | 45 + .../utility_bar/utility_bar_action.tsx | 72 ++ .../utility_bar/utility_bar_group.test.tsx | 29 + .../utility_bar/utility_bar_group.tsx | 18 + .../utility_bar/utility_bar_section.test.tsx | 31 + .../utility_bar/utility_bar_section.tsx | 18 + .../utility_bar/utility_bar_text.test.tsx | 27 + .../utility_bar/utility_bar_text.tsx | 18 + .../events_viewer/events_viewer.test.tsx | 2 +- .../events_viewer/events_viewer.tsx | 4 +- .../filters_global/filters_global.tsx | 21 +- .../__snapshots__/index.test.tsx.snap | 7 + .../components/header_global/index.test.tsx | 34 + .../public/components/header_global/index.tsx | 82 ++ .../components/header_global/translations.ts | 15 + .../__snapshots__/header_page.test.tsx.snap | 14 - .../__snapshots__/index.test.tsx.snap | 23 + .../header_page/header_page.test.tsx | 42 - .../components/header_page/header_page.tsx | 78 -- .../components/header_page/index.test.tsx | 228 ++++ .../public/components/header_page/index.tsx | 141 ++- .../__snapshots__/index.test.tsx.snap | 4 +- .../index.test.tsx | 88 +- .../index.tsx | 40 +- .../__snapshots__/index.test.tsx.snap | 14 + .../components/link_icon/index.test.tsx | 121 ++ .../public/components/link_icon/index.tsx | 61 + .../siem/public/components/link_to/index.ts | 4 + .../public/components/link_to/link_to.tsx | 52 +- .../link_to/redirect_to_detection_engine.tsx | 51 + .../matrix_over_time/index.test.tsx | 4 +- .../components/matrix_over_time/index.tsx | 4 +- .../ml/tables/anomalies_host_table.tsx | 4 +- .../ml/tables/anomalies_network_table.tsx | 4 +- .../components/ml_popover/ml_popover.tsx | 37 +- .../components/navigation/index.test.tsx | 33 +- .../public/components/navigation/index.tsx | 7 +- .../navigation/tab_navigation/index.test.tsx | 12 +- .../navigation/tab_navigation/index.tsx | 68 +- .../public/components/navigation/types.ts | 1 - .../components/open_timeline/index.test.tsx | 2 +- .../open_timeline/title_row/index.test.tsx | 2 +- .../open_timeline/title_row/index.tsx | 6 +- .../__snapshots__/index.test.tsx.snap | 7 + .../histogram_signals/index.test.tsx | 27 + .../histogram_signals/index.tsx | 85 ++ .../page/overview/overview_host/index.tsx | 6 +- .../page/overview/overview_network/index.tsx | 6 +- .../components/paginated_table/index.tsx | 6 +- .../__snapshots__/index.test.tsx.snap | 13 + .../components/progress_inline/index.test.tsx | 29 + .../components/progress_inline/index.tsx | 51 + .../__snapshots__/index.test.tsx.snap | 9 + .../public/components/subtitle/index.test.tsx | 77 ++ .../siem/public/components/subtitle/index.tsx | 60 + .../public/components/url_state/constants.ts | 5 +- .../public/components/url_state/helpers.ts | 16 +- .../siem/public/components/url_state/types.ts | 8 +- .../__snapshots__/index.test.tsx.snap | 47 + .../components/wrapper_page/index.test.tsx | 67 + .../public/components/wrapper_page/index.tsx | 61 + .../plugins/siem/public/lib/helpers/index.tsx | 6 + .../legacy/plugins/siem/public/pages/404.tsx | 7 +- .../detection_engine/create_rule/index.tsx | 29 + .../create_rule/translations.ts | 11 + .../detection_engine/detection_engine.tsx | 205 ++++ .../detection_engine_empty_page.tsx | 29 + .../detection_engine/edit_rule/index.tsx | 128 ++ .../edit_rule/translations.ts | 11 + .../public/pages/detection_engine/index.tsx | 45 + .../detection_engine/rule_details/index.tsx | 660 ++++++++++ .../rule_details/translations.ts | 11 + .../pages/detection_engine/rules/index.tsx | 1076 +++++++++++++++++ .../detection_engine/rules/translations.ts | 11 + .../pages/detection_engine/translations.ts | 45 + .../public/pages/home/home_navigations.tsx | 13 +- .../plugins/siem/public/pages/home/index.tsx | 169 +-- .../siem/public/pages/home/translations.ts | 4 + .../plugins/siem/public/pages/home/types.ts | 2 + .../siem/public/pages/hosts/details/index.tsx | 77 +- .../plugins/siem/public/pages/hosts/hosts.tsx | 127 +- .../pages/network/ip_details/index.test.tsx | 2 +- .../public/pages/network/ip_details/index.tsx | 327 ++--- .../siem/public/pages/network/network.tsx | 144 +-- .../siem/public/pages/overview/overview.tsx | 84 +- .../public/pages/timelines/timelines_page.tsx | 26 +- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 98 files changed, 4904 insertions(+), 843 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar.test.tsx.snap create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_action.test.tsx.snap create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/index.ts create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/styles.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/header_global/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/header_global/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/header_global/translations.ts delete mode 100644 x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/header_page.test.tsx.snap create mode 100644 x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap delete mode 100644 x-pack/legacy/plugins/siem/public/components/header_page/header_page.test.tsx delete mode 100644 x-pack/legacy/plugins/siem/public/components/header_page/header_page.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx rename x-pack/legacy/plugins/siem/public/components/{header_panel => header_section}/__snapshots__/index.test.tsx.snap (62%) rename x-pack/legacy/plugins/siem/public/components/{header_panel => header_section}/index.test.tsx (52%) rename x-pack/legacy/plugins/siem/public/components/{header_panel => header_section}/index.tsx (64%) create mode 100644 x-pack/legacy/plugins/siem/public/components/link_icon/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/link_icon/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/progress_inline/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/progress_inline/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/subtitle/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/subtitle/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/wrapper_page/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/translations.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/edit_rule/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/edit_rule/translations.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rule_details/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rule_details/translations.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/navigation/selectors.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/navigation/selectors.ts index 08bf4cebedc9c..0d5f40ae53966 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/navigation/selectors.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/navigation/selectors.ts @@ -5,16 +5,16 @@ */ /** Top-level (global) navigation link to the `Hosts` page */ -export const NAVIGATION_HOSTS = '[data-test-subj="navigation-link-hosts"]'; +export const NAVIGATION_HOSTS = '[data-test-subj="navigation-hosts"]'; /** Top-level (global) navigation link to the `Network` page */ -export const NAVIGATION_NETWORK = '[data-test-subj="navigation-link-network"]'; +export const NAVIGATION_NETWORK = '[data-test-subj="navigation-network"]'; /** Top-level (global) navigation link to the `Overview` page */ -export const NAVIGATION_OVERVIEW = '[data-test-subj="navigation-link-overview"]'; +export const NAVIGATION_OVERVIEW = '[data-test-subj="navigation-overview"]'; /** Top-level (global) navigation link to the `Timelines` page */ -export const NAVIGATION_TIMELINES = '[data-test-subj="navigation-link-timelines"]'; +export const NAVIGATION_TIMELINES = '[data-test-subj="navigation-timelines"]'; export const HOSTS_PAGE_TABS = { allHosts: '[data-test-subj="navigation-allHosts"]', diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar.test.tsx.snap new file mode 100644 index 0000000000000..1f892acef7ef3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar.test.tsx.snap @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UtilityBar it renders 1`] = ` + + + + + + Test text + + + + + Test popover +

+ } + > + Test action +
+
+
+ + + + Test action + + + +
+
+`; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_action.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_action.test.tsx.snap new file mode 100644 index 0000000000000..470b40cd1d960 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_action.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UtilityBarAction it renders 1`] = ` + + + Test action + + +`; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap new file mode 100644 index 0000000000000..62ff1b17dd55f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UtilityBarGroup it renders 1`] = ` + + + + Test text + + + +`; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap new file mode 100644 index 0000000000000..f81717c892755 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UtilityBarSection it renders 1`] = ` + + + + + Test text + + + + +`; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap new file mode 100644 index 0000000000000..446b5556945d8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UtilityBarText it renders 1`] = ` + + + Test text + + +`; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/index.ts b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/index.ts new file mode 100644 index 0000000000000..b07fe8bb847c7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/index.ts @@ -0,0 +1,11 @@ +/* + * 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. + */ + +export { UtilityBar } from './utility_bar'; +export { UtilityBarAction } from './utility_bar_action'; +export { UtilityBarGroup } from './utility_bar_group'; +export { UtilityBarSection } from './utility_bar_section'; +export { UtilityBarText } from './utility_bar_text'; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/styles.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/styles.tsx new file mode 100644 index 0000000000000..9d746bf3b0515 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/styles.tsx @@ -0,0 +1,118 @@ +/* + * 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 styled, { css } from 'styled-components'; + +/** + * UTILITY BAR + */ + +export interface BarProps { + border?: boolean; +} + +export const Bar = styled.aside.attrs({ + className: 'siemUtilityBar', +})` + ${({ border, theme }) => css` + ${border && + css` + border-bottom: ${theme.eui.euiBorderThin}; + padding-bottom: ${theme.eui.paddingSizes.s}; + `} + + @media only screen and (min-width: ${theme.eui.euiBreakpoints.l}) { + display: flex; + justify-content: space-between; + } + `} +`; +Bar.displayName = 'Bar'; + +export const BarSection = styled.div.attrs({ + className: 'siemUtilityBar__section', +})` + ${({ theme }) => css` + & + & { + margin-top: ${theme.eui.euiSizeS}; + } + + @media only screen and (min-width: ${theme.eui.euiBreakpoints.m}) { + display: flex; + flex-wrap: wrap; + } + + @media only screen and (min-width: ${theme.eui.euiBreakpoints.l}) { + & + & { + margin-top: 0; + margin-left: ${theme.eui.euiSize}; + } + } + `} +`; +BarSection.displayName = 'BarSection'; + +export const BarGroup = styled.div.attrs({ + className: 'siemUtilityBar__group', +})` + ${({ theme }) => css` + align-items: flex-start; + display: flex; + flex-wrap: wrap; + + & + & { + margin-top: ${theme.eui.euiSizeS}; + } + + @media only screen and (min-width: ${theme.eui.euiBreakpoints.m}) { + border-right: ${theme.eui.euiBorderThin}; + flex-wrap: nowrap; + margin-right: ${theme.eui.paddingSizes.m}; + padding-right: ${theme.eui.paddingSizes.m}; + + & + & { + margin-top: 0; + } + + &:last-child { + border-right: none; + margin-right: 0; + padding-right: 0; + } + } + + & > * { + margin-right: ${theme.eui.euiSize}; + + &:last-child { + margin-right: 0; + } + } + `} +`; +BarGroup.displayName = 'BarGroup'; + +export const BarText = styled.p.attrs({ + className: 'siemUtilityBar__text', +})` + ${({ theme }) => css` + color: ${theme.eui.textColors.subdued}; + font-size: ${theme.eui.euiFontSizeXS}; + line-height: ${theme.eui.euiLineHeight}; + white-space: nowrap; + `} +`; +BarText.displayName = 'BarText'; + +export const BarAction = styled.div.attrs({ + className: 'siemUtilityBar__action', +})` + ${({ theme }) => css` + font-size: ${theme.eui.euiFontSizeXS}; + line-height: ${theme.eui.euiLineHeight}; + `} +`; +BarAction.displayName = 'BarAction'; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx new file mode 100644 index 0000000000000..bf13a503838cf --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx @@ -0,0 +1,113 @@ +/* + * 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 euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { mount, shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import 'jest-styled-components'; +import React from 'react'; + +import '../../../mock/ui_settings'; +import { TestProviders } from '../../../mock'; +import { + UtilityBar, + UtilityBarAction, + UtilityBarGroup, + UtilityBarSection, + UtilityBarText, +} from './index'; + +jest.mock('../../../lib/settings/use_kibana_ui_setting'); + +describe('UtilityBar', () => { + test('it renders', () => { + const wrapper = shallow( + + + + + {'Test text'} + + + + {'Test popover'}

}> + {'Test action'} +
+
+
+ + + + {'Test action'} + + +
+
+ ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + + test('it applies border styles when border is true', () => { + const wrapper = mount( + + + + + {'Test text'} + + + + {'Test popover'}

}> + {'Test action'} +
+
+
+ + + + {'Test action'} + + +
+
+ ); + const siemUtilityBar = wrapper.find('.siemUtilityBar').first(); + + expect(siemUtilityBar).toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); + expect(siemUtilityBar).toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.s); + }); + + test('it DOES NOT apply border styles when border is false', () => { + const wrapper = mount( + + + + + {'Test text'} + + + + {'Test popover'}

}> + {'Test action'} +
+
+
+ + + + {'Test action'} + + +
+
+ ); + const siemUtilityBar = wrapper.find('.siemUtilityBar').first(); + + expect(siemUtilityBar).not.toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); + expect(siemUtilityBar).not.toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.s); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.tsx new file mode 100644 index 0000000000000..f226e0e055391 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.tsx @@ -0,0 +1,18 @@ +/* + * 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 React from 'react'; + +import { Bar, BarProps } from './styles'; + +export interface UtilityBarProps extends BarProps { + children: React.ReactNode; +} + +export const UtilityBar = React.memo(({ border, children }) => ( + {children} +)); +UtilityBar.displayName = 'UtilityBar'; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx new file mode 100644 index 0000000000000..7a1c35183e503 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx @@ -0,0 +1,45 @@ +/* + * 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, shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import 'jest-styled-components'; +import React from 'react'; + +import '../../../mock/ui_settings'; +import { TestProviders } from '../../../mock'; +import { UtilityBarAction } from './index'; + +jest.mock('../../../lib/settings/use_kibana_ui_setting'); + +describe('UtilityBarAction', () => { + test('it renders', () => { + const wrapper = shallow( + + {'Test action'} + + ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + + test('it renders a popover', () => { + const wrapper = mount( + + {'Test popover'}

}> + {'Test action'} +
+
+ ); + + expect( + wrapper + .find('.euiPopover') + .first() + .exists() + ).toBe(true); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.tsx new file mode 100644 index 0000000000000..ae4362bdbcd7b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.tsx @@ -0,0 +1,72 @@ +/* + * 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 { EuiPopover } from '@elastic/eui'; +import React, { useState } from 'react'; + +import { LinkIcon, LinkIconProps } from '../../link_icon'; +import { BarAction } from './styles'; + +const Popover = React.memo( + ({ children, color, iconSide, iconSize, iconType, popoverContent }) => { + const [popoverState, setPopoverState] = useState(false); + + return ( + setPopoverState(!popoverState)} + > + {children} + + } + closePopover={() => setPopoverState(false)} + isOpen={popoverState} + > + {popoverContent} + + ); + } +); +Popover.displayName = 'Popover'; + +export interface UtilityBarActionProps extends LinkIconProps { + popoverContent?: React.ReactNode; +} + +export const UtilityBarAction = React.memo( + ({ children, color, href, iconSide, iconSize, iconType, onClick, popoverContent }) => ( + + {popoverContent ? ( + + {children} + + ) : ( + + {children} + + )} + + ) +); +UtilityBarAction.displayName = 'UtilityBarAction'; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx new file mode 100644 index 0000000000000..84ad96c5a1e5e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx @@ -0,0 +1,29 @@ +/* + * 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 { shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import React from 'react'; + +import '../../../mock/ui_settings'; +import { TestProviders } from '../../../mock'; +import { UtilityBarGroup, UtilityBarText } from './index'; + +jest.mock('../../../lib/settings/use_kibana_ui_setting'); + +describe('UtilityBarGroup', () => { + test('it renders', () => { + const wrapper = shallow( + + + {'Test text'} + + + ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.tsx new file mode 100644 index 0000000000000..1e23fd3498199 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.tsx @@ -0,0 +1,18 @@ +/* + * 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 React from 'react'; + +import { BarGroup } from './styles'; + +export interface UtilityBarGroupProps { + children: React.ReactNode; +} + +export const UtilityBarGroup = React.memo(({ children }) => ( + {children} +)); +UtilityBarGroup.displayName = 'UtilityBarGroup'; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx new file mode 100644 index 0000000000000..2dfc1d3b8d193 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx @@ -0,0 +1,31 @@ +/* + * 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 { shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import React from 'react'; + +import '../../../mock/ui_settings'; +import { TestProviders } from '../../../mock'; +import { UtilityBarGroup, UtilityBarSection, UtilityBarText } from './index'; + +jest.mock('../../../lib/settings/use_kibana_ui_setting'); + +describe('UtilityBarSection', () => { + test('it renders', () => { + const wrapper = shallow( + + + + {'Test text'} + + + + ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.tsx new file mode 100644 index 0000000000000..c457e6bc3dee0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.tsx @@ -0,0 +1,18 @@ +/* + * 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 React from 'react'; + +import { BarSection } from './styles'; + +export interface UtilityBarSectionProps { + children: React.ReactNode; +} + +export const UtilityBarSection = React.memo(({ children }) => ( + {children} +)); +UtilityBarSection.displayName = 'UtilityBarSection'; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx new file mode 100644 index 0000000000000..0743e5cab02b4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx @@ -0,0 +1,27 @@ +/* + * 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 { shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import React from 'react'; + +import '../../../mock/ui_settings'; +import { TestProviders } from '../../../mock'; +import { UtilityBarText } from './index'; + +jest.mock('../../../lib/settings/use_kibana_ui_setting'); + +describe('UtilityBarText', () => { + test('it renders', () => { + const wrapper = shallow( + + {'Test text'} + + ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.tsx new file mode 100644 index 0000000000000..f8eb25f03d4ad --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.tsx @@ -0,0 +1,18 @@ +/* + * 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 React from 'react'; + +import { BarText } from './styles'; + +export interface UtilityBarTextProps { + children: string; +} + +export const UtilityBarText = React.memo(({ children }) => ( + {children} +)); +UtilityBarText.displayName = 'UtilityBarText'; diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx index a97ef2cf5ca0c..4e59acc4f6713 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx @@ -52,7 +52,7 @@ describe('EventsViewer', () => { expect( wrapper - .find(`[data-test-subj="header-panel-subtitle"]`) + .find(`[data-test-subj="header-section-subtitle"]`) .first() .text() ).toEqual('Showing: 12 events'); diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx index 2b588283bd198..c5c0fe3503561 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx @@ -16,7 +16,7 @@ import { Direction } from '../../graphql/types'; import { useKibanaCore } from '../../lib/compose/kibana_core'; import { KqlMode } from '../../store/timeline/model'; import { AutoSizer } from '../auto_sizer'; -import { HeaderPanel } from '../header_panel'; +import { HeaderSection } from '../header_section'; import { ColumnHeader } from '../timeline/body/column_headers/column_header'; import { defaultHeaders } from '../timeline/body/column_headers/default_headers'; import { Sort } from '../timeline/body/sort'; @@ -130,7 +130,7 @@ export const EventsViewer = React.memo( totalCount = 0, }) => ( <> - ` +const Wrapper = styled.aside<{ isSticky?: boolean }>` ${props => css` position: relative; z-index: ${props.theme.eui.euiZNavigation}; background: ${props.theme.eui.euiColorEmptyShade}; border-bottom: ${props.theme.eui.euiBorderThin}; - box-sizing: content-box; - margin: 0 -${gutterTimeline} 0 -${props.theme.eui.euiSizeL}; - padding: ${props.theme.eui.euiSize} ${gutterTimeline} ${props.theme.eui.euiSize} ${ - props.theme.eui.euiSizeL - }; + padding: ${props.theme.eui.paddingSizes.m} ${gutterTimeline} ${ + props.theme.eui.paddingSizes.m + } ${props.theme.eui.paddingSizes.l}; ${props.isSticky && ` @@ -39,8 +38,7 @@ const Aside = styled.aside<{ isSticky?: boolean }>` } `} `; - -Aside.displayName = 'Aside'; +Wrapper.displayName = 'Wrapper'; export interface FiltersGlobalProps { children: React.ReactNode; @@ -49,11 +47,10 @@ export interface FiltersGlobalProps { export const FiltersGlobal = pure(({ children }) => ( {({ style, isSticky }) => ( - + )} )); - FiltersGlobal.displayName = 'FiltersGlobal'; diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/header_global/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..665a5c75f3684 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_global/__snapshots__/index.test.tsx.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`HeaderGlobal it renders 1`] = ` + + + +`; diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx new file mode 100644 index 0000000000000..ebd1da634ed1a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx @@ -0,0 +1,34 @@ +/* + * 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 { shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import React from 'react'; + +import { TestProviders } from '../../mock'; +import '../../mock/match_media'; +import '../../mock/ui_settings'; +import { HeaderGlobal } from './index'; + +jest.mock('../../lib/settings/use_kibana_ui_setting'); + +// Test will fail because we will to need to mock some core services to make the test work +// For now let's forget about SiemSearchBar +jest.mock('../search_bar', () => ({ + SiemSearchBar: () => null, +})); + +describe('HeaderGlobal', () => { + test('it renders', () => { + const wrapper = shallow( + + + + ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx new file mode 100644 index 0000000000000..168cacf3e97e1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx @@ -0,0 +1,82 @@ +/* + * 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 { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink } from '@elastic/eui'; +import { pickBy } from 'lodash/fp'; +import React from 'react'; +import styled, { css } from 'styled-components'; + +import { gutterTimeline } from '../../lib/helpers'; +import { navTabs } from '../../pages/home/home_navigations'; +import { SiemPageName } from '../../pages/home/types'; +import { getOverviewUrl } from '../link_to'; +import { MlPopover } from '../ml_popover/ml_popover'; +import { SiemNavigation } from '../navigation'; +import * as i18n from './translations'; + +const Wrapper = styled.header` + ${({ theme }) => css` + background: ${theme.eui.euiColorEmptyShade}; + border-bottom: ${theme.eui.euiBorderThin}; + padding: ${theme.eui.paddingSizes.m} ${gutterTimeline} ${theme.eui.paddingSizes.m} + ${theme.eui.paddingSizes.l}; + `} +`; +Wrapper.displayName = 'Wrapper'; + +const FlexItem = styled(EuiFlexItem)` + min-width: 0; +`; +FlexItem.displayName = 'FlexItem'; + +interface HeaderGlobalProps { + hideDetectionEngine?: boolean; +} +export const HeaderGlobal = React.memo(({ hideDetectionEngine = true }) => ( + + + + + + + + + + + + key !== SiemPageName.detectionEngine, navTabs) + : navTabs + } + /> + + + + + + + + + + + + + {i18n.BUTTON_ADD_DATA} + + + + + + +)); +HeaderGlobal.displayName = 'HeaderGlobal'; diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/translations.ts b/x-pack/legacy/plugins/siem/public/components/header_global/translations.ts new file mode 100644 index 0000000000000..c713f63016594 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_global/translations.ts @@ -0,0 +1,15 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const SIEM = i18n.translate('xpack.siem.headerGlobal.siem', { + defaultMessage: 'SIEM', +}); + +export const BUTTON_ADD_DATA = i18n.translate('xpack.siem.headerGlobal.buttonAddData', { + defaultMessage: 'Add data', +}); diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/header_page.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/header_page.test.tsx.snap deleted file mode 100644 index 280acc0c63334..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/header_page.test.tsx.snap +++ /dev/null @@ -1,14 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`rendering renders correctly 1`] = ` - -

- My test supplement. -

-
-`; diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..0fe2890dc9f24 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`HeaderPage it renders 1`] = ` + + +

+ Test supplement +

+
+
+`; diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/header_page.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/header_page.test.tsx deleted file mode 100644 index 16f2156e568e5..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/header_page/header_page.test.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import React from 'react'; - -import { HeaderPage } from './index'; - -describe('rendering', () => { - test('renders correctly', () => { - const wrapper = shallow( - -

{'My test supplement.'}

-
- ); - expect(toJson(wrapper)).toMatchSnapshot(); - }); - test('renders as a draggable when provided arguments', () => { - const wrapper = shallow( - -

{'My test supplement.'}

-
- ); - const draggableHeader = wrapper.dive().find('[data-test-subj="page_headline_draggable"]'); - expect(draggableHeader.exists()).toBeTruthy(); - }); -}); diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/header_page.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/header_page.tsx deleted file mode 100644 index 2ba543b34307a..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/header_page/header_page.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui'; -import React from 'react'; -import { pure } from 'recompose'; -import styled from 'styled-components'; -import { DefaultDraggable } from '../draggables'; - -const Header = styled.header` - ${({ theme }) => ` - border-bottom: ${theme.eui.euiBorderThin}; - padding-bottom: ${theme.eui.euiSizeL}; - margin: ${theme.eui.euiSizeL} 0; - `} -`; - -Header.displayName = 'Header'; - -interface DraggableArguments { - field: string; - value: string; -} - -export interface HeaderPageProps { - badgeLabel?: string; - badgeTooltip?: string; - children?: React.ReactNode; - draggableArguments?: DraggableArguments; - subtitle?: string | React.ReactNode; - title: string | React.ReactNode; -} - -export const HeaderPage = pure( - ({ badgeLabel, badgeTooltip, children, draggableArguments, subtitle, title, ...rest }) => ( -
- - - -

- {!draggableArguments ? ( - title - ) : ( - - )} - {badgeLabel && ( - <> - {' '} - - - )} -

-
- - - {subtitle} - -
- - {children && {children}} -
-
- ) -); - -HeaderPage.displayName = 'HeaderPage'; diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx new file mode 100644 index 0000000000000..ae33b63e93d39 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx @@ -0,0 +1,228 @@ +/* + * 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 euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { mount, shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import 'jest-styled-components'; +import React from 'react'; + +import { TestProviders } from '../../mock'; +import '../../mock/ui_settings'; +import { HeaderPage } from './index'; + +jest.mock('../../lib/settings/use_kibana_ui_setting'); + +describe('HeaderPage', () => { + test('it renders', () => { + const wrapper = shallow( + + +

{'Test supplement'}

+
+
+ ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + + test('it renders the title', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('[data-test-subj="header-page-title"]') + .first() + .exists() + ).toBe(true); + }); + + test('it renders the back link when provided', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('.siemHeaderPage__linkBack') + .first() + .exists() + ).toBe(true); + }); + + test('it DOES NOT render the back link when not provided', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('.siemHeaderPage__linkBack') + .first() + .exists() + ).toBe(false); + }); + + test('it renders the first subtitle when provided', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('[data-test-subj="header-page-subtitle"]') + .first() + .exists() + ).toBe(true); + }); + + test('it DOES NOT render the first subtitle when not provided', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('[data-test-subj="header-section-subtitle"]') + .first() + .exists() + ).toBe(false); + }); + + test('it renders the second subtitle when provided', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('[data-test-subj="header-page-subtitle-2"]') + .first() + .exists() + ).toBe(true); + }); + + test('it DOES NOT render the second subtitle when not provided', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('[data-test-subj="header-section-subtitle-2"]') + .first() + .exists() + ).toBe(false); + }); + + test('it renders supplements when children provided', () => { + const wrapper = mount( + + +

{'Test supplement'}

+
+
+ ); + + expect( + wrapper + .find('[data-test-subj="header-page-supplements"]') + .first() + .exists() + ).toBe(true); + }); + + test('it DOES NOT render supplements when children not provided', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('[data-test-subj="header-page-supplements"]') + .first() + .exists() + ).toBe(false); + }); + + test('it applies border styles when border is true', () => { + const wrapper = mount( + + + + ); + const siemHeaderPage = wrapper.find('.siemHeaderPage').first(); + + expect(siemHeaderPage).toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); + expect(siemHeaderPage).toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.l); + }); + + test('it DOES NOT apply border styles when border is false', () => { + const wrapper = mount( + + + + ); + const siemHeaderPage = wrapper.find('.siemHeaderPage').first(); + + expect(siemHeaderPage).not.toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); + expect(siemHeaderPage).not.toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.l); + }); + + test('it renders as a draggable when arguments provided', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('[data-test-subj="header-page-draggable"]') + .first() + .exists() + ).toBe(true); + }); + + test('it DOES NOT render as a draggable when arguments not provided', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('[data-test-subj="header-page-draggable"]') + .first() + .exists() + ).toBe(false); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx index 9d89cdfc32893..4db2a35c600e9 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx @@ -4,4 +4,143 @@ * you may not use this file except in compliance with the Elastic License. */ -export { HeaderPage } from './header_page'; +import { EuiBetaBadge, EuiBadge, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; +import React from 'react'; +import styled, { css } from 'styled-components'; + +import { DefaultDraggable } from '../draggables'; +import { LinkIcon, LinkIconProps } from '../link_icon'; +import { Subtitle, SubtitleProps } from '../subtitle'; + +interface HeaderProps { + border?: boolean; +} + +const Header = styled.header.attrs({ + className: 'siemHeaderPage', +})` + ${({ border, theme }) => css` + margin-bottom: ${theme.eui.euiSizeL}; + + ${border && + css` + border-bottom: ${theme.eui.euiBorderThin}; + padding-bottom: ${theme.eui.paddingSizes.l}; + `} + `} +`; +Header.displayName = 'Header'; + +const FlexItem = styled(EuiFlexItem)` + display: block; +`; +FlexItem.displayName = 'FlexItem'; + +const LinkBack = styled.div.attrs({ + className: 'siemHeaderPage__linkBack', +})` + ${({ theme }) => css` + font-size: ${theme.eui.euiFontSizeXS}; + line-height: ${theme.eui.euiLineHeight}; + margin-bottom: ${theme.eui.euiSizeS}; + `} +`; +LinkBack.displayName = 'LinkBack'; + +const Badge = styled(EuiBadge)` + letter-spacing: 0; +`; +Badge.displayName = 'Badge'; + +interface BackOptions { + href: LinkIconProps['href']; + text: LinkIconProps['children']; +} + +interface BadgeOptions { + beta?: boolean; + text: string; + tooltip?: string; +} + +interface DraggableArguments { + field: string; + value: string; +} + +export interface HeaderPageProps extends HeaderProps { + backOptions?: BackOptions; + badgeOptions?: BadgeOptions; + children?: React.ReactNode; + draggableArguments?: DraggableArguments; + subtitle?: SubtitleProps['items']; + subtitle2?: SubtitleProps['items']; + title: string | React.ReactNode; +} + +export const HeaderPage = React.memo( + ({ + backOptions, + badgeOptions, + border, + children, + draggableArguments, + subtitle, + subtitle2, + title, + ...rest + }) => ( +
+ + + {backOptions && ( + + + {backOptions.text} + + + )} + + +

+ {!draggableArguments ? ( + title + ) : ( + + )} + {badgeOptions && ( + <> + {' '} + {badgeOptions.beta ? ( + + ) : ( + {badgeOptions.text} + )} + + )} +

+
+ + {subtitle && } + {subtitle2 && } +
+ + {children && ( + + {children} + + )} +
+
+ ) +); +HeaderPage.displayName = 'HeaderPage'; diff --git a/x-pack/legacy/plugins/siem/public/components/header_panel/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/header_section/__snapshots__/index.test.tsx.snap similarity index 62% rename from x-pack/legacy/plugins/siem/public/components/header_panel/__snapshots__/index.test.tsx.snap rename to x-pack/legacy/plugins/siem/public/components/header_section/__snapshots__/index.test.tsx.snap index 39250c38ef8fc..ecd2b15a841f6 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_panel/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/header_section/__snapshots__/index.test.tsx.snap @@ -1,8 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`HeaderPanel it renders 1`] = ` +exports[`HeaderSection it renders 1`] = ` - diff --git a/x-pack/legacy/plugins/siem/public/components/header_panel/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx similarity index 52% rename from x-pack/legacy/plugins/siem/public/components/header_panel/index.test.tsx rename to x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx index 9cdb85bcb3d76..fffeece818d13 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_panel/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx @@ -10,17 +10,17 @@ import toJson from 'enzyme-to-json'; import 'jest-styled-components'; import React from 'react'; -import '../../mock/ui_settings'; import { TestProviders } from '../../mock'; -import { HeaderPanel } from './index'; +import '../../mock/ui_settings'; +import { HeaderSection } from './index'; jest.mock('../../lib/settings/use_kibana_ui_setting'); -describe('HeaderPanel', () => { +describe('HeaderSection', () => { test('it renders', () => { const wrapper = shallow( - + ); @@ -30,13 +30,13 @@ describe('HeaderPanel', () => { test('it renders the title', () => { const wrapper = mount( - + ); expect( wrapper - .find('[data-test-subj="header-panel-title"]') + .find('[data-test-subj="header-section-title"]') .first() .exists() ).toBe(true); @@ -45,13 +45,13 @@ describe('HeaderPanel', () => { test('it renders the subtitle when provided', () => { const wrapper = mount( - + ); expect( wrapper - .find(`[data-test-subj="header-panel-subtitle"]`) + .find('[data-test-subj="header-section-subtitle"]') .first() .exists() ).toBe(true); @@ -60,13 +60,13 @@ describe('HeaderPanel', () => { test('it DOES NOT render the subtitle when not provided', () => { const wrapper = mount( - + ); expect( wrapper - .find(`[data-test-subj="header-panel-subtitle"]`) + .find('[data-test-subj="header-section-subtitle"]') .first() .exists() ).toBe(false); @@ -75,13 +75,13 @@ describe('HeaderPanel', () => { test('it renders a transparent inspect button when showInspect is false', () => { const wrapper = mount( - + ); expect( wrapper - .find(`[data-test-subj="transparent-inspect-container"]`) + .find('[data-test-subj="transparent-inspect-container"]') .first() .exists() ).toBe(true); @@ -90,13 +90,13 @@ describe('HeaderPanel', () => { test('it renders an opaque inspect button when showInspect is true', () => { const wrapper = mount( - + ); expect( wrapper - .find(`[data-test-subj="opaque-inspect-container"]`) + .find('[data-test-subj="opaque-inspect-container"]') .first() .exists() ).toBe(true); @@ -105,15 +105,15 @@ describe('HeaderPanel', () => { test('it renders supplements when children provided', () => { const wrapper = mount( - +

{'Test children'}

-
+
); expect( wrapper - .find('[data-test-subj="header-panel-supplements"]') + .find('[data-test-subj="header-section-supplements"]') .first() .exists() ).toBe(true); @@ -122,13 +122,13 @@ describe('HeaderPanel', () => { test('it DOES NOT render supplements when children not provided', () => { const wrapper = mount( - + ); expect( wrapper - .find('[data-test-subj="header-panel-supplements"]') + .find('[data-test-subj="header-section-supplements"]') .first() .exists() ).toBe(false); @@ -137,24 +137,58 @@ describe('HeaderPanel', () => { test('it applies border styles when border is true', () => { const wrapper = mount( - + ); - const siemHeaderPanel = wrapper.find('.siemHeaderPanel').first(); + const siemHeaderSection = wrapper.find('.siemHeaderSection').first(); - expect(siemHeaderPanel).toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); - expect(siemHeaderPanel).toHaveStyleRule('padding-bottom', euiDarkVars.euiSizeL); + expect(siemHeaderSection).toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); + expect(siemHeaderSection).toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.l); }); test('it DOES NOT apply border styles when border is false', () => { const wrapper = mount( - + + + ); + const siemHeaderSection = wrapper.find('.siemHeaderSection').first(); + + expect(siemHeaderSection).not.toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); + expect(siemHeaderSection).not.toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.l); + }); + + test('it splits the title and supplement areas evenly when split is true', () => { + const wrapper = mount( + + +

{'Test children'}

+
+
+ ); + + expect( + wrapper + .find('.euiFlexItem--flexGrowZero[data-test-subj="header-section-supplements"]') + .first() + .exists() + ).toBe(false); + }); + + test('it DOES NOT split the title and supplement areas evenly when split is false', () => { + const wrapper = mount( + + +

{'Test children'}

+
); - const siemHeaderPanel = wrapper.find('.siemHeaderPanel').first(); - expect(siemHeaderPanel).not.toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); - expect(siemHeaderPanel).not.toHaveStyleRule('padding-bottom', euiDarkVars.euiSizeL); + expect( + wrapper + .find('.euiFlexItem--flexGrowZero[data-test-subj="header-section-supplements"]') + .first() + .exists() + ).toBe(true); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/header_panel/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_section/index.tsx similarity index 64% rename from x-pack/legacy/plugins/siem/public/components/header_panel/index.tsx rename to x-pack/legacy/plugins/siem/public/components/header_section/index.tsx index e7b3fb9f2f400..e46ae55a57a45 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_panel/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_section/index.tsx @@ -4,51 +4,52 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiIconTip, EuiText, EuiTitle } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiIconTip, EuiTitle } from '@elastic/eui'; import React from 'react'; import styled, { css } from 'styled-components'; import { InspectButton } from '../inspect'; +import { Subtitle } from '../subtitle'; interface HeaderProps { border?: boolean; } const Header = styled.header.attrs({ - className: 'siemHeaderPanel', + className: 'siemHeaderSection', })` - ${props => css` - margin-bottom: ${props.theme.eui.euiSizeL}; + ${({ border, theme }) => css` + margin-bottom: ${theme.eui.euiSizeL}; user-select: text; - ${props.border && - ` - border-bottom: ${props.theme.eui.euiBorderThin}; - padding-bottom: ${props.theme.eui.euiSizeL}; - `} + ${border && + css` + border-bottom: ${theme.eui.euiBorderThin}; + padding-bottom: ${theme.eui.paddingSizes.l}; + `} `} `; - Header.displayName = 'Header'; -export interface HeaderPanelProps extends HeaderProps { +export interface HeaderSectionProps extends HeaderProps { children?: React.ReactNode; id?: string; + split?: boolean; subtitle?: string | React.ReactNode; showInspect?: boolean; title: string | React.ReactNode; tooltip?: string; } -export const HeaderPanel = React.memo( - ({ border, children, id, showInspect = false, subtitle, title, tooltip }) => ( +export const HeaderSection = React.memo( + ({ border, children, id, showInspect = false, split, subtitle, title, tooltip }) => (
-

+

{title} {tooltip && ( <> @@ -59,11 +60,7 @@ export const HeaderPanel = React.memo(

- {subtitle && ( - -

{subtitle}

-
- )} + {subtitle && }
{id && ( @@ -75,7 +72,7 @@ export const HeaderPanel = React.memo(
{children && ( - + {children} )} @@ -83,5 +80,4 @@ export const HeaderPanel = React.memo(
) ); - -HeaderPanel.displayName = 'HeaderPanel'; +HeaderSection.displayName = 'HeaderSection'; diff --git a/x-pack/legacy/plugins/siem/public/components/link_icon/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/link_icon/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..5902768383cb0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/link_icon/__snapshots__/index.test.tsx.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LinkIcon it renders 1`] = ` + + + Test link + + +`; diff --git a/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx new file mode 100644 index 0000000000000..8e4387f35056e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx @@ -0,0 +1,121 @@ +/* + * 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, shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import 'jest-styled-components'; +import React from 'react'; + +import { TestProviders } from '../../mock'; +import '../../mock/ui_settings'; +import { LinkIcon } from './index'; + +jest.mock('../../lib/settings/use_kibana_ui_setting'); + +describe('LinkIcon', () => { + test('it renders', () => { + const wrapper = shallow( + + + {'Test link'} + + + ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + + test('it renders an action button when onClick is provided', () => { + const wrapper = mount( + + alert('Test alert')}> + {'Test link'} + + + ); + + expect( + wrapper + .find('button') + .first() + .exists() + ).toBe(true); + }); + + test('it renders an action link when href is provided', () => { + const wrapper = mount( + + + {'Test link'} + + + ); + + expect( + wrapper + .find('a') + .first() + .exists() + ).toBe(true); + }); + + test('it renders an icon', () => { + const wrapper = mount( + + {'Test link'} + + ); + + expect( + wrapper + .find('.euiIcon') + .first() + .exists() + ).toBe(true); + }); + + test('it positions the icon to the right when iconSide is right', () => { + const wrapper = mount( + + + {'Test link'} + + + ); + + expect(wrapper.find('.siemLinkIcon').first()).toHaveStyleRule('flex-direction', 'row-reverse'); + }); + + test('it positions the icon to the left when iconSide is left (or not provided)', () => { + const wrapper = mount( + + + {'Test link'} + + + ); + + expect(wrapper.find('.siemLinkIcon').first()).not.toHaveStyleRule( + 'flex-direction', + 'row-reverse' + ); + }); + + test('it renders a label', () => { + const wrapper = mount( + + {'Test link'} + + ); + + expect( + wrapper + .find('.siemLinkIcon__label') + .first() + .exists() + ).toBe(true); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/link_icon/index.tsx b/x-pack/legacy/plugins/siem/public/components/link_icon/index.tsx new file mode 100644 index 0000000000000..d83183adcf5e5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/link_icon/index.tsx @@ -0,0 +1,61 @@ +/* + * 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 { EuiIcon, EuiLink, IconSize, IconType } from '@elastic/eui'; +import { LinkAnchorProps } from '@elastic/eui/src/components/link/link'; +import React from 'react'; +import styled, { css } from 'styled-components'; + +interface LinkProps { + color?: LinkAnchorProps['color']; + href?: string; + iconSide?: 'left' | 'right'; + onClick?: Function; +} + +const Link = styled(({ iconSide, children, ...rest }) => {children})< + LinkProps +>` + ${({ iconSide, theme }) => css` + align-items: center; + display: inline-flex; + vertical-align: top; + white-space: nowrap; + + ${iconSide === 'left' && + css` + .euiIcon { + margin-right: ${theme.eui.euiSizeXS}; + } + `} + + ${iconSide === 'right' && + css` + flex-direction: row-reverse; + + .euiIcon { + margin-left: ${theme.eui.euiSizeXS}; + } + `} + `} +`; +Link.displayName = 'Link'; + +export interface LinkIconProps extends LinkProps { + children: string; + iconSize?: IconSize; + iconType: IconType; +} + +export const LinkIcon = React.memo( + ({ children, color, href, iconSide = 'left', iconSize = 's', iconType, onClick }) => ( + + + {children} + + ) +); +LinkIcon.displayName = 'LinkIcon'; diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/index.ts b/x-pack/legacy/plugins/siem/public/components/link_to/index.ts index 7eb39de3d96b4..10198345755c3 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/index.ts +++ b/x-pack/legacy/plugins/siem/public/components/link_to/index.ts @@ -5,6 +5,10 @@ */ export { LinkToPage } from './link_to'; +export { + getDetectionEngineUrl, + RedirectToDetectionEnginePage, +} from './redirect_to_detection_engine'; export { getOverviewUrl, RedirectToOverviewPage } from './redirect_to_overview'; export { getHostsUrl, getHostDetailsUrl } from './redirect_to_hosts'; export { getNetworkUrl, getIPDetailsUrl, RedirectToNetworkPage } from './redirect_to_network'; diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx index 0360c1004f151..0125b52e3ad33 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx @@ -8,12 +8,19 @@ import React from 'react'; import { match as RouteMatch, Redirect, Route, Switch } from 'react-router-dom'; import { pure } from 'recompose'; +import { SiemPageName } from '../../pages/home/types'; +import { HostsTableType } from '../../store/hosts/model'; +import { + RedirectToCreateRulePage, + RedirectToDetectionEnginePage, + RedirectToEditRulePage, + RedirectToRuleDetailsPage, + RedirectToRulesPage, +} from './redirect_to_detection_engine'; import { RedirectToHostsPage, RedirectToHostDetailsPage } from './redirect_to_hosts'; import { RedirectToNetworkPage } from './redirect_to_network'; import { RedirectToOverviewPage } from './redirect_to_overview'; import { RedirectToTimelinesPage } from './redirect_to_timelines'; -import { HostsTableType } from '../../store/hosts/model'; -import { SiemPageName } from '../../pages/home/types'; interface LinkToPageProps { match: RouteMatch<{}>; @@ -22,39 +29,62 @@ interface LinkToPageProps { export const LinkToPage = pure(({ match }) => ( - + + + + + diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.tsx new file mode 100644 index 0000000000000..74aec076ec4d5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_detection_engine.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 React from 'react'; +import { RouteComponentProps } from 'react-router-dom'; + +import { RedirectWrapper } from './redirect_wrapper'; + +export type DetectionEngineComponentProps = RouteComponentProps<{ + search: string; +}>; + +export const DETECTION_ENGINE_PAGE_NAME = 'detection-engine'; + +export const RedirectToDetectionEnginePage = ({ + location: { search }, +}: DetectionEngineComponentProps) => ( + +); + +export const RedirectToRulesPage = ({ location: { search } }: DetectionEngineComponentProps) => { + return ; +}; + +export const RedirectToCreateRulePage = ({ + location: { search }, +}: DetectionEngineComponentProps) => { + return ; +}; + +export const RedirectToRuleDetailsPage = ({ + location: { search }, +}: DetectionEngineComponentProps) => { + return ; +}; + +export const RedirectToEditRulePage = ({ location: { search } }: DetectionEngineComponentProps) => { + return ( + + ); +}; + +export const getDetectionEngineUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`; +export const getRulesUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules`; +export const getCreateRuleUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/create-rule`; +export const getRuleDetailsUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details`; +export const getEditRuleUrl = () => + `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details/edit-rule`; diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.test.tsx index 3334447739fc5..9d2ef203361bf 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.test.tsx @@ -26,9 +26,9 @@ jest.mock('../../lib/settings/use_kibana_ui_setting', () => { return { useKibanaUiSetting: () => [false] }; }); -jest.mock('../header_panel', () => { +jest.mock('../header_section', () => { return { - HeaderPanel: () =>
, + HeaderSection: () =>
, }; }); diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx index 3523723574be6..75e1531ea2b5b 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx @@ -12,7 +12,7 @@ import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; import { EuiLoadingContent } from '@elastic/eui'; import { BarChart } from '../charts/barchart'; -import { HeaderPanel } from '../header_panel'; +import { HeaderSection } from '../header_section'; import { ChartSeriesData, UpdateDateRange } from '../charts/common'; import { MatrixOverTimeHistogramData } from '../../graphql/types'; import { DEFAULT_DARK_MODE } from '../../../common/constants'; @@ -113,7 +113,7 @@ export const MatrixOverTimeHistogram = ({ onMouseEnter={() => setShowInspect(true)} onMouseLeave={() => setShowInspect(false)} > - ( } else { return ( - ( } else { return ( - { anchorPosition="downRight" id="integrations-popover" button={ - setIsPopoverOpen(!isPopoverOpen)} > {i18n.ANOMALY_DETECTION} - + } isOpen={isPopoverOpen} closePopover={() => setIsPopoverOpen(!isPopoverOpen)} @@ -183,7 +184,7 @@ export const MlPopover = React.memo(() => { anchorPosition="downRight" id="integrations-popover" button={ - { }} > {i18n.ANOMALY_DETECTION} - + } isOpen={isPopoverOpen} closePopover={() => setIsPopoverOpen(!isPopoverOpen)} diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx index cf519da617183..97cf9522f488f 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx @@ -61,6 +61,13 @@ describe('SIEM Navigation', () => { expect(setBreadcrumbs).toHaveBeenNthCalledWith(1, { detailName: undefined, navTabs: { + 'detection-engine': { + disabled: false, + href: '#/link-to/detection-engine', + id: 'detection-engine', + name: 'Detection engine', + urlKey: 'detection-engine', + }, hosts: { disabled: false, href: '#/link-to/hosts', @@ -132,9 +139,17 @@ describe('SIEM Navigation', () => { tabName: undefined, }); wrapper.update(); - expect(setBreadcrumbs).toHaveBeenNthCalledWith(2, { + expect(setBreadcrumbs).toHaveBeenNthCalledWith(1, { detailName: undefined, + filters: [], navTabs: { + 'detection-engine': { + disabled: false, + href: '#/link-to/detection-engine', + id: 'detection-engine', + name: 'Detection engine', + urlKey: 'detection-engine', + }, hosts: { disabled: false, href: '#/link-to/hosts', @@ -164,17 +179,13 @@ describe('SIEM Navigation', () => { urlKey: 'timeline', }, }, - pageName: 'network', - pathName: '/network', - search: '', - tabName: undefined, - query: { query: '', language: 'kuery' }, - filters: [], + pageName: 'hosts', + pathName: '/hosts', + query: { language: 'kuery', query: '' }, savedQuery: undefined, - timeline: { - id: '', - isOpen: false, - }, + search: '', + tabName: 'authentications', + timeline: { id: '', isOpen: false }, timerange: { global: { linkTo: ['timeline'], diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx index ae8d09eeff112..7209be4d715f3 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx @@ -6,17 +6,16 @@ import { isEqual } from 'lodash/fp'; import React, { useEffect } from 'react'; -import { compose } from 'redux'; import { connect } from 'react-redux'; +import { compose } from 'redux'; import { RouteSpyState } from '../../utils/route/types'; import { useRouteSpy } from '../../utils/route/use_route_spy'; - +import { makeMapStateToProps } from '../url_state/helpers'; import { setBreadcrumbs } from './breadcrumbs'; import { TabNavigation } from './tab_navigation'; import { TabNavigationProps } from './tab_navigation/types'; import { SiemNavigationComponentProps } from './types'; -import { makeMapStateToProps } from '../url_state/helpers'; export const SiemNavigationComponent = React.memo( ({ @@ -29,7 +28,6 @@ export const SiemNavigationComponent = React.memo { const pageName = SiemPageName.hosts; @@ -78,7 +78,7 @@ describe('Tab Navigation', () => { }); test('it carries the url state in the link', () => { const wrapper = shallow(); - const firstTab = wrapper.find('[data-test-subj="navigation-link-network"]'); + const firstTab = wrapper.find('[data-test-subj="navigation-network"]'); expect(firstTab.props().href).toBe( "#/link-to/network?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))" ); @@ -147,7 +147,7 @@ describe('Tab Navigation', () => { test('it carries the url state in the link', () => { const wrapper = shallow(); const firstTab = wrapper.find( - `[data-test-subj="navigation-link-${HostsTableType.authentications}"]` + `[data-test-subj="navigation-${HostsTableType.authentications}"]` ); expect(firstTab.props().href).toBe( `#/${pageName}/${hostName}/${HostsTableType.authentications}?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))` diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx index 3e3c02a1abfa4..27d10cb02a856 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx @@ -3,40 +3,17 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiTab, EuiTabs, EuiLink } from '@elastic/eui'; +import { EuiTab, EuiTabs } from '@elastic/eui'; import { getOr } from 'lodash/fp'; - import React, { useEffect, useState } from 'react'; -import styled from 'styled-components'; -import classnames from 'classnames'; import { trackUiAction as track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../lib/track_usage'; import { getSearch } from '../helpers'; import { TabNavigationProps } from './types'; -const TabContainer = styled.div` - .euiLink { - color: inherit !important; - - &:focus { - outline: 0; - background: none; - } - - .euiTab.euiTab-isSelected { - cursor: pointer; - } - } - - &.showBorder { - padding: 8px 8px 0; - } -`; - -TabContainer.displayName = 'TabContainer'; - export const TabNavigation = React.memo(props => { - const { display = 'condensed', navTabs, pageName, showBorder, tabName } = props; + const { display, navTabs, pageName, tabName } = props; + const mapLocationToTab = (): string => { return getOr( '', @@ -44,6 +21,7 @@ export const TabNavigation = React.memo(props => { Object.values(navTabs).find(item => tabName === item.id || pageName === item.id) ); }; + const [selectedTabId, setSelectedTabId] = useState(mapLocationToTab()); useEffect(() => { const currentTabSelected = mapLocationToTab(); @@ -57,31 +35,21 @@ export const TabNavigation = React.memo(props => { const renderTabs = (): JSX.Element[] => Object.values(navTabs).map(tab => ( - { + track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.TAB_CLICKED}${tab.id}`); + }} > - - { - track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.TAB_CLICKED}${tab.id}`); - }} - > - {tab.name} - - - + {tab.name} + )); - return ( - - {renderTabs()} - - ); + + return {renderTabs()}; }); +TabNavigation.displayName = 'TabNavigation'; diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/types.ts b/x-pack/legacy/plugins/siem/public/components/navigation/types.ts index 2918a19df52fd..a8e16c82fbf80 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/navigation/types.ts @@ -9,7 +9,6 @@ import { UrlStateType } from '../url_state/constants'; export interface SiemNavigationComponentProps { display?: 'default' | 'condensed'; navTabs: Record; - showBorder?: boolean; } export type SearchNavTab = NavTab | { urlKey: UrlStateType; isDetailPage: boolean }; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx index 7a0caf14af302..f5207fc6a35fd 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx @@ -492,7 +492,7 @@ describe('StatefulOpenTimeline', () => { expect( wrapper - .find('[data-test-subj="header-panel-title"]') + .find('[data-test-subj="header-section-title"]') .first() .text() ).toEqual(title); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.test.tsx index db3d192f06ba1..9303c09c994aa 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.test.tsx @@ -30,7 +30,7 @@ describe('TitleRow', () => { expect( wrapper - .find('[data-test-subj="header-panel-title"]') + .find('[data-test-subj="header-section-title"]') .first() .text() ).toEqual(title); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.tsx index f9b107e08afa2..78281a27bb360 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.tsx @@ -10,7 +10,7 @@ import { pure } from 'recompose'; import * as i18n from '../translations'; import { OpenTimelineProps } from '../types'; -import { HeaderPanel } from '../../header_panel'; +import { HeaderSection } from '../../header_section'; type Props = Pick & { /** The number of timelines currently selected */ @@ -23,7 +23,7 @@ type Props = Pick( ({ onAddTimelinesToFavorites, onDeleteSelected, selectedTimelinesCount, title }) => ( - + {(onAddTimelinesToFavorites || onDeleteSelected) && ( {onAddTimelinesToFavorites && ( @@ -55,7 +55,7 @@ export const TitleRow = pure( )} )} - + ) ); diff --git a/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..caf4334cacf57 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/__snapshots__/index.test.tsx.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`HistogramSignals it renders 1`] = ` + + + +`; diff --git a/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.test.tsx new file mode 100644 index 0000000000000..2412d05f3f47d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.test.tsx @@ -0,0 +1,27 @@ +/* + * 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 { shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import React from 'react'; + +import '../../../../mock/ui_settings'; +import { TestProviders } from '../../../../mock'; +import { HistogramSignals } from './index'; + +jest.mock('../../../../lib/settings/use_kibana_ui_setting'); + +describe('HistogramSignals', () => { + test('it renders', () => { + const wrapper = shallow( + + + + ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.tsx new file mode 100644 index 0000000000000..fa26664930fe5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.tsx @@ -0,0 +1,85 @@ +/* + * 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 { + Axis, + Chart, + HistogramBarSeries, + Settings, + getAxisId, + getSpecId, + niceTimeFormatByDay, + timeFormatter, +} from '@elastic/charts'; +import React from 'react'; +import { npStart } from 'ui/new_platform'; + +export const HistogramSignals = React.memo(() => { + const sampleChartData = [ + { x: 1571090784000, y: 2, a: 'a' }, + { x: 1571090784000, y: 2, b: 'b' }, + { x: 1571093484000, y: 7, a: 'a' }, + { x: 1571096184000, y: 3, a: 'a' }, + { x: 1571098884000, y: 2, a: 'a' }, + { x: 1571101584000, y: 7, a: 'a' }, + { x: 1571104284000, y: 3, a: 'a' }, + { x: 1571106984000, y: 2, a: 'a' }, + { x: 1571109684000, y: 7, a: 'a' }, + { x: 1571112384000, y: 3, a: 'a' }, + { x: 1571115084000, y: 2, a: 'a' }, + { x: 1571117784000, y: 7, a: 'a' }, + { x: 1571120484000, y: 3, a: 'a' }, + { x: 1571123184000, y: 2, a: 'a' }, + { x: 1571125884000, y: 7, a: 'a' }, + { x: 1571128584000, y: 3, a: 'a' }, + { x: 1571131284000, y: 2, a: 'a' }, + { x: 1571133984000, y: 7, a: 'a' }, + { x: 1571136684000, y: 3, a: 'a' }, + { x: 1571139384000, y: 2, a: 'a' }, + { x: 1571142084000, y: 7, a: 'a' }, + { x: 1571144784000, y: 3, a: 'a' }, + { x: 1571147484000, y: 2, a: 'a' }, + { x: 1571150184000, y: 7, a: 'a' }, + { x: 1571152884000, y: 3, a: 'a' }, + { x: 1571155584000, y: 2, a: 'a' }, + { x: 1571158284000, y: 7, a: 'a' }, + { x: 1571160984000, y: 3, a: 'a' }, + { x: 1571163684000, y: 2, a: 'a' }, + { x: 1571166384000, y: 7, a: 'a' }, + { x: 1571169084000, y: 3, a: 'a' }, + { x: 1571171784000, y: 2, a: 'a' }, + { x: 1571174484000, y: 7, a: 'a' }, + ]; + + return ( + + + + + + + + + + ); +}); +HistogramSignals.displayName = 'HistogramSignals'; 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 3d4a2bc31f2fc..ebabde44c61e9 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 @@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { useState } from 'react'; import { pure } from 'recompose'; -import { HeaderPanel } from '../../../header_panel'; +import { HeaderSection } from '../../../header_section'; import { manageQuery } from '../../../page/manage_query'; import { ID as OverviewHostQueryId, @@ -42,7 +42,7 @@ export const OverviewHost = pure(({ endDate, startDate, setQu return ( setIsHover(true)} onMouseLeave={() => setIsHover(false)}> - (({ endDate, startDate, setQu - + {({ overviewHost, loading, id, inspect, refetch }) => ( 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 c1629a50341db..b6f1a9cdf26e4 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 @@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { useState } from 'react'; import { pure } from 'recompose'; -import { HeaderPanel } from '../../../header_panel'; +import { HeaderSection } from '../../../header_section'; import { manageQuery } from '../../../page/manage_query'; import { ID as OverviewNetworkQueryId, @@ -42,7 +42,7 @@ export const OverviewNetwork = pure(({ endDate, startDate, setQuery }) return ( setIsHover(true)} onMouseLeave={() => setIsHover(false)}> - (({ endDate, startDate, setQuery }) defaultMessage="View network" /> - + {({ overviewNetwork, loading, id, inspect, refetch }) => ( diff --git a/x-pack/legacy/plugins/siem/public/components/paginated_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/paginated_table/index.tsx index 646d003051e83..7be0c1885811b 100644 --- a/x-pack/legacy/plugins/siem/public/components/paginated_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/paginated_table/index.tsx @@ -35,7 +35,7 @@ import { import { TlsColumns } from '../page/network/tls_table/columns'; import { UncommonProcessTableColumns } from '../page/hosts/uncommon_process_table'; import { UsersColumns } from '../page/network/users_table/columns'; -import { HeaderPanel } from '../header_panel'; +import { HeaderSection } from '../header_section'; import { Loader } from '../loader'; import { useStateToaster } from '../toasters'; import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants'; @@ -234,7 +234,7 @@ export const PaginatedTable = memo( onMouseEnter={() => setShowInspect(true)} onMouseLeave={() => setShowInspect(false)} > - ( tooltip={headerTooltip} > {!loadingInitial && headerSupplement} - + {loadingInitial ? ( diff --git a/x-pack/legacy/plugins/siem/public/components/progress_inline/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/progress_inline/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..c62712e6cfe59 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/progress_inline/__snapshots__/index.test.tsx.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ProgressInline it renders 1`] = ` + + + Test progress + + +`; diff --git a/x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx new file mode 100644 index 0000000000000..269bcebdae01a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx @@ -0,0 +1,29 @@ +/* + * 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 { shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import React from 'react'; + +import { TestProviders } from '../../mock'; +import '../../mock/ui_settings'; +import { ProgressInline } from './index'; + +jest.mock('../../lib/settings/use_kibana_ui_setting'); + +describe('ProgressInline', () => { + test('it renders', () => { + const wrapper = shallow( + + + {'Test progress'} + + + ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/progress_inline/index.tsx b/x-pack/legacy/plugins/siem/public/components/progress_inline/index.tsx new file mode 100644 index 0000000000000..90eca051e3d11 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/progress_inline/index.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 { EuiProgress } from '@elastic/eui'; +import React from 'react'; +import styled, { css } from 'styled-components'; + +const Wrapper = styled.dl` + ${({ theme }) => css` + align-items: center; + display: inline-flex; + + & > * + * { + margin-left: ${theme.eui.euiSizeS}; + } + + .siemProgressInline__bar { + width: 100px; + } + `} +`; +Wrapper.displayName = 'Wrapper'; + +export interface ProgressInlineProps { + children: string; + current: number; + max: number; + unit: string; +} + +export const ProgressInline = React.memo( + ({ children, current, max, unit }) => ( + +
{children}
+ +
+ +
+ +
+ {current.toLocaleString()} + {'/'} + {max.toLocaleString()} {unit} +
+
+ ) +); +ProgressInline.displayName = 'ProgressInline'; diff --git a/x-pack/legacy/plugins/siem/public/components/subtitle/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/subtitle/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..2522d4d1de084 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/subtitle/__snapshots__/index.test.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Subtitle it renders 1`] = ` + + + +`; diff --git a/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx new file mode 100644 index 0000000000000..77506f8a466a5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx @@ -0,0 +1,77 @@ +/* + * 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, shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import React from 'react'; + +import '../../mock/ui_settings'; +import { TestProviders } from '../../mock'; +import { Subtitle } from './index'; + +jest.mock('../../lib/settings/use_kibana_ui_setting'); + +describe('Subtitle', () => { + test('it renders', () => { + const wrapper = shallow( + + + + ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + + test('it renders one subtitle string item', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('.siemSubtitle__item--text').length).toEqual(1); + }); + + test('it renders multiple subtitle string items', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('.siemSubtitle__item--text').length).toEqual(2); + }); + + test('it renders one subtitle React.ReactNode item', () => { + const wrapper = mount( + + {'Test subtitle'}} /> + + ); + + expect(wrapper.find('.siemSubtitle__item--node').length).toEqual(1); + }); + + test('it renders multiple subtitle React.ReactNode items', () => { + const wrapper = mount( + + {'Test subtitle 1'}, {'Test subtitle 2'}]} /> + + ); + + expect(wrapper.find('.siemSubtitle__item--node').length).toEqual(2); + }); + + test('it renders multiple subtitle items of mixed type', () => { + const wrapper = mount( + + {'Test subtitle 2'}]} /> + + ); + + expect(wrapper.find('.siemSubtitle__item').length).toEqual(2); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/subtitle/index.tsx b/x-pack/legacy/plugins/siem/public/components/subtitle/index.tsx new file mode 100644 index 0000000000000..123e14d239182 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/subtitle/index.tsx @@ -0,0 +1,60 @@ +/* + * 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 React from 'react'; +import styled, { css } from 'styled-components'; + +const Wrapper = styled.div` + ${({ theme }) => css` + margin-top: ${theme.eui.euiSizeS}; + + .siemSubtitle__item { + color: ${theme.eui.textColors.subdued}; + font-size: ${theme.eui.euiFontSizeXS}; + line-height: ${theme.eui.euiLineHeight}; + + @media only screen and (min-width: ${theme.eui.euiBreakpoints.s}) { + display: inline-block; + margin-right: ${theme.eui.euiSize}; + + &:last-child { + margin-right: 0; + } + } + } + `} +`; +Wrapper.displayName = 'Wrapper'; + +interface SubtitleItemProps { + children: string | React.ReactNode; +} + +const SubtitleItem = React.memo(({ children }) => { + if (typeof children === 'string') { + return

{children}

; + } else { + return
{children}
; + } +}); +SubtitleItem.displayName = 'SubtitleItem'; + +export interface SubtitleProps { + items: string | React.ReactNode | Array; +} + +export const Subtitle = React.memo(({ items }) => { + return ( + + {Array.isArray(items) ? ( + items.map((item, i) => {item}) + ) : ( + {items} + )} + + ); +}); +Subtitle.displayName = 'Subtitle'; diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/constants.ts b/x-pack/legacy/plugins/siem/public/components/url_state/constants.ts index c709a9370ec61..2e700e3e23b64 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/constants.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/constants.ts @@ -6,17 +6,18 @@ export enum CONSTANTS { appQuery = 'query', + detectionEnginePage = 'detectionEngine.page', filters = 'filters', - savedQuery = 'savedQuery', hostsDetails = 'hosts.details', hostsPage = 'hosts.page', networkDetails = 'network.details', networkPage = 'network.page', overviewPage = 'overview.page', + savedQuery = 'savedQuery', timelinePage = 'timeline.page', timerange = 'timerange', timeline = 'timeline', unknown = 'unknown', } -export type UrlStateType = 'host' | 'network' | 'overview' | 'timeline'; +export type UrlStateType = 'detection-engine' | 'host' | 'network' | 'overview' | 'timeline'; diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts b/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts index f7487d7a81a7a..aa340b54c1699 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts @@ -72,12 +72,14 @@ export const replaceQueryStringInLocation = (location: Location, queryString: st }; export const getUrlType = (pageName: string): UrlStateType => { - if (pageName === SiemPageName.hosts) { + if (pageName === SiemPageName.overview) { + return 'overview'; + } else if (pageName === SiemPageName.hosts) { return 'host'; } else if (pageName === SiemPageName.network) { return 'network'; - } else if (pageName === SiemPageName.overview) { - return 'overview'; + } else if (pageName === SiemPageName.detectionEngine) { + return 'detection-engine'; } else if (pageName === SiemPageName.timelines) { return 'timeline'; } @@ -97,7 +99,9 @@ export const getCurrentLocation = ( pageName: string, detailName: string | undefined ): LocationTypes => { - if (pageName === SiemPageName.hosts) { + if (pageName === SiemPageName.overview) { + return CONSTANTS.overviewPage; + } else if (pageName === SiemPageName.hosts) { if (detailName != null) { return CONSTANTS.hostsDetails; } @@ -107,8 +111,8 @@ export const getCurrentLocation = ( return CONSTANTS.networkDetails; } return CONSTANTS.networkPage; - } else if (pageName === SiemPageName.overview) { - return CONSTANTS.overviewPage; + } else if (pageName === SiemPageName.detectionEngine) { + return CONSTANTS.detectionEnginePage; } else if (pageName === SiemPageName.timelines) { return CONSTANTS.timelinePage; } diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts index 44c050a1990ce..13618125325e1 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts @@ -25,6 +25,7 @@ export const ALL_URL_STATE_KEYS: KeyUrlState[] = [ ]; export const URL_STATE_KEYS: Record = { + 'detection-engine': [], host: [ CONSTANTS.appQuery, CONSTANTS.filters, @@ -39,15 +40,16 @@ export const URL_STATE_KEYS: Record = { CONSTANTS.timerange, CONSTANTS.timeline, ], - timeline: [CONSTANTS.timeline, CONSTANTS.timerange], overview: [CONSTANTS.timeline, CONSTANTS.timerange], + timeline: [CONSTANTS.timeline, CONSTANTS.timerange], }; export type LocationTypes = - | CONSTANTS.networkDetails - | CONSTANTS.networkPage + | CONSTANTS.detectionEnginePage | CONSTANTS.hostsDetails | CONSTANTS.hostsPage + | CONSTANTS.networkDetails + | CONSTANTS.networkPage | CONSTANTS.overviewPage | CONSTANTS.timelinePage | CONSTANTS.unknown; diff --git a/x-pack/legacy/plugins/siem/public/components/wrapper_page/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/wrapper_page/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..e5311bfb050a3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/wrapper_page/__snapshots__/index.test.tsx.snap @@ -0,0 +1,47 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`WrapperPage it renders 1`] = ` + + +

+ Test page +

+
+
+`; + +exports[`WrapperPage restrict width custom max width when restrictWidth is number 1`] = ` + + +

+ Test page +

+
+
+`; + +exports[`WrapperPage restrict width custom max width when restrictWidth is string 1`] = ` + + +

+ Test page +

+
+
+`; + +exports[`WrapperPage restrict width default max width when restrictWidth is true 1`] = ` + + +

+ Test page +

+
+
+`; diff --git a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx new file mode 100644 index 0000000000000..95e80e8b9e5de --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx @@ -0,0 +1,67 @@ +/* + * 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 { shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import React from 'react'; + +import { TestProviders } from '../../mock'; +import '../../mock/ui_settings'; +import { WrapperPage } from './index'; + +jest.mock('../../lib/settings/use_kibana_ui_setting'); + +describe('WrapperPage', () => { + test('it renders', () => { + const wrapper = shallow( + + +

{'Test page'}

+
+
+ ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + + describe('restrict width', () => { + test('default max width when restrictWidth is true', () => { + const wrapper = shallow( + + +

{'Test page'}

+
+
+ ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + + test('custom max width when restrictWidth is number', () => { + const wrapper = shallow( + + +

{'Test page'}

+
+
+ ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + + test('custom max width when restrictWidth is string', () => { + const wrapper = shallow( + + +

{'Test page'}

+
+
+ ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx new file mode 100644 index 0000000000000..5998aa527206e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx @@ -0,0 +1,61 @@ +/* + * 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 classNames from 'classnames'; +import React from 'react'; +import styled, { css } from 'styled-components'; + +import { gutterTimeline } from '../../lib/helpers'; + +const Wrapper = styled.div` + ${({ theme }) => css` + padding: ${theme.eui.paddingSizes.l} ${gutterTimeline} ${theme.eui.paddingSizes.l} + ${theme.eui.paddingSizes.l}; + + &.siemWrapperPage--restrictWidthDefault, + &.siemWrapperPage--restrictWidthCustom { + box-sizing: content-box; + margin: 0 auto; + } + + &.siemWrapperPage--restrictWidthDefault { + max-width: 1000px; + } + `} +`; +Wrapper.displayName = 'Wrapper'; + +export interface WrapperPageProps { + children: React.ReactNode; + className?: string; + restrictWidth?: boolean | number | string; + style?: Record; +} + +export const WrapperPage = React.memo( + ({ children, className, restrictWidth, style }) => { + const classes = classNames(className, { + siemWrapperPage: true, + 'siemWrapperPage--restrictWidthDefault': + restrictWidth && typeof restrictWidth === 'boolean' && restrictWidth === true, + 'siemWrapperPage--restrictWidthCustom': restrictWidth && typeof restrictWidth !== 'boolean', + }); + + let customStyle: WrapperPageProps['style']; + + if (restrictWidth && typeof restrictWidth !== 'boolean') { + const value = typeof restrictWidth === 'number' ? `${restrictWidth}px` : restrictWidth; + customStyle = { ...style, maxWidth: value }; + } + + return ( + + {children} + + ); + } +); +WrapperPage.displayName = 'WrapperPage'; diff --git a/x-pack/legacy/plugins/siem/public/lib/helpers/index.tsx b/x-pack/legacy/plugins/siem/public/lib/helpers/index.tsx index 659ecbadc34d2..5706dcc50ed25 100644 --- a/x-pack/legacy/plugins/siem/public/lib/helpers/index.tsx +++ b/x-pack/legacy/plugins/siem/public/lib/helpers/index.tsx @@ -42,3 +42,9 @@ export const assertUnreachable = ( ): never => { throw new Error(`${message}: ${x}`); }; + +/** + * Global variables + */ + +export const gutterTimeline = '70px'; // Michael: Temporary until timeline is moved. diff --git a/x-pack/legacy/plugins/siem/public/pages/404.tsx b/x-pack/legacy/plugins/siem/public/pages/404.tsx index 58a3c904b89a0..f806a5a7fcdd3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/404.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/404.tsx @@ -8,13 +8,14 @@ import React from 'react'; import { pure } from 'recompose'; import { FormattedMessage } from '@kbn/i18n/react'; +import { WrapperPage } from '../components/wrapper_page'; + export const NotFoundPage = pure(() => ( -
+ -
+ )); - NotFoundPage.displayName = 'NotFoundPage'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/index.tsx new file mode 100644 index 0000000000000..47a3527aff99c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/index.tsx @@ -0,0 +1,29 @@ +/* + * 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 React from 'react'; + +import { HeaderPage } from '../../../components/header_page'; +import { WrapperPage } from '../../../components/wrapper_page'; +import { SpyRoute } from '../../../utils/route/spy_routes'; +import * as i18n from './translations'; + +export const CreateRuleComponent = React.memo(() => { + return ( + <> + + + + + + + ); +}); +CreateRuleComponent.displayName = 'CreateRuleComponent'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/translations.ts new file mode 100644 index 0000000000000..884f3f3741228 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/translations.ts @@ -0,0 +1,11 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.createRule.pageTitle', { + defaultMessage: 'Create new rule', +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx new file mode 100644 index 0000000000000..9b63a6e160e42 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -0,0 +1,205 @@ +/* + * 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 { + EuiButton, + EuiFilterButton, + EuiFilterGroup, + EuiPanel, + EuiSelect, + EuiSpacer, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { StickyContainer } from 'react-sticky'; + +import { FiltersGlobal } from '../../components/filters_global'; +import { HeaderPage } from '../../components/header_page'; +import { HeaderSection } from '../../components/header_section'; +import { HistogramSignals } from '../../components/page/detection_engine/histogram_signals'; +import { SiemSearchBar } from '../../components/search_bar'; +import { + UtilityBar, + UtilityBarAction, + UtilityBarGroup, + UtilityBarSection, + UtilityBarText, +} from '../../components/detection_engine/utility_bar'; +import { WrapperPage } from '../../components/wrapper_page'; +import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; +import { SpyRoute } from '../../utils/route/spy_routes'; +import { DetectionEngineEmptyPage } from './detection_engine_empty_page'; +import * as i18n from './translations'; + +const OpenSignals = React.memo(() => { + return ( + <> + + + + {`${i18n.PANEL_SUBTITLE_SHOWING}: 7,712 signals`} + + + + {'Selected: 20 signals'} + + {'Batch actions context menu here.'}

} + > + {'Batch actions'} +
+ + + {'Select all signals on all pages'} + +
+ + + {'Clear 7 filters'} + + {'Clear aggregation'} + +
+ + + + {'Customize columns context menu here.'}

} + > + {'Customize columns'} +
+ + {'Aggregate data'} +
+
+
+ + {/* Michael: Open signals datagrid here. Talk to Chandler Prall about possibility of early access. If not possible, use basic table. */} + + ); +}); + +const ClosedSignals = React.memo(() => { + return ( + <> + + + + {`${i18n.PANEL_SUBTITLE_SHOWING}: 7,712 signals`} + + + + + + {'Customize columns context menu here.'}

} + > + {'Customize columns'} +
+ + {'Aggregate data'} +
+
+
+ + {/* Michael: Closed signals datagrid here. Talk to Chandler Prall about possibility of early access. If not possible, use basic table. */} + + ); +}); + +export const DetectionEngineComponent = React.memo(() => { + const sampleChartOptions = [ + { text: 'Risk scores', value: 'risk_scores' }, + { text: 'Severities', value: 'severities' }, + { text: 'Top destination IPs', value: 'destination_ips' }, + { text: 'Top event actions', value: 'event_actions' }, + { text: 'Top event categories', value: 'event_categories' }, + { text: 'Top host names', value: 'host_names' }, + { text: 'Top rule types', value: 'rule_types' }, + { text: 'Top rules', value: 'rules' }, + { text: 'Top source IPs', value: 'source_ips' }, + { text: 'Top users', value: 'users' }, + ]; + + const filterGroupOptions = ['open', 'closed']; + const [filterGroupState, setFilterGroupState] = useState(filterGroupOptions[0]); + + return ( + <> + + {({ indicesExist, indexPattern }) => { + return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + + + + + + + + {i18n.BUTTON_MANAGE_RULES} + + + + + + {}} + prepend="Stack by" + value={sampleChartOptions[0].value} + /> + + + + + + + + + + + setFilterGroupState(filterGroupOptions[0])} + withNext + > + {'Open signals'} + + + setFilterGroupState(filterGroupOptions[1])} + > + {'Closed signals'} + + + + + {filterGroupState === filterGroupOptions[0] ? : } + + + + ) : ( + + + + + + ); + }} + + + + + ); +}); +DetectionEngineComponent.displayName = 'DetectionEngineComponent'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.tsx new file mode 100644 index 0000000000000..cb3e690615395 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.tsx @@ -0,0 +1,29 @@ +/* + * 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 React from 'react'; +import chrome from 'ui/chrome'; +import { documentationLinks } from 'ui/documentation_links'; + +import { EmptyPage } from '../../components/empty_page'; +import * as i18n from './translations'; + +const basePath = chrome.getBasePath(); + +export const DetectionEngineEmptyPage = React.memo(() => ( + +)); +DetectionEngineEmptyPage.displayName = 'DetectionEngineEmptyPage'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/edit_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/edit_rule/index.tsx new file mode 100644 index 0000000000000..9b8607fdc7685 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/edit_rule/index.tsx @@ -0,0 +1,128 @@ +/* + * 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 { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiTabbedContent, +} from '@elastic/eui'; +import React from 'react'; + +import { HeaderPage } from '../../../components/header_page'; +import { HeaderSection } from '../../../components/header_section'; +import { WrapperPage } from '../../../components/wrapper_page'; +import { SpyRoute } from '../../../utils/route/spy_routes'; +import * as i18n from './translations'; + +const Define = React.memo(() => ( + <> + + + + + + +)); +Define.displayName = 'Define'; + +const About = React.memo(() => ( + <> + + + + + + +)); +About.displayName = 'About'; + +const Schedule = React.memo(() => ( + <> + + + + + + +)); +Schedule.displayName = 'Schedule'; + +export const EditRuleComponent = React.memo(() => { + return ( + <> + + + + + + {'Cancel'} + + + + + + {'Save changes'} + + + + + + , + }, + { + id: 'tabAbout', + name: 'About', + content: , + }, + { + id: 'tabSchedule', + name: 'Schedule', + content: , + }, + ]} + /> + + + + + + + {'Cancel'} + + + + + + {'Save changes'} + + + + + + + + ); +}); +EditRuleComponent.displayName = 'EditRuleComponent'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/edit_rule/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/edit_rule/translations.ts new file mode 100644 index 0000000000000..cc2e2565eb8d0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/edit_rule/translations.ts @@ -0,0 +1,11 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.editRule.pageTitle', { + defaultMessage: 'Edit rule settings', +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx new file mode 100644 index 0000000000000..90524b4da0af4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx @@ -0,0 +1,45 @@ +/* + * 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 React from 'react'; +import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom'; + +import { CreateRuleComponent } from './create_rule'; +import { DetectionEngineComponent } from './detection_engine'; +import { EditRuleComponent } from './edit_rule'; +import { RuleDetailsComponent } from './rule_details'; +import { RulesComponent } from './rules'; + +const detectionEnginePath = `/:pageName(detection-engine)`; + +type Props = Partial> & { url: string }; + +export const DetectionEngineContainer = React.memo(() => ( + + } strict /> + } /> + } + /> + } + /> + } + /> + ( + + )} + /> + +)); +DetectionEngineContainer.displayName = 'DetectionEngineContainer'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rule_details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rule_details/index.tsx new file mode 100644 index 0000000000000..da3e5fb2083dd --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rule_details/index.tsx @@ -0,0 +1,660 @@ +/* + * 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 { + EuiBasicTable, + EuiButton, + EuiButtonIcon, + EuiCallOut, + EuiFilterButton, + EuiFilterGroup, + EuiFlexGroup, + EuiFlexItem, + EuiIconTip, + EuiPanel, + EuiPopover, + EuiSelect, + EuiSpacer, + EuiSwitch, + EuiTabbedContent, + EuiTextColor, +} from '@elastic/eui'; +import moment from 'moment'; +import React, { useState } from 'react'; +import { StickyContainer } from 'react-sticky'; + +import { getEmptyTagValue } from '../../../components/empty_value'; +import { FiltersGlobal } from '../../../components/filters_global'; +import { HeaderPage } from '../../../components/header_page'; +import { HeaderSection } from '../../../components/header_section'; +import { HistogramSignals } from '../../../components/page/detection_engine/histogram_signals'; +import { ProgressInline } from '../../../components/progress_inline'; +import { SiemSearchBar } from '../../../components/search_bar'; +import { + UtilityBar, + UtilityBarAction, + UtilityBarGroup, + UtilityBarSection, + UtilityBarText, +} from '../../../components/detection_engine/utility_bar'; +import { WrapperPage } from '../../../components/wrapper_page'; +import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../../containers/source'; +import { SpyRoute } from '../../../utils/route/spy_routes'; +import { DetectionEngineEmptyPage } from '../detection_engine_empty_page'; +import * as i18n from './translations'; + +// Michael: Will need to change this to get the current datetime format from Kibana settings. +const dateTimeFormat = (value: string) => { + return moment(value).format('M/D/YYYY, h:mm A'); +}; + +const OpenSignals = React.memo(() => { + return ( + <> + + + + {'Showing: 439 signals'} + + + + {'Selected: 20 signals'} + + {'Batch actions context menu here.'}

} + > + {'Batch actions'} +
+ + + {'Select all signals on all pages'} + +
+ + + {'Clear 7 filters'} + + {'Clear aggregation'} + +
+ + + + {'Customize columns context menu here.'}

} + > + {'Customize columns'} +
+ + {'Aggregate data'} +
+
+
+ + {/* Michael: Open signals datagrid here. Talk to Chandler Prall about possibility of early access. If not possible, use basic table. */} + + ); +}); + +const ClosedSignals = React.memo(() => { + return ( + <> + + + + {'Showing: 439 signals'} + + + + + + {'Customize columns context menu here.'}

} + > + {'Customize columns'} +
+ + {'Aggregate data'} +
+
+
+ + {/* Michael: Closed signals datagrid here. Talk to Chandler Prall about possibility of early access. If not possible, use basic table. */} + + ); +}); + +const Signals = React.memo(() => { + const sampleChartOptions = [ + { text: 'Risk scores', value: 'risk_scores' }, + { text: 'Severities', value: 'severities' }, + { text: 'Top destination IPs', value: 'destination_ips' }, + { text: 'Top event actions', value: 'event_actions' }, + { text: 'Top event categories', value: 'event_categories' }, + { text: 'Top host names', value: 'host_names' }, + { text: 'Top source IPs', value: 'source_ips' }, + { text: 'Top users', value: 'users' }, + ]; + + const filterGroupOptions = ['open', 'closed']; + const [filterGroupState, setFilterGroupState] = useState(filterGroupOptions[0]); + + return ( + <> + + + + + {}} + prepend="Stack by" + value={sampleChartOptions[0].value} + /> + + + + + + + + + + + setFilterGroupState(filterGroupOptions[0])} + withNext + > + {'Open signals'} + + + setFilterGroupState(filterGroupOptions[1])} + > + {'Closed signals'} + + + + + {filterGroupState === filterGroupOptions[0] ? : } + + + ); +}); +Signals.displayName = 'Signals'; + +const ActivityMonitor = React.memo(() => { + interface ColumnTypes { + id: number; + ran: string; + lookedBackTo: string; + status: string; + response: string | undefined; + } + + interface PageTypes { + index: number; + size: number; + } + + interface SortTypes { + field: string; + direction: string; + } + + const actions = [ + { + available: (item: ColumnTypes) => item.status === 'Running', + description: 'Stop', + icon: 'stop', + isPrimary: true, + name: 'Stop', + onClick: () => {}, + type: 'icon', + }, + { + available: (item: ColumnTypes) => item.status === 'Stopped', + description: 'Resume', + icon: 'play', + isPrimary: true, + name: 'Resume', + onClick: () => {}, + type: 'icon', + }, + ]; + + // Michael: Are we able to do custom, in-table-header filters, as shown in my wireframes? + const columns = [ + { + field: 'ran', + name: 'Ran', + render: (value: ColumnTypes['ran']) => , + sortable: true, + truncateText: true, + }, + { + field: 'lookedBackTo', + name: 'Looked back to', + render: (value: ColumnTypes['lookedBackTo']) => ( + + ), + sortable: true, + truncateText: true, + }, + { + field: 'status', + name: 'Status', + sortable: true, + truncateText: true, + }, + { + field: 'response', + name: 'Response', + render: (value: ColumnTypes['response']) => { + return value === undefined ? ( + getEmptyTagValue() + ) : ( + <> + {value === 'Fail' ? ( + + {value} + + ) : ( + {value} + )} + + ); + }, + sortable: true, + truncateText: true, + }, + { + actions, + width: '40px', + }, + ]; + + const sampleTableData = [ + { + id: 1, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Running', + }, + { + id: 2, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Stopped', + }, + { + id: 3, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Fail', + }, + { + id: 4, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 5, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 6, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 7, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 8, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 9, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 10, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 11, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 12, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 13, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 14, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 15, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 16, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 17, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 18, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 19, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 20, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 21, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + ]; + + const [itemsTotalState] = useState(sampleTableData.length); + const [pageState, setPageState] = useState({ index: 0, size: 20 }); + // const [selectedState, setSelectedState] = useState([]); + const [sortState, setSortState] = useState({ field: 'ran', direction: 'desc' }); + + return ( + <> + + + + + + + + + {'Showing: 39 activites'} + + + + {'Selected: 2 activities'} + + {'Stop selected'} + + + + {'Clear 7 filters'} + + + + + { + setPageState(page); + setSortState(sort); + }} + pagination={{ + pageIndex: pageState.index, + pageSize: pageState.size, + totalItemCount: itemsTotalState, + pageSizeOptions: [5, 10, 20], + }} + selection={{ + selectable: (item: ColumnTypes) => item.status !== 'Completed', + selectableMessage: (selectable: boolean) => + selectable ? undefined : 'Completed runs cannot be acted upon', + onSelectionChange: (selectedItems: ColumnTypes[]) => { + // setSelectedState(selectedItems); + }, + }} + sorting={{ + sort: sortState, + }} + /> + + + ); +}); +ActivityMonitor.displayName = 'ActivityMonitor'; + +export const RuleDetailsComponent = React.memo(() => { + const [popoverState, setPopoverState] = useState(false); + + return ( + <> + + {({ indicesExist, indexPattern }) => { + return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + + + + + + + {'Status: Running'} + , + ]} + title="Automated exfiltration" + > + + + {}} /> + + + + + + + {'Edit rule settings'} + + + + + setPopoverState(!popoverState)} + /> + } + closePopover={() => setPopoverState(false)} + isOpen={popoverState} + > +

{'Overflow context menu here.'}

+
+
+
+
+
+
+ + +

{'Full fail message here.'}

+
+ + + + + + + + + + + + + + + {/*

{'Description'}

*/} + + {/* + +

{'Description'}

+
+ + +

{'Severity'}

+
+ + +

{'Risk score boost'}

+
+ + +

{'References'}

+
+ + +

{'False positives'}

+
+ + +

{'Mitre ATT&CK types'}

+
+ + +

{'Tags'}

+
+
*/} +
+
+ + + + + + +
+ + + + , + }, + { + id: 'tabActivityMonitor', + name: 'Activity monitor', + content: , + }, + ]} + /> +
+
+ ) : ( + + + + + + ); + }} +
+ + + + ); +}); +RuleDetailsComponent.displayName = 'RuleDetailsComponent'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rule_details/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rule_details/translations.ts new file mode 100644 index 0000000000000..3dd5945ff597c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rule_details/translations.ts @@ -0,0 +1,11 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.ruleDetails.pageTitle', { + defaultMessage: 'Rule details', +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx new file mode 100644 index 0000000000000..41a6cf54ff5ab --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx @@ -0,0 +1,1076 @@ +/* + * 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 euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { + EuiBadge, + EuiBasicTable, + EuiButton, + EuiFieldSearch, + EuiFlexGroup, + EuiFlexItem, + EuiHealth, + EuiIconTip, + EuiLink, + EuiPanel, + EuiSpacer, + EuiSwitch, + EuiTabbedContent, + EuiTextColor, +} from '@elastic/eui'; +import moment from 'moment'; +import React, { useState } from 'react'; + +import { getEmptyTagValue } from '../../../components/empty_value'; +import { HeaderPage } from '../../../components/header_page'; +import { HeaderSection } from '../../../components/header_section'; +import { + UtilityBar, + UtilityBarAction, + UtilityBarGroup, + UtilityBarSection, + UtilityBarText, +} from '../../../components/detection_engine/utility_bar'; +import { WrapperPage } from '../../../components/wrapper_page'; +import { SpyRoute } from '../../../utils/route/spy_routes'; +import * as i18n from './translations'; + +// Michael: Will need to change this to get the current datetime format from Kibana settings. +const dateTimeFormat = (value: string) => { + return moment(value).format('M/D/YYYY, h:mm A'); +}; + +const AllRules = React.memo(() => { + interface RuleTypes { + href: string; + name: string; + status: string; + } + + interface LastResponseTypes { + type: string; + message?: string; + } + + interface ColumnTypes { + id: number; + rule: RuleTypes; + method: string; + severity: string; + lastCompletedRun: string; + lastResponse: LastResponseTypes; + tags: string | string[]; + activate: boolean; + } + + interface PageTypes { + index: number; + size: number; + } + + interface SortTypes { + field: string; + direction: string; + } + + const actions = [ + { + description: 'Edit rule settings', + icon: 'visControls', + name: 'Edit rule settings', + onClick: () => {}, + }, + { + description: 'Run rule manually…', + icon: 'play', + name: 'Run rule manually…', + onClick: () => {}, + }, + { + description: 'Duplicate rule…', + icon: 'copy', + name: 'Duplicate rule…', + onClick: () => {}, + }, + { + description: 'Export rule', + icon: 'exportAction', + name: 'Export rule', + onClick: () => {}, + }, + { + description: 'Delete rule…', + icon: 'trash', + name: 'Delete rule…', + onClick: () => {}, + }, + ]; + + // Michael: Are we able to do custom, in-table-header filters, as shown in my wireframes? + const columns = [ + { + field: 'rule', + name: 'Rule', + render: (value: ColumnTypes['rule']) => ( +
+ {value.name}{' '} + {value.status} +
+ ), + sortable: true, + truncateText: true, + width: '24%', + }, + { + field: 'method', + name: 'Method', + sortable: true, + truncateText: true, + }, + { + field: 'severity', + name: 'Severity', + render: (value: ColumnTypes['severity']) => ( + + {value} + + ), + sortable: true, + truncateText: true, + }, + { + field: 'lastCompletedRun', + name: 'Last completed run', + render: (value: ColumnTypes['lastCompletedRun']) => { + return value === undefined ? ( + getEmptyTagValue() + ) : ( + + ); + }, + sortable: true, + truncateText: true, + width: '16%', + }, + { + field: 'lastResponse', + name: 'Last response', + render: (value: ColumnTypes['lastResponse']) => { + return value === undefined ? ( + getEmptyTagValue() + ) : ( + <> + {value.type === 'Fail' ? ( + + {value.type} + + ) : ( + {value.type} + )} + + ); + }, + sortable: true, + truncateText: true, + }, + { + field: 'tags', + name: 'Tags', + render: (value: ColumnTypes['tags']) => ( +
+ {typeof value !== 'string' ? ( + <> + {value.map((tag, i) => ( + + {tag} + + ))} + + ) : ( + {value} + )} +
+ ), + sortable: true, + truncateText: true, + width: '20%', + }, + { + align: 'center', + field: 'activate', + name: 'Activate', + render: (value: ColumnTypes['activate']) => ( + // Michael: Uncomment props below when EUI 14.9.0 is added to Kibana. + {}} showLabel={false} /> + ), + sortable: true, + width: '65px', + }, + { + actions, + width: '40px', + }, + ]; + + const sampleTableData = [ + { + id: 1, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Low', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: ['attack.t1234', 'attack.t4321'], + activate: true, + }, + { + id: 2, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Medium', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Fail', + message: 'Full fail message here.', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 3, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'High', + tags: 'attack.t1234', + activate: false, + }, + { + id: 4, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 5, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 6, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 7, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 8, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 9, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 10, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 11, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 12, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 13, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 14, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 15, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 16, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 17, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 18, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 19, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 20, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + { + id: 21, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + status: 'Experimental', + }, + method: 'Custom query', + severity: 'Critical', + lastCompletedRun: '2019-12-28 00:00:00.000-05:00', + lastResponse: { + type: 'Success', + }, + tags: 'attack.t1234', + activate: true, + }, + ]; + + const [itemsTotalState] = useState(sampleTableData.length); + const [pageState, setPageState] = useState({ index: 0, size: 20 }); + // const [selectedState, setSelectedState] = useState([]); + const [sortState, setSortState] = useState({ field: 'rule', direction: 'asc' }); + + return ( + <> + + + + + + + + + + + {'Showing: 39 rules'} + + + + {'Selected: 2 rules'} + + {'Batch actions context menu here.'}

} + > + {'Batch actions'} +
+
+ + + {'Clear 7 filters'} + +
+
+ + { + setPageState(page); + setSortState(sort); + }} + pagination={{ + pageIndex: pageState.index, + pageSize: pageState.size, + totalItemCount: itemsTotalState, + pageSizeOptions: [5, 10, 20], + }} + selection={{ + selectable: () => true, + onSelectionChange: (selectedItems: ColumnTypes[]) => { + // setSelectedState(selectedItems); + }, + }} + sorting={{ + sort: sortState, + }} + /> +
+ + ); +}); +AllRules.displayName = 'AllRules'; + +const ActivityMonitor = React.memo(() => { + interface RuleTypes { + href: string; + name: string; + } + + interface ColumnTypes { + id: number; + rule: RuleTypes; + ran: string; + lookedBackTo: string; + status: string; + response: string | undefined; + } + + interface PageTypes { + index: number; + size: number; + } + + interface SortTypes { + field: string; + direction: string; + } + + const actions = [ + { + available: (item: ColumnTypes) => item.status === 'Running', + description: 'Stop', + icon: 'stop', + isPrimary: true, + name: 'Stop', + onClick: () => {}, + type: 'icon', + }, + { + available: (item: ColumnTypes) => item.status === 'Stopped', + description: 'Resume', + icon: 'play', + isPrimary: true, + name: 'Resume', + onClick: () => {}, + type: 'icon', + }, + ]; + + // Michael: Are we able to do custom, in-table-header filters, as shown in my wireframes? + const columns = [ + { + field: 'rule', + name: 'Rule', + render: (value: ColumnTypes['rule']) => {value.name}, + sortable: true, + truncateText: true, + }, + { + field: 'ran', + name: 'Ran', + render: (value: ColumnTypes['ran']) => , + sortable: true, + truncateText: true, + }, + { + field: 'lookedBackTo', + name: 'Looked back to', + render: (value: ColumnTypes['lookedBackTo']) => ( + + ), + sortable: true, + truncateText: true, + }, + { + field: 'status', + name: 'Status', + sortable: true, + truncateText: true, + }, + { + field: 'response', + name: 'Response', + render: (value: ColumnTypes['response']) => { + return value === undefined ? ( + getEmptyTagValue() + ) : ( + <> + {value === 'Fail' ? ( + + {value} + + ) : ( + {value} + )} + + ); + }, + sortable: true, + truncateText: true, + }, + { + actions, + width: '40px', + }, + ]; + + const sampleTableData = [ + { + id: 1, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Running', + }, + { + id: 2, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Stopped', + }, + { + id: 3, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Fail', + }, + { + id: 4, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 5, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 6, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 7, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 8, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 9, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 10, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 11, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 12, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 13, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 14, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 15, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 16, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 17, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 18, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 19, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 20, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + { + id: 21, + rule: { + href: '#/detection-engine/rules/rule-details', + name: 'Automated exfiltration', + }, + ran: '2019-12-28 00:00:00.000-05:00', + lookedBackTo: '2019-12-28 00:00:00.000-05:00', + status: 'Completed', + response: 'Success', + }, + ]; + + const [itemsTotalState] = useState(sampleTableData.length); + const [pageState, setPageState] = useState({ index: 0, size: 20 }); + // const [selectedState, setSelectedState] = useState([]); + const [sortState, setSortState] = useState({ field: 'ran', direction: 'desc' }); + + return ( + <> + + + + + + + + + {'Showing: 39 activites'} + + + + {'Selected: 2 activities'} + + {'Stop selected'} + + + + {'Clear 7 filters'} + + + + + { + setPageState(page); + setSortState(sort); + }} + pagination={{ + pageIndex: pageState.index, + pageSize: pageState.size, + totalItemCount: itemsTotalState, + pageSizeOptions: [5, 10, 20], + }} + selection={{ + selectable: (item: ColumnTypes) => item.status !== 'Completed', + selectableMessage: (selectable: boolean) => + selectable ? undefined : 'Completed runs cannot be acted upon', + onSelectionChange: (selectedItems: ColumnTypes[]) => { + // setSelectedState(selectedItems); + }, + }} + sorting={{ + sort: sortState, + }} + /> + + + ); +}); +ActivityMonitor.displayName = 'ActivityMonitor'; + +export const RulesComponent = React.memo(() => { + return ( + <> + + + + + + {'Import rule…'} + + + + + + {'Add new rule'} + + + + + + , + }, + { + id: 'tabActivityMonitor', + name: 'Activity monitor', + content: , + }, + ]} + /> + + + + + ); +}); +RulesComponent.displayName = 'RulesComponent'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts new file mode 100644 index 0000000000000..2b20c726d4b3f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts @@ -0,0 +1,11 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.pageTitle', { + defaultMessage: 'Rules', +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts new file mode 100644 index 0000000000000..a7e7fa5133a64 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts @@ -0,0 +1,45 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', { + defaultMessage: 'Detection engine', +}); + +export const PAGE_SUBTITLE = i18n.translate('xpack.siem.detectionEngine.pageSubtitle', { + defaultMessage: 'Last signal: X minutes ago', +}); + +export const BUTTON_MANAGE_RULES = i18n.translate('xpack.siem.detectionEngine.buttonManageRules', { + defaultMessage: 'Manage rules', +}); + +export const PANEL_SUBTITLE_SHOWING = i18n.translate( + 'xpack.siem.detectionEngine.panelSubtitleShowing', + { + defaultMessage: 'Showing', + } +); + +export const EMPTY_TITLE = i18n.translate('xpack.siem.detectionEngine.emptyTitle', { + defaultMessage: + 'It looks like you don’t have any indices relevant to the detction engine in the SIEM application', +}); + +export const EMPTY_ACTION_PRIMARY = i18n.translate( + 'xpack.siem.detectionEngine.emptyActionPrimary', + { + defaultMessage: 'View setup instructions', + } +); + +export const EMPTY_ACTION_SECONDARY = i18n.translate( + 'xpack.siem.detectionEngine.emptyActionSecondary', + { + defaultMessage: 'Go to documentation', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx b/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx index 53bcac028b877..220f8a958aa43 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx @@ -3,14 +3,16 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import * as i18n from './translations'; -import { SiemPageName, SiemNavTab } from './types'; + import { + getDetectionEngineUrl, getOverviewUrl, getNetworkUrl, getTimelinesUrl, getHostsUrl, } from '../../components/link_to'; +import * as i18n from './translations'; +import { SiemPageName, SiemNavTab } from './types'; export const navTabs: SiemNavTab = { [SiemPageName.overview]: { @@ -34,6 +36,13 @@ export const navTabs: SiemNavTab = { disabled: false, urlKey: 'network', }, + [SiemPageName.detectionEngine]: { + id: SiemPageName.detectionEngine, + name: i18n.DETECTION_ENGINE, + href: getDetectionEngineUrl(), + disabled: false, + urlKey: 'detection-engine', + }, [SiemPageName.timelines]: { id: SiemPageName.timelines, name: i18n.TIMELINES, diff --git a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx index da53ac8fceac4..eb816876bdba8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx @@ -4,35 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiPage, EuiPageBody } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; import * as React from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; import { pure } from 'recompose'; import styled from 'styled-components'; -import { i18n } from '@kbn/i18n'; import { AutoSizer } from '../../components/auto_sizer'; import { DragDropContextWrapper } from '../../components/drag_and_drop/drag_drop_context_wrapper'; import { Flyout, flyoutHeaderHeight } from '../../components/flyout'; +import { HeaderGlobal } from '../../components/header_global'; import { HelpMenu } from '../../components/help_menu'; import { LinkToPage } from '../../components/link_to'; -import { SiemNavigation } from '../../components/navigation'; +import { MlHostConditionalContainer } from '../../components/ml/conditional_links/ml_host_conditional_container'; +import { MlNetworkConditionalContainer } from '../../components/ml/conditional_links/ml_network_conditional_container'; import { StatefulTimeline } from '../../components/timeline'; import { AutoSaveWarningMsg } from '../../components/timeline/auto_save_warning'; +import { UseUrlState } from '../../components/url_state'; +import { WithSource } from '../../containers/source'; +import { SpyRoute } from '../../utils/route/spy_routes'; import { NotFoundPage } from '../404'; +import { DetectionEngineContainer } from '../detection_engine'; import { HostsContainer } from '../hosts'; import { NetworkContainer } from '../network'; import { Overview } from '../overview'; import { Timelines } from '../timelines'; -import { WithSource } from '../../containers/source'; -import { MlPopover } from '../../components/ml_popover/ml_popover'; -import { MlHostConditionalContainer } from '../../components/ml/conditional_links/ml_host_conditional_container'; -import { MlNetworkConditionalContainer } from '../../components/ml/conditional_links/ml_network_conditional_container'; import { navTabs } from './home_navigations'; import { SiemPageName } from './types'; -import { UseUrlState } from '../../components/url_state'; -import { SpyRoute } from '../../utils/route/spy_routes'; /* * This is import is important to keep because if we do not have it @@ -44,30 +41,8 @@ import 'uiExports/embeddableFactories'; const WrappedByAutoSizer = styled.div` height: 100%; `; - WrappedByAutoSizer.displayName = 'WrappedByAutoSizer'; -const gutterTimeline = '70px'; // Temporary until timeline is moved - MichaelMarcialis - -const Page = styled(EuiPage)` - ${({ theme }) => ` - padding: 0 ${gutterTimeline} ${theme.eui.euiSizeL} ${theme.eui.euiSizeL}; - `} -`; - -Page.displayName = 'Page'; - -const NavGlobal = styled.nav` - ${({ theme }) => ` - background: ${theme.eui.euiColorEmptyShade}; - border-bottom: ${theme.eui.euiBorderThin}; - margin: 0 -${gutterTimeline} 0 -${theme.eui.euiSizeL}; - padding: ${theme.eui.euiSize} ${gutterTimeline} ${theme.eui.euiSize} ${theme.eui.euiSizeL}; - `} -`; - -NavGlobal.displayName = 'NavGlobal'; - const usersViewing = ['elastic']; // TODO: get the users viewing this timeline from Elasticsearch (persistance) /** the global Kibana navigation at the top of every page */ @@ -85,8 +60,9 @@ export const HomePage = pure(() => ( {({ measureRef, windowMeasurement: { height: windowHeight = 0 } }) => ( - - + + +
{({ browserFields, indexPattern }) => ( @@ -111,90 +87,59 @@ export const HomePage = pure(() => ( /> - - - - - - - - - - - - - - - - - - - - - - - - - } - /> - ( - - )} - /> - ( - - )} - /> - } - /> - - ( - - )} - /> - ( - - )} - /> - - - + + + } + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + } + /> + + ( + + )} + /> + ( + + )} + /> + + )} - +
+ + +
)}
)); - HomePage.displayName = 'HomePage'; diff --git a/x-pack/legacy/plugins/siem/public/pages/home/translations.ts b/x-pack/legacy/plugins/siem/public/pages/home/translations.ts index 30725828ac5eb..b87ea1c17a117 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/home/translations.ts @@ -18,6 +18,10 @@ export const NETWORK = i18n.translate('xpack.siem.navigation.network', { defaultMessage: 'Network', }); +export const DETECTION_ENGINE = i18n.translate('xpack.siem.navigation.detectionEngine', { + defaultMessage: 'Detection engine', +}); + export const TIMELINES = i18n.translate('xpack.siem.navigation.timelines', { defaultMessage: 'Timelines', }); diff --git a/x-pack/legacy/plugins/siem/public/pages/home/types.ts b/x-pack/legacy/plugins/siem/public/pages/home/types.ts index 4adf4485d8e29..101c6a69b08d1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/home/types.ts @@ -10,6 +10,7 @@ export enum SiemPageName { overview = 'overview', hosts = 'hosts', network = 'network', + detectionEngine = 'detection-engine', timelines = 'timelines', } @@ -17,6 +18,7 @@ export type SiemNavTabKey = | SiemPageName.overview | SiemPageName.hosts | SiemPageName.network + | SiemPageName.detectionEngine | SiemPageName.timelines; export type SiemNavTab = Record; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx index 738c0771ffc32..d30665c5a2142 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx @@ -6,39 +6,40 @@ import { EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; import React, { useContext, useEffect } from 'react'; -import { compose } from 'redux'; import { connect } from 'react-redux'; import { StickyContainer } from 'react-sticky'; +import { compose } from 'redux'; -import { inputsSelectors, State } from '../../../store'; import { FiltersGlobal } from '../../../components/filters_global'; import { HeaderPage } from '../../../components/header_page'; -import { KpiHostDetailsQuery } from '../../../containers/kpi_host_details'; import { LastEventTime } from '../../../components/last_event_time'; +import { AnomalyTableProvider } from '../../../components/ml/anomaly/anomaly_table_provider'; import { hostToCriteria } from '../../../components/ml/criteria/host_to_criteria'; -import { MlCapabilitiesContext } from '../../../components/ml/permissions/ml_capabilities_provider'; import { hasMlUserPermissions } from '../../../components/ml/permissions/has_ml_user_permissions'; -import { AnomalyTableProvider } from '../../../components/ml/anomaly/anomaly_table_provider'; +import { MlCapabilitiesContext } from '../../../components/ml/permissions/ml_capabilities_provider'; import { scoreIntervalToDateTime } from '../../../components/ml/score/score_interval_to_datetime'; -import { setHostDetailsTablesActivePageToZero as dispatchHostDetailsTablesActivePageToZero } from '../../../store/hosts/actions'; import { SiemNavigation } from '../../../components/navigation'; -import { manageQuery } from '../../../components/page/manage_query'; -import { HostOverview } from '../../../components/page/hosts/host_overview'; import { KpiHostsComponent } from '../../../components/page/hosts'; +import { HostOverview } from '../../../components/page/hosts/host_overview'; +import { manageQuery } from '../../../components/page/manage_query'; import { SiemSearchBar } from '../../../components/search_bar'; +import { WrapperPage } from '../../../components/wrapper_page'; import { HostOverviewByNameQuery } from '../../../containers/hosts/overview'; +import { KpiHostDetailsQuery } from '../../../containers/kpi_host_details'; import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../../containers/source'; import { LastEventIndexKey } from '../../../graphql/types'; +import { useKibanaCore } from '../../../lib/compose/kibana_core'; import { convertToBuildEsQuery } from '../../../lib/keury'; +import { inputsSelectors, State } from '../../../store'; +import { setHostDetailsTablesActivePageToZero as dispatchHostDetailsTablesActivePageToZero } from '../../../store/hosts/actions'; import { setAbsoluteRangeDatePicker as dispatchAbsoluteRangeDatePicker } from '../../../store/inputs/actions'; import { SpyRoute } from '../../../utils/route/spy_routes'; -import { useKibanaCore } from '../../../lib/compose/kibana_core'; import { esQuery } from '../../../../../../../../src/plugins/data/public'; import { HostsEmptyPage } from '../hosts_empty_page'; +import { HostDetailsTabs } from './details_tabs'; import { navTabsHostDetails } from './nav_tabs'; import { HostDetailsComponentProps, HostDetailsProps } from './types'; -import { HostDetailsTabs } from './details_tabs'; import { type } from './utils'; const HostOverviewManage = manageQuery(HostOverview); @@ -63,6 +64,7 @@ const HostDetailsComponent = React.memo( }, [detailName]); const capabilities = useContext(MlCapabilitiesContext); const core = useKibanaCore(); + return ( <> @@ -96,14 +98,16 @@ const HostDetailsComponent = React.memo( ...filters, ], }); + return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - <> - - - - + + + + + ( } title={detailName} /> + ( - - - + + + + ) : ( - <> - + + + - + ); }} + ); } ); - HostDetailsComponent.displayName = 'HostDetailsComponent'; export const makeMapStateToProps = () => { 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 a149b5224bd67..4ff666464404e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx @@ -6,35 +6,35 @@ import { EuiSpacer } from '@elastic/eui'; import * as React from 'react'; -import { compose } from 'redux'; import { connect } from 'react-redux'; import { StickyContainer } from 'react-sticky'; +import { compose } from 'redux'; import { FiltersGlobal } from '../../components/filters_global'; -import { GlobalTimeArgs } from '../../containers/global_time'; import { HeaderPage } from '../../components/header_page'; -import { KpiHostsQuery } from '../../containers/kpi_hosts'; import { LastEventTime } from '../../components/last_event_time'; +import { hasMlUserPermissions } from '../../components/ml/permissions/has_ml_user_permissions'; +import { MlCapabilitiesContext } from '../../components/ml/permissions/ml_capabilities_provider'; import { SiemNavigation } from '../../components/navigation'; import { KpiHostsComponent } from '../../components/page/hosts'; import { manageQuery } from '../../components/page/manage_query'; -import { hasMlUserPermissions } from '../../components/ml/permissions/has_ml_user_permissions'; -import { MlCapabilitiesContext } from '../../components/ml/permissions/ml_capabilities_provider'; import { SiemSearchBar } from '../../components/search_bar'; +import { WrapperPage } from '../../components/wrapper_page'; +import { GlobalTimeArgs } from '../../containers/global_time'; +import { KpiHostsQuery } from '../../containers/kpi_hosts'; import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; import { LastEventIndexKey } from '../../graphql/types'; +import { useKibanaCore } from '../../lib/compose/kibana_core'; import { convertToBuildEsQuery } from '../../lib/keury'; import { inputsSelectors, State, hostsModel } from '../../store'; import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { SpyRoute } from '../../utils/route/spy_routes'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; - import { esQuery } from '../../../../../../../src/plugins/data/public'; import { HostsEmptyPage } from './hosts_empty_page'; +import { HostsTabs } from './hosts_tabs'; import { navTabsHosts } from './nav_tabs'; import * as i18n from './translations'; import { HostsComponentProps, HostsComponentReduxProps } from './types'; -import { HostsTabs } from './hosts_tabs'; const KpiHostsComponentManage = manageQuery(KpiHostsComponent); @@ -64,76 +64,77 @@ const HostsComponent = React.memo( filters, }); return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - <> - - - - + + + + + } title={i18n.PAGE_TITLE} /> - <> - - {({ kpiHosts, loading, id, inspect, refetch }) => ( - { - setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); - }} - /> - )} - - - - - - - - + + + {({ kpiHosts, loading, id, inspect, refetch }) => ( + { + setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); + }} + /> + )} + + + + + + + + + + + ) : ( - <> - + + + - + ); }} + ); } ); - HostsComponent.displayName = 'HostsComponent'; const makeMapStateToProps = () => { diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx index 174b6fc5ad5d1..a059c4beeaa28 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx @@ -163,7 +163,7 @@ describe('Ip Details', () => { wrapper.update(); expect( wrapper - .find('[data-test-subj="ip-details-headline"] [data-test-subj="page_headline_title"]') + .find('[data-test-subj="ip-details-headline"] [data-test-subj="header-page-title"]') .text() ).toEqual('fe80::24ce:f7ff:fede:a571'); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx index 66a96e71808ef..96111f0479938 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx @@ -20,9 +20,11 @@ import { manageQuery } from '../../../components/page/manage_query'; import { FlowTargetSelectConnected } from '../../../components/page/network/flow_target_select_connected'; import { IpOverview } from '../../../components/page/network/ip_overview'; import { SiemSearchBar } from '../../../components/search_bar'; +import { WrapperPage } from '../../../components/wrapper_page'; import { IpOverviewQuery } from '../../../containers/ip_overview'; import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../../containers/source'; import { FlowTargetSourceDest, LastEventIndexKey } from '../../../graphql/types'; +import { useKibanaCore } from '../../../lib/compose/kibana_core'; import { decodeIpv6 } from '../../../lib/helpers'; import { convertToBuildEsQuery } from '../../../lib/keury'; import { ConditionalFlexGroup } from '../../../pages/network/navigation/conditional_flex_group'; @@ -31,16 +33,15 @@ import { setAbsoluteRangeDatePicker as dispatchAbsoluteRangeDatePicker } from '. import { setIpDetailsTablesActivePageToZero as dispatchIpDetailsTablesActivePageToZero } from '../../../store/network/actions'; import { SpyRoute } from '../../../utils/route/spy_routes'; import { NetworkEmptyPage } from '../network_empty_page'; - -import { IPDetailsComponentProps } from './types'; -export { getBreadcrumbs } from './utils'; -import { TlsQueryTable } from './tls_query_table'; -import { UsersQueryTable } from './users_query_table'; -import { NetworkTopNFlowQueryTable } from './network_top_n_flow_query_table'; import { NetworkHttpQueryTable } from './network_http_query_table'; import { NetworkTopCountriesQueryTable } from './network_top_countries_query_table'; +import { NetworkTopNFlowQueryTable } from './network_top_n_flow_query_table'; +import { TlsQueryTable } from './tls_query_table'; +import { IPDetailsComponentProps } from './types'; +import { UsersQueryTable } from './users_query_table'; import { esQuery } from '../../../../../../../../src/plugins/data/public'; -import { useKibanaCore } from '../../../lib/compose/kibana_core'; + +export { getBreadcrumbs } from './utils'; const IpOverviewManage = manageQuery(IpOverview); @@ -85,193 +86,197 @@ export const IPDetailsComponent = React.memo( queries: [query], filters, }); + return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - } - title={ip} - draggableArguments={{ field: `${flowTarget}.ip`, value: ip }} - > - - + + } + title={ip} + > + + - - {({ id, inspect, ipOverviewData, loading, refetch }) => ( - - {({ isLoadingAnomaliesData, anomaliesData }) => ( - { - const fromTo = scoreIntervalToDateTime(score, interval); - setAbsoluteRangeDatePicker({ - id: 'global', - from: fromTo.from, - to: fromTo.to, - }); - }} - /> - )} - - )} - + + {({ id, inspect, ipOverviewData, loading, refetch }) => ( + + {({ isLoadingAnomaliesData, anomaliesData }) => ( + { + const fromTo = scoreIntervalToDateTime(score, interval); + setAbsoluteRangeDatePicker({ + id: 'global', + from: fromTo.from, + to: fromTo.to, + }); + }} + /> + )} + + )} + - + - - - - + + + + - - - - + + + + - + - - - - + + + + - - - - + + + + - + - + - + - + - + - + - + - + + ) : ( - <> - + + - + ); }} + ); } ); - IPDetailsComponent.displayName = 'IPDetailsComponent'; const makeMapStateToProps = () => { 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 33e901d5dbac8..aa7572e5741bd 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/network.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/network.tsx @@ -17,6 +17,7 @@ import { SiemNavigation } from '../../components/navigation'; import { manageQuery } from '../../components/page/manage_query'; import { KpiNetworkComponent } from '../../components/page/network'; import { SiemSearchBar } from '../../components/search_bar'; +import { WrapperPage } from '../../components/wrapper_page'; import { KpiNetworkQuery } from '../../containers/kpi_network'; import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; import { LastEventIndexKey } from '../../graphql/types'; @@ -48,6 +49,7 @@ const NetworkComponent = React.memo( capabilitiesFetched, }) => { const core = useKibanaCore(); + return ( <> @@ -58,95 +60,95 @@ const NetworkComponent = React.memo( queries: [query], filters, }); + return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - } - title={i18n.PAGE_TITLE} - /> - - - - - - - {({ kpiNetwork, loading, id, inspect, refetch }) => ( - { - setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); - }} - /> + + } + title={i18n.PAGE_TITLE} + /> + + + + + + + {({ kpiNetwork, loading, id, inspect, refetch }) => ( + { + setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); + }} + /> + )} + + + {capabilitiesFetched && !isInitializing ? ( + <> + + + + + + + + + ) : ( + )} - - - {capabilitiesFetched && !isInitializing ? ( - <> - - - - - - - - - ) : ( - - )} - - + + + ) : ( - <> - + + - + ); }} + ); } ); - NetworkComponent.displayName = 'NetworkComponent'; const makeMapStateToProps = () => { 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 d8965f4d49491..de976b1a5c5a3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx @@ -7,68 +7,72 @@ import { EuiFlexGroup } from '@elastic/eui'; import moment from 'moment'; import React from 'react'; -import { pure } from 'recompose'; import chrome from 'ui/chrome'; import { documentationLinks } from 'ui/documentation_links'; +import { EmptyPage } from '../../components/empty_page'; import { HeaderPage } from '../../components/header_page'; import { OverviewHost } from '../../components/page/overview/overview_host'; import { OverviewNetwork } from '../../components/page/overview/overview_network'; +import { WrapperPage } from '../../components/wrapper_page'; import { GlobalTime } from '../../containers/global_time'; - -import { Summary } from './summary'; -import { EmptyPage } from '../../components/empty_page'; import { WithSource, indicesExistOrDataTemporarilyUnavailable } from '../../containers/source'; import { SpyRoute } from '../../utils/route/spy_routes'; - +import { Summary } from './summary'; import * as i18n from './translations'; const basePath = chrome.getBasePath(); -export const OverviewComponent = pure(() => { +export const OverviewComponent = React.memo(() => { const dateEnd = Date.now(); const dateRange = moment.duration(24, 'hours').asMilliseconds(); const dateStart = dateEnd - dateRange; return ( <> - + + + + + {({ indicesExist }) => + indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + {({ setQuery }) => ( + + + + + + )} + + ) : ( + + ) + } + + - - {({ indicesExist }) => - indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - {({ setQuery }) => ( - - - - - - )} - - ) : ( - - ) - } - ); }); - OverviewComponent.displayName = 'OverviewComponent'; diff --git a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx index 90eae605de4b7..93c8397e21431 100644 --- a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx @@ -10,14 +10,13 @@ import styled from 'styled-components'; import { HeaderPage } from '../../components/header_page'; import { StatefulOpenTimeline } from '../../components/open_timeline'; +import { WrapperPage } from '../../components/wrapper_page'; import { SpyRoute } from '../../utils/route/spy_routes'; - import * as i18n from './translations'; const TimelinesContainer = styled.div` width: 100%: `; - TimelinesContainer.displayName = 'TimelinesContainer'; interface TimelinesProps { @@ -30,16 +29,19 @@ export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10; export const TimelinesPage = React.memo(({ apolloClient }) => ( <> - - - - - + + + + + + + + )); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 645b8c427dc15..a5c950a3f2be0 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10511,7 +10511,6 @@ "xpack.siem.formatted.duration.noDurationTooltip": "期間がありません", "xpack.siem.formatted.duration.zeroNanosecondsTooltip": "0ナノ秒", "xpack.siem.formattedDuration.tooltipLabel": "生", - "xpack.siem.global.addData": "データの投入", "xpack.siem.headerPage.pageSubtitle": "前回のイベント: {beat}", "xpack.siem.host.details.architectureLabel": "アーキテクチャー", "xpack.siem.host.details.firstSeenTitle": "初回の認識", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d542e66b247ec..bb876ca1eb8b9 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10601,7 +10601,6 @@ "xpack.siem.formatted.duration.noDurationTooltip": "无持续时间", "xpack.siem.formatted.duration.zeroNanosecondsTooltip": "零纳秒", "xpack.siem.formattedDuration.tooltipLabel": "原始", - "xpack.siem.global.addData": "添加数据", "xpack.siem.headerPage.pageSubtitle": "最后事件:{beat}", "xpack.siem.host.details.architectureLabel": "架构", "xpack.siem.host.details.firstSeenTitle": "首次看到时间",