diff --git a/src/legacy/core_plugins/telemetry/common/constants.ts b/src/legacy/core_plugins/telemetry/common/constants.ts index b80396aac62a1f..7b0c62276f2902 100644 --- a/src/legacy/core_plugins/telemetry/common/constants.ts +++ b/src/legacy/core_plugins/telemetry/common/constants.ts @@ -51,7 +51,7 @@ export const LOCALSTORAGE_KEY = 'telemetry.data'; /** * Link to the Elastic Telemetry privacy statement. */ -export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/telemetry-privacy-statement`; +export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/privacy-statement`; /** * The type name used within the Monitoring index to publish localization stats. diff --git a/src/legacy/core_plugins/telemetry/public/components/__snapshots__/opt_in_message.test.tsx.snap b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/opt_in_message.test.tsx.snap new file mode 100644 index 00000000000000..c80485332fa8a9 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/opt_in_message.test.tsx.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`OptInMessage renders as expected 1`] = ` + + + + , + } + } + /> + +`; diff --git a/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap index e1aead3798de7a..b96313fd700ac5 100644 --- a/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap +++ b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap @@ -52,7 +52,7 @@ exports[`TelemetryForm renders as expected when allows to change optIn status 1`

{ const title = ( ); return ( @@ -45,12 +45,18 @@ export class OptInBanner extends React.PureComponent { this.props.optInClick(true)}> - + this.props.optInClick(false)}> - + diff --git a/src/legacy/core_plugins/telemetry/public/components/opt_in_message.test.tsx b/src/legacy/core_plugins/telemetry/public/components/opt_in_message.test.tsx new file mode 100644 index 00000000000000..1a9fabceda907d --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/components/opt_in_message.test.tsx @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { OptInMessage } from './opt_in_message'; + +describe('OptInMessage', () => { + it('renders as expected', () => { + expect( + shallowWithIntl( [])} />) + ).toMatchSnapshot(); + }); +}); diff --git a/src/legacy/core_plugins/telemetry/public/components/opt_in_message.tsx b/src/legacy/core_plugins/telemetry/public/components/opt_in_message.tsx index 928bb1015b7158..4221d78516e103 100644 --- a/src/legacy/core_plugins/telemetry/public/components/opt_in_message.tsx +++ b/src/legacy/core_plugins/telemetry/public/components/opt_in_message.tsx @@ -21,8 +21,7 @@ import * as React from 'react'; import { EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getConfigTelemetryDesc, PRIVACY_STATEMENT_URL } from '../../common/constants'; -import { OptInExampleFlyout } from './opt_in_details_component'; +import { PRIVACY_STATEMENT_URL } from '../../common/constants'; interface Props { fetchTelemetry: () => Promise; @@ -46,60 +45,22 @@ export class OptInMessage extends React.PureComponent { }; render() { - const { showDetails, showExample } = this.state; - - const getDetails = () => ( - - - - ), - telemetryPrivacyStatementLink: ( - - - - ), - }} - /> - ); - - const getFlyoutDetails = () => ( - this.setState({ showExample: false })} - fetchTelemetry={this.props.fetchTelemetry} - /> - ); - - const getReadMore = () => ( - this.setState({ showDetails: true })}> - - - ); - return ( - {getConfigTelemetryDesc()} {!showDetails && getReadMore()} - {showDetails && ( - - {getDetails()} - {showExample && getFlyoutDetails()} - - )} + + + + ), + }} + /> ); } diff --git a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/telemetry_opt_in.test.js.snap b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/telemetry_opt_in.test.js.snap index f82e8b03527c04..575c47205f9c09 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/telemetry_opt_in.test.js.snap +++ b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/telemetry_opt_in.test.js.snap @@ -188,7 +188,7 @@ exports[`TelemetryOptIn should display when telemetry not opted in 1`] = ` /> , "telemetryPrivacyStatementLink": , "telemetryPrivacyStatementLink": + + + + + 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 00000000000000..470b40cd1d960a --- /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 00000000000000..62ff1b17dd55fe --- /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 00000000000000..f81717c8927553 --- /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 00000000000000..446b5556945d8d --- /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 00000000000000..b07fe8bb847c7b --- /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 00000000000000..9d746bf3b0515b --- /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 00000000000000..bf13a503838cff --- /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 00000000000000..f226e0e0553912 --- /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 00000000000000..7a1c35183e5031 --- /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 00000000000000..ae4362bdbcd7bc --- /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 00000000000000..84ad96c5a1e5ee --- /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 00000000000000..1e23fd3498199d --- /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 00000000000000..2dfc1d3b8d1932 --- /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 00000000000000..c457e6bc3dee08 --- /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 00000000000000..0743e5cab02b49 --- /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 00000000000000..f8eb25f03d4adf --- /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 a97ef2cf5ca0c4..4e59acc4f67136 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 6b79a6402586e8..b2ea42622b63a5 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 @@ -18,7 +18,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'; @@ -132,7 +132,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 00000000000000..665a5c75f36840 --- /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 00000000000000..ebd1da634ed1a8 --- /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 00000000000000..168cacf3e97e12 --- /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 00000000000000..c713f63016594d --- /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 280acc0c633343..00000000000000 --- 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 00000000000000..0fe2890dc9f248 --- /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 16f2156e568e58..00000000000000 --- 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 2ba543b34307a3..00000000000000 --- 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 00000000000000..ae33b63e93d39f --- /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 9d89cdfc328936..4db2a35c600e9c 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 39250c38ef8fcd..ecd2b15a841f61 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 9cdb85bcb3d763..fffeece818d13e 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 e7b3fb9f2f4003..e46ae55a57a45e 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 00000000000000..5902768383cb09 --- /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 00000000000000..8e4387f35056ef --- /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 00000000000000..d83183adcf5e50 --- /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 7eb39de3d96b44..10198345755c37 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 0360c1004f1516..0125b52e3ad33c 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 00000000000000..74aec076ec4d52 --- /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 3334447739fc50..9d2ef203361bf9 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 3523723574be63..75e1531ea2b5ba 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 cf519da617183d..97cf9522f488fa 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 ae8d09eeff112b..7209be4d715f3e 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 3e3c02a1abfa4a..27d10cb02a856a 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 2918a19df52fd1..a8e16c82fbf806 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 7a0caf14af302e..f5207fc6a35fd4 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 db3d192f06ba18..9303c09c994aa3 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 f9b107e08afa23..78281a27bb360a 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 00000000000000..caf4334cacf571 --- /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 00000000000000..2412d05f3f47dd --- /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 00000000000000..fa26664930fe5e --- /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 3d4a2bc31f2fc9..ebabde44c61e93 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 c1629a50341db0..b6f1a9cdf26e40 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 646d003051e836..7be0c1885811ba 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 00000000000000..c62712e6cfe59d --- /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 00000000000000..269bcebdae01a2 --- /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 00000000000000..90eca051e3d110 --- /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 00000000000000..2522d4d1de084c --- /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 00000000000000..77506f8a466a5f --- /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 00000000000000..123e14d2391828 --- /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 c709a9370ec61e..2e700e3e23b64d 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 f7487d7a81a7a3..aa340b54c1699f 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 44c050a1990ce3..13618125325e1e 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 00000000000000..e5311bfb050a3d --- /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 00000000000000..95e80e8b9e5dec --- /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 00000000000000..5998aa527206e7 --- /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 659ecbadc34d2f..5706dcc50ed254 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 58a3c904b89a03..f806a5a7fcdd31 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 00000000000000..47a3527aff99c7 --- /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 00000000000000..884f3f3741228a --- /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 00000000000000..9b63a6e160e42c --- /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 00000000000000..cb3e690615395d --- /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 00000000000000..9b8607fdc7685f --- /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 00000000000000..cc2e2565eb8d0f --- /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 00000000000000..90524b4da0af44 --- /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 00000000000000..da3e5fb2083dd8 --- /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 00000000000000..3dd5945ff597ca --- /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 00000000000000..a046d7eaefb158 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx @@ -0,0 +1,1081 @@ +/* + * 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 00000000000000..2b20c726d4b3f4 --- /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 00000000000000..a7e7fa5133a646 --- /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 53bcac028b877a..220f8a958aa43b 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 da53ac8fceac4e..eb816876bdba80 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 30725828ac5ebf..b87ea1c17a117e 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 4adf4485d8e291..101c6a69b08d19 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 d3a242b41da7b5..2cb193fb47c6b9 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 @@ -7,38 +7,38 @@ import { EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; import { getEsQueryConfig } from '@kbn/es-query'; 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 { 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 +63,7 @@ const HostDetailsComponent = React.memo( }, [detailName]); const capabilities = useContext(MlCapabilitiesContext); const core = useKibanaCore(); + return ( <> @@ -96,14 +97,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 334d730378b236..1dc21c9d0284f1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx @@ -7,34 +7,34 @@ import { EuiSpacer } from '@elastic/eui'; import { getEsQueryConfig } from '@kbn/es-query'; 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 { 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 ed93061ba25266..b56b9d931af47c 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 @@ -164,7 +164,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 0fd4e073ebd13c..b1751cca0b3d09 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 @@ -21,9 +21,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'; @@ -32,15 +34,14 @@ 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 { useKibanaCore } from '../../../lib/compose/kibana_core'; +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'; + +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 f7b3cfb4962fcf..6b4c54737eb100 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/network.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/network.tsx @@ -18,6 +18,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 d8965f4d494917..de976b1a5c5a37 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 90eae605de4b7e..93c8397e214317 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/legacy/plugins/uptime/public/components/functional/snapshot.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx index 8d89e53a41a45d..ddc6df14c2adee 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx @@ -21,16 +21,22 @@ interface SnapshotQueryResult { snapshot?: SnapshotType; } +interface SnapshotProps { + /** + * Height is needed, since by default charts takes height of 100% + */ + height?: string; +} + +export type SnapshotComponentProps = SnapshotProps & UptimeGraphQLQueryProps; + /** * This component visualizes a KPI and histogram chart to help users quickly * glean the status of their uptime environment. * @param props the props required by the component */ -export const SnapshotComponent = ({ - data, - loading, -}: UptimeGraphQLQueryProps) => ( - +export const SnapshotComponent = ({ data, loading, height }: SnapshotComponentProps) => ( + (data, 'snapshot.counts.down', 0)} total={get(data, 'snapshot.counts.total', 0)} @@ -49,4 +55,7 @@ export const SnapshotComponent = ({ * This component visualizes a KPI and histogram chart to help users quickly * glean the status of their uptime environment. */ -export const Snapshot = withUptimeGraphQL(SnapshotComponent, snapshotQuery); +export const Snapshot = withUptimeGraphQL( + SnapshotComponent, + snapshotQuery +); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx index e941c2dad87d2d..a58d06ece0ede7 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx @@ -15,6 +15,8 @@ interface StatusPanelProps { sharedProps: { [key: string]: any }; } +const STATUS_CHART_HEIGHT = '160px'; + export const StatusPanel = ({ absoluteDateRangeStart, absoluteDateRangeEnd, @@ -23,13 +25,13 @@ export const StatusPanel = ({ - + diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e8621e5e18633e..1a66ab3e26fc29 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8868,7 +8868,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": "初回の認識", @@ -9918,12 +9917,7 @@ "telemetry.telemetryErrorNotificationMessageDescription.unableToSaveTelemetryPreferenceText": "遠隔測定設定を保存できません。", "telemetry.telemetryErrorNotificationMessageTitle": "遠隔測定エラー", "telemetry.usageDataTitle": "使用データ", - "telemetry.welcomeBanner.noButtonLabel": "いいえ", - "telemetry.welcomeBanner.telemetryConfigDescription.readMoreLinkText": "続きを読む", - "telemetry.welcomeBanner.telemetryConfigDetailsDescription": "ユーザーが処理したり保管したりするデータに関する情報は一切送信されません。この機能は定期的に基本的な機能利用に関する統計情報を送信します。{exampleLink} をご覧いただくか、{telemetryPrivacyStatementLink} をお読みください。この機能はいつでも無効にできます。", - "telemetry.welcomeBanner.telemetryConfigDetailsDescription.exampleLinkText": "例", "telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "遠隔測定に関するプライバシーステートメント", - "telemetry.welcomeBanner.yesButtonLabel": "はい", "xpack.upgradeAssistant.appTitle": "{version} アップグレードアシスタント", "xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.calloutDetail": "{snapshotRestoreDocsButton} でデータをバックアップします。", "xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.snapshotRestoreDocsButtonLabel": "API のスナップショットと復元", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 66bb7c6811a591..38df1b1f02b8f3 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9023,7 +9023,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": "首次看到时间", @@ -10073,12 +10072,7 @@ "telemetry.telemetryErrorNotificationMessageDescription.unableToSaveTelemetryPreferenceText": "无法保存遥测首选项。", "telemetry.telemetryErrorNotificationMessageTitle": "遥测错误", "telemetry.usageDataTitle": "使用情况数据", - "telemetry.welcomeBanner.noButtonLabel": "否", - "telemetry.welcomeBanner.telemetryConfigDescription.readMoreLinkText": "阅读更多内容", - "telemetry.welcomeBanner.telemetryConfigDetailsDescription": "不会发送有关您处理或存储的数据的信息。此功能将定期发送基本功能使用情况统计信息。请参阅{exampleLink}或阅读我们的{telemetryPrivacyStatementLink}。您可以随时禁用此功能。", - "telemetry.welcomeBanner.telemetryConfigDetailsDescription.exampleLinkText": "示例", "telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "遥测隐私声明", - "telemetry.welcomeBanner.yesButtonLabel": "是", "xpack.upgradeAssistant.appTitle": "{version} 升级助手", "xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.calloutDetail": "使用 {snapshotRestoreDocsButton} 备份您的数据。", "xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.snapshotRestoreDocsButtonLabel": "快照和还原 API",