From 4d0805053b5c588051c57a7c7a04a78d8dfffacc Mon Sep 17 00:00:00 2001
From: Steph Milovic
Date: Wed, 2 Oct 2019 18:21:14 -0600
Subject: [PATCH] [SIEM] Start of deprecated lifecycle refactor (#46293)
(#47151)
---
.../timeline/data_providers.spec.ts | 6 +-
.../timeline/flyout_button.spec.ts | 2 +-
.../autocomplete_field/suggestion_item.tsx | 13 +-
.../drag_drop_context_wrapper.tsx | 60 ++-
.../drag_and_drop/draggable_wrapper.tsx | 42 +-
.../components/edit_data_provider/index.tsx | 196 ++++----
.../components/embeddables/embedded_map.tsx | 2 +-
.../event_details/stateful_event_details.tsx | 37 +-
.../events_viewer/events_viewer.test.tsx | 11 +-
.../components/events_viewer/index.test.tsx | 10 +
.../public/components/events_viewer/index.tsx | 2 +-
.../fields_browser/field_browser.tsx | 163 ++++---
.../components/fields_browser/index.tsx | 251 +++++------
.../public/components/flyout/pane/index.tsx | 58 ++-
.../public/components/help_menu/help_menu.tsx | 52 ++-
.../components/lazy_accordion/index.tsx | 77 ++--
.../__snapshots__/index.test.tsx.snap | 104 -----
.../components/load_more_table/index.mock.tsx | 118 -----
.../components/load_more_table/index.test.tsx | 360 ---------------
.../components/load_more_table/index.tsx | 320 -------------
.../load_more_table/translations.ts | 23 -
.../permissions/ml_capabilities_provider.tsx | 2 +-
.../get_anomalies_host_table_columns.test.tsx | 2 +-
.../get_anomalies_host_table_columns.tsx | 2 +-
...t_anomalies_network_table_columns.test.tsx | 2 +-
.../get_anomalies_network_table_columns.tsx | 2 +-
.../components/navigation/index.test.tsx | 4 +-
.../public/components/navigation/index.tsx | 124 ++---
.../navigation/tab_navigation/index.test.tsx | 10 +-
.../navigation/tab_navigation/index.tsx | 103 ++---
.../siem/public/components/notes/index.tsx | 28 +-
.../components/notes/note_cards/index.tsx | 55 +--
.../delete_timeline_modal.test.tsx | 22 +-
.../delete_timeline_modal.tsx | 6 +-
.../delete_timeline_modal/index.test.tsx | 30 --
.../delete_timeline_modal/index.tsx | 53 +--
.../components/open_timeline/index.test.tsx | 190 ++++----
.../public/components/open_timeline/index.tsx | 424 ++++++++----------
.../open_timeline_modal/index.test.tsx | 23 +-
.../open_timeline_modal/index.tsx | 124 ++---
.../components/page/add_to_kql/index.tsx | 28 +-
.../page/hosts/hosts_table/index.tsx | 124 +++--
.../page/network/domains_table/columns.tsx | 2 +-
.../network/network_dns_table/columns.tsx | 2 +-
.../network_top_n_flow_table/columns.tsx | 2 +-
.../network_top_n_flow_table/index.tsx | 83 ++--
.../page/network/tls_table/columns.tsx | 2 +-
.../page/network/users_table/columns.tsx | 2 +-
.../components/paginated_table/index.tsx | 12 +-
.../public/components/resize_handle/index.tsx | 147 +++---
.../components/super_date_picker/index.tsx | 264 +++++------
.../body/column_headers/header/index.tsx | 77 ++--
.../body/data_driven_columns/index.tsx | 10 +-
.../timeline/body/events/stateful_event.tsx | 192 ++++----
.../timeline/body/stateful_body.tsx | 138 +++---
.../data_providers/provider_item_badge.tsx | 92 ++--
.../components/timeline/footer/index.test.tsx | 60 ++-
.../components/timeline/footer/index.tsx | 190 +++-----
.../timeline/footer/last_updated.tsx | 71 ++-
.../siem/public/components/timeline/index.tsx | 276 ++++++------
.../components/timeline/properties/index.tsx | 150 +++----
.../timeline/search_or_filter/index.tsx | 31 +-
.../components/url_state/use_url_state.tsx | 2 +-
.../components/with_hover_actions/index.tsx | 40 +-
.../siem/public/containers/hosts/filter.tsx | 115 +++--
.../containers/hosts/first_last_seen/index.ts | 6 +-
.../containers/kuery_autocompletion/index.tsx | 117 +++--
.../siem/public/containers/network/filter.tsx | 124 +++--
.../siem/public/containers/source/index.tsx | 81 ++--
.../public/containers/timeline/all/index.tsx | 90 ++--
.../containers/timeline/details/index.tsx | 22 +-
.../public/pages/timelines/timelines_page.tsx | 34 +-
.../translations/translations/ja-JP.json | 4 -
.../translations/translations/zh-CN.json | 4 -
74 files changed, 2091 insertions(+), 3616 deletions(-)
delete mode 100644 x-pack/legacy/plugins/siem/public/components/load_more_table/__snapshots__/index.test.tsx.snap
delete mode 100644 x-pack/legacy/plugins/siem/public/components/load_more_table/index.mock.tsx
delete mode 100644 x-pack/legacy/plugins/siem/public/components/load_more_table/index.test.tsx
delete mode 100644 x-pack/legacy/plugins/siem/public/components/load_more_table/index.tsx
delete mode 100644 x-pack/legacy/plugins/siem/public/components/load_more_table/translations.ts
diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/data_providers.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/data_providers.spec.ts
index 7c9a0edebe53e..236d5a53481b7 100644
--- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/data_providers.spec.ts
+++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/data_providers.spec.ts
@@ -61,7 +61,7 @@ describe('timeline data providers', () => {
cy.get(TIMELINE_DATA_PROVIDERS).should(
'have.css',
'background',
- 'rgba(125, 226, 209, 0.1) none repeat scroll 0% 0% / auto padding-box border-box'
+ 'rgba(1, 125, 115, 0.1) none repeat scroll 0% 0% / auto padding-box border-box'
);
});
@@ -81,7 +81,7 @@ describe('timeline data providers', () => {
cy.get(TIMELINE_DATA_PROVIDERS_EMPTY).should(
'have.css',
'background',
- 'rgba(125, 226, 209, 0.2) none repeat scroll 0% 0% / auto padding-box border-box'
+ 'rgba(1, 125, 115, 0.2) none repeat scroll 0% 0% / auto padding-box border-box'
);
});
@@ -101,7 +101,7 @@ describe('timeline data providers', () => {
cy.get(TIMELINE_DATA_PROVIDERS).should(
'have.css',
'border',
- '3.1875px dashed rgb(125, 226, 209)'
+ '3.1875px dashed rgb(1, 125, 115)'
);
});
});
diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/flyout_button.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/flyout_button.spec.ts
index 811c529b8bec5..c1c35e497d081 100644
--- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/flyout_button.spec.ts
+++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/flyout_button.spec.ts
@@ -41,7 +41,7 @@ describe('timeline flyout button', () => {
cy.get(TIMELINE_NOT_READY_TO_DROP_BUTTON).should(
'have.css',
'background',
- 'rgba(125, 226, 209, 0.1) none repeat scroll 0% 0% / auto padding-box border-box'
+ 'rgba(1, 125, 115, 0.1) none repeat scroll 0% 0% / auto padding-box border-box'
);
});
});
diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx
index 997a19b0e8a2e..aaf7be2f7f5a6 100644
--- a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx
@@ -18,13 +18,8 @@ interface SuggestionItemProps {
suggestion: AutocompleteSuggestion;
}
-export class SuggestionItem extends React.PureComponent {
- public static defaultProps: Partial = {
- isSelected: false,
- };
-
- public render() {
- const { isSelected, onClick, onMouseEnter, suggestion } = this.props;
+export const SuggestionItem = React.memo(
+ ({ isSelected = false, onClick, onMouseEnter, suggestion }) => {
return (
{
);
}
-}
+);
+
+SuggestionItem.displayName = 'SuggestionItem';
const SuggestionItemContainer = euiStyled.div<{
isSelected?: boolean;
diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx
index aab83ec7908fe..11b604571378b 100644
--- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx
@@ -6,7 +6,7 @@
import { defaultTo, noop } from 'lodash/fp';
import * as React from 'react';
-import { DragDropContext, DropResult, ResponderProvided, DragStart } from 'react-beautiful-dnd';
+import { DragDropContext, DropResult, DragStart } from 'react-beautiful-dnd';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
@@ -57,43 +57,39 @@ const onDragEndHandler = ({
/**
* DragDropContextWrapperComponent handles all drag end events
*/
-export class DragDropContextWrapperComponent extends React.Component {
- public shouldComponentUpdate = ({ children, dataProviders }: Props) =>
- children === this.props.children && dataProviders !== this.props.dataProviders // prevent re-renders when data providers are added or removed, but all other props are the same
- ? false
- : true;
-
- public render() {
- const { children } = this.props;
-
+export const DragDropContextWrapperComponent = React.memo(
+ ({ browserFields, children, dataProviders, dispatch }) => {
+ function onDragEnd(result: DropResult) {
+ enableScrolling();
+
+ if (dataProviders != null) {
+ onDragEndHandler({
+ browserFields,
+ result,
+ dataProviders,
+ dispatch,
+ });
+ }
+
+ if (!draggableIsField(result)) {
+ document.body.classList.remove(IS_DRAGGING_CLASS_NAME);
+ }
+ }
return (
-
+
{children}
);
+ },
+ (prevProps, nextProps) => {
+ return (
+ prevProps.children === nextProps.children &&
+ prevProps.dataProviders === nextProps.dataProviders
+ ); // prevent re-renders when data providers are added or removed, but all other props are the same
}
+);
- private onDragEnd: (result: DropResult, provided: ResponderProvided) => void = (
- result: DropResult
- ) => {
- const { browserFields, dataProviders, dispatch } = this.props;
-
- enableScrolling();
-
- if (dataProviders != null) {
- onDragEndHandler({
- browserFields,
- result,
- dataProviders,
- dispatch,
- });
- }
-
- if (!draggableIsField(result)) {
- document.body.classList.remove(IS_DRAGGING_CLASS_NAME);
- }
- };
-}
+DragDropContextWrapperComponent.displayName = 'DragDropContextWrapperComponent';
const emptyDataProviders: dragAndDropModel.IdToDataProvider = {}; // stable reference
diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx
index 0755ef0e5592c..8a12a5035fc3a 100644
--- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx
@@ -5,7 +5,7 @@
*/
import { isEqual } from 'lodash/fp';
-import * as React from 'react';
+import React, { useEffect } from 'react';
import {
Draggable,
DraggableProvided,
@@ -161,28 +161,15 @@ type Props = OwnProps & DispatchProps;
* Wraps a draggable component to handle registration / unregistration of the
* data provider associated with the item being dropped
*/
-class DraggableWrapperComponent extends React.Component {
- public shouldComponentUpdate = ({ dataProvider, render, truncate }: Props) =>
- isEqual(dataProvider, this.props.dataProvider) &&
- render !== this.props.render &&
- truncate === this.props.truncate
- ? false
- : true;
-
- public componentDidMount() {
- const { dataProvider, registerProvider } = this.props;
-
- registerProvider!({ provider: dataProvider });
- }
-
- public componentWillUnmount() {
- const { dataProvider, unRegisterProvider } = this.props;
-
- unRegisterProvider!({ id: dataProvider.id });
- }
- public render() {
- const { dataProvider, render, truncate } = this.props;
+const DraggableWrapperComponent = React.memo(
+ ({ dataProvider, registerProvider, render, truncate, unRegisterProvider }) => {
+ useEffect(() => {
+ registerProvider!({ provider: dataProvider });
+ return () => {
+ unRegisterProvider!({ id: dataProvider.id });
+ };
+ }, []);
return (
@@ -223,8 +210,17 @@ class DraggableWrapperComponent extends React.Component {
);
+ },
+ (prevProps, nextProps) => {
+ return (
+ isEqual(prevProps.dataProvider, nextProps.dataProvider) &&
+ prevProps.render !== nextProps.render &&
+ prevProps.truncate === nextProps.truncate
+ );
}
-}
+);
+
+DraggableWrapperComponent.displayName = 'DraggableWrapperComponent';
export const DraggableWrapper = connect(
null,
diff --git a/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.tsx b/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.tsx
index 10b4340b6a88d..dc7f2185c26b7 100644
--- a/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.tsx
@@ -9,15 +9,15 @@ import {
EuiButton,
EuiComboBox,
EuiComboBoxOptionProps,
+ EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
- EuiFieldText,
EuiFormRow,
EuiPanel,
EuiSpacer,
EuiToolTip,
} from '@elastic/eui';
-import * as React from 'react';
+import React, { useEffect, useState } from 'react';
import styled, { injectGlobal } from 'styled-components';
import { BrowserFields } from '../../containers/source';
@@ -37,8 +37,8 @@ import * as i18n from './translations';
const EDIT_DATA_PROVIDER_WIDTH = 400;
const FIELD_COMBO_BOX_WIDTH = 195;
const OPERATOR_COMBO_BOX_WIDTH = 160;
-const VALUE_INPUT_CLASS_NAME = 'edit-data-provider-value';
const SAVE_CLASS_NAME = 'edit-data-provider-save';
+const VALUE_INPUT_CLASS_NAME = 'edit-data-provider-value';
export const HeaderContainer = styled.div`
width: ${EDIT_DATA_PROVIDER_WIDTH};
@@ -68,12 +68,6 @@ interface Props {
value: string | number;
}
-interface State {
- updatedField: EuiComboBoxOptionProps[];
- updatedOperator: EuiComboBoxOptionProps[];
- updatedValue: string | number;
-}
-
const sanatizeValue = (value: string | number): string =>
Array.isArray(value) ? `${value[0]}` : `${value}`; // fun fact: value should never be an array
@@ -88,37 +82,80 @@ export const getInitialOperatorLabel = (
}
};
-export class StatefulEditDataProvider extends React.PureComponent {
- constructor(props: Props) {
- super(props);
+export const StatefulEditDataProvider = React.memo(
+ ({
+ andProviderId,
+ browserFields,
+ field,
+ isExcluded,
+ onDataProviderEdited,
+ operator,
+ providerId,
+ timelineId,
+ value,
+ }) => {
+ const [updatedField, setUpdatedField] = useState([{ label: field }]);
+ const [updatedOperator, setUpdatedOperator] = useState(
+ getInitialOperatorLabel(isExcluded, operator)
+ );
+ const [updatedValue, setUpdatedValue] = useState(value);
- const { field, isExcluded, operator, value } = props;
+ /** Focuses the Value input if it is visible, falling back to the Save button if it's not */
+ function focusInput() {
+ const elements = document.getElementsByClassName(VALUE_INPUT_CLASS_NAME);
- this.state = {
- updatedField: [{ label: field }],
- updatedOperator: getInitialOperatorLabel(isExcluded, operator),
- updatedValue: value,
- };
- }
+ if (elements.length > 0) {
+ (elements[0] as HTMLElement).focus(); // this cast is required because focus() does not exist on every `Element` returned by `getElementsByClassName`
+ } else {
+ const saveElements = document.getElementsByClassName(SAVE_CLASS_NAME);
- public componentDidMount() {
- this.disableScrolling();
- this.focusInput();
- }
+ if (saveElements.length > 0) {
+ (saveElements[0] as HTMLElement).focus();
+ }
+ }
+ }
- public componentWillUnmount() {
- this.enableScrolling();
- }
+ function onFieldSelected(selectedField: EuiComboBoxOptionProps[]) {
+ setUpdatedField(selectedField);
+
+ focusInput();
+ }
+
+ function onOperatorSelected(operatorSelected: EuiComboBoxOptionProps[]) {
+ setUpdatedOperator(operatorSelected);
+
+ focusInput();
+ }
+
+ function onValueChange(e: React.ChangeEvent) {
+ setUpdatedValue(e.target.value);
+ }
+
+ function disableScrolling() {
+ const x =
+ window.pageXOffset !== undefined
+ ? window.pageXOffset
+ : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
- public render() {
- const {
- andProviderId,
- browserFields,
- onDataProviderEdited,
- providerId,
- timelineId,
- } = this.props;
- const { updatedField, updatedOperator, updatedValue } = this.state;
+ const y =
+ window.pageYOffset !== undefined
+ ? window.pageYOffset
+ : (document.documentElement || document.body.parentNode || document.body).scrollTop;
+
+ window.onscroll = () => window.scrollTo(x, y);
+ }
+
+ function enableScrolling() {
+ window.onscroll = () => noop;
+ }
+
+ useEffect(() => {
+ disableScrolling();
+ focusInput();
+ return () => {
+ enableScrolling();
+ };
+ }, []);
return (
@@ -127,18 +164,14 @@ export class StatefulEditDataProvider extends React.PureComponent
- 0 ? this.state.updatedField[0].label : null
- }
- >
+ 0 ? updatedField[0].label : null}>
@@ -151,10 +184,10 @@ export class StatefulEditDataProvider extends React.PureComponent
@@ -167,17 +200,17 @@ export class StatefulEditDataProvider extends React.PureComponent
- {this.state.updatedOperator.length > 0 &&
- this.state.updatedOperator[0].label !== i18n.EXISTS &&
- this.state.updatedOperator[0].label !== i18n.DOES_NOT_EXIST ? (
+ {updatedOperator.length > 0 &&
+ updatedOperator[0].label !== i18n.EXISTS &&
+ updatedOperator[0].label !== i18n.DOES_NOT_EXIST ? (
@@ -196,6 +229,13 @@ export class StatefulEditDataProvider extends React.PureComponent
color="primary"
data-test-subj="save"
fill={true}
+ isDisabled={
+ !selectionsAreValid({
+ browserFields,
+ selectedField: updatedField,
+ selectedOperator: updatedOperator,
+ })
+ }
onClick={() => {
onDataProviderEdited({
andProviderId,
@@ -207,13 +247,6 @@ export class StatefulEditDataProvider extends React.PureComponent
value: updatedValue,
});
}}
- isDisabled={
- !selectionsAreValid({
- browserFields: this.props.browserFields,
- selectedField: updatedField,
- selectedOperator: updatedOperator,
- })
- }
size="s"
>
{i18n.SAVE}
@@ -225,53 +258,6 @@ export class StatefulEditDataProvider extends React.PureComponent
);
}
+);
- /** Focuses the Value input if it is visible, falling back to the Save button if it's not */
- private focusInput = () => {
- const elements = document.getElementsByClassName(VALUE_INPUT_CLASS_NAME);
-
- if (elements.length > 0) {
- (elements[0] as HTMLElement).focus(); // this cast is required because focus() does not exist on every `Element` returned by `getElementsByClassName`
- } else {
- const saveElements = document.getElementsByClassName(SAVE_CLASS_NAME);
-
- if (saveElements.length > 0) {
- (saveElements[0] as HTMLElement).focus();
- }
- }
- };
-
- private onFieldSelected = (selectedField: EuiComboBoxOptionProps[]) => {
- this.setState({ updatedField: selectedField });
-
- this.focusInput();
- };
-
- private onOperatorSelected = (updatedOperator: EuiComboBoxOptionProps[]) => {
- this.setState({ updatedOperator });
-
- this.focusInput();
- };
-
- private onValueChange = (e: React.ChangeEvent) => {
- this.setState({
- updatedValue: e.target.value,
- });
- };
-
- private disableScrolling = () => {
- const x =
- window.pageXOffset !== undefined
- ? window.pageXOffset
- : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
-
- const y =
- window.pageYOffset !== undefined
- ? window.pageYOffset
- : (document.documentElement || document.body.parentNode || document.body).scrollTop;
-
- window.onscroll = () => window.scrollTo(x, y);
- };
-
- private enableScrolling = () => (window.onscroll = () => noop);
-}
+StatefulEditDataProvider.displayName = 'StatefulEditDataProvider';
diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx
index 86696503dbda3..18040a35a5280 100644
--- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx
@@ -43,7 +43,7 @@ export interface EmbeddedMapProps {
}
export const EmbeddedMap = React.memo(
- ({ applyFilterQueryFromKueryExpression, queryExpression, startDate, endDate, setQuery }) => {
+ ({ applyFilterQueryFromKueryExpression, endDate, queryExpression, setQuery, startDate }) => {
const [embeddable, setEmbeddable] = React.useState(null);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/stateful_event_details.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/stateful_event_details.tsx
index ec76d8f90c3de..cb67736829878 100644
--- a/x-pack/legacy/plugins/siem/public/components/event_details/stateful_event_details.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/event_details/stateful_event_details.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import * as React from 'react';
+import React, { useState } from 'react';
import { BrowserFields } from '../../containers/source';
import { ColumnHeader } from '../timeline/body/column_headers/column_header';
@@ -23,43 +23,24 @@ interface Props {
toggleColumn: (column: ColumnHeader) => void;
}
-interface State {
- view: View;
-}
-
-export class StatefulEventDetails extends React.PureComponent {
- constructor(props: Props) {
- super(props);
-
- this.state = { view: 'table-view' };
- }
+export const StatefulEventDetails = React.memo(
+ ({ browserFields, columnHeaders, data, id, onUpdateColumns, timelineId, toggleColumn }) => {
+ const [view, setView] = useState('table-view');
- public onViewSelected = (view: View): void => {
- this.setState({ view });
- };
-
- public render() {
- const {
- browserFields,
- columnHeaders,
- data,
- id,
- onUpdateColumns,
- timelineId,
- toggleColumn,
- } = this.props;
return (
setView(newView)}
timelineId={timelineId}
toggleColumn={toggleColumn}
+ view={view}
/>
);
}
-}
+);
+
+StatefulEventDetails.displayName = 'StatefulEventDetails';
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 03fb37760bc35..d85231b564da8 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
@@ -20,8 +20,17 @@ jest.mock('../../lib/settings/use_kibana_ui_setting');
const from = 1566943856794;
const to = 1566857456791;
-
+// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769
+/* eslint-disable no-console */
+const originalError = console.error;
describe('EventsViewer', () => {
+ beforeAll(() => {
+ console.error = jest.fn();
+ });
+
+ afterAll(() => {
+ console.error = originalError;
+ });
test('it renders the "Showing..." subtitle with the expected event count', async () => {
const wrapper = mount(
diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx
index bef5e66faecd1..dc0e1288f40f8 100644
--- a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx
@@ -20,7 +20,17 @@ jest.mock('../../lib/settings/use_kibana_ui_setting');
const from = 1566943856794;
const to = 1566857456791;
+// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769
+/* eslint-disable no-console */
+const originalError = console.error;
describe('StatefulEventsViewer', () => {
+ beforeAll(() => {
+ console.error = jest.fn();
+ });
+
+ afterAll(() => {
+ console.error = originalError;
+ });
test('it renders the events viewer', async () => {
const wrapper = mount(
diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx
index 52b724525f5a9..d572d6dd4913b 100644
--- a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx
@@ -87,7 +87,7 @@ const StatefulEventsViewerComponent = React.memo(
updateItemsPerPage,
upsertColumn,
}) => {
- const [showInspect, setShowInspect] = useState(false);
+ const [showInspect, setShowInspect] = useState(false);
useEffect(() => {
if (createTimeline != null) {
diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.tsx
index 17785ff582a3c..fb47672512de5 100644
--- a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.tsx
@@ -5,8 +5,8 @@
*/
import { EuiFlexGroup, EuiFlexItem, EuiOutsideClickDetector } from '@elastic/eui';
+import React, { useEffect } from 'react';
import { noop } from 'lodash/fp';
-import * as React from 'react';
import styled, { css } from 'styled-components';
import { BrowserFields } from '../../containers/source';
@@ -22,7 +22,7 @@ import {
getFieldBrowserSearchInputClassName,
PANES_FLEX_GROUP_WIDTH,
} from './helpers';
-import { FieldBrowserProps, OnFieldSelected, OnHideFieldBrowser } from './types';
+import { FieldBrowserProps, OnHideFieldBrowser } from './types';
const FieldsBrowserContainer = styled.div<{ width: number }>`
${({ theme, width }) => css`
@@ -102,34 +102,80 @@ type Props = Pick<
* This component has no internal state, but it uses lifecycle methods to
* set focus to the search input, scroll to the selected category, etc
*/
-export class FieldsBrowser extends React.PureComponent {
- public componentDidMount() {
- this.scrollViews();
- this.focusInput();
- }
+export const FieldsBrowser = React.memo(
+ ({
+ browserFields,
+ columnHeaders,
+ filteredBrowserFields,
+ isEventViewer,
+ isSearching,
+ onCategorySelected,
+ onFieldSelected,
+ onHideFieldBrowser,
+ onSearchInputChange,
+ onOutsideClick,
+ onUpdateColumns,
+ searchInput,
+ selectedCategoryId,
+ timelineId,
+ toggleColumn,
+ width,
+ }) => {
+ /** Focuses the input that filters the field browser */
+ function focusInput() {
+ const elements = document.getElementsByClassName(
+ getFieldBrowserSearchInputClassName(timelineId)
+ );
- public componentDidUpdate() {
- this.scrollViews();
- this.focusInput(); // always re-focus the input to enable additional filtering
- }
+ if (elements.length > 0) {
+ (elements[0] as HTMLElement).focus(); // this cast is required because focus() does not exist on every `Element` returned by `getElementsByClassName`
+ }
+ }
+
+ /** Invoked when the user types in the input to filter the field browser */
+ function onInputChange(event: React.ChangeEvent) {
+ onSearchInputChange(event.target.value);
+ }
+
+ function selectFieldAndHide(fieldId: string) {
+ if (onFieldSelected != null) {
+ onFieldSelected(fieldId);
+ }
- public render() {
- const {
- columnHeaders,
- browserFields,
- filteredBrowserFields,
- searchInput,
- isEventViewer,
- isSearching,
- onCategorySelected,
- onFieldSelected,
- onOutsideClick,
- onUpdateColumns,
- selectedCategoryId,
- timelineId,
- toggleColumn,
- width,
- } = this.props;
+ onHideFieldBrowser();
+ }
+
+ function scrollViews() {
+ if (selectedCategoryId !== '') {
+ const categoryPaneTitles = document.getElementsByClassName(
+ getCategoryPaneCategoryClassName({
+ categoryId: selectedCategoryId,
+ timelineId,
+ })
+ );
+
+ if (categoryPaneTitles.length > 0) {
+ categoryPaneTitles[0].scrollIntoView();
+ }
+
+ const fieldPaneTitles = document.getElementsByClassName(
+ getFieldBrowserCategoryTitleClassName({
+ categoryId: selectedCategoryId,
+ timelineId,
+ })
+ );
+
+ if (fieldPaneTitles.length > 0) {
+ fieldPaneTitles[0].scrollIntoView();
+ }
+ }
+
+ focusInput(); // always re-focus the input to enable additional filtering
+ }
+
+ useEffect(() => {
+ scrollViews();
+ }, [selectedCategoryId, timelineId]);
return (
{
isEventViewer={isEventViewer}
isSearching={isSearching}
onOutsideClick={onOutsideClick}
- onSearchInputChange={this.onInputChange}
+ onSearchInputChange={onInputChange}
onUpdateColumns={onUpdateColumns}
searchInput={searchInput}
timelineId={timelineId}
@@ -170,7 +216,7 @@ export class FieldsBrowser extends React.PureComponent {
data-test-subj="fields-pane"
filteredBrowserFields={filteredBrowserFields}
onCategorySelected={onCategorySelected}
- onFieldSelected={this.selectFieldAndHide}
+ onFieldSelected={selectFieldAndHide}
onUpdateColumns={onUpdateColumns}
searchInput={searchInput}
selectedCategoryId={selectedCategoryId}
@@ -184,59 +230,4 @@ export class FieldsBrowser extends React.PureComponent {
);
}
-
- /** Focuses the input that filters the field browser */
- private focusInput = () => {
- const elements = document.getElementsByClassName(
- getFieldBrowserSearchInputClassName(this.props.timelineId)
- );
-
- if (elements.length > 0) {
- (elements[0] as HTMLElement).focus(); // this cast is required because focus() does not exist on every `Element` returned by `getElementsByClassName`
- }
- };
-
- /** Invoked when the user types in the input to filter the field browser */
- private onInputChange = (event: React.ChangeEvent) =>
- this.props.onSearchInputChange(event.target.value);
-
- private selectFieldAndHide: OnFieldSelected = (fieldId: string) => {
- const { onFieldSelected, onHideFieldBrowser } = this.props;
-
- if (onFieldSelected != null) {
- onFieldSelected(fieldId);
- }
-
- onHideFieldBrowser();
- };
-
- private scrollViews = () => {
- const { selectedCategoryId, timelineId } = this.props;
-
- if (this.props.selectedCategoryId !== '') {
- const categoryPaneTitles = document.getElementsByClassName(
- getCategoryPaneCategoryClassName({
- categoryId: selectedCategoryId,
- timelineId,
- })
- );
-
- if (categoryPaneTitles.length > 0) {
- categoryPaneTitles[0].scrollIntoView();
- }
-
- const fieldPaneTitles = document.getElementsByClassName(
- getFieldBrowserCategoryTitleClassName({
- categoryId: selectedCategoryId,
- timelineId,
- })
- );
-
- if (fieldPaneTitles.length > 0) {
- fieldPaneTitles[0].scrollIntoView();
- }
- }
-
- this.focusInput(); // always re-focus the input to enable additional filtering
- };
-}
+);
diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx
index 69720c76cab80..7d21e1f44d04b 100644
--- a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx
@@ -6,7 +6,7 @@
import { EuiButtonEmpty, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { noop } from 'lodash/fp';
-import * as React from 'react';
+import React, { useEffect, useRef, useState } from 'react';
import { connect } from 'react-redux';
import styled from 'styled-components';
import { ActionCreator } from 'typescript-fsa';
@@ -15,7 +15,6 @@ import { BrowserFields } from '../../containers/source';
import { timelineActions } from '../../store/actions';
import { ColumnHeader } from '../timeline/body/column_headers/column_header';
import { DEFAULT_CATEGORY_NAME } from '../timeline/body/column_headers/default_headers';
-import { OnUpdateColumns } from '../timeline/events';
import { FieldsBrowser } from './field_browser';
import { filterBrowserFieldsByFieldName, mergeBrowserFieldsWithDefaultCategory } from './helpers';
import * as i18n from './translations';
@@ -26,19 +25,6 @@ const fieldsButtonClassName = 'fields-button';
/** wait this many ms after the user completes typing before applying the filter input */
const INPUT_TIMEOUT = 250;
-interface State {
- /** all field names shown in the field browser must contain this string (when specified) */
- filterInput: string;
- /** all fields in this collection have field names that match the filterInput */
- filteredBrowserFields: BrowserFields | null;
- /** when true, show a spinner in the input to indicate the field browser is searching for matching field names */
- isSearching: boolean;
- /** this category will be displayed in the right-hand pane of the field browser */
- selectedCategoryId: string;
- /** show the field browser */
- show: boolean;
-}
-
const FieldsBrowserButtonContainer = styled.div`
position: relative;
`;
@@ -60,52 +46,110 @@ interface DispatchProps {
/**
* Manages the state of the field browser
*/
-export class StatefulFieldsBrowserComponent extends React.PureComponent<
- FieldBrowserProps & DispatchProps,
- State
-> {
- /** tracks the latest timeout id from `setTimeout`*/
- private inputTimeoutId: number = 0;
-
- constructor(props: FieldBrowserProps) {
- super(props);
-
- this.state = {
- filterInput: '',
- filteredBrowserFields: null,
- isSearching: false,
- selectedCategoryId: DEFAULT_CATEGORY_NAME,
- show: false,
- };
- }
+export const StatefulFieldsBrowserComponent = React.memo(
+ ({
+ columnHeaders,
+ browserFields,
+ height,
+ isEventViewer = false,
+ onFieldSelected,
+ onUpdateColumns,
+ timelineId,
+ toggleColumn,
+ width,
+ }) => {
+ /** tracks the latest timeout id from `setTimeout`*/
+ const inputTimeoutId = useRef(0);
+
+ /** all field names shown in the field browser must contain this string (when specified) */
+ const [filterInput, setFilterInput] = useState('');
+ /** all fields in this collection have field names that match the filterInput */
+ const [filteredBrowserFields, setFilteredBrowserFields] = useState(null);
+ /** when true, show a spinner in the input to indicate the field browser is searching for matching field names */
+ const [isSearching, setIsSearching] = useState(false);
+ /** this category will be displayed in the right-hand pane of the field browser */
+ const [selectedCategoryId, setSelectedCategoryId] = useState(DEFAULT_CATEGORY_NAME);
+ /** show the field browser */
+ const [show, setShow] = useState(false);
+ useEffect(() => {
+ return () => {
+ if (inputTimeoutId.current !== 0) {
+ // ⚠️ mutation: cancel any remaining timers and zero-out the timer id:
+ clearTimeout(inputTimeoutId.current);
+ inputTimeoutId.current = 0;
+ }
+ };
+ }, []);
+
+ /** Shows / hides the field browser */
+ function toggleShow() {
+ setShow(!show);
+ }
+
+ /** Invoked when the user types in the filter input */
+ function updateFilter(newFilterInput: string) {
+ setFilterInput(newFilterInput);
+ setIsSearching(true);
+
+ if (inputTimeoutId.current !== 0) {
+ clearTimeout(inputTimeoutId.current); // ⚠️ mutation: cancel any previous timers
+ }
+
+ // ⚠️ mutation: schedule a new timer that will apply the filter when it fires:
+ inputTimeoutId.current = window.setTimeout(() => {
+ const newFilteredBrowserFields = filterBrowserFieldsByFieldName({
+ browserFields: mergeBrowserFieldsWithDefaultCategory(browserFields),
+ substring: filterInput,
+ });
+
+ setFilteredBrowserFields(newFilteredBrowserFields);
+ setIsSearching(false);
+
+ const newSelectedCategoryId =
+ filterInput === '' || Object.keys(newFilteredBrowserFields).length === 0
+ ? DEFAULT_CATEGORY_NAME
+ : Object.keys(newFilteredBrowserFields)
+ .sort()
+ .reduce(
+ (selected, category) =>
+ newFilteredBrowserFields[category].fields != null &&
+ newFilteredBrowserFields[selected].fields != null &&
+ newFilteredBrowserFields[category].fields!.length >
+ newFilteredBrowserFields[selected].fields!.length
+ ? category
+ : selected,
+ Object.keys(newFilteredBrowserFields)[0]
+ );
+ setSelectedCategoryId(newSelectedCategoryId);
+ }, INPUT_TIMEOUT);
+ }
- public componentWillUnmount() {
- if (this.inputTimeoutId !== 0) {
- // ⚠️ mutation: cancel any remaining timers and zero-out the timer id:
- clearTimeout(this.inputTimeoutId);
- this.inputTimeoutId = 0;
+ /**
+ * Invoked when the user clicks a category name in the left-hand side of
+ * the field browser
+ */
+ function updateSelectedCategoryId(categoryId: string) {
+ setSelectedCategoryId(categoryId);
}
- }
- public render() {
- const {
- columnHeaders,
- browserFields,
- height,
- isEventViewer = false,
- onFieldSelected,
- timelineId,
- toggleColumn,
- width,
- } = this.props;
- const {
- filterInput,
- filteredBrowserFields,
- isSearching,
- selectedCategoryId,
- show,
- } = this.state;
+ /**
+ * Invoked when the user clicks on the context menu to view a category's
+ * columns in the timeline, this function dispatches the action that
+ * causes the timeline display those columns.
+ */
+ function updateColumnsAndSelectCategoryId(columns: ColumnHeader[]) {
+ onUpdateColumns(columns); // show the category columns in the timeline
+ }
+ /** Invoked when the field browser should be hidden */
+ function hideFieldBrowser() {
+ setFilterInput('');
+ setFilterInput('');
+ setFilteredBrowserFields(null);
+ setIsSearching(false);
+ setSelectedCategoryId(DEFAULT_CATEGORY_NAME);
+ setShow(false);
+ }
// only merge in the default category if the field browser is visible
const browserFieldsWithDefaultCategory = show
? mergeBrowserFieldsWithDefaultCategory(browserFields)
@@ -121,14 +165,14 @@ export class StatefulFieldsBrowserComponent extends React.PureComponent<
className={fieldsButtonClassName}
data-test-subj="show-field-browser-gear"
iconType="list"
- onClick={this.toggleShow}
+ onClick={toggleShow}
/>
) : (
{i18n.FIELDS}
@@ -148,12 +192,12 @@ export class StatefulFieldsBrowserComponent extends React.PureComponent<
height={height}
isEventViewer={isEventViewer}
isSearching={isSearching}
- onCategorySelected={this.updateSelectedCategoryId}
+ onCategorySelected={updateSelectedCategoryId}
onFieldSelected={onFieldSelected}
- onHideFieldBrowser={this.hideFieldBrowser}
- onOutsideClick={show ? this.hideFieldBrowser : noop}
- onSearchInputChange={this.updateFilter}
- onUpdateColumns={this.updateColumnsAndSelectCategoryId}
+ onHideFieldBrowser={hideFieldBrowser}
+ onOutsideClick={show ? hideFieldBrowser : noop}
+ onSearchInputChange={updateFilter}
+ onUpdateColumns={updateColumnsAndSelectCategoryId}
searchInput={filterInput}
selectedCategoryId={selectedCategoryId}
timelineId={timelineId}
@@ -165,84 +209,9 @@ export class StatefulFieldsBrowserComponent extends React.PureComponent<
>
);
}
+);
- /** Shows / hides the field browser */
- private toggleShow = () => {
- this.setState(({ show }) => ({
- show: !show,
- }));
- };
-
- /** Invoked when the user types in the filter input */
- private updateFilter = (filterInput: string): void => {
- this.setState({
- filterInput,
- isSearching: true,
- });
-
- if (this.inputTimeoutId !== 0) {
- clearTimeout(this.inputTimeoutId); // ⚠️ mutation: cancel any previous timers
- }
-
- // ⚠️ mutation: schedule a new timer that will apply the filter when it fires:
- this.inputTimeoutId = window.setTimeout(() => {
- const filteredBrowserFields = filterBrowserFieldsByFieldName({
- browserFields: mergeBrowserFieldsWithDefaultCategory(this.props.browserFields),
- substring: this.state.filterInput,
- });
-
- this.setState(currentState => ({
- filteredBrowserFields,
- isSearching: false,
- selectedCategoryId:
- currentState.filterInput === '' || Object.keys(filteredBrowserFields).length === 0
- ? DEFAULT_CATEGORY_NAME
- : Object.keys(filteredBrowserFields)
- .sort()
- .reduce(
- (selected, category) =>
- filteredBrowserFields[category].fields != null &&
- filteredBrowserFields[selected].fields != null &&
- filteredBrowserFields[category].fields!.length >
- filteredBrowserFields[selected].fields!.length
- ? category
- : selected,
- Object.keys(filteredBrowserFields)[0]
- ),
- }));
- }, INPUT_TIMEOUT);
- };
-
- /**
- * Invoked when the user clicks a category name in the left-hand side of
- * the field browser
- */
- private updateSelectedCategoryId = (categoryId: string): void => {
- this.setState({
- selectedCategoryId: categoryId,
- });
- };
-
- /**
- * Invoked when the user clicks on the context menu to view a category's
- * columns in the timeline, this function dispatches the action that
- * causes the timeline display those columns.
- */
- private updateColumnsAndSelectCategoryId: OnUpdateColumns = (columns: ColumnHeader[]): void => {
- this.props.onUpdateColumns(columns); // show the category columns in the timeline
- };
-
- /** Invoked when the field browser should be hidden */
- private hideFieldBrowser = () => {
- this.setState({
- filterInput: '',
- filteredBrowserFields: null,
- isSearching: false,
- selectedCategoryId: DEFAULT_CATEGORY_NAME,
- show: false,
- });
- };
-}
+StatefulFieldsBrowserComponent.displayName = 'StatefulFieldsBrowserComponent';
export const StatefulFieldsBrowser = connect(
null,
diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx
index 15ce42c6a16b6..ceaff289f776c 100644
--- a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx
@@ -111,18 +111,30 @@ const FlyoutHeaderWithCloseButton = React.memo<{
FlyoutHeaderWithCloseButton.displayName = 'FlyoutHeaderWithCloseButton';
-class FlyoutPaneComponent extends React.PureComponent {
- public render() {
- const {
- children,
- flyoutHeight,
- headerHeight,
- onClose,
- timelineId,
- usersViewing,
- width,
- } = this.props;
-
+const FlyoutPaneComponent = React.memo(
+ ({
+ applyDeltaToWidth,
+ children,
+ flyoutHeight,
+ headerHeight,
+ onClose,
+ timelineId,
+ usersViewing,
+ width,
+ }) => {
+ const renderFlyout = () => <>>;
+
+ const onResize: OnResize = ({ delta, id }) => {
+ const bodyClientWidthPixels = document.body.clientWidth;
+
+ applyDeltaToWidth({
+ bodyClientWidthPixels,
+ delta,
+ id,
+ maxWidthPercent,
+ minWidthPixels,
+ });
+ };
return (
{
}
id={timelineId}
- onResize={this.onResize}
- render={this.renderFlyout}
+ onResize={onResize}
+ render={renderFlyout}
/>
{
);
}
+);
- private renderFlyout = () => <>>;
-
- private onResize: OnResize = ({ delta, id }) => {
- const { applyDeltaToWidth } = this.props;
-
- const bodyClientWidthPixels = document.body.clientWidth;
-
- applyDeltaToWidth({
- bodyClientWidthPixels,
- delta,
- id,
- maxWidthPercent,
- minWidthPixels,
- });
- };
-}
+FlyoutPaneComponent.displayName = 'FlyoutPaneComponent';
export const Pane = connect(
null,
diff --git a/x-pack/legacy/plugins/siem/public/components/help_menu/help_menu.tsx b/x-pack/legacy/plugins/siem/public/components/help_menu/help_menu.tsx
index 90af0d56c1582..b59753e8add6a 100644
--- a/x-pack/legacy/plugins/siem/public/components/help_menu/help_menu.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/help_menu/help_menu.tsx
@@ -16,30 +16,28 @@ export const Icon = styled(EuiIcon)`
Icon.displayName = 'Icon';
-export class HelpMenuComponent extends React.PureComponent {
- public render() {
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
- }
-}
+export const HelpMenuComponent = React.memo(() => (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+));
+
+HelpMenuComponent.displayName = 'HelpMenuComponent';
diff --git a/x-pack/legacy/plugins/siem/public/components/lazy_accordion/index.tsx b/x-pack/legacy/plugins/siem/public/components/lazy_accordion/index.tsx
index 5ed9a3b623c1c..da2e7334756e4 100644
--- a/x-pack/legacy/plugins/siem/public/components/lazy_accordion/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/lazy_accordion/index.tsx
@@ -5,7 +5,7 @@
*/
import { EuiAccordion, EuiAccordionProps } from '@elastic/eui';
-import * as React from 'react';
+import React, { useState } from 'react';
type Props = Pick> & {
forceExpand?: boolean;
@@ -14,10 +14,6 @@ type Props = Pick React.ReactNode;
};
-interface State {
- expanded: boolean;
-}
-
/**
* An accordion that doesn't render it's content unless it's expanded.
* This component was created because `EuiAccordion`'s eager rendering of
@@ -33,29 +29,36 @@ interface State {
* TODO: animate the expansion and collapse of content rendered "below"
* the real `EuiAccordion`.
*/
-export class LazyAccordion extends React.PureComponent {
- constructor(props: Props) {
- super(props);
-
- this.state = {
- expanded: false,
+export const LazyAccordion = React.memo(
+ ({
+ buttonContent,
+ buttonContentClassName,
+ extraAction,
+ forceExpand,
+ id,
+ onCollapse,
+ onExpand,
+ paddingSize,
+ renderExpandedContent,
+ }) => {
+ const [expanded, setExpanded] = useState(false);
+ const onCollapsedClick = () => {
+ setExpanded(true);
+ if (onExpand != null) {
+ onExpand();
+ }
};
- }
- public render() {
- const {
- id,
- buttonContentClassName,
- buttonContent,
- forceExpand,
- extraAction,
- renderExpandedContent,
- paddingSize,
- } = this.props;
+ const onExpandedClick = () => {
+ setExpanded(false);
+ if (onCollapse != null) {
+ onCollapse();
+ }
+ };
return (
<>
- {forceExpand || this.state.expanded ? (
+ {forceExpand || expanded ? (
<>
{
extraAction={extraAction}
id={id}
initialIsOpen={true}
- onClick={this.onExpandedClick}
+ onClick={onExpandedClick}
paddingSize={paddingSize}
>
<>>
- {renderExpandedContent(this.state.expanded)}
+ {renderExpandedContent(expanded)}
>
) : (
{
data-test-subj="lazy-accordion-placeholder"
extraAction={extraAction}
id={id}
- onClick={this.onCollapsedClick}
+ onClick={onCollapsedClick}
paddingSize={paddingSize}
/>
)}
>
);
}
+);
- private onCollapsedClick = () => {
- const { onExpand } = this.props;
-
- this.setState({ expanded: true });
-
- if (onExpand != null) {
- onExpand();
- }
- };
-
- private onExpandedClick = () => {
- const { onCollapse } = this.props;
-
- this.setState({ expanded: false });
-
- if (onCollapse != null) {
- onCollapse();
- }
- };
-}
+LazyAccordion.displayName = 'LazyAccordion';
diff --git a/x-pack/legacy/plugins/siem/public/components/load_more_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/load_more_table/__snapshots__/index.test.tsx.snap
deleted file mode 100644
index 4bf3f647502e2..0000000000000
--- a/x-pack/legacy/plugins/siem/public/components/load_more_table/__snapshots__/index.test.tsx.snap
+++ /dev/null
@@ -1,104 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Load More Table Component rendering it renders the default load more table 1`] = `
-
- My test supplement.
-
- }
- headerTitle="Hosts"
- headerTooltip="My test tooltip"
- headerUnit="Test Unit"
- itemsPerRow={
- Array [
- Object {
- "numberOfRow": 2,
- "text": "2 rows",
- },
- Object {
- "numberOfRow": 5,
- "text": "5 rows",
- },
- Object {
- "numberOfRow": 10,
- "text": "10 rows",
- },
- Object {
- "numberOfRow": 20,
- "text": "20 rows",
- },
- Object {
- "numberOfRow": 50,
- "text": "50 rows",
- },
- ]
- }
- limit={1}
- loadMore={[Function]}
- loading={false}
- pageOfItems={
- Array [
- Object {
- "cursor": Object {
- "value": "98966fa2013c396155c460d35c0902be",
- },
- "host": Object {
- "_id": "cPsuhGcB0WOhS6qyTKC0",
- "firstSeen": "2018-12-06T15:40:53.319Z",
- "name": "elrond.elstc.co",
- "os": "Ubuntu",
- "version": "18.04.1 LTS (Bionic Beaver)",
- },
- },
- Object {
- "cursor": Object {
- "value": "aa7ca589f1b8220002f2fc61c64cfbf1",
- },
- "host": Object {
- "_id": "KwQDiWcB0WOhS6qyXmrW",
- "firstSeen": "2018-12-07T14:12:38.560Z",
- "name": "siem-kibana",
- "os": "Debian GNU/Linux",
- "version": "9 (stretch)",
- },
- },
- ]
- }
- updateLimitPagination={[Function]}
-/>
-`;
diff --git a/x-pack/legacy/plugins/siem/public/components/load_more_table/index.mock.tsx b/x-pack/legacy/plugins/siem/public/components/load_more_table/index.mock.tsx
deleted file mode 100644
index 02ec00a78bc91..0000000000000
--- a/x-pack/legacy/plugins/siem/public/components/load_more_table/index.mock.tsx
+++ /dev/null
@@ -1,118 +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 { getOrEmptyTagFromValue } from '../empty_value';
-
-import { Columns, ItemsPerRow } from './index';
-
-export const mockData = {
- Hosts: {
- totalCount: 4,
- edges: [
- {
- host: {
- _id: 'cPsuhGcB0WOhS6qyTKC0',
- name: 'elrond.elstc.co',
- os: 'Ubuntu',
- version: '18.04.1 LTS (Bionic Beaver)',
- firstSeen: '2018-12-06T15:40:53.319Z',
- },
- cursor: {
- value: '98966fa2013c396155c460d35c0902be',
- },
- },
- {
- host: {
- _id: 'KwQDiWcB0WOhS6qyXmrW',
- name: 'siem-kibana',
- os: 'Debian GNU/Linux',
- version: '9 (stretch)',
- firstSeen: '2018-12-07T14:12:38.560Z',
- },
- cursor: {
- value: 'aa7ca589f1b8220002f2fc61c64cfbf1',
- },
- },
- ],
- pageInfo: {
- endCursor: {
- value: 'aa7ca589f1b8220002f2fc61c64cfbf1',
- },
- hasNextPage: true,
- },
- },
-};
-
-export const getHostsColumns = (): [
- Columns,
- Columns,
- Columns,
- Columns
-] => [
- {
- field: 'node.host.name',
- name: 'Host',
- truncateText: false,
- hideForMobile: false,
- render: (name: string) => getOrEmptyTagFromValue(name),
- },
- {
- field: 'node.host.firstSeen',
- name: 'First seen',
- truncateText: false,
- hideForMobile: false,
- render: (firstSeen: string) => getOrEmptyTagFromValue(firstSeen),
- },
- {
- field: 'node.host.os',
- name: 'OS',
- truncateText: false,
- hideForMobile: false,
- render: (os: string) => getOrEmptyTagFromValue(os),
- },
- {
- field: 'node.host.version',
- name: 'Version',
- truncateText: false,
- hideForMobile: false,
- render: (version: string) => getOrEmptyTagFromValue(version),
- },
-];
-
-export const sortedHosts: [
- Columns,
- Columns,
- Columns,
- Columns
-] = getHostsColumns().map(h => ({ ...h, sortable: true })) as [
- Columns,
- Columns,
- Columns,
- Columns
-];
-
-export const rowItems: ItemsPerRow[] = [
- {
- text: '2 rows',
- numberOfRow: 2,
- },
- {
- text: '5 rows',
- numberOfRow: 5,
- },
- {
- text: '10 rows',
- numberOfRow: 10,
- },
- {
- text: '20 rows',
- numberOfRow: 20,
- },
- {
- text: '50 rows',
- numberOfRow: 50,
- },
-];
diff --git a/x-pack/legacy/plugins/siem/public/components/load_more_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/load_more_table/index.test.tsx
deleted file mode 100644
index 3c42d3d2acfe3..0000000000000
--- a/x-pack/legacy/plugins/siem/public/components/load_more_table/index.test.tsx
+++ /dev/null
@@ -1,360 +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 { mount, shallow } from 'enzyme';
-import toJson from 'enzyme-to-json';
-import * as React from 'react';
-
-import { Direction } from '../../graphql/types';
-
-import { LoadMoreTable } from './index';
-import { getHostsColumns, mockData, rowItems, sortedHosts } from './index.mock';
-import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
-import { ThemeProvider } from 'styled-components';
-
-describe('Load More Table Component', () => {
- const theme = () => ({ eui: euiDarkVars, darkMode: true });
- const loadMore = jest.fn();
- const updateLimitPagination = jest.fn();
- describe('rendering', () => {
- test('it renders the default load more table', () => {
- const wrapper = shallow(
-
- {'My test supplement.'}}
- headerTitle="Hosts"
- headerTooltip="My test tooltip"
- headerUnit="Test Unit"
- itemsPerRow={rowItems}
- limit={1}
- loading={false}
- loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
- pageOfItems={mockData.Hosts.edges}
- updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
- />
-
- );
-
- expect(toJson(wrapper)).toMatchSnapshot();
- });
-
- test('it renders the loading panel at the beginning ', () => {
- const wrapper = mount(
-
- {'My test supplement.'}}
- headerTitle="Hosts"
- headerTooltip="My test tooltip"
- headerUnit="Test Unit"
- itemsPerRow={rowItems}
- limit={1}
- loading={true}
- loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
- pageOfItems={[]}
- updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
- />
-
- );
-
- expect(
- wrapper.find('[data-test-subj="initialLoadingPanelLoadMoreTable"]').exists()
- ).toBeTruthy();
- });
-
- test('it renders the over loading panel after data has been in the table ', () => {
- const wrapper = mount(
-
- {'My test supplement.'}}
- headerTitle="Hosts"
- headerTooltip="My test tooltip"
- headerUnit="Test Unit"
- itemsPerRow={rowItems}
- limit={1}
- loading={true}
- loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
- pageOfItems={mockData.Hosts.edges}
- updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
- />
-
- );
-
- expect(wrapper.find('[data-test-subj="loadingPanelLoadMoreTable"]').exists()).toBeTruthy();
- });
-
- test('it renders the loadMore button if need to fetch more', () => {
- const wrapper = mount(
-
- {'My test supplement.'}}
- headerTitle="Hosts"
- headerTooltip="My test tooltip"
- headerUnit="Test Unit"
- itemsPerRow={rowItems}
- limit={1}
- loading={false}
- loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
- pageOfItems={mockData.Hosts.edges}
- updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
- />
-
- );
-
- expect(
- wrapper
- .find('[data-test-subj="loadingMoreButton"]')
- .first()
- .text()
- ).toContain('Load more');
- });
-
- test('it renders the Loading... in the more load button when fetching new data', () => {
- const wrapper = mount(
-
- {'My test supplement.'}}
- headerTitle="Hosts"
- headerTooltip="My test tooltip"
- headerUnit="Test Unit"
- itemsPerRow={rowItems}
- limit={1}
- loading={true}
- loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
- pageOfItems={mockData.Hosts.edges}
- updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
- />
-
- );
-
- expect(
- wrapper.find('[data-test-subj="initialLoadingPanelLoadMoreTable"]').exists()
- ).toBeFalsy();
- expect(
- wrapper
- .find('[data-test-subj="loadingMoreButton"]')
- .first()
- .text()
- ).toContain('Loading…');
- });
-
- test('it does NOT render the loadMore button because there is nothing else to fetch', () => {
- const wrapper = mount(
-
- {'My test supplement.'}}
- headerTitle="Hosts"
- headerTooltip="My test tooltip"
- headerUnit="Test Unit"
- itemsPerRow={rowItems}
- limit={2}
- loading={false}
- loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
- pageOfItems={mockData.Hosts.edges}
- updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
- />
-
- );
-
- expect(wrapper.find('[data-test-subj="loadingMoreButton"]').exists()).toBeFalsy();
- });
-
- test('it render popover to select new limit in table', () => {
- const wrapper = mount(
-
- {'My test supplement.'}}
- headerTitle="Hosts"
- headerTooltip="My test tooltip"
- headerUnit="Test Unit"
- itemsPerRow={rowItems}
- limit={2}
- loading={false}
- loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
- pageOfItems={mockData.Hosts.edges}
- updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
- />
-
- );
-
- wrapper
- .find('[data-test-subj="loadingMoreSizeRowPopover"] button')
- .first()
- .simulate('click');
- expect(wrapper.find('[data-test-subj="loadingMorePickSizeRow"]').exists()).toBeTruthy();
- });
-
- test('it will NOT render popover to select new limit in table if props itemsPerRow is empty', () => {
- const wrapper = mount(
-
- {'My test supplement.'}}
- headerTitle="Hosts"
- headerTooltip="My test tooltip"
- headerUnit="Test Unit"
- itemsPerRow={[]}
- limit={2}
- loading={false}
- loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
- pageOfItems={mockData.Hosts.edges}
- updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
- />
-
- );
-
- expect(wrapper.find('[data-test-subj="loadingMoreSizeRowPopover"]').exists()).toBeFalsy();
- });
-
- test('It should render a sort icon if sorting is defined', () => {
- const mockOnChange = jest.fn();
- const wrapper = mount(
-
- {'My test supplement.'}}
- headerTitle="Hosts"
- headerTooltip="My test tooltip"
- headerUnit="Test Unit"
- itemsPerRow={rowItems}
- limit={2}
- loading={false}
- loadMore={jest.fn()}
- onChange={mockOnChange}
- pageOfItems={mockData.Hosts.edges}
- sorting={{ direction: Direction.asc, field: 'node.host.name' }}
- updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
- />
-
- );
-
- expect(wrapper.find('.euiTable thead tr th button svg')).toBeTruthy();
- });
- });
-
- describe('Events', () => {
- test('should call loadmore when clicking on the button load more', () => {
- const wrapper = mount(
-
- {'My test supplement.'}}
- headerTitle="Hosts"
- headerTooltip="My test tooltip"
- headerUnit="Test Unit"
- itemsPerRow={rowItems}
- limit={1}
- loading={false}
- loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
- pageOfItems={mockData.Hosts.edges}
- updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
- />
-
- );
-
- wrapper
- .find('[data-test-subj="loadingMoreButton"]')
- .first()
- .simulate('click');
-
- expect(loadMore).toBeCalled();
- });
-
- test('Should call updateLimitPagination when you pick a new limit', () => {
- const wrapper = mount(
-
- {'My test supplement.'}}
- headerTitle="Hosts"
- headerTooltip="My test tooltip"
- headerUnit="Test Unit"
- itemsPerRow={rowItems}
- limit={2}
- loading={false}
- loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
- pageOfItems={mockData.Hosts.edges}
- updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
- />
-
- );
-
- wrapper
- .find('[data-test-subj="loadingMoreSizeRowPopover"] button')
- .first()
- .simulate('click');
-
- wrapper
- .find('[data-test-subj="loadingMorePickSizeRow"] button')
- .first()
- .simulate('click');
- expect(updateLimitPagination).toBeCalled();
- });
-
- test('Should call onChange when you choose a new sort in the table', () => {
- const mockOnChange = jest.fn();
- const wrapper = mount(
-
- {'My test supplement.'}}
- headerTitle="Hosts"
- headerTooltip="My test tooltip"
- headerUnit="Test Unit"
- itemsPerRow={rowItems}
- limit={2}
- loading={false}
- loadMore={jest.fn()}
- onChange={mockOnChange}
- pageOfItems={mockData.Hosts.edges}
- sorting={{ direction: Direction.asc, field: 'node.host.name' }}
- updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
- />
-
- );
-
- wrapper
- .find('.euiTable thead tr th button')
- .first()
- .simulate('click');
-
- expect(mockOnChange).toBeCalled();
- expect(mockOnChange.mock.calls[0]).toEqual([
- { page: undefined, sort: { direction: 'desc', field: 'node.host.name' } },
- ]);
- });
- });
-});
diff --git a/x-pack/legacy/plugins/siem/public/components/load_more_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/load_more_table/index.tsx
deleted file mode 100644
index 0663246039cb8..0000000000000
--- a/x-pack/legacy/plugins/siem/public/components/load_more_table/index.tsx
+++ /dev/null
@@ -1,320 +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 {
- EuiBasicTable,
- EuiButton,
- EuiButtonEmpty,
- EuiContextMenuItem,
- EuiContextMenuPanel,
- EuiFlexGroup,
- EuiFlexItem,
- EuiLoadingContent,
- EuiPopover,
-} from '@elastic/eui';
-import { isEmpty, noop } from 'lodash/fp';
-import React from 'react';
-import styled from 'styled-components';
-
-import { Direction } from '../../graphql/types';
-import { HeaderPanel } from '../header_panel';
-import { Loader } from '../loader';
-
-import * as i18n from './translations';
-import { Panel } from '../panel';
-
-const DEFAULT_DATA_TEST_SUBJ = 'load-more-table';
-
-export interface ItemsPerRow {
- text: string;
- numberOfRow: number;
-}
-
-export interface SortingBasicTable {
- field: string;
- direction: Direction;
- allowNeutralSort?: boolean;
-}
-
-export interface Criteria {
- page?: { index: number; size: number };
- sort?: SortingBasicTable;
-}
-
-// Using telescoping templates to remove 'any' that was polluting downstream column type checks
-interface BasicTableProps {
- columns:
- | [Columns]
- | [Columns, Columns]
- | [Columns, Columns, Columns]
- | [Columns, Columns, Columns, Columns]
- | [Columns, Columns, Columns, Columns, Columns]
- | [Columns, Columns, Columns, Columns, Columns, Columns]
- | [Columns, Columns, Columns, Columns, Columns, Columns, Columns]
- | [
- Columns,
- Columns,
- Columns,
- Columns,
- Columns,
- Columns,
- Columns,
- Columns
- ]
- | [
- Columns,
- Columns,
- Columns,
- Columns,
- Columns,
- Columns,
- Columns,
- Columns,
- Columns
- ];
- hasNextPage: boolean;
- dataTestSubj?: string;
- headerCount: number;
- headerSupplement?: React.ReactElement;
- headerTitle: string | React.ReactElement;
- headerTooltip?: string;
- headerUnit: string | React.ReactElement;
- id?: string;
- itemsPerRow?: ItemsPerRow[];
- limit: number;
- loading: boolean;
- loadMore: () => void;
- onChange?: (criteria: Criteria) => void;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- pageOfItems: any[];
- sorting?: SortingBasicTable;
- updateLimitPagination: (limit: number) => void;
-}
-
-interface BasicTableState {
- loadingInitial: boolean;
- isPopoverOpen: boolean;
- showInspect: boolean;
-}
-
-type Func = (arg: T) => string | number;
-
-export interface Columns {
- field?: string;
- align?: string;
- name: string | React.ReactNode;
- isMobileHeader?: boolean;
- sortable?: boolean | Func;
- truncateText?: boolean;
- hideForMobile?: boolean;
- render?: (item: T, node: U) => React.ReactNode;
- width?: string;
-}
-
-export class LoadMoreTable extends React.PureComponent<
- BasicTableProps,
- BasicTableState
-> {
- public readonly state = {
- loadingInitial: this.props.headerCount === -1,
- isPopoverOpen: false,
- showInspect: false,
- };
-
- static getDerivedStateFromProps(
- props: BasicTableProps,
- state: BasicTableState
- ) {
- if (state.loadingInitial && props.headerCount >= 0) {
- return {
- ...state,
- loadingInitial: false,
- };
- }
- return null;
- }
-
- public render() {
- const {
- columns,
- dataTestSubj = DEFAULT_DATA_TEST_SUBJ,
- hasNextPage,
- headerCount,
- headerSupplement,
- headerTitle,
- headerTooltip,
- headerUnit,
- id,
- itemsPerRow,
- limit,
- loading,
- onChange = noop,
- pageOfItems,
- sorting = null,
- updateLimitPagination,
- } = this.props;
- const { loadingInitial } = this.state;
-
- const button = (
-
- {`${i18n.ROWS}: ${limit}`}
-
- );
-
- const rowItems =
- itemsPerRow &&
- itemsPerRow.map(item => (
- {
- this.closePopover();
- updateLimitPagination(item.numberOfRow);
- }}
- >
- {item.text}
-
- ));
-
- return (
-
- = 0 ? headerCount.toLocaleString() : 0} ${headerUnit}`
- }
- title={headerTitle}
- tooltip={headerTooltip}
- >
- {!loadingInitial && headerSupplement}
-
-
- {loadingInitial ? (
-
- ) : (
- <>
-
-
- {hasNextPage && (
-
-
- {!isEmpty(itemsPerRow) && (
-
- )}
-
-
-
-
- {loading ? `${i18n.LOADING}` : i18n.LOAD_MORE}
-
-
-
- )}
-
- {loading && }
- >
- )}
-
- );
- }
-
- private mouseEnter = () => {
- this.setState(prevState => ({
- ...prevState,
- showInspect: true,
- }));
- };
-
- private mouseLeave = () => {
- this.setState(prevState => ({
- ...prevState,
- showInspect: false,
- }));
- };
-
- private onButtonClick = () => {
- this.setState(prevState => ({
- ...prevState,
- isPopoverOpen: !prevState.isPopoverOpen,
- }));
- };
-
- private closePopover = () => {
- this.setState(prevState => ({
- ...prevState,
- isPopoverOpen: false,
- }));
- };
-}
-
-const BasicTable = styled(EuiBasicTable)`
- tbody {
- th,
- td {
- vertical-align: top;
- }
-
- .euiTableCellContent {
- display: block;
- }
- }
-`;
-
-BasicTable.displayName = 'BasicTable';
-
-const FooterAction = styled(EuiFlexGroup).attrs({
- alignItems: 'center',
- responsive: false,
-})`
- margin-top: ${props => props.theme.eui.euiSizeXS};
-`;
-
-FooterAction.displayName = 'FooterAction';
diff --git a/x-pack/legacy/plugins/siem/public/components/load_more_table/translations.ts b/x-pack/legacy/plugins/siem/public/components/load_more_table/translations.ts
deleted file mode 100644
index ec093f9721624..0000000000000
--- a/x-pack/legacy/plugins/siem/public/components/load_more_table/translations.ts
+++ /dev/null
@@ -1,23 +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 { i18n } from '@kbn/i18n';
-
-export const LOADING = i18n.translate('xpack.siem.loadMoreTable.loadingButtonLabel', {
- defaultMessage: 'Loading…',
-});
-
-export const LOAD_MORE = i18n.translate('xpack.siem.loadMoreTable.loadMoreButtonLabel', {
- defaultMessage: 'Load more',
-});
-
-export const SHOWING = i18n.translate('xpack.siem.loadMoreTable.showingSubtitle', {
- defaultMessage: 'Showing',
-});
-
-export const ROWS = i18n.translate('xpack.siem.loadMoreTable.rowsButtonLabel', {
- defaultMessage: 'Rows per page',
-});
diff --git a/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx b/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx
index b2470bc0f5abd..0956e93829e5a 100644
--- a/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx
@@ -21,7 +21,7 @@ export const MlCapabilitiesContext = React.createContext(emptyMl
MlCapabilitiesContext.displayName = 'MlCapabilitiesContext';
export const MlCapabilitiesProvider = React.memo<{ children: JSX.Element }>(({ children }) => {
- const [capabilities, setCapabilities] = useState(emptyMlCapabilities);
+ const [capabilities, setCapabilities] = useState(emptyMlCapabilities);
const [, dispatchToaster] = useStateToaster();
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
diff --git a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.test.tsx
index 3a1fcbb653efe..04fed8e4fff3f 100644
--- a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.test.tsx
@@ -8,7 +8,7 @@ import { getAnomaliesHostTableColumnsCurated } from './get_anomalies_host_table_
import { HostsType } from '../../../store/hosts/model';
import * as i18n from './translations';
import { AnomaliesByHost, Anomaly } from '../types';
-import { Columns } from '../../load_more_table';
+import { Columns } from '../../paginated_table';
import { TestProviders } from '../../../mock';
import { mount } from 'enzyme';
import React from 'react';
diff --git a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.tsx b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.tsx
index f0cf2e5a6e662..6650449dd8200 100644
--- a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
import moment from 'moment';
-import { Columns } from '../../load_more_table';
+import { Columns } from '../../paginated_table';
import { AnomaliesByHost, Anomaly, NarrowDateRange } from '../types';
import { getRowItemDraggable } from '../../tables/helpers';
import { EntityDraggable } from '../entity_draggable';
diff --git a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.test.tsx
index d063ed023bca6..768c7af8f4b2c 100644
--- a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.test.tsx
@@ -8,7 +8,7 @@ import { getAnomaliesNetworkTableColumnsCurated } from './get_anomalies_network_
import { NetworkType } from '../../../store/network/model';
import * as i18n from './translations';
import { AnomaliesByNetwork, Anomaly } from '../types';
-import { Columns } from '../../load_more_table';
+import { Columns } from '../../paginated_table';
import { mount } from 'enzyme';
import React from 'react';
import { TestProviders } from '../../../mock';
diff --git a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx
index fb43175686e3d..1e1628fb077dd 100644
--- a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
import moment from 'moment';
-import { Columns } from '../../load_more_table';
+import { Columns } from '../../paginated_table';
import { Anomaly, NarrowDateRange, AnomaliesByNetwork } from '../types';
import { getRowItemDraggable } from '../../tables/helpers';
import { EntityDraggable } from '../entity_draggable';
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 25ebb8ad89ecd..96cb85b246a49 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
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { shallow } from 'enzyme';
+import { mount } from 'enzyme';
import * as React from 'react';
import { CONSTANTS } from '../url_state/constants';
@@ -63,7 +63,7 @@ describe('SIEM Navigation', () => {
},
[CONSTANTS.timelineId]: '',
};
- const wrapper = shallow();
+ const wrapper = mount();
test('it calls setBreadcrumbs with correct path on mount', () => {
expect(setBreadcrumbs).toHaveBeenNthCalledWith(1, {
detailName: undefined,
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 6c5cac1464e79..06f7a2ffb0566 100644
--- a/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx
@@ -5,7 +5,7 @@
*/
import { isEqual } from 'lodash/fp';
-import React from 'react';
+import React, { useEffect } from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
@@ -27,78 +27,23 @@ import { TabNavigation } from './tab_navigation';
import { TabNavigationProps } from './tab_navigation/types';
import { SiemNavigationComponentProps } from './types';
-export class SiemNavigationComponent extends React.Component {
- public shouldComponentUpdate(nextProps: Readonly): boolean {
- if (
- this.props.pathName === nextProps.pathName &&
- this.props.search === nextProps.search &&
- isEqual(this.props.hosts, nextProps.hosts) &&
- isEqual(this.props.hostDetails, nextProps.hostDetails) &&
- isEqual(this.props.network, nextProps.network) &&
- isEqual(this.props.navTabs, nextProps.navTabs) &&
- isEqual(this.props.timerange, nextProps.timerange) &&
- isEqual(this.props.timelineId, nextProps.timelineId)
- ) {
- return false;
- }
- return true;
- }
-
- public componentWillMount(): void {
- const {
- detailName,
- hosts,
- hostDetails,
- navTabs,
- network,
- pageName,
- pathName,
- search,
- tabName,
- timerange,
- timelineId,
- } = this.props;
- if (pathName) {
- setBreadcrumbs({
- detailName,
- hosts,
- hostDetails,
- navTabs,
- network,
- pageName,
- pathName,
- search,
- tabName,
- timerange,
- timelineId,
- });
- }
- }
-
- public componentWillReceiveProps(nextProps: Readonly): void {
- if (
- this.props.pathName !== nextProps.pathName ||
- this.props.search !== nextProps.search ||
- !isEqual(this.props.hosts, nextProps.hosts) ||
- !isEqual(this.props.hostDetails, nextProps.hostDetails) ||
- !isEqual(this.props.network, nextProps.network) ||
- !isEqual(this.props.navTabs, nextProps.navTabs) ||
- !isEqual(this.props.timerange, nextProps.timerange) ||
- !isEqual(this.props.timelineId, nextProps.timelineId)
- ) {
- const {
- detailName,
- hosts,
- hostDetails,
- navTabs,
- network,
- pageName,
- pathName,
- search,
- tabName,
- timelineId,
- timerange,
- } = nextProps;
+export const SiemNavigationComponent = React.memo(
+ ({
+ detailName,
+ display,
+ hostDetails,
+ hosts,
+ navTabs,
+ network,
+ pageName,
+ pathName,
+ search,
+ showBorder,
+ tabName,
+ timelineId,
+ timerange,
+ }) => {
+ useEffect(() => {
if (pathName) {
setBreadcrumbs({
detailName,
@@ -114,23 +59,8 @@ export class SiemNavigationComponent extends React.Component
);
+ },
+ (prevProps, nextProps) => {
+ return (
+ prevProps.pathName === nextProps.pathName &&
+ prevProps.search === nextProps.search &&
+ isEqual(prevProps.hosts, nextProps.hosts) &&
+ isEqual(prevProps.hostDetails, nextProps.hostDetails) &&
+ isEqual(prevProps.network, nextProps.network) &&
+ isEqual(prevProps.navTabs, nextProps.navTabs) &&
+ isEqual(prevProps.timerange, nextProps.timerange) &&
+ isEqual(prevProps.timelineId, nextProps.timelineId)
+ );
}
-}
+);
+
+SiemNavigationComponent.displayName = 'SiemNavigationComponent';
const makeMapStateToProps = () => {
const getInputsSelector = inputsSelectors.inputsSelector();
diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx
index b6ec9e5ee0e02..61a0ec9c06c2d 100644
--- a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { shallow } from 'enzyme';
+import { mount, shallow } from 'enzyme';
import * as React from 'react';
import { TabNavigation } from './';
@@ -74,8 +74,8 @@ describe('Tab Navigation', () => {
expect(hostsTab.prop('isSelected')).toBeTruthy();
});
test('it changes active tab when nav changes by props', () => {
- const wrapper = shallow();
- const networkTab = () => wrapper.find('[data-test-subj="navigation-network"]');
+ const wrapper = mount();
+ const networkTab = () => wrapper.find('[data-test-subj="navigation-network"]').first();
expect(networkTab().prop('isSelected')).toBeFalsy();
wrapper.setProps({
pageName: 'network',
@@ -151,9 +151,9 @@ describe('Tab Navigation', () => {
expect(tableNavigationTab.prop('isSelected')).toBeTruthy();
});
test('it changes active tab when nav changes by props', () => {
- const wrapper = shallow();
+ const wrapper = mount();
const tableNavigationTab = () =>
- wrapper.find(`[data-test-subj="navigation-${HostsTableType.events}"]`);
+ wrapper.find(`[data-test-subj="navigation-${HostsTableType.events}"]`).first();
expect(tableNavigationTab().prop('isSelected')).toBeFalsy();
wrapper.setProps({
pageName: SiemPageName.hosts,
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 9a409b9f53d8c..c62335ea1c06d 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
@@ -4,14 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiTab, EuiTabs, EuiLink } from '@elastic/eui';
-import { get, getOr } from 'lodash/fp';
+import { getOr } from 'lodash/fp';
-import * as React from 'react';
+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 { HostsTableType } from '../../../store/hosts/model';
import { getSearch } from '../helpers';
import { TabNavigationProps } from './types';
@@ -36,71 +35,51 @@ const TabContainer = styled.div`
TabContainer.displayName = 'TabContainer';
-interface TabNavigationState {
- selectedTabId: string;
-}
-
-export class TabNavigation extends React.PureComponent {
- constructor(props: TabNavigationProps) {
- super(props);
- const selectedTabId = this.mapLocationToTab(props.pageName, props.tabName);
- this.state = { selectedTabId };
- }
- public componentWillReceiveProps(nextProps: TabNavigationProps): void {
- const selectedTabId = this.mapLocationToTab(nextProps.pageName, nextProps.tabName);
-
- if (this.state.selectedTabId !== selectedTabId) {
- this.setState(prevState => ({
- ...prevState,
- selectedTabId,
- }));
- }
- }
- public render() {
- const { display = 'condensed' } = this.props;
- return (
-
- {this.renderTabs()}
-
- );
- }
-
- public mapLocationToTab = (pageName: string, tabName?: HostsTableType): string => {
- const { navTabs } = this.props;
+export const TabNavigation = React.memo(props => {
+ const { display = 'condensed', navTabs, pageName, showBorder, tabName } = props;
+ const mapLocationToTab = (): string => {
return getOr(
'',
'id',
Object.values(navTabs).find(item => tabName === item.id || pageName === item.id)
);
};
+ const [selectedTabId, setSelectedTabId] = useState(mapLocationToTab());
+ useEffect(() => {
+ const currentTabSelected = mapLocationToTab();
- private renderTabs = (): JSX.Element[] => {
- const { navTabs } = this.props;
- return Object.keys(navTabs).map(tabName => {
- const tab = get(tabName, navTabs);
- return (
-
+ 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()}
+
+ );
+});
diff --git a/x-pack/legacy/plugins/siem/public/components/notes/index.tsx b/x-pack/legacy/plugins/siem/public/components/notes/index.tsx
index 8eaf368058631..29f7686ade88b 100644
--- a/x-pack/legacy/plugins/siem/public/components/notes/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/notes/index.tsx
@@ -5,7 +5,7 @@
*/
import { EuiInMemoryTable, EuiModalBody, EuiModalHeader, EuiPanel, EuiSpacer } from '@elastic/eui';
-import * as React from 'react';
+import React, { useState } from 'react';
import styled from 'styled-components';
import { Note } from '../../lib/note';
@@ -23,10 +23,6 @@ interface Props {
updateNote: UpdateNote;
}
-interface State {
- newNote: string;
-}
-
const NotesPanel = styled(EuiPanel)`
height: ${NOTES_PANEL_HEIGHT}px;
width: ${NOTES_PANEL_WIDTH}px;
@@ -47,15 +43,9 @@ const InMemoryTable = styled(EuiInMemoryTable)`
InMemoryTable.displayName = 'InMemoryTable';
/** A view for entering and reviewing notes */
-export class Notes extends React.PureComponent {
- constructor(props: Props) {
- super(props);
-
- this.state = { newNote: '' };
- }
-
- public render() {
- const { associateNote, getNotesByIds, getNewNoteId, noteIds, updateNote } = this.props;
+export const Notes = React.memo(
+ ({ associateNote, getNotesByIds, getNewNoteId, noteIds, updateNote }) => {
+ const [newNote, setNewNote] = useState('');
return (
@@ -67,8 +57,8 @@ export class Notes extends React.PureComponent {
@@ -84,8 +74,6 @@ export class Notes extends React.PureComponent {
);
}
+);
- private updateNewNote = (newNote: string): void => {
- this.setState({ newNote });
- };
-}
+Notes.displayName = 'Notes';
diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.tsx
index 51992e00313a4..aa9415aadeda1 100644
--- a/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.tsx
@@ -5,7 +5,7 @@
*/
import { EuiFlexGroup, EuiPanel } from '@elastic/eui';
-import * as React from 'react';
+import React, { useState } from 'react';
import styled from 'styled-components';
import { Note } from '../../../lib/note';
@@ -53,27 +53,23 @@ interface Props {
updateNote: UpdateNote;
}
-interface State {
- newNote: string;
-}
-
/** A view for entering and reviewing notes */
-export class NoteCards extends React.PureComponent {
- constructor(props: Props) {
- super(props);
-
- this.state = { newNote: '' };
- }
-
- public render() {
- const {
- getNotesByIds,
- getNewNoteId,
- noteIds,
- showAddNote,
- toggleShowAddNote,
- updateNote,
- } = this.props;
+export const NoteCards = React.memo(
+ ({
+ associateNote,
+ getNotesByIds,
+ getNewNoteId,
+ noteIds,
+ showAddNote,
+ toggleShowAddNote,
+ updateNote,
+ }) => {
+ const [newNote, setNewNote] = useState('');
+
+ const associateNoteAndToggleShow = (noteId: string) => {
+ associateNote(noteId);
+ toggleShowAddNote();
+ };
return (
@@ -90,11 +86,11 @@ export class NoteCards extends React.PureComponent {
{showAddNote ? (
@@ -102,13 +98,6 @@ export class NoteCards extends React.PureComponent {
);
}
+);
- private associateNoteAndToggleShow = (noteId: string) => {
- this.props.associateNote(noteId);
- this.props.toggleShowAddNote();
- };
-
- private updateNewNote = (newNote: string): void => {
- this.setState({ newNote });
- };
-}
+NoteCards.displayName = 'NoteCards';
diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx
index e91feed536f93..917ec3f1bf0b8 100644
--- a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx
@@ -17,7 +17,7 @@ describe('DeleteTimelineModal', () => {
);
@@ -34,7 +34,7 @@ describe('DeleteTimelineModal', () => {
);
@@ -48,7 +48,7 @@ describe('DeleteTimelineModal', () => {
test('it displays `Untitled Timeline` in the title when title is undefined', () => {
const wrapper = mountWithIntl(
-
+
);
expect(
@@ -61,7 +61,7 @@ describe('DeleteTimelineModal', () => {
test('it displays `Untitled Timeline` in the title when title is null', () => {
const wrapper = mountWithIntl(
-
+
);
expect(
@@ -74,7 +74,7 @@ describe('DeleteTimelineModal', () => {
test('it displays `Untitled Timeline` in the title when title is just whitespace', () => {
const wrapper = mountWithIntl(
-
+
);
expect(
@@ -90,7 +90,7 @@ describe('DeleteTimelineModal', () => {
);
@@ -102,14 +102,14 @@ describe('DeleteTimelineModal', () => {
).toEqual(i18n.DELETE_WARNING);
});
- test('it invokes toggleShowModal when the Cancel button is clicked', () => {
- const toggleShowModal = jest.fn();
+ test('it invokes closeModal when the Cancel button is clicked', () => {
+ const closeModal = jest.fn();
const wrapper = mountWithIntl(
);
@@ -118,7 +118,7 @@ describe('DeleteTimelineModal', () => {
.first()
.simulate('click');
- expect(toggleShowModal).toBeCalled();
+ expect(closeModal).toBeCalled();
});
test('it invokes onDelete when the Delete button is clicked', () => {
@@ -128,7 +128,7 @@ describe('DeleteTimelineModal', () => {
);
diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx
index 9c416419066e6..e9e438a8c5e2e 100644
--- a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx
@@ -14,7 +14,7 @@ import * as i18n from '../translations';
interface Props {
title?: string | null;
onDelete: () => void;
- toggleShowModal: () => void;
+ closeModal: () => void;
}
export const DELETE_TIMELINE_MODAL_WIDTH = 600; // px
@@ -22,7 +22,7 @@ export const DELETE_TIMELINE_MODAL_WIDTH = 600; // px
/**
* Renders a modal that confirms deletion of a timeline
*/
-export const DeleteTimelineModal = pure(({ title, toggleShowModal, onDelete }) => (
+export const DeleteTimelineModal = pure(({ title, closeModal, onDelete }) => (
(({ title, toggleShowModal, onDele
}}
/>
}
- onCancel={toggleShowModal}
+ onCancel={closeModal}
onConfirm={onDelete}
cancelButtonText={i18n.CANCEL}
confirmButtonText={i18n.DELETE}
diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.test.tsx
index 1700e86f57c84..561eac000bbf7 100644
--- a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.test.tsx
@@ -5,7 +5,6 @@
*/
import { EuiButtonIconProps } from '@elastic/eui';
-import { get } from 'lodash/fp';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import * as React from 'react';
@@ -79,35 +78,6 @@ describe('DeleteTimelineModal', () => {
expect(props.isDisabled).toBe(false);
});
- test('it defaults showModal to false until the trash button is clicked', () => {
- const wrapper = mountWithIntl(
-
- );
-
- expect(get('showModal', wrapper.state())).toBe(false);
- });
-
- test('it sets showModal to true when the trash button is clicked', () => {
- const wrapper = mountWithIntl(
-
- );
-
- wrapper
- .find('[data-test-subj="delete-timeline"]')
- .first()
- .simulate('click');
-
- expect(get('showModal', wrapper.state())).toBe(true);
- });
-
test('it does NOT render the modal when showModal is false', () => {
const wrapper = mountWithIntl(
{
- constructor(props: Props) {
- super(props);
+export const DeleteTimelineModalButton = React.memo(
+ ({ deleteTimelines, savedObjectId, title }) => {
+ const [showModal, setShowModal] = useState(false);
- this.state = { showModal: false };
- }
+ const openModal = () => setShowModal(true);
+ const closeModal = () => setShowModal(false);
- public render() {
- const { deleteTimelines, savedObjectId, title } = this.props;
+ const onDelete = () => {
+ if (deleteTimelines != null && savedObjectId != null) {
+ deleteTimelines([savedObjectId]);
+ }
+ closeModal();
+ };
return (
<>
@@ -44,19 +43,19 @@ export class DeleteTimelineModalButton extends React.PureComponent
iconSize="s"
iconType="trash"
isDisabled={deleteTimelines == null || savedObjectId == null || savedObjectId === ''}
- onClick={this.toggleShowModal}
+ onClick={openModal}
size="s"
/>
- {this.state.showModal ? (
+ {showModal ? (
-
+
@@ -64,20 +63,6 @@ export class DeleteTimelineModalButton extends React.PureComponent
>
);
}
+);
- private toggleShowModal = () => {
- this.setState(state => ({
- showModal: !state.showModal,
- }));
- };
-
- private onDelete = () => {
- const { deleteTimelines, savedObjectId } = this.props;
-
- if (deleteTimelines != null && savedObjectId != null) {
- deleteTimelines([savedObjectId]);
- }
-
- this.toggleShowModal();
- };
-}
+DeleteTimelineModalButton.displayName = 'DeleteTimelineModalButton';
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 62de2ea30542a..7a0caf14af302 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
@@ -5,8 +5,7 @@
*/
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
-import { mount, ReactWrapper } from 'enzyme';
-import { get } from 'lodash/fp';
+import { mount } from 'enzyme';
import { MockedProvider } from 'react-apollo/test-utils';
import * as React from 'react';
import { ThemeProvider } from 'styled-components';
@@ -22,21 +21,11 @@ import { OPEN_TIMELINE_CLASS_NAME } from './helpers';
jest.mock('../../lib/settings/use_kibana_ui_setting');
-const getStateChildComponent = (
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- wrapper: ReactWrapper, React.Component<{}, {}, any>>
-): // eslint-disable-next-line @typescript-eslint/no-explicit-any
-React.Component<{}, {}, any> =>
- wrapper
- .find('[data-test-subj="stateful-timeline"]')
- .last()
- .instance();
-
describe('StatefulOpenTimeline', () => {
const theme = () => ({ eui: euiDarkVars, darkMode: true });
const title = 'All Timelines / Open Timelines';
- test('it has the expected initial state', async () => {
+ test('it has the expected initial state', () => {
const wrapper = mount(
@@ -53,15 +42,18 @@ describe('StatefulOpenTimeline', () => {
);
- await wait();
- wrapper.update();
+ const componentProps = wrapper
+ .find('[data-test-subj="open-timeline"]')
+ .last()
+ .props();
- expect(getStateChildComponent(wrapper).state).toEqual({
+ expect(componentProps).toEqual({
+ ...componentProps,
itemIdToExpandedNotesRowMap: {},
onlyFavorites: false,
pageIndex: 0,
pageSize: 10,
- search: '',
+ query: '',
selectedItems: [],
sortDirection: 'desc',
sortField: 'updated',
@@ -69,7 +61,7 @@ describe('StatefulOpenTimeline', () => {
});
describe('#onQueryChange', () => {
- test('it updates the query state with the expected trimmed value when the user enters a query', async () => {
+ test('it updates the query state with the expected trimmed value when the user enters a query', () => {
const wrapper = mount(
@@ -85,26 +77,15 @@ describe('StatefulOpenTimeline', () => {
);
-
- await wait();
- wrapper.update();
-
wrapper
.find('[data-test-subj="search-bar"] input')
.simulate('keyup', { keyCode: 13, target: { value: ' abcd ' } });
-
- wrapper.update();
-
- expect(getStateChildComponent(wrapper).state).toEqual({
- itemIdToExpandedNotesRowMap: {},
- onlyFavorites: false,
- pageIndex: 0,
- pageSize: 10,
- search: 'abcd',
- selectedItems: [],
- sortDirection: 'desc',
- sortField: 'updated',
- });
+ expect(
+ wrapper
+ .find('[data-test-subj="search-row"]')
+ .first()
+ .prop('query')
+ ).toEqual('abcd');
});
test('it appends the word "with" to the Showing in Timelines message when the user enters a query', async () => {
@@ -129,8 +110,6 @@ describe('StatefulOpenTimeline', () => {
.find('[data-test-subj="search-bar"] input')
.simulate('keyup', { keyCode: 13, target: { value: ' abcd ' } });
- wrapper.update();
-
expect(
wrapper
.find('[data-test-subj="query-message"]')
@@ -161,8 +140,6 @@ describe('StatefulOpenTimeline', () => {
.find('[data-test-subj="search-bar"] input')
.simulate('keyup', { keyCode: 13, target: { value: ' abcd ' } });
- wrapper.update();
-
expect(
wrapper
.find('[data-test-subj="selectable-query-text"]')
@@ -226,7 +203,6 @@ describe('StatefulOpenTimeline', () => {
.find('.euiCheckbox__input')
.first()
.simulate('change', { target: { checked: true } });
- wrapper.update();
wrapper
.find('[data-test-subj="favorite-selected"]')
@@ -273,7 +249,6 @@ describe('StatefulOpenTimeline', () => {
.find('.euiCheckbox__input')
.first()
.simulate('change', { target: { checked: true } });
- wrapper.update();
wrapper
.find('[data-test-subj="delete-selected"]')
@@ -319,14 +294,17 @@ describe('StatefulOpenTimeline', () => {
.first()
.simulate('change', { target: { checked: true } });
- wrapper.update();
+ const selectedItems: [] = wrapper
+ .find('[data-test-subj="open-timeline"]')
+ .last()
+ .prop('selectedItems');
- expect(get('selectedItems', getStateChildComponent(wrapper).state).length).toEqual(13); // 13 because we did mock 13 timelines in the query
+ expect(selectedItems.length).toEqual(13); // 13 because we did mock 13 timelines in the query
});
});
describe('#onTableChange', () => {
- test('it updates the sort state when the user clicks on a column to sort it', async () => {
+ test('it updates the sort state when the user clicks on a column to sort it', () => {
const wrapper = mount(
@@ -343,32 +321,29 @@ describe('StatefulOpenTimeline', () => {
);
- await wait();
-
- wrapper.update();
+ expect(
+ wrapper
+ .find('[data-test-subj="open-timeline"]')
+ .last()
+ .prop('sortDirection')
+ ).toEqual('desc');
wrapper
.find('thead tr th button')
.at(0)
.simulate('click');
- wrapper.update();
-
- expect(getStateChildComponent(wrapper).state).toEqual({
- itemIdToExpandedNotesRowMap: {},
- onlyFavorites: false,
- pageIndex: 0,
- pageSize: 10,
- search: '',
- selectedItems: [],
- sortDirection: 'asc',
- sortField: 'updated',
- });
+ expect(
+ wrapper
+ .find('[data-test-subj="open-timeline"]')
+ .last()
+ .prop('sortDirection')
+ ).toEqual('asc');
});
});
describe('#onToggleOnlyFavorites', () => {
- test('it updates the onlyFavorites state when the user clicks the Only Favorites button', async () => {
+ test('it updates the onlyFavorites state when the user clicks the Only Favorites button', () => {
const wrapper = mount(
@@ -385,25 +360,24 @@ describe('StatefulOpenTimeline', () => {
);
- await wait();
+ expect(
+ wrapper
+ .find('[data-test-subj="open-timeline"]')
+ .last()
+ .prop('onlyFavorites')
+ ).toEqual(false);
wrapper
.find('[data-test-subj="only-favorites-toggle"]')
.first()
.simulate('click');
- wrapper.update();
-
- expect(getStateChildComponent(wrapper).state).toEqual({
- itemIdToExpandedNotesRowMap: {},
- onlyFavorites: true,
- pageIndex: 0,
- pageSize: 10,
- search: '',
- selectedItems: [],
- sortDirection: 'desc',
- sortField: 'updated',
- });
+ expect(
+ wrapper
+ .find('[data-test-subj="open-timeline"]')
+ .last()
+ .prop('onlyFavorites')
+ ).toEqual(true);
});
});
@@ -426,38 +400,38 @@ describe('StatefulOpenTimeline', () => {
);
await wait();
-
wrapper.update();
+ expect(
+ wrapper
+ .find('[data-test-subj="open-timeline"]')
+ .last()
+ .prop('itemIdToExpandedNotesRowMap')
+ ).toEqual({});
+
wrapper
.find('[data-test-subj="expand-notes"]')
.first()
.simulate('click');
- wrapper.update();
- expect(getStateChildComponent(wrapper).state).toEqual({
- itemIdToExpandedNotesRowMap: {
- '10849df0-7b44-11e9-a608-ab3d811609': (
- ({ ...note, savedObjectId: note.noteId })
- )
- : []
- }
- />
- ),
- },
- onlyFavorites: false,
- pageIndex: 0,
- pageSize: 10,
- search: '',
- selectedItems: [],
- sortDirection: 'desc',
- sortField: 'updated',
+ expect(
+ wrapper
+ .find('[data-test-subj="open-timeline"]')
+ .last()
+ .prop('itemIdToExpandedNotesRowMap')
+ ).toEqual({
+ '10849df0-7b44-11e9-a608-ab3d811609': (
+ ({ ...note, savedObjectId: note.noteId })
+ )
+ : []
+ }
+ />
+ ),
});
});
@@ -487,8 +461,6 @@ describe('StatefulOpenTimeline', () => {
.first()
.simulate('click');
- wrapper.update();
-
expect(
wrapper
.find('[data-test-subj="note-previews-container"]')
@@ -543,25 +515,23 @@ describe('StatefulOpenTimeline', () => {
);
-
+ const getSelectedItem = (): [] =>
+ wrapper
+ .find('[data-test-subj="open-timeline"]')
+ .last()
+ .prop('selectedItems');
await wait();
-
- wrapper.update();
-
+ expect(getSelectedItem().length).toEqual(0);
wrapper
.find('.euiCheckbox__input')
.first()
.simulate('change', { target: { checked: true } });
- wrapper.update();
-
+ expect(getSelectedItem().length).toEqual(13);
wrapper
.find('[data-test-subj="delete-selected"]')
.first()
.simulate('click');
-
- wrapper.update();
-
- expect(get('selectedItems', getStateChildComponent(wrapper).state).length).toEqual(0);
+ expect(getSelectedItem().length).toEqual(0);
});
});
diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx
index c686228ed31e8..d101d1f4d39f4 100644
--- a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx
@@ -5,7 +5,7 @@
*/
import ApolloClient from 'apollo-client';
-import * as React from 'react';
+import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
@@ -43,25 +43,6 @@ import {
import { DEFAULT_SORT_FIELD, DEFAULT_SORT_DIRECTION } from './constants';
import { ColumnHeader } from '../timeline/body/column_headers/column_header';
-export interface OpenTimelineState {
- /** Required by EuiTable for expandable rows: a map of `TimelineResult.savedObjectId` to rendered notes */
- itemIdToExpandedNotesRowMap: Record;
- /** Only query for favorite timelines when true */
- onlyFavorites: boolean;
- /** The requested page of results */
- pageIndex: number;
- /** The requested size of each page of search results */
- pageSize: number;
- /** The current search criteria */
- search: string;
- /** The currently-selected timelines in the table */
- selectedItems: OpenTimelineResult[];
- /** The requested sort direction of the query results */
- sortDirection: 'asc' | 'desc';
- /** The requested field to sort on */
- sortField: string;
-}
-
interface OwnProps {
apolloClient: ApolloClient;
/** Displays open timeline in modal */
@@ -85,70 +66,208 @@ export const getSelectedTimelineIds = (selectedItems: OpenTimelineResult[]): str
);
/** Manages the state (e.g table selection) of the (pure) `OpenTimeline` component */
-export class StatefulOpenTimelineComponent extends React.PureComponent<
- OpenTimelineOwnProps,
- OpenTimelineState
-> {
- constructor(props: OpenTimelineOwnProps) {
- super(props);
+export const StatefulOpenTimelineComponent = React.memo(
+ ({
+ defaultPageSize,
+ isModal = false,
+ title,
+ apolloClient,
+ closeModalTimeline,
+ updateTimeline,
+ updateIsLoading,
+ timeline,
+ createNewTimeline,
+ }) => {
+ /** Required by EuiTable for expandable rows: a map of `TimelineResult.savedObjectId` to rendered notes */
+ const [itemIdToExpandedNotesRowMap, setItemIdToExpandedNotesRowMap] = useState<
+ Record
+ >({});
+ /** Only query for favorite timelines when true */
+ const [onlyFavorites, setOnlyFavorites] = useState(false);
+ /** The requested page of results */
+ const [pageIndex, setPageIndex] = useState(0);
+ /** The requested size of each page of search results */
+ const [pageSize, setPageSize] = useState(defaultPageSize);
+ /** The current search criteria */
+ const [search, setSearch] = useState('');
+ /** The currently-selected timelines in the table */
+ const [selectedItems, setSelectedItems] = useState([]);
+ /** The requested sort direction of the query results */
+ const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>(DEFAULT_SORT_DIRECTION);
+ /** The requested field to sort on */
+ const [sortField, setSortField] = useState(DEFAULT_SORT_FIELD);
+
+ /** Invoked when the user presses enters to submit the text in the search input */
+ const onQueryChange: OnQueryChange = (query: EuiSearchBarQuery) => {
+ setSearch(query.queryText.trim());
+ };
+
+ /** Focuses the input that filters the field browser */
+ const focusInput = () => {
+ const elements = document.querySelector(`.${OPEN_TIMELINE_CLASS_NAME} input`);
- this.state = {
- itemIdToExpandedNotesRowMap: {},
- onlyFavorites: false,
- search: '',
- pageIndex: 0,
- pageSize: props.defaultPageSize,
- sortField: DEFAULT_SORT_FIELD,
- sortDirection: DEFAULT_SORT_DIRECTION,
- selectedItems: [],
+ if (elements != null) {
+ elements.focus();
+ }
};
- }
- public componentDidMount() {
- this.focusInput();
- }
+ /* This feature will be implemented in the near future, so we are keeping it to know what to do */
+
+ /** Invoked when the user clicks the action to add the selected timelines to favorites */
+ // const onAddTimelinesToFavorites: OnAddTimelinesToFavorites = () => {
+ // const { addTimelinesToFavorites } = this.props;
+ // const { selectedItems } = this.state;
+ // if (addTimelinesToFavorites != null) {
+ // addTimelinesToFavorites(getSelectedTimelineIds(selectedItems));
+ // TODO: it's not possible to clear the selection state of the newly-favorited
+ // items, because we can't pass the selection state as props to the table.
+ // See: https://github.com/elastic/eui/issues/1077
+ // TODO: the query must re-execute to show the results of the mutation
+ // }
+ // };
+
+ const onDeleteOneTimeline: OnDeleteOneTimeline = (timelineIds: string[]) => {
+ deleteTimelines(timelineIds, {
+ search,
+ pageInfo: {
+ pageIndex: pageIndex + 1,
+ pageSize,
+ },
+ sort: {
+ sortField: sortField as SortFieldTimeline,
+ sortOrder: sortDirection as Direction,
+ },
+ onlyUserFavorite: onlyFavorites,
+ });
+ };
+
+ /** Invoked when the user clicks the action to delete the selected timelines */
+ const onDeleteSelected: OnDeleteSelected = () => {
+ deleteTimelines(getSelectedTimelineIds(selectedItems), {
+ search,
+ pageInfo: {
+ pageIndex: pageIndex + 1,
+ pageSize,
+ },
+ sort: {
+ sortField: sortField as SortFieldTimeline,
+ sortOrder: sortDirection as Direction,
+ },
+ onlyUserFavorite: onlyFavorites,
+ });
+
+ // NOTE: we clear the selection state below, but if the server fails to
+ // delete a timeline, it will remain selected in the table:
+ resetSelectionState();
+
+ // TODO: the query must re-execute to show the results of the deletion
+ };
+
+ /** Invoked when the user selects (or de-selects) timelines */
+ const onSelectionChange: OnSelectionChange = (newSelectedItems: OpenTimelineResult[]) => {
+ setSelectedItems(newSelectedItems); // <-- this is NOT passed down as props to the table: https://github.com/elastic/eui/issues/1077
+ };
+
+ /** Invoked by the EUI table implementation when the user interacts with the table (i.e. to update sorting) */
+ const onTableChange: OnTableChange = ({ page, sort }: OnTableChangeParams) => {
+ const { index, size } = page;
+ const { field, direction } = sort;
+ setPageIndex(index);
+ setPageSize(size);
+ setSortDirection(direction);
+ setSortField(field);
+ };
+
+ /** Invoked when the user toggles the option to only view favorite timelines */
+ const onToggleOnlyFavorites: OnToggleOnlyFavorites = () => {
+ setOnlyFavorites(!onlyFavorites);
+ };
+
+ /** Invoked when the user toggles the expansion or collapse of inline notes in a table row */
+ const onToggleShowNotes: OnToggleShowNotes = (
+ newItemIdToExpandedNotesRowMap: Record
+ ) => {
+ setItemIdToExpandedNotesRowMap(newItemIdToExpandedNotesRowMap);
+ };
+
+ /** Resets the selection state such that all timelines are unselected */
+ const resetSelectionState = () => {
+ setSelectedItems([]);
+ };
+
+ const openTimeline: OnOpenTimeline = ({
+ duplicate,
+ timelineId,
+ }: {
+ duplicate: boolean;
+ timelineId: string;
+ }) => {
+ if (isModal && closeModalTimeline != null) {
+ closeModalTimeline();
+ }
+
+ queryTimelineById({
+ apolloClient,
+ duplicate,
+ timelineId,
+ updateIsLoading,
+ updateTimeline,
+ });
+ };
+
+ const deleteTimelines: DeleteTimelines = (
+ timelineIds: string[],
+ variables?: AllTimelinesVariables
+ ) => {
+ if (timelineIds.includes(timeline.savedObjectId || '')) {
+ createNewTimeline({ id: 'timeline-1', columns: defaultHeaders, show: false });
+ }
+ apolloClient.mutate({
+ mutation: deleteTimelineMutation,
+ fetchPolicy: 'no-cache',
+ variables: { id: timelineIds },
+ refetchQueries: [
+ {
+ query: allTimelinesQuery,
+ variables,
+ },
+ ],
+ });
+ };
+ useEffect(() => {
+ focusInput();
+ }, []);
- public render() {
- const { defaultPageSize, isModal = false, title } = this.props;
- const {
- itemIdToExpandedNotesRowMap,
- onlyFavorites,
- pageIndex,
- pageSize,
- search: query,
- selectedItems,
- sortDirection,
- sortField,
- } = this.state;
return (
{({ timelines, loading, totalCount }) => {
return !isModal ? (
) : (
);
}
-
- /** Invoked when the user presses enters to submit the text in the search input */
- private onQueryChange: OnQueryChange = (query: EuiSearchBarQuery) => {
- this.setState({
- search: query.queryText.trim(),
- });
- };
-
- /** Focuses the input that filters the field browser */
- private focusInput = () => {
- const elements = document.querySelector(`.${OPEN_TIMELINE_CLASS_NAME} input`);
-
- if (elements != null) {
- elements.focus();
- }
- };
-
- /* This feature will be implemented in the near future, so we are keeping it to know what to do */
-
- /** Invoked when the user clicks the action to add the selected timelines to favorites */
- // private onAddTimelinesToFavorites: OnAddTimelinesToFavorites = () => {
- // const { addTimelinesToFavorites } = this.props;
- // const { selectedItems } = this.state;
- // if (addTimelinesToFavorites != null) {
- // addTimelinesToFavorites(getSelectedTimelineIds(selectedItems));
- // TODO: it's not possible to clear the selection state of the newly-favorited
- // items, because we can't pass the selection state as props to the table.
- // See: https://github.com/elastic/eui/issues/1077
- // TODO: the query must re-execute to show the results of the mutation
- // }
- // };
-
- private onDeleteOneTimeline: OnDeleteOneTimeline = (timelineIds: string[]) => {
- const { onlyFavorites, pageIndex, pageSize, search, sortDirection, sortField } = this.state;
-
- this.deleteTimelines(timelineIds, {
- search,
- pageInfo: {
- pageIndex: pageIndex + 1,
- pageSize,
- },
- sort: {
- sortField: sortField as SortFieldTimeline,
- sortOrder: sortDirection as Direction,
- },
- onlyUserFavorite: onlyFavorites,
- });
- };
-
- /** Invoked when the user clicks the action to delete the selected timelines */
- private onDeleteSelected: OnDeleteSelected = () => {
- const { selectedItems, onlyFavorites } = this.state;
-
- this.deleteTimelines(getSelectedTimelineIds(selectedItems), {
- search: this.state.search,
- pageInfo: {
- pageIndex: this.state.pageIndex + 1,
- pageSize: this.state.pageSize,
- },
- sort: {
- sortField: this.state.sortField as SortFieldTimeline,
- sortOrder: this.state.sortDirection as Direction,
- },
- onlyUserFavorite: onlyFavorites,
- });
-
- // NOTE: we clear the selection state below, but if the server fails to
- // delete a timeline, it will remain selected in the table:
- this.resetSelectionState();
-
- // TODO: the query must re-execute to show the results of the deletion
- };
-
- /** Invoked when the user selects (or de-selects) timelines */
- private onSelectionChange: OnSelectionChange = (selectedItems: OpenTimelineResult[]) => {
- this.setState({ selectedItems }); // <-- this is NOT passed down as props to the table: https://github.com/elastic/eui/issues/1077
- };
-
- /** Invoked by the EUI table implementation when the user interacts with the table (i.e. to update sorting) */
- private onTableChange: OnTableChange = ({ page, sort }: OnTableChangeParams) => {
- const { index: pageIndex, size: pageSize } = page;
- const { field: sortField, direction: sortDirection } = sort;
-
- this.setState({
- pageIndex,
- pageSize,
- sortDirection,
- sortField,
- });
- };
-
- /** Invoked when the user toggles the option to only view favorite timelines */
- private onToggleOnlyFavorites: OnToggleOnlyFavorites = () => {
- this.setState(state => ({
- onlyFavorites: !state.onlyFavorites,
- }));
- };
-
- /** Invoked when the user toggles the expansion or collapse of inline notes in a table row */
- private onToggleShowNotes: OnToggleShowNotes = (
- itemIdToExpandedNotesRowMap: Record
- ) => {
- this.setState(() => ({
- itemIdToExpandedNotesRowMap,
- }));
- };
-
- /** Resets the selection state such that all timelines are unselected */
- private resetSelectionState = () => {
- this.setState({
- selectedItems: [],
- });
- };
-
- private openTimeline: OnOpenTimeline = ({
- duplicate,
- timelineId,
- }: {
- duplicate: boolean;
- timelineId: string;
- }) => {
- const {
- apolloClient,
- closeModalTimeline,
- isModal,
- updateTimeline,
- updateIsLoading,
- } = this.props;
-
- if (isModal && closeModalTimeline != null) {
- closeModalTimeline();
- }
-
- queryTimelineById({
- apolloClient,
- duplicate,
- timelineId,
- updateIsLoading,
- updateTimeline,
- });
- };
-
- private deleteTimelines: DeleteTimelines = (
- timelineIds: string[],
- variables?: AllTimelinesVariables
- ) => {
- if (timelineIds.includes(this.props.timeline.savedObjectId || '')) {
- this.props.createNewTimeline({ id: 'timeline-1', columns: defaultHeaders, show: false });
- }
- this.props.apolloClient.mutate<
- DeleteTimelineMutation.Mutation,
- DeleteTimelineMutation.Variables
- >({
- mutation: deleteTimelineMutation,
- fetchPolicy: 'no-cache',
- variables: { id: timelineIds },
- refetchQueries: [
- {
- query: allTimelinesQuery,
- variables,
- },
- ],
- });
- };
-}
+);
const makeMapStateToProps = () => {
const getTimeline = timelineSelectors.getTimelineByIdSelector();
diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx
index bcafed20a50ff..146afa85e10a7 100644
--- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx
@@ -5,8 +5,7 @@
*/
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
-import { get } from 'lodash/fp';
-import { mount, ReactWrapper } from 'enzyme';
+import { mount } from 'enzyme';
import * as React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import { ThemeProvider } from 'styled-components';
@@ -20,12 +19,6 @@ import { OpenTimelineModalButton } from '.';
jest.mock('../../../lib/settings/use_kibana_ui_setting');
-const getStateChildComponent = (
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- wrapper: ReactWrapper, React.Component<{}, {}, any>>
-): // eslint-disable-next-line @typescript-eslint/no-explicit-any
-React.Component<{}, {}, any> => wrapper.find('[data-test-subj="state-child-component"]').instance();
-
describe('OpenTimelineModalButton', () => {
const theme = () => ({ eui: euiDarkVars, darkMode: true });
@@ -56,10 +49,7 @@ describe('OpenTimelineModalButton', () => {
-
+
@@ -69,7 +59,7 @@ describe('OpenTimelineModalButton', () => {
wrapper.update();
- expect(get('showModal', getStateChildComponent(wrapper).state)).toEqual(false);
+ expect(wrapper.find('div[data-test-subj="open-timeline-modal"].euiModal').length).toEqual(0);
});
test('it sets showModal to true when the button is clicked', async () => {
@@ -151,10 +141,7 @@ describe('OpenTimelineModalButton', () => {
-
+
@@ -169,7 +156,7 @@ describe('OpenTimelineModalButton', () => {
wrapper.update();
- expect(get('showModal', getStateChildComponent(wrapper).state)).toEqual(true);
+ expect(wrapper.find('div[data-test-subj="open-timeline-modal"].euiModal').length).toEqual(1);
});
test('it invokes the optional onToggle function provided as a prop when the open timeline button is clicked', async () => {
diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx
index 79fa747aee081..41907e07d5c1b 100644
--- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx
@@ -5,8 +5,7 @@
*/
import { EuiButtonEmpty, EuiModal, EuiOverlayMask } from '@elastic/eui';
-import * as React from 'react';
-import styled from 'styled-components';
+import React, { useState } from 'react';
import { ApolloConsumer } from 'react-apollo';
import * as i18n from '../translations';
@@ -20,90 +19,61 @@ export interface OpenTimelineModalButtonProps {
onToggle?: () => void;
}
-export interface OpenTimelineModalButtonState {
- showModal: boolean;
-}
-
const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10;
const OPEN_TIMELINE_MODAL_WIDTH = 1000; // px
-// TODO: this container can be removed when
-// the following EUI PR is available (in Kibana):
-// https://github.com/elastic/eui/pull/1902/files#diff-d662c14c5dcd7e4b41028bf60b9bc77b
-const ModalContainer = styled.div`
- .euiModalBody {
- display: flex;
- flex-direction: column;
- }
-`;
-
-ModalContainer.displayName = 'ModalContainer';
-
/**
* Renders a button that when clicked, displays the `Open Timelines` modal
*/
-export class OpenTimelineModalButton extends React.PureComponent<
- OpenTimelineModalButtonProps,
- OpenTimelineModalButtonState
-> {
- constructor(props: OpenTimelineModalButtonProps) {
- super(props);
+export const OpenTimelineModalButton = React.memo(({ onToggle }) => {
+ const [showModal, setShowModal] = useState(false);
- this.state = { showModal: false };
+ /** shows or hides the `Open Timeline` modal */
+ function toggleShowModal() {
+ if (onToggle != null) {
+ onToggle();
+ }
+ setShowModal(!showModal);
}
- public render() {
- return (
-
- {client => (
- <>
-
- {i18n.OPEN_TIMELINE}
-
-
- {this.state.showModal && (
-
-
-
-
-
-
-
- )}
- >
- )}
-
- );
+ function closeModalTimeline() {
+ toggleShowModal();
}
+ return (
+
+ {client => (
+ <>
+
+ {i18n.OPEN_TIMELINE}
+
- /** shows or hides the `Open Timeline` modal */
- private toggleShowModal = () => {
- if (this.props.onToggle != null) {
- this.props.onToggle();
- }
-
- this.setState(state => ({
- showModal: !state.showModal,
- }));
- };
+ {showModal && (
+
+
+
+
+
+ )}
+ >
+ )}
+
+ );
+});
- private closeModalTimeline = () => {
- this.toggleShowModal();
- };
-}
+OpenTimelineModalButton.displayName = 'OpenTimelineModalButton';
diff --git a/x-pack/legacy/plugins/siem/public/components/page/add_to_kql/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/add_to_kql/index.tsx
index 693dcf7516bc4..5c0b449916a1f 100644
--- a/x-pack/legacy/plugins/siem/public/components/page/add_to_kql/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/page/add_to_kql/index.tsx
@@ -7,7 +7,6 @@
import { EuiIcon, EuiPanel, EuiToolTip } from '@elastic/eui';
import { isEmpty } from 'lodash/fp';
import React from 'react';
-import { pure } from 'recompose';
import styled from 'styled-components';
import { StaticIndexPattern } from 'ui/index_patterns';
@@ -25,15 +24,21 @@ interface Props {
filterQueryDraft: KueryFilterQuery;
}
-class AddToKqlComponent extends React.PureComponent {
- public render() {
- const { children } = this.props;
+const AddToKqlComponent = React.memo(
+ ({ children, expression, filterQueryDraft, applyFilterQueryFromKueryExpression }) => {
+ const addToKql = () => {
+ applyFilterQueryFromKueryExpression(
+ filterQueryDraft && !isEmpty(filterQueryDraft.expression)
+ ? `${filterQueryDraft.expression} and ${expression}`
+ : expression
+ );
+ };
return (
-
+
}
@@ -41,16 +46,9 @@ class AddToKqlComponent extends React.PureComponent {
/>
);
}
+);
- private addToKql = () => {
- const { expression, filterQueryDraft, applyFilterQueryFromKueryExpression } = this.props;
- applyFilterQueryFromKueryExpression(
- filterQueryDraft && !isEmpty(filterQueryDraft.expression)
- ? `${filterQueryDraft.expression} and ${expression}`
- : expression
- );
- };
-}
+AddToKqlComponent.displayName = 'AddToKqlComponent';
const HoverActionsContainer = styled(EuiPanel)`
align-items: center;
@@ -75,7 +73,7 @@ interface AddToKqlProps {
type: networkModel.NetworkType | hostsModel.HostsType;
}
-export const AddToKql = pure(
+export const AddToKql = React.memo(
({ children, expression, type, componentFilterType, indexPattern }) => {
switch (componentFilterType) {
case 'hosts':
diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx
index 65c8e9a654686..d4b3b5e875989 100644
--- a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx
@@ -4,8 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import memoizeOne from 'memoize-one';
-import React from 'react';
+import React, { useMemo } from 'react';
import { connect } from 'react-redux';
import { ActionCreator } from 'typescript-fsa';
import { StaticIndexPattern } from 'ui/index_patterns';
@@ -38,8 +37,8 @@ interface OwnProps {
data: HostsEdges[];
fakeTotalCount: number;
id: string;
- isInspect: boolean;
indexPattern: StaticIndexPattern;
+ isInspect: boolean;
loading: boolean;
loadPage: (newActivePage: number) => void;
showMorePagesIndicator: boolean;
@@ -49,15 +48,15 @@ interface OwnProps {
interface HostsTableReduxProps {
activePage: number;
+ direction: Direction;
limit: number;
sortField: HostsFields;
- direction: Direction;
}
interface HostsTableDispatchProps {
updateHostsSort: ActionCreator<{
- sort: HostsSortField;
hostsType: hostsModel.HostsType;
+ sort: HostsSortField;
}>;
updateTableActivePage: ActionCreator<{
activePage: number;
@@ -65,8 +64,8 @@ interface HostsTableDispatchProps {
tableType: hostsModel.HostsTableType;
}>;
updateTableLimit: ActionCreator<{
- limit: number;
hostsType: hostsModel.HostsType;
+ limit: number;
tableType: hostsModel.HostsTableType;
}>;
}
@@ -90,47 +89,58 @@ const rowItems: ItemsPerRow[] = [
numberOfRow: 10,
},
];
+const getSorting = (
+ trigger: string,
+ sortField: HostsFields,
+ direction: Direction
+): SortingBasicTable => ({ field: getNodeField(sortField), direction });
+
+const HostsTableComponent = React.memo(
+ ({
+ activePage,
+ data,
+ direction,
+ fakeTotalCount,
+ id,
+ indexPattern,
+ isInspect,
+ limit,
+ loading,
+ loadPage,
+ showMorePagesIndicator,
+ sortField,
+ totalCount,
+ type,
+ updateHostsSort,
+ updateTableActivePage,
+ updateTableLimit,
+ }) => {
+ const onChange = (criteria: Criteria) => {
+ if (criteria.sort != null) {
+ const sort: HostsSortField = {
+ field: getSortField(criteria.sort.field),
+ direction: criteria.sort.direction,
+ };
+ if (sort.direction !== direction || sort.field !== sortField) {
+ updateHostsSort({
+ sort,
+ hostsType: type,
+ });
+ }
+ }
+ };
-class HostsTableComponent extends React.PureComponent {
- private memoizedColumns: (
- type: hostsModel.HostsType,
- indexPattern: StaticIndexPattern
- ) => HostsTableColumns;
- private memoizedSorting: (
- trigger: string,
- sortField: HostsFields,
- direction: Direction
- ) => SortingBasicTable;
-
- constructor(props: HostsTableProps) {
- super(props);
- this.memoizedColumns = memoizeOne(this.getMemoizeHostsColumns);
- this.memoizedSorting = memoizeOne(this.getSorting);
- }
+ const hostsColumns = useMemo(() => getHostsColumns(type, indexPattern), [type, indexPattern]);
- public render() {
- const {
- activePage,
- data,
- direction,
- fakeTotalCount,
- id,
- isInspect,
- indexPattern,
- limit,
- loading,
- loadPage,
- showMorePagesIndicator,
- totalCount,
+ const sorting = useMemo(() => getSorting(`${sortField}-${direction}`, sortField, direction), [
sortField,
- type,
- updateTableActivePage,
- updateTableLimit,
- } = this.props;
+ direction,
+ ]);
+
return (
{
limit={limit}
loading={loading}
loadPage={newActivePage => loadPage(newActivePage)}
- onChange={this.onChange}
+ onChange={onChange}
pageOfItems={data}
showMorePagesIndicator={showMorePagesIndicator}
- sorting={this.memoizedSorting(`${sortField}-${direction}`, sortField, direction)}
+ sorting={sorting}
totalCount={fakeTotalCount}
updateLimitPagination={newLimit =>
updateTableLimit({
@@ -163,33 +173,9 @@ class HostsTableComponent extends React.PureComponent {
/>
);
}
+);
- private getSorting = (
- trigger: string,
- sortField: HostsFields,
- direction: Direction
- ): SortingBasicTable => ({ field: getNodeField(sortField), direction });
-
- private getMemoizeHostsColumns = (
- type: hostsModel.HostsType,
- indexPattern: StaticIndexPattern
- ): HostsTableColumns => getHostsColumns(type, indexPattern);
-
- private onChange = (criteria: Criteria) => {
- if (criteria.sort != null) {
- const sort: HostsSortField = {
- field: getSortField(criteria.sort.field),
- direction: criteria.sort.direction,
- };
- if (sort.direction !== this.props.direction || sort.field !== this.props.sortField) {
- this.props.updateHostsSort({
- sort,
- hostsType: this.props.type,
- });
- }
- }
- };
-}
+HostsTableComponent.displayName = 'HostsTableComponent';
const getSortField = (field: string): HostsFields => {
switch (field) {
diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/columns.tsx
index 24820b637d388..cf5da3fbebba6 100644
--- a/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/columns.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/columns.tsx
@@ -25,7 +25,7 @@ import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_
import { escapeDataProviderId } from '../../../drag_and_drop/helpers';
import { defaultToEmptyTag, getEmptyTagValue } from '../../../empty_value';
import { PreferenceFormattedDate } from '../../../formatted_date';
-import { Columns } from '../../../load_more_table';
+import { Columns } from '../../../paginated_table';
import { LocalizedDateTooltip } from '../../../localized_date_tooltip';
import { IS_OPERATOR } from '../../../timeline/data_providers/data_provider';
import { PreferenceFormattedBytes } from '../../../formatted_bytes';
diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/columns.tsx
index 1fdea3f2b0332..353699c5158bc 100644
--- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/columns.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/columns.tsx
@@ -12,7 +12,7 @@ import { networkModel } from '../../../../store';
import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper';
import { escapeDataProviderId } from '../../../drag_and_drop/helpers';
import { defaultToEmptyTag, getEmptyTagValue } from '../../../empty_value';
-import { Columns } from '../../../load_more_table';
+import { Columns } from '../../../paginated_table';
import { IS_OPERATOR } from '../../../timeline/data_providers/data_provider';
import { PreferenceFormattedBytes } from '../../../formatted_bytes';
import { Provider } from '../../../timeline/data_providers/provider';
diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx
index 38eda9810740c..97fa601a49af1 100644
--- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx
@@ -21,7 +21,7 @@ import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_
import { escapeDataProviderId } from '../../../drag_and_drop/helpers';
import { getEmptyTagValue } from '../../../empty_value';
import { IPDetailsLink } from '../../../links';
-import { Columns } from '../../../load_more_table';
+import { Columns } from '../../../paginated_table';
import { IS_OPERATOR } from '../../../timeline/data_providers/data_provider';
import { Provider } from '../../../timeline/data_providers/provider';
import * as i18n from './translations';
diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx
index 5abbdab9c980f..714d3f7a8131c 100644
--- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx
@@ -79,26 +79,45 @@ const rowItems: ItemsPerRow[] = [
export const NetworkTopNFlowTableId = 'networkTopSourceFlow-top-talkers';
-class NetworkTopNFlowTableComponent extends React.PureComponent {
- public render() {
- const {
- activePage,
- data,
- fakeTotalCount,
- flowTargeted,
- id,
- indexPattern,
- isInspect,
- limit,
- loading,
- loadPage,
- showMorePagesIndicator,
- topNFlowSort,
- totalCount,
- type,
- updateTopNFlowLimit,
- updateTableActivePage,
- } = this.props;
+const NetworkTopNFlowTableComponent = React.memo(
+ ({
+ activePage,
+ data,
+ fakeTotalCount,
+ flowTargeted,
+ id,
+ indexPattern,
+ isInspect,
+ limit,
+ loading,
+ loadPage,
+ showMorePagesIndicator,
+ topNFlowSort,
+ totalCount,
+ type,
+ updateTopNFlowLimit,
+ updateTopNFlowSort,
+ updateTableActivePage,
+ }) => {
+ const onChange = (criteria: Criteria, tableType: networkModel.TopNTableType) => {
+ if (criteria.sort != null) {
+ const splitField = criteria.sort.field.split('.');
+ const field = last(splitField);
+ const newSortDirection =
+ field !== topNFlowSort.field ? Direction.desc : criteria.sort.direction; // sort by desc on init click
+ const newTopNFlowSort: NetworkTopNFlowSortField = {
+ field: field as NetworkTopNFlowFields,
+ direction: newSortDirection,
+ };
+ if (!isEqual(newTopNFlowSort, topNFlowSort)) {
+ updateTopNFlowSort({
+ topNFlowSort: newTopNFlowSort,
+ networkType: type,
+ tableType,
+ });
+ }
+ }
+ };
let tableType: networkModel.TopNTableType;
let headerTitle: string;
@@ -136,7 +155,7 @@ class NetworkTopNFlowTableComponent extends React.PureComponent loadPage(newActivePage)}
- onChange={criteria => this.onChange(criteria, tableType)}
+ onChange={criteria => onChange(criteria, tableType)}
pageOfItems={data}
showMorePagesIndicator={showMorePagesIndicator}
sorting={{ field, direction: topNFlowSort.direction }}
@@ -153,27 +172,9 @@ class NetworkTopNFlowTableComponent extends React.PureComponent
);
}
+);
- private onChange = (criteria: Criteria, tableType: networkModel.TopNTableType) => {
- if (criteria.sort != null) {
- const splitField = criteria.sort.field.split('.');
- const field = last(splitField);
- const newSortDirection =
- field !== this.props.topNFlowSort.field ? Direction.desc : criteria.sort.direction; // sort by desc on init click
- const newTopNFlowSort: NetworkTopNFlowSortField = {
- field: field as NetworkTopNFlowFields,
- direction: newSortDirection,
- };
- if (!isEqual(newTopNFlowSort, this.props.topNFlowSort)) {
- this.props.updateTopNFlowSort({
- topNFlowSort: newTopNFlowSort,
- networkType: this.props.type,
- tableType,
- });
- }
- }
- };
-}
+NetworkTopNFlowTableComponent.displayName = 'NetworkTopNFlowTableComponent';
const mapStateToProps = (state: State, ownProps: OwnProps) =>
networkSelectors.topNFlowSelector(ownProps.flowTargeted);
diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx
index 7578c5decc851..aea8ee9e6b9e1 100644
--- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import moment from 'moment';
import { TlsNode } from '../../../../graphql/types';
-import { Columns } from '../../../load_more_table';
+import { Columns } from '../../../paginated_table';
import { getRowItemDraggables, getRowItemDraggable } from '../../../tables/helpers';
import { LocalizedDateTooltip } from '../../../localized_date_tooltip';
diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/columns.tsx
index b17ec74fa0540..2c51fb8f94561 100644
--- a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/columns.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/columns.tsx
@@ -6,7 +6,7 @@
import { FlowTarget, UsersItem } from '../../../../graphql/types';
import { defaultToEmptyTag } from '../../../empty_value';
-import { Columns } from '../../../load_more_table';
+import { Columns } from '../../../paginated_table';
import * as i18n from './translations';
import { getRowItemDraggables, getRowItemDraggable } from '../../../tables/helpers';
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 b5678a36c1eed..257ee03c944bf 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
@@ -100,15 +100,17 @@ export interface BasicTableProps {
updateActivePage: (activePage: number) => void;
updateLimitPagination: (limit: number) => void;
}
+type Func = (arg: T) => string | number;
-export interface Columns {
+export interface Columns {
+ align?: string;
field?: string;
- name: string | React.ReactNode;
+ hideForMobile?: boolean;
isMobileHeader?: boolean;
- sortable?: boolean;
+ name: string | React.ReactNode;
+ render?: (item: T, node: U) => React.ReactNode;
+ sortable?: boolean | Func;
truncateText?: boolean;
- hideForMobile?: boolean;
- render?: (item: T) => void;
width?: string;
}
diff --git a/x-pack/legacy/plugins/siem/public/components/resize_handle/index.tsx b/x-pack/legacy/plugins/siem/public/components/resize_handle/index.tsx
index 40df2c134047f..0a6203056fd20 100644
--- a/x-pack/legacy/plugins/siem/public/components/resize_handle/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/resize_handle/index.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import * as React from 'react';
+import React, { useEffect, useRef } from 'react';
import { fromEvent, Observable, Subscription } from 'rxjs';
import { concatMap, takeUntil } from 'rxjs/operators';
import styled, { css } from 'styled-components';
@@ -41,10 +41,6 @@ interface Props extends ResizeHandleContainerProps {
render: (isResizing: boolean) => React.ReactNode;
}
-interface State {
- isResizing: boolean;
-}
-
const ResizeHandleContainer = styled.div`
${({ bottom, height, left, positionAbsolute, right, theme, top }) => css`
bottom: ${positionAbsolute && bottom};
@@ -67,88 +63,75 @@ export const removeGlobalResizeCursorStyleFromBody = () => {
document.body.classList.remove(globalResizeCursorClassName);
};
-export class Resizeable extends React.PureComponent {
- private drag$: Observable | null;
- private dragEventTargets: Array<{ htmlElement: HTMLElement; prevCursor: string }>;
- private dragSubscription: Subscription | null;
- private prevX: number = 0;
- private ref: React.RefObject;
- private upSubscription: Subscription | null;
-
- constructor(props: Props) {
- super(props);
-
- // NOTE: the ref and observable below are NOT stored in component `State`
- this.ref = React.createRef();
- this.drag$ = null;
- this.dragSubscription = null;
- this.upSubscription = null;
- this.dragEventTargets = [];
-
- this.state = {
- isResizing: false,
+export const Resizeable = React.memo(
+ ({ bottom, handle, height, id, left, onResize, positionAbsolute, render, right, top }) => {
+ const drag$ = useRef | null>(null);
+ const dragEventTargets = useRef>([]);
+ const dragSubscription = useRef(null);
+ const prevX = useRef(0);
+ const ref = useRef>(React.createRef());
+ const upSubscription = useRef(null);
+ const isResizingRef = useRef(false);
+
+ const calculateDelta = (e: MouseEvent) => {
+ const deltaX = calculateDeltaX({ prevX: prevX.current, screenX: e.screenX });
+ prevX.current = e.screenX;
+ return deltaX;
};
- }
-
- public componentDidMount() {
- const { id, onResize } = this.props;
-
- const move$ = fromEvent(document, 'mousemove');
- const down$ = fromEvent(this.ref.current!, 'mousedown');
- const up$ = fromEvent(document, 'mouseup');
-
- this.drag$ = down$.pipe(concatMap(() => move$.pipe(takeUntil(up$))));
- this.dragSubscription = this.drag$.subscribe(event => {
- // We do a feature detection of event.movementX here and if it is missing
- // we calculate the delta manually. Browsers IE-11 and Safari will call calculateDelta
- const delta =
- event.movementX == null || isSafari ? this.calculateDelta(event) : event.movementX;
- if (!this.state.isResizing) {
- this.setState({ isResizing: true });
- }
- onResize({ id, delta });
- if (event.target != null && event.target instanceof HTMLElement) {
- const htmlElement: HTMLElement = event.target;
- this.dragEventTargets = [
- ...this.dragEventTargets,
- { htmlElement, prevCursor: htmlElement.style.cursor },
- ];
- htmlElement.style.cursor = resizeCursorStyle;
- }
- });
-
- this.upSubscription = up$.subscribe(() => {
- if (this.state.isResizing) {
- this.dragEventTargets.reverse().forEach(eventTarget => {
- eventTarget.htmlElement.style.cursor = eventTarget.prevCursor;
+ useEffect(() => {
+ const move$ = fromEvent(document, 'mousemove');
+ const down$ = fromEvent(ref.current.current!, 'mousedown');
+ const up$ = fromEvent(document, 'mouseup');
+
+ drag$.current = down$.pipe(concatMap(() => move$.pipe(takeUntil(up$))));
+ dragSubscription.current =
+ drag$.current &&
+ drag$.current.subscribe(event => {
+ // We do a feature detection of event.movementX here and if it is missing
+ // we calculate the delta manually. Browsers IE-11 and Safari will call calculateDelta
+ const delta =
+ event.movementX == null || isSafari ? calculateDelta(event) : event.movementX;
+ if (!isResizingRef.current) {
+ isResizingRef.current = true;
+ }
+ onResize({ id, delta });
+ if (event.target != null && event.target instanceof HTMLElement) {
+ const htmlElement: HTMLElement = event.target;
+ dragEventTargets.current = [
+ ...dragEventTargets.current,
+ { htmlElement, prevCursor: htmlElement.style.cursor },
+ ];
+ htmlElement.style.cursor = resizeCursorStyle;
+ }
});
- this.dragEventTargets = [];
- this.setState({ isResizing: false });
- }
- });
- }
- public componentWillUnmount() {
- if (this.dragSubscription != null) {
- this.dragSubscription.unsubscribe();
- }
-
- if (this.upSubscription != null) {
- this.upSubscription.unsubscribe();
- }
- }
-
- public render() {
- const { bottom, handle, height, left, positionAbsolute, render, right, top } = this.props;
+ upSubscription.current = up$.subscribe(() => {
+ if (isResizingRef.current) {
+ dragEventTargets.current.reverse().forEach(eventTarget => {
+ eventTarget.htmlElement.style.cursor = eventTarget.prevCursor;
+ });
+ dragEventTargets.current = [];
+ isResizingRef.current = false;
+ }
+ });
+ return () => {
+ if (dragSubscription.current != null) {
+ dragSubscription.current.unsubscribe();
+ }
+ if (upSubscription.current != null) {
+ upSubscription.current.unsubscribe();
+ }
+ };
+ }, []);
return (
<>
- {render(this.state.isResizing)}
+ {render(isResizingRef.current)}
{
>
);
}
+);
- private calculateDelta = (e: MouseEvent) => {
- const deltaX = calculateDeltaX({ prevX: this.prevX, screenX: e.screenX });
-
- this.prevX = e.screenX;
-
- return deltaX;
- };
-}
+Resizeable.displayName = 'Resizeable';
diff --git a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx
index 722cf9db731f7..fa695d76f9f3e 100644
--- a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx
@@ -7,13 +7,13 @@
import dateMath from '@elastic/datemath';
import {
EuiSuperDatePicker,
- EuiSuperDatePickerProps,
OnRefreshChangeProps,
+ EuiSuperDatePickerRecentRange,
OnRefreshProps,
OnTimeChangeProps,
} from '@elastic/eui';
import { getOr, take } from 'lodash/fp';
-import React, { Component } from 'react';
+import React, { useState } from 'react';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
@@ -36,33 +36,17 @@ import { InputsRange, Policy } from '../../store/inputs/model';
const MAX_RECENTLY_USED_RANGES = 9;
-type MyEuiSuperDatePickerProps = Pick<
- EuiSuperDatePickerProps,
- | 'end'
- | 'isPaused'
- | 'onTimeChange'
- | 'onRefreshChange'
- | 'onRefresh'
- | 'recentlyUsedRanges'
- | 'refreshInterval'
- | 'showUpdateButton'
- | 'start'
-> & {
- isLoading?: boolean;
-};
-const MyEuiSuperDatePicker: React.SFC = EuiSuperDatePicker;
-
interface SuperDatePickerStateRedux {
duration: number;
- policy: Policy['kind'];
- kind: string;
- fromStr: string;
- toStr: string;
- start: number;
end: number;
+ fromStr: string;
isLoading: boolean;
- queries: inputsModel.GlobalGraphqlQuery[];
+ kind: string;
kqlQuery: inputsModel.GlobalKqlQuery;
+ policy: Policy['kind'];
+ queries: inputsModel.GlobalGraphqlQuery[];
+ start: number;
+ toStr: string;
}
interface UpdateReduxTime extends OnTimeChangeProps {
@@ -85,145 +69,137 @@ type DispatchUpdateReduxTime = ({
}: UpdateReduxTime) => ReturnUpdateReduxTime;
interface SuperDatePickerDispatchProps {
+ setDuration: ({ id, duration }: { id: InputsModelId; duration: number }) => void;
startAutoReload: ({ id }: { id: InputsModelId }) => void;
stopAutoReload: ({ id }: { id: InputsModelId }) => void;
- setDuration: ({ id, duration }: { id: InputsModelId; duration: number }) => void;
updateReduxTime: DispatchUpdateReduxTime;
}
interface OwnProps {
- id: InputsModelId;
disabled?: boolean;
+ id: InputsModelId;
timelineId?: string;
}
-interface TimeArgs {
- start: string;
- end: string;
-}
-
export type SuperDatePickerProps = OwnProps &
SuperDatePickerDispatchProps &
SuperDatePickerStateRedux;
-export interface SuperDatePickerState {
- isQuickSelection: boolean;
- recentlyUsedRanges: TimeArgs[];
- showUpdateButton: boolean;
-}
+export const SuperDatePickerComponent = React.memo(
+ ({
+ duration,
+ end,
+ fromStr,
+ id,
+ isLoading,
+ kind,
+ kqlQuery,
+ policy,
+ queries,
+ setDuration,
+ start,
+ startAutoReload,
+ stopAutoReload,
+ timelineId,
+ toStr,
+ updateReduxTime,
+ }) => {
+ const [isQuickSelection, setIsQuickSelection] = useState(true);
+ const [recentlyUsedRanges, setRecentlyUsedRanges] = useState(
+ []
+ );
+ const onRefresh = ({ start: newStart, end: newEnd }: OnRefreshProps): void => {
+ const { kqlHasBeenUpdated } = updateReduxTime({
+ end: newEnd,
+ id,
+ isInvalid: false,
+ isQuickSelection,
+ kql: kqlQuery,
+ start: newStart,
+ timelineId,
+ });
+ const currentStart = formatDate(newStart);
+ const currentEnd = isQuickSelection
+ ? formatDate(newEnd, { roundUp: true })
+ : formatDate(newEnd);
+ if (
+ !kqlHasBeenUpdated &&
+ (!isQuickSelection || (start === currentStart && end === currentEnd))
+ ) {
+ refetchQuery(queries);
+ }
+ };
+
+ const onRefreshChange = ({ isPaused, refreshInterval }: OnRefreshChangeProps): void => {
+ if (duration !== refreshInterval) {
+ setDuration({ id, duration: refreshInterval });
+ }
-export const SuperDatePickerComponent = class extends Component<
- SuperDatePickerProps,
- SuperDatePickerState
-> {
- constructor(props: SuperDatePickerProps) {
- super(props);
+ if (isPaused && policy === 'interval') {
+ stopAutoReload({ id });
+ } else if (!isPaused && policy === 'manual') {
+ startAutoReload({ id });
+ }
- this.state = {
- isQuickSelection: true,
- recentlyUsedRanges: [],
- showUpdateButton: true,
+ if (!isPaused && (!isQuickSelection || (isQuickSelection && toStr !== 'now'))) {
+ refetchQuery(queries);
+ }
+ };
+
+ const refetchQuery = (newQueries: inputsModel.GlobalGraphqlQuery[]) => {
+ newQueries.forEach(q => q.refetch && (q.refetch as inputsModel.Refetch)());
};
- }
- public render() {
- const { duration, end, start, kind, fromStr, policy, toStr, isLoading } = this.props;
+ const onTimeChange = ({
+ start: newStart,
+ end: newEnd,
+ isQuickSelection: newIsQuickSelection,
+ isInvalid,
+ }: OnTimeChangeProps) => {
+ if (!isInvalid) {
+ updateReduxTime({
+ end: newEnd,
+ id,
+ isInvalid,
+ isQuickSelection: newIsQuickSelection,
+ kql: kqlQuery,
+ start: newStart,
+ timelineId,
+ });
+ const newRecentlyUsedRanges = [
+ { start: newStart, end: newEnd },
+ ...take(
+ MAX_RECENTLY_USED_RANGES,
+ recentlyUsedRanges.filter(
+ recentlyUsedRange =>
+ !(recentlyUsedRange.start === newStart && recentlyUsedRange.end === newEnd)
+ )
+ ),
+ ];
+
+ setRecentlyUsedRanges(newRecentlyUsedRanges);
+ setIsQuickSelection(newIsQuickSelection);
+ }
+ };
const endDate = kind === 'relative' ? toStr : new Date(end).toISOString();
const startDate = kind === 'relative' ? fromStr : new Date(start).toISOString();
return (
-
);
}
- private onRefresh = ({ start, end }: OnRefreshProps): void => {
- const { kqlHasBeenUpdated } = this.props.updateReduxTime({
- end,
- id: this.props.id,
- isInvalid: false,
- isQuickSelection: this.state.isQuickSelection,
- kql: this.props.kqlQuery,
- start,
- timelineId: this.props.timelineId,
- });
- const currentStart = formatDate(start);
- const currentEnd = this.state.isQuickSelection
- ? formatDate(end, { roundUp: true })
- : formatDate(end);
- if (
- !kqlHasBeenUpdated &&
- (!this.state.isQuickSelection ||
- (this.props.start === currentStart && this.props.end === currentEnd))
- ) {
- this.refetchQuery(this.props.queries);
- }
- };
-
- private onRefreshChange = ({ isPaused, refreshInterval }: OnRefreshChangeProps): void => {
- const { id, duration, policy, stopAutoReload, startAutoReload } = this.props;
- if (duration !== refreshInterval) {
- this.props.setDuration({ id, duration: refreshInterval });
- }
-
- if (isPaused && policy === 'interval') {
- stopAutoReload({ id });
- } else if (!isPaused && policy === 'manual') {
- startAutoReload({ id });
- }
-
- if (
- !isPaused &&
- (!this.state.isQuickSelection || (this.state.isQuickSelection && this.props.toStr !== 'now'))
- ) {
- this.refetchQuery(this.props.queries);
- }
- };
-
- private refetchQuery = (queries: inputsModel.GlobalGraphqlQuery[]) => {
- queries.forEach(q => q.refetch && (q.refetch as inputsModel.Refetch)());
- };
-
- private onTimeChange = ({ start, end, isQuickSelection, isInvalid }: OnTimeChangeProps) => {
- if (!isInvalid) {
- this.props.updateReduxTime({
- end,
- id: this.props.id,
- isInvalid,
- isQuickSelection,
- kql: this.props.kqlQuery,
- start,
- timelineId: this.props.timelineId,
- });
- this.setState((prevState: SuperDatePickerState) => {
- const recentlyUsedRanges = [
- { start, end },
- ...take(
- MAX_RECENTLY_USED_RANGES,
- prevState.recentlyUsedRanges.filter(
- recentlyUsedRange =>
- !(recentlyUsedRange.start === start && recentlyUsedRange.end === end)
- )
- ),
- ];
-
- return {
- recentlyUsedRanges,
- isQuickSelection,
- };
- });
- }
- };
-};
+);
const formatDate = (
date: string,
@@ -292,33 +268,35 @@ const dispatchUpdateReduxTime = (dispatch: Dispatch) => ({
};
export const makeMapStateToProps = () => {
- const getPolicySelector = policySelector();
const getDurationSelector = durationSelector();
- const getKindSelector = kindSelector();
- const getStartSelector = startSelector();
const getEndSelector = endSelector();
const getFromStrSelector = fromStrSelector();
- const getToStrSelector = toStrSelector();
const getIsLoadingSelector = isLoadingSelector();
- const getQueriesSelector = queriesSelector();
+ const getKindSelector = kindSelector();
const getKqlQuerySelector = kqlQuerySelector();
+ const getPolicySelector = policySelector();
+ const getQueriesSelector = queriesSelector();
+ const getStartSelector = startSelector();
+ const getToStrSelector = toStrSelector();
return (state: State, { id }: OwnProps) => {
const inputsRange: InputsRange = getOr({}, `inputs.${id}`, state);
return {
- policy: getPolicySelector(inputsRange),
duration: getDurationSelector(inputsRange),
- kind: getKindSelector(inputsRange),
- start: getStartSelector(inputsRange),
end: getEndSelector(inputsRange),
fromStr: getFromStrSelector(inputsRange),
- toStr: getToStrSelector(inputsRange),
isLoading: getIsLoadingSelector(inputsRange),
- queries: getQueriesSelector(inputsRange),
+ kind: getKindSelector(inputsRange),
kqlQuery: getKqlQuerySelector(inputsRange),
+ policy: getPolicySelector(inputsRange),
+ queries: getQueriesSelector(inputsRange),
+ start: getStartSelector(inputsRange),
+ toStr: getToStrSelector(inputsRange),
};
};
};
+SuperDatePickerComponent.displayName = 'SuperDatePickerComponent';
+
const mapDispatchToProps = (dispatch: Dispatch) => ({
startAutoReload: ({ id }: { id: InputsModelId }) =>
dispatch(inputsActions.startAutoReload({ id })),
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.tsx
index 62f58e1b585d9..1e603b0c15779 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.tsx
@@ -91,53 +91,56 @@ interface Props {
}
/** Renders a header */
-export class Header extends React.PureComponent {
- public render() {
- const { header } = this.props;
+export const Header = React.memo(
+ ({
+ header,
+ onColumnRemoved,
+ onColumnResized,
+ onColumnSorted,
+ onFilterChange = noop,
+ setIsResizing,
+ sort,
+ }) => {
+ const onClick = () => {
+ onColumnSorted!({
+ columnId: header.id,
+ sortDirection: getNewSortDirectionOnClick({
+ clickedHeader: header,
+ currentSort: sort,
+ }),
+ });
+ };
+
+ const onResize: OnResize = ({ delta, id }) => {
+ onColumnResized({ columnId: id, delta });
+ };
+
+ const renderActions = (isResizing: boolean) => {
+ setIsResizing(isResizing);
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+ };
return (
}
id={header.id}
- onResize={this.onResize}
+ onResize={onResize}
positionAbsolute
- render={this.renderActions}
+ render={renderActions}
right="-1px"
top={0}
/>
);
}
+);
- private renderActions = (isResizing: boolean) => {
- const { header, onColumnRemoved, onFilterChange = noop, setIsResizing, sort } = this.props;
-
- setIsResizing(isResizing);
-
- return (
- <>
-
-
-
-
-
- >
- );
- };
-
- private onClick = () => {
- const { header, onColumnSorted, sort } = this.props;
-
- onColumnSorted!({
- columnId: header.id,
- sortDirection: getNewSortDirectionOnClick({
- clickedHeader: header,
- currentSort: sort,
- }),
- });
- };
-
- private onResize: OnResize = ({ delta, id }) => {
- this.props.onColumnResized({ columnId: id, delta });
- };
-}
+Header.displayName = 'Header';
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx
index 44d0480bc5f28..2b2401519eb32 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx
@@ -22,10 +22,8 @@ interface Props {
timelineId: string;
}
-export class DataDrivenColumns extends React.PureComponent {
- public render() {
- const { _id, columnHeaders, columnRenderers, data, timelineId } = this.props;
-
+export const DataDrivenColumns = React.memo(
+ ({ _id, columnHeaders, columnRenderers, data, timelineId }) => {
// Passing the styles directly to the component because the width is
// being calculated and is recommended by Styled Components for performance
// https://github.com/styled-components/styled-components/issues/134#issuecomment-312415291
@@ -51,7 +49,9 @@ export class DataDrivenColumns extends React.PureComponent {
);
}
-}
+);
+
+DataDrivenColumns.displayName = 'DataDrivenColumns';
const getMappedNonEcsValue = ({
data,
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx
index 1e3f7303c2e1d..766a75c05f17c 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx
@@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import * as React from 'react';
-import VisibilitySensor from 'react-visibility-sensor';
+import React, { useEffect, useRef, useState } from 'react';
import uuid from 'uuid';
+import VisibilitySensor from 'react-visibility-sensor';
import { BrowserFields } from '../../../../containers/source';
import { TimelineDetailsComponentQuery } from '../../../../containers/timeline/details';
@@ -35,24 +35,18 @@ interface Props {
columnRenderers: ColumnRenderer[];
event: TimelineItem;
eventIdToNoteIds: Readonly>;
- isEventViewer?: boolean;
getNotesByIds: (noteIds: string[]) => Note[];
+ isEventViewer?: boolean;
+ maxDelay?: number;
onColumnResized: OnColumnResized;
onPinEvent: OnPinEvent;
- onUpdateColumns: OnUpdateColumns;
onUnPinEvent: OnUnPinEvent;
+ onUpdateColumns: OnUpdateColumns;
pinnedEventIds: Readonly>;
rowRenderers: RowRenderer[];
timelineId: string;
toggleColumn: (column: ColumnHeader) => void;
updateNote: UpdateNote;
- maxDelay?: number;
-}
-
-interface State {
- expanded: { [eventId: string]: boolean };
- showNotes: { [eventId: string]: boolean };
- initialRender: boolean;
}
export const getNewNoteId = (): string => uuid.v4();
@@ -105,69 +99,86 @@ const Attributes = React.memo(({ children }) => {
);
});
-export class StatefulEvent extends React.Component {
- private _isMounted: boolean = false;
+export const StatefulEvent = React.memo(
+ ({
+ actionsColumnWidth,
+ addNoteToEvent,
+ browserFields,
+ columnHeaders,
+ columnRenderers,
+ event,
+ eventIdToNoteIds,
+ getNotesByIds,
+ isEventViewer = false,
+ maxDelay = 0,
+ onColumnResized,
+ onPinEvent,
+ onUnPinEvent,
+ onUpdateColumns,
+ pinnedEventIds,
+ rowRenderers,
+ timelineId,
+ toggleColumn,
+ updateNote,
+ }) => {
+ const [expanded, setExpanded] = useState<{ [eventId: string]: boolean }>({});
+ const [initialRender, setInitialRender] = useState(false);
+ const [showNotes, setShowNotes] = useState<{ [eventId: string]: boolean }>({});
- public readonly state: State = {
- expanded: {},
- showNotes: {},
- initialRender: false,
- };
+ const divElement = useRef(null);
- public divElement: HTMLDivElement | null = null;
+ const onToggleShowNotes = (eventId: string): (() => void) => () => {
+ setShowNotes({ ...showNotes, [eventId]: !showNotes[eventId] });
+ };
- /**
- * Incrementally loads the events when it mounts by trying to
- * see if it resides within a window frame and if it is it will
- * indicate to React that it should render its self by setting
- * its initialRender to true.
- */
- public componentDidMount() {
- this._isMounted = true;
+ const onToggleExpanded = (eventId: string): (() => void) => () => {
+ setExpanded({
+ ...expanded,
+ [eventId]: !expanded[eventId],
+ });
+ };
- requestIdleCallbackViaScheduler(
- () => {
- if (!this.state.initialRender && this._isMounted) {
- this.setState({ initialRender: true });
- }
- },
- { timeout: this.props.maxDelay ? this.props.maxDelay : 0 }
- );
- }
+ const associateNote = (
+ eventId: string,
+ addNoteToEventChild: AddNoteToEvent,
+ onPinEventChild: OnPinEvent
+ ): ((noteId: string) => void) => (noteId: string) => {
+ addNoteToEventChild({ eventId, noteId });
+ if (!eventIsPinned({ eventId, pinnedEventIds })) {
+ onPinEventChild(eventId); // pin the event, because it has notes
+ }
+ };
- componentWillUnmount() {
- this._isMounted = false;
- }
+ /**
+ * Incrementally loads the events when it mounts by trying to
+ * see if it resides within a window frame and if it is it will
+ * indicate to React that it should render its self by setting
+ * its initialRender to true.
+ */
- public render() {
- const {
- actionsColumnWidth,
- addNoteToEvent,
- browserFields,
- columnHeaders,
- columnRenderers,
- event,
- eventIdToNoteIds,
- getNotesByIds,
- isEventViewer = false,
- onColumnResized,
- onPinEvent,
- onUpdateColumns,
- onUnPinEvent,
- pinnedEventIds,
- rowRenderers,
- timelineId,
- toggleColumn,
- updateNote,
- } = this.props;
+ useEffect(() => {
+ let _isMounted = true;
+
+ requestIdleCallbackViaScheduler(
+ () => {
+ if (!initialRender && _isMounted) {
+ setInitialRender(true);
+ }
+ },
+ { timeout: maxDelay }
+ );
+ return () => {
+ _isMounted = false;
+ };
+ }, []);
// Number of current columns plus one for actions.
const columnCount = columnHeaders.length + 1;
// If we are not ready to render yet, just return null
- // see componentDidMount() for when it schedules the first
+ // see useEffect() for when it schedules the first
// time this stateful component should be rendered.
- if (!this.state.initialRender) {
+ if (!initialRender) {
return ;
}
@@ -184,7 +195,7 @@ export class StatefulEvent extends React.Component {
sourceId="default"
indexName={event._index!}
eventId={event._id}
- executeQuery={!!this.state.expanded[event._id]}
+ executeQuery={!!expanded[event._id]}
>
{({ detailsData, loading }) => (
{
data-test-subj="event"
innerRef={c => {
if (c != null) {
- this.divElement = c;
+ divElement.current = c;
}
}}
>
@@ -201,26 +212,26 @@ export class StatefulEvent extends React.Component {
data: event.ecs,
children: (
),
@@ -231,9 +242,9 @@ export class StatefulEvent extends React.Component {
{
} else {
// Height place holder for visibility detection as well as re-rendering sections.
const height =
- this.divElement != null ? this.divElement.clientHeight + 'px' : DEFAULT_ROW_HEIGHT;
+ divElement.current != null
+ ? `${divElement.current.clientHeight}px`
+ : DEFAULT_ROW_HEIGHT;
// height is being inlined directly in here because of performance with StyledComponents
// involving quick and constant changes to the DOM.
@@ -257,33 +270,6 @@ export class StatefulEvent extends React.Component {
);
}
+);
- private onToggleShowNotes = (eventId: string): (() => void) => () => {
- this.setState(state => ({
- showNotes: {
- ...state.showNotes,
- [eventId]: !state.showNotes[eventId],
- },
- }));
- };
-
- private onToggleExpanded = (eventId: string): (() => void) => () => {
- this.setState(state => ({
- expanded: {
- ...state.expanded,
- [eventId]: !state.expanded[eventId],
- },
- }));
- };
-
- private associateNote = (
- eventId: string,
- addNoteToEvent: AddNoteToEvent,
- onPinEvent: OnPinEvent
- ): ((noteId: string) => void) => (noteId: string) => {
- addNoteToEvent({ eventId, noteId });
- if (!eventIsPinned({ eventId, pinnedEventIds: this.props.pinnedEventIds })) {
- onPinEvent(eventId); // pin the event, because it has notes
- }
- };
-}
+StatefulEvent.displayName = 'StatefulEvent';
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx
index 871a60d18404a..d93446b2af95b 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx
@@ -84,8 +84,10 @@ type StatefulBodyComponentProps = OwnProps & ReduxProps & DispatchProps;
export const emptyColumnHeaders: ColumnHeader[] = [];
-class StatefulBodyComponent extends React.Component {
- public shouldComponentUpdate({
+const StatefulBodyComponent = React.memo(
+ ({
+ addNoteToEvent,
+ applyDeltaToColumnWidth,
browserFields,
columnHeaders,
data,
@@ -93,45 +95,46 @@ class StatefulBodyComponent extends React.Component
getNotesByIds,
height,
id,
- isEventViewer,
+ isEventViewer = false,
+ pinEvent,
pinnedEventIds,
range,
+ removeColumn,
sort,
- }: StatefulBodyComponentProps) {
- return (
- browserFields !== this.props.browserFields ||
- columnHeaders !== this.props.columnHeaders ||
- data !== this.props.data ||
- eventIdToNoteIds !== this.props.eventIdToNoteIds ||
- getNotesByIds !== this.props.getNotesByIds ||
- height !== this.props.height ||
- id !== this.props.id ||
- isEventViewer !== this.props.isEventViewer ||
- pinnedEventIds !== this.props.pinnedEventIds ||
- range !== this.props.range ||
- sort !== this.props.sort
- );
- }
+ toggleColumn,
+ unPinEvent,
+ updateColumns,
+ updateNote,
+ updateSort,
+ }) => {
+ const onAddNoteToEvent: AddNoteToEvent = ({
+ eventId,
+ noteId,
+ }: {
+ eventId: string;
+ noteId: string;
+ }) => addNoteToEvent!({ id, eventId, noteId });
+
+ const onColumnSorted: OnColumnSorted = sorted => {
+ updateSort!({ id, sort: sorted });
+ };
- public render() {
- const {
- browserFields,
- columnHeaders,
- data,
- eventIdToNoteIds,
- getNotesByIds,
- height,
- id,
- isEventViewer = false,
- pinnedEventIds,
- range,
- sort,
- toggleColumn,
- } = this.props;
+ const onColumnRemoved: OnColumnRemoved = columnId => removeColumn!({ id, columnId });
+
+ const onColumnResized: OnColumnResized = ({ columnId, delta }) =>
+ applyDeltaToColumnWidth!({ id, columnId, delta });
+
+ const onPinEvent: OnPinEvent = eventId => pinEvent!({ id, eventId });
+
+ const onUnPinEvent: OnUnPinEvent = eventId => unPinEvent!({ id, eventId });
+
+ const onUpdateNote: UpdateNote = (note: Note) => updateNote!({ note });
+
+ const onUpdateColumns: OnUpdateColumns = columns => updateColumns!({ id, columns });
return (
height={height}
id={id}
isEventViewer={isEventViewer}
- onColumnResized={this.onColumnResized}
- onColumnRemoved={this.onColumnRemoved}
- onColumnSorted={this.onColumnSorted}
+ onColumnRemoved={onColumnRemoved}
+ onColumnResized={onColumnResized}
+ onColumnSorted={onColumnSorted}
onFilterChange={noop} // TODO: this is the callback for column filters, which is out scope for this phase of delivery
- onPinEvent={this.onPinEvent}
- onUpdateColumns={this.onUpdateColumns}
- onUnPinEvent={this.onUnPinEvent}
+ onPinEvent={onPinEvent}
+ onUnPinEvent={onUnPinEvent}
+ onUpdateColumns={onUpdateColumns}
pinnedEventIds={pinnedEventIds}
range={range!}
rowRenderers={rowRenderers}
sort={sort}
toggleColumn={toggleColumn}
- updateNote={this.onUpdateNote}
+ updateNote={onUpdateNote}
/>
);
+ },
+ (prevProps, nextProps) => {
+ return (
+ prevProps.browserFields === nextProps.browserFields &&
+ prevProps.columnHeaders === nextProps.columnHeaders &&
+ prevProps.data === nextProps.data &&
+ prevProps.eventIdToNoteIds === nextProps.eventIdToNoteIds &&
+ prevProps.getNotesByIds === nextProps.getNotesByIds &&
+ prevProps.height === nextProps.height &&
+ prevProps.id === nextProps.id &&
+ prevProps.isEventViewer === nextProps.isEventViewer &&
+ prevProps.pinnedEventIds === nextProps.pinnedEventIds &&
+ prevProps.range === nextProps.range &&
+ prevProps.sort === nextProps.sort
+ );
}
+);
- private onAddNoteToEvent: AddNoteToEvent = ({
- eventId,
- noteId,
- }: {
- eventId: string;
- noteId: string;
- }) => this.props.addNoteToEvent!({ id: this.props.id, eventId, noteId });
-
- private onColumnSorted: OnColumnSorted = sorted => {
- this.props.updateSort!({ id: this.props.id, sort: sorted });
- };
-
- private onColumnRemoved: OnColumnRemoved = columnId =>
- this.props.removeColumn!({ id: this.props.id, columnId });
-
- private onColumnResized: OnColumnResized = ({ columnId, delta }) =>
- this.props.applyDeltaToColumnWidth!({ id: this.props.id, columnId, delta });
-
- private onPinEvent: OnPinEvent = eventId => this.props.pinEvent!({ id: this.props.id, eventId });
-
- private onUnPinEvent: OnUnPinEvent = eventId =>
- this.props.unPinEvent!({ id: this.props.id, eventId });
-
- private onUpdateNote: UpdateNote = (note: Note) => this.props.updateNote!({ note });
-
- private onUpdateColumns: OnUpdateColumns = columns =>
- this.props.updateColumns!({ id: this.props.id, columns });
-}
+StatefulBodyComponent.displayName = 'StatefulBodyComponent';
const makeMapStateToProps = () => {
const memoizedColumnHeaders: (
@@ -201,9 +193,9 @@ const makeMapStateToProps = () => {
return {
columnHeaders: memoizedColumnHeaders(columns, browserFields),
- id,
eventIdToNoteIds,
getNotesByIds: getNotesByIds(state),
+ id,
pinnedEventIds,
};
};
@@ -215,12 +207,12 @@ export const StatefulBody = connect(
{
addNoteToEvent: timelineActions.addNoteToEvent,
applyDeltaToColumnWidth: timelineActions.applyDeltaToColumnWidth,
- unPinEvent: timelineActions.unPinEvent,
- updateColumns: timelineActions.updateColumns,
- updateSort: timelineActions.updateSort,
pinEvent: timelineActions.pinEvent,
removeColumn: timelineActions.removeColumn,
removeProvider: timelineActions.removeProvider,
+ unPinEvent: timelineActions.unPinEvent,
+ updateColumns: timelineActions.updateColumns,
updateNote: appActions.updateNote,
+ updateSort: timelineActions.updateSort,
}
)(StatefulBodyComponent);
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_badge.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_badge.tsx
index 29417bd0b578b..98cf0a78b1d1f 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_badge.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_badge.tsx
@@ -5,7 +5,7 @@
*/
import { noop } from 'lodash/fp';
-import React, { PureComponent } from 'react';
+import React, { useState } from 'react';
import { BrowserFields } from '../../../containers/source';
@@ -32,30 +32,42 @@ interface ProviderItemBadgeProps {
val: string | number;
}
-interface OwnState {
- isPopoverOpen: boolean;
-}
+export const ProviderItemBadge = React.memo(
+ ({
+ andProviderId,
+ browserFields,
+ deleteProvider,
+ field,
+ kqlQuery,
+ isEnabled,
+ isExcluded,
+ onDataProviderEdited,
+ operator,
+ providerId,
+ timelineId,
+ toggleEnabledProvider,
+ toggleExcludedProvider,
+ val,
+ }) => {
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+
+ function togglePopover() {
+ setIsPopoverOpen(!isPopoverOpen);
+ }
-export class ProviderItemBadge extends PureComponent {
- public readonly state = {
- isPopoverOpen: false,
- };
+ function closePopover() {
+ setIsPopoverOpen(false);
+ }
- public render() {
- const {
- andProviderId,
- browserFields,
- deleteProvider,
- field,
- kqlQuery,
- isEnabled,
- isExcluded,
- onDataProviderEdited,
- operator,
- providerId,
- timelineId,
- val,
- } = this.props;
+ function onToggleEnabledProvider() {
+ toggleEnabledProvider();
+ closePopover();
+ }
+
+ function onToggleExcludedProvider() {
+ toggleExcludedProvider();
+ closePopover();
+ }
return (
@@ -71,51 +83,31 @@ export class ProviderItemBadge extends PureComponent
}
- closePopover={this.closePopover}
+ closePopover={closePopover}
deleteProvider={deleteProvider}
field={field}
kqlQuery={kqlQuery}
isEnabled={isEnabled}
isExcluded={isExcluded}
isLoading={isLoading}
- isOpen={this.state.isPopoverOpen}
+ isOpen={isPopoverOpen}
onDataProviderEdited={onDataProviderEdited}
operator={operator}
providerId={providerId}
timelineId={timelineId}
- toggleEnabledProvider={this.toggleEnabledProvider}
- toggleExcludedProvider={this.toggleExcludedProvider}
+ toggleEnabledProvider={onToggleEnabledProvider}
+ toggleExcludedProvider={onToggleExcludedProvider}
value={val}
/>
)}
);
}
+);
- private togglePopover = () => {
- this.setState(prevState => ({
- isPopoverOpen: !prevState.isPopoverOpen,
- }));
- };
-
- private closePopover = () => {
- this.setState({
- isPopoverOpen: false,
- });
- };
-
- private toggleEnabledProvider = () => {
- this.props.toggleEnabledProvider();
- this.closePopover();
- };
-
- private toggleExcludedProvider = () => {
- this.props.toggleExcludedProvider();
- this.closePopover();
- };
-}
+ProviderItemBadge.displayName = 'ProviderItemBadge';
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx
index 79f85103077b7..6e8a0e8cfb17f 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx
@@ -11,7 +11,7 @@ import * as React from 'react';
import { TestProviders } from '../../../mock/test_providers';
-import { Footer } from './index';
+import { Footer, PagingControl } from './index';
import { mockData } from './mock';
describe('Footer Timeline Component', () => {
@@ -93,38 +93,36 @@ describe('Footer Timeline Component', () => {
});
test('it renders the Loading... in the more load button when fetching new data', () => {
- const wrapper = mount(
-
-
-
+ const wrapper = shallow(
+
);
- wrapper
- .find(Footer)
- .instance()
- .setState({ paginationLoading: true });
- wrapper.update();
+
+ const loadButton = wrapper
+ .find('[data-test-subj="TimelineMoreButton"]')
+ .dive()
+ .text();
expect(wrapper.find('[data-test-subj="LoadingPanelTimeline"]').exists()).toBeFalsy();
- expect(
- wrapper
- .find('[data-test-subj="TimelineMoreButton"]')
- .first()
- .text()
- ).toContain('Loading...');
+ expect(loadButton).toContain('Loading...');
+ });
+
+ test('it renders the Load More in the more load button when fetching new data', () => {
+ const wrapper = shallow(
+
+ );
+
+ const loadButton = wrapper
+ .find('[data-test-subj="TimelineMoreButton"]')
+ .dive()
+ .text();
+ expect(loadButton).toContain('Load More');
});
test('it does NOT render the loadMore button because there is nothing else to fetch', () => {
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx
index 104c8c9c03ddf..c1772d9e55577 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx
@@ -18,7 +18,7 @@ import {
EuiToolTip,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-import * as React from 'react';
+import React, { useEffect, useState } from 'react';
import { pure } from 'recompose';
import styled from 'styled-components';
@@ -73,27 +73,21 @@ ServerSideEventCount.displayName = 'ServerSideEventCount';
export const footerHeight = 40; // px
interface FooterProps {
- itemsCount: number;
+ compact: boolean;
+ getUpdatedAt: () => number;
+ hasNextPage: boolean;
+ height: number;
isEventViewer?: boolean;
isLive: boolean;
isLoading: boolean;
+ itemsCount: number;
itemsPerPage: number;
itemsPerPageOptions: number[];
- hasNextPage: boolean;
- height: number;
nextCursor: string;
onChangeItemsPerPage: OnChangeItemsPerPage;
onLoadMore: OnLoadMore;
serverSideEventCount: number;
tieBreaker: string;
- getUpdatedAt: () => number;
- compact: boolean;
-}
-
-interface FooterState {
- isPopoverOpen: boolean;
- paginationLoading: boolean;
- updatedAt: number | null;
}
/** Displays the server-side count of events */
@@ -144,7 +138,7 @@ export const EventsCount = pure<{
EventsCount.displayName = 'EventsCount';
-export const PagingControl = pure<{
+export const PagingControl = React.memo<{
hasNextPage: boolean;
isLoading: boolean;
loadMore: () => void;
@@ -166,81 +160,49 @@ export const PagingControl = pure<{
PagingControl.displayName = 'PagingControl';
/** Renders a loading indicator and paging controls */
-export class Footer extends React.Component {
- public readonly state = {
- isPopoverOpen: false,
- paginationLoading: false,
- updatedAt: null,
- };
-
- public shouldComponentUpdate(
- {
- compact,
- hasNextPage,
- height,
- isEventViewer,
- isLive,
- isLoading,
- itemsCount,
- itemsPerPage,
- itemsPerPageOptions,
- serverSideEventCount,
- }: FooterProps,
- { isPopoverOpen, paginationLoading, updatedAt }: FooterState
- ) {
- return (
- compact !== this.props.compact ||
- hasNextPage !== this.props.hasNextPage ||
- height !== this.props.height ||
- isEventViewer !== this.props.isEventViewer ||
- isLive !== this.props.isLive ||
- isLoading !== this.props.isLoading ||
- isPopoverOpen !== this.state.isPopoverOpen ||
- itemsCount !== this.props.itemsCount ||
- itemsPerPage !== this.props.itemsPerPage ||
- itemsPerPageOptions !== this.props.itemsPerPageOptions ||
- paginationLoading !== this.state.paginationLoading ||
- serverSideEventCount !== this.props.serverSideEventCount ||
- updatedAt !== this.state.updatedAt
- );
- }
-
- public componentDidUpdate(prevProps: FooterProps) {
- const { paginationLoading, updatedAt } = this.state;
- const { isLoading, getUpdatedAt } = this.props;
- if (paginationLoading && prevProps.isLoading && !isLoading) {
- this.setState(prevState => ({
- ...prevState,
- paginationLoading: false,
- updatedAt: getUpdatedAt(),
- }));
- }
+export const Footer = React.memo(
+ ({
+ compact,
+ getUpdatedAt,
+ hasNextPage,
+ height,
+ isEventViewer,
+ isLive,
+ isLoading,
+ itemsCount,
+ itemsPerPage,
+ itemsPerPageOptions,
+ nextCursor,
+ onChangeItemsPerPage,
+ onLoadMore,
+ serverSideEventCount,
+ tieBreaker,
+ }) => {
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+ const [paginationLoading, setPaginationLoading] = useState(false);
+ const [updatedAt, setUpdatedAt] = useState(null);
+
+ const loadMore = () => {
+ setPaginationLoading(true);
+ onLoadMore(nextCursor, tieBreaker);
+ };
+
+ const onButtonClick = () => setIsPopoverOpen(!isPopoverOpen);
+
+ const closePopover = () => setIsPopoverOpen(false);
+
+ useEffect(() => {
+ if (paginationLoading && !isLoading) {
+ setPaginationLoading(false);
+ setUpdatedAt(getUpdatedAt());
+ }
- if (updatedAt === null || (prevProps.isLoading && !isLoading)) {
- this.setState(prevState => ({
- ...prevState,
- updatedAt: getUpdatedAt(),
- }));
- }
- }
+ if (updatedAt === null || !isLoading) {
+ setUpdatedAt(getUpdatedAt());
+ }
+ }, [isLoading]);
- public render() {
- const {
- height,
- isEventViewer,
- isLive,
- isLoading,
- itemsCount,
- itemsPerPage,
- itemsPerPageOptions,
- onChangeItemsPerPage,
- serverSideEventCount,
- hasNextPage,
- getUpdatedAt,
- compact,
- } = this.props;
-
- if (isLoading && !this.state.paginationLoading) {
+ if (isLoading && !paginationLoading) {
return (
{
key={item}
icon={itemsPerPage === item ? 'check' : 'empty'}
onClick={() => {
- this.closePopover();
+ closePopover();
onChangeItemsPerPage(item);
}}
>
{`${item} ${i18n.ROWS}`}
));
+
return (
<>
{
gutterSize="none"
>
@@ -327,44 +290,35 @@ export class Footer extends React.Component {
data-test-subj="paging-control"
hasNextPage={hasNextPage}
isLoading={isLoading}
- loadMore={this.loadMore}
+ loadMore={loadMore}
/>
)}
-
+
>
);
+ },
+ (prevProps, nextProps) => {
+ return (
+ prevProps.compact === nextProps.compact &&
+ prevProps.hasNextPage === nextProps.hasNextPage &&
+ prevProps.height === nextProps.height &&
+ prevProps.isEventViewer === nextProps.isEventViewer &&
+ prevProps.isLive === nextProps.isLive &&
+ prevProps.isLoading === nextProps.isLoading &&
+ prevProps.itemsCount === nextProps.itemsCount &&
+ prevProps.itemsPerPage === nextProps.itemsPerPage &&
+ prevProps.itemsPerPageOptions === nextProps.itemsPerPageOptions &&
+ prevProps.serverSideEventCount === nextProps.serverSideEventCount
+ );
}
+);
- private loadMore = () => {
- this.setState(prevState => ({
- ...prevState,
- paginationLoading: true,
- }));
- this.props.onLoadMore(this.props.nextCursor, this.props.tieBreaker);
- };
-
- private onButtonClick = () => {
- this.setState(prevState => ({
- ...prevState,
- isPopoverOpen: !prevState.isPopoverOpen,
- }));
- };
-
- private closePopover = () => {
- this.setState(prevState => ({
- ...prevState,
- isPopoverOpen: false,
- }));
- };
-}
+Footer.displayName = 'Footer';
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/footer/last_updated.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/footer/last_updated.tsx
index 0953341fe8a90..a17e5a6da6331 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/last_updated.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/last_updated.tsx
@@ -6,7 +6,7 @@
import { EuiIcon, EuiText, EuiToolTip } from '@elastic/eui';
import { FormattedRelative } from '@kbn/i18n/react';
-import * as React from 'react';
+import React, { useEffect, useState } from 'react';
import { pure } from 'recompose';
import * as i18n from './translations';
@@ -16,10 +16,6 @@ interface LastUpdatedAtProps {
updatedAt: number;
}
-interface LastUpdatedAtState {
- date: number;
-}
-
export const Updated = pure<{ date: number; prefix: string; updatedAt: number }>(
({ date, prefix, updatedAt }) => (
<>
@@ -37,46 +33,37 @@ export const Updated = pure<{ date: number; prefix: string; updatedAt: number }>
Updated.displayName = 'Updated';
-export class LastUpdatedAt extends React.PureComponent {
- public readonly state = {
- date: Date.now(),
- };
- private timerID?: NodeJS.Timeout;
+const prefix = ` ${i18n.UPDATED} `;
- public componentDidMount() {
- this.timerID = setInterval(() => this.tick(), 10000);
- }
+export const LastUpdatedAt = React.memo(({ compact = false, updatedAt }) => {
+ const [date, setDate] = useState(Date.now());
- public componentWillUnmount() {
- clearInterval(this.timerID!);
+ function tick() {
+ setDate(Date.now());
}
- public tick() {
- this.setState({
- date: Date.now(),
- });
- }
+ useEffect(() => {
+ const timerID = setInterval(() => tick(), 10000);
+ return () => {
+ clearInterval(timerID);
+ };
+ }, []);
- public render() {
- const { compact = false } = this.props;
- const prefix = ` ${i18n.UPDATED} `;
+ return (
+
+
+ >
+ }
+ >
+
+
+ {!compact ? : null}
+
+
+ );
+});
- return (
-
-
- >
- }
- >
-
-
- {!compact ? (
-
- ) : null}
-
-
- );
- }
-}
+LastUpdatedAt.displayName = 'LastUpdatedAt';
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx
index 153ca2abd24d1..ab92f22a4c89f 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx
@@ -5,7 +5,7 @@
*/
import { isEqual } from 'lodash/fp';
-import * as React from 'react';
+import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { ActionCreator } from 'typescript-fsa';
@@ -131,70 +131,113 @@ interface DispatchProps {
type Props = OwnProps & StateReduxProps & DispatchProps;
-class StatefulTimelineComponent extends React.Component {
- public shouldComponentUpdate = ({
- id,
- flyoutHeaderHeight,
- flyoutHeight,
- activePage,
+const StatefulTimelineComponent = React.memo(
+ ({
columns,
+ createTimeline,
dataProviders,
end,
+ flyoutHeaderHeight,
+ flyoutHeight,
+ id,
isLive,
itemsPerPage,
itemsPerPageOptions,
kqlMode,
kqlQueryExpression,
- pageCount,
- sort,
- start,
+ onDataProviderEdited,
+ removeColumn,
+ removeProvider,
show,
showCallOutUnauthorizedMsg,
- }: Props) =>
- id !== this.props.id ||
- flyoutHeaderHeight !== this.props.flyoutHeaderHeight ||
- flyoutHeight !== this.props.flyoutHeight ||
- activePage !== this.props.activePage ||
- !isEqual(columns, this.props.columns) ||
- !isEqual(dataProviders, this.props.dataProviders) ||
- end !== this.props.end ||
- isLive !== this.props.isLive ||
- itemsPerPage !== this.props.itemsPerPage ||
- !isEqual(itemsPerPageOptions, this.props.itemsPerPageOptions) ||
- kqlMode !== this.props.kqlMode ||
- kqlQueryExpression !== this.props.kqlQueryExpression ||
- pageCount !== this.props.pageCount ||
- !isEqual(sort, this.props.sort) ||
- start !== this.props.start ||
- show !== this.props.show ||
- showCallOutUnauthorizedMsg !== this.props.showCallOutUnauthorizedMsg;
+ sort,
+ start,
+ updateDataProviderEnabled,
+ updateDataProviderExcluded,
+ updateDataProviderKqlQuery,
+ updateHighlightedDropAndProviderId,
+ updateItemsPerPage,
+ upsertColumn,
+ }) => {
+ const onDataProviderRemoved: OnDataProviderRemoved = (
+ providerId: string,
+ andProviderId?: string
+ ) => removeProvider!({ id, providerId, andProviderId });
- public componentDidMount() {
- const { createTimeline, id } = this.props;
+ const onToggleDataProviderEnabled: OnToggleDataProviderEnabled = ({
+ providerId,
+ enabled,
+ andProviderId,
+ }) =>
+ updateDataProviderEnabled!({
+ id,
+ enabled,
+ providerId,
+ andProviderId,
+ });
- if (createTimeline != null) {
- createTimeline({ id, columns: defaultHeaders, show: false });
- }
- }
+ const onToggleDataProviderExcluded: OnToggleDataProviderExcluded = ({
+ providerId,
+ excluded,
+ andProviderId,
+ }) =>
+ updateDataProviderExcluded!({
+ id,
+ excluded,
+ providerId,
+ andProviderId,
+ });
- public render() {
- const {
- columns,
- dataProviders,
- end,
- flyoutHeight,
- flyoutHeaderHeight,
- id,
- isLive,
- itemsPerPage,
- itemsPerPageOptions,
- kqlMode,
- kqlQueryExpression,
- show,
- showCallOutUnauthorizedMsg,
- start,
- sort,
- } = this.props;
+ const onDataProviderEditedLocal: OnDataProviderEdited = ({
+ andProviderId,
+ excluded,
+ field,
+ operator,
+ providerId,
+ value,
+ }) =>
+ onDataProviderEdited!({
+ andProviderId,
+ excluded,
+ field,
+ id,
+ operator,
+ providerId,
+ value,
+ });
+ const onChangeDataProviderKqlQuery: OnChangeDataProviderKqlQuery = ({ providerId, kqlQuery }) =>
+ updateDataProviderKqlQuery!({ id, kqlQuery, providerId });
+
+ const onChangeItemsPerPage: OnChangeItemsPerPage = itemsChangedPerPage =>
+ updateItemsPerPage!({ id, itemsPerPage: itemsChangedPerPage });
+
+ const onChangeDroppableAndProvider: OnChangeDroppableAndProvider = providerId =>
+ updateHighlightedDropAndProviderId!({ id, providerId });
+
+ const toggleColumn = (column: ColumnHeader) => {
+ const exists = columns.findIndex(c => c.id === column.id) !== -1;
+
+ if (!exists && upsertColumn != null) {
+ upsertColumn({
+ column,
+ id,
+ index: 1,
+ });
+ }
+
+ if (exists && removeColumn != null) {
+ removeColumn({
+ columnId: column.id,
+ id,
+ });
+ }
+ };
+
+ useEffect(() => {
+ if (createTimeline != null) {
+ createTimeline({ id, columns: defaultHeaders, show: false });
+ }
+ }, []);
return (
@@ -202,111 +245,58 @@ class StatefulTimelineComponent extends React.Component {
)}
);
+ },
+ (prevProps, nextProps) => {
+ return (
+ prevProps.activePage === nextProps.activePage &&
+ prevProps.end === nextProps.end &&
+ prevProps.flyoutHeaderHeight === nextProps.flyoutHeaderHeight &&
+ prevProps.flyoutHeight === nextProps.flyoutHeight &&
+ prevProps.id === nextProps.id &&
+ prevProps.isLive === nextProps.isLive &&
+ prevProps.itemsPerPage === nextProps.itemsPerPage &&
+ prevProps.kqlMode === nextProps.kqlMode &&
+ prevProps.kqlQueryExpression === nextProps.kqlQueryExpression &&
+ prevProps.pageCount === nextProps.pageCount &&
+ prevProps.show === nextProps.show &&
+ prevProps.showCallOutUnauthorizedMsg === nextProps.showCallOutUnauthorizedMsg &&
+ prevProps.start === nextProps.start &&
+ isEqual(prevProps.columns, nextProps.columns) &&
+ isEqual(prevProps.dataProviders, nextProps.dataProviders) &&
+ isEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions) &&
+ isEqual(prevProps.sort, nextProps.sort)
+ );
}
+);
- private onDataProviderRemoved: OnDataProviderRemoved = (
- providerId: string,
- andProviderId?: string
- ) => this.props.removeProvider!({ id: this.props.id, providerId, andProviderId });
-
- private onToggleDataProviderEnabled: OnToggleDataProviderEnabled = ({
- providerId,
- enabled,
- andProviderId,
- }) =>
- this.props.updateDataProviderEnabled!({
- id: this.props.id,
- enabled,
- providerId,
- andProviderId,
- });
-
- private onToggleDataProviderExcluded: OnToggleDataProviderExcluded = ({
- providerId,
- excluded,
- andProviderId,
- }) =>
- this.props.updateDataProviderExcluded!({
- id: this.props.id,
- excluded,
- providerId,
- andProviderId,
- });
-
- private onDataProviderEdited: OnDataProviderEdited = ({
- andProviderId,
- excluded,
- field,
- operator,
- providerId,
- value,
- }) =>
- this.props.onDataProviderEdited!({
- andProviderId,
- excluded,
- field,
- id: this.props.id,
- operator,
- providerId,
- value,
- });
-
- private onChangeDataProviderKqlQuery: OnChangeDataProviderKqlQuery = ({ providerId, kqlQuery }) =>
- this.props.updateDataProviderKqlQuery!({ id: this.props.id, kqlQuery, providerId });
-
- private onChangeItemsPerPage: OnChangeItemsPerPage = itemsChangedPerPage =>
- this.props.updateItemsPerPage!({ id: this.props.id, itemsPerPage: itemsChangedPerPage });
-
- private onChangeDroppableAndProvider: OnChangeDroppableAndProvider = providerId =>
- this.props.updateHighlightedDropAndProviderId!({ id: this.props.id, providerId });
-
- private toggleColumn = (column: ColumnHeader) => {
- const { columns, removeColumn, id, upsertColumn } = this.props;
- const exists = columns.findIndex(c => c.id === column.id) !== -1;
-
- if (!exists && upsertColumn != null) {
- upsertColumn({
- column,
- id,
- index: 1,
- });
- }
-
- if (exists && removeColumn != null) {
- removeColumn({
- columnId: column.id,
- id,
- });
- }
- };
-}
+StatefulTimelineComponent.displayName = 'StatefulTimelineComponent';
const makeMapStateToProps = () => {
const getShowCallOutUnauthorizedMsg = timelineSelectors.getShowCallOutUnauthorizedMsg();
@@ -322,8 +312,8 @@ const makeMapStateToProps = () => {
itemsPerPage,
itemsPerPageOptions,
kqlMode,
- sort,
show,
+ sort,
} = timeline;
const kqlQueryExpression = getKqlQueryTimeline(state, id);
@@ -337,10 +327,10 @@ const makeMapStateToProps = () => {
itemsPerPageOptions,
kqlMode,
kqlQueryExpression,
+ show,
+ showCallOutUnauthorizedMsg: getShowCallOutUnauthorizedMsg(state),
sort,
start: input.timerange.from,
- showCallOutUnauthorizedMsg: getShowCallOutUnauthorizedMsg(state),
- show,
};
};
return mapStateToProps;
@@ -352,6 +342,8 @@ export const StatefulTimeline = connect(
addProvider: timelineActions.addProvider,
createTimeline: timelineActions.createTimeline,
onDataProviderEdited: timelineActions.dataProviderEdited,
+ removeColumn: timelineActions.removeColumn,
+ removeProvider: timelineActions.removeProvider,
updateColumns: timelineActions.updateColumns,
updateDataProviderEnabled: timelineActions.updateDataProviderEnabled,
updateDataProviderExcluded: timelineActions.updateDataProviderExcluded,
@@ -360,8 +352,6 @@ export const StatefulTimeline = connect(
updateItemsPerPage: timelineActions.updateItemsPerPage,
updateItemsPerPageOptions: timelineActions.updateItemsPerPageOptions,
updateSort: timelineActions.updateSort,
- removeProvider: timelineActions.removeProvider,
- removeColumn: timelineActions.removeColumn,
upsertColumn: timelineActions.upsertColumn,
}
)(StatefulTimelineComponent);
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx
index b5daad42f7c3a..b983963c34f55 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx
@@ -5,7 +5,7 @@
*/
import { EuiAvatar, EuiFlexItem, EuiIcon } from '@elastic/eui';
-import * as React from 'react';
+import React, { useState } from 'react';
import styled, { injectGlobal } from 'styled-components';
import { Note } from '../../../lib/note';
@@ -62,28 +62,23 @@ HiddenFlexItem.displayName = 'HiddenFlexItem';
interface Props {
associateNote: AssociateNote;
createTimeline: CreateTimeline;
+ description: string;
+ getNotesByIds: (noteIds: string[]) => Note[];
isDataInTimeline: boolean;
isDatepickerLocked: boolean;
isFavorite: boolean;
- title: string;
- description: string;
- getNotesByIds: (noteIds: string[]) => Note[];
noteIds: string[];
timelineId: string;
+ title: string;
toggleLock: ToggleLock;
updateDescription: UpdateDescription;
updateIsFavorite: UpdateIsFavorite;
- updateTitle: UpdateTitle;
updateNote: UpdateNote;
+ updateTitle: UpdateTitle;
usersViewing: string[];
width: number;
}
-interface State {
- showActions: boolean;
- showNotes: boolean;
-}
-
const rightGutter = 60; // px
export const datePickerThreshold = 600;
export const showNotesThreshold = 810;
@@ -96,51 +91,40 @@ const noteWidth = 130;
const settingsWidth = 50;
/** Displays the properties of a timeline, i.e. name, description, notes, etc */
-export class Properties extends React.PureComponent {
- constructor(props: Props) {
- super(props);
+export const Properties = React.memo(
+ ({
+ associateNote,
+ createTimeline,
+ description,
+ getNotesByIds,
+ isDataInTimeline,
+ isDatepickerLocked,
+ isFavorite,
+ noteIds,
+ timelineId,
+ title,
+ toggleLock,
+ updateDescription,
+ updateIsFavorite,
+ updateNote,
+ updateTitle,
+ usersViewing,
+ width,
+ }) => {
+ const [showActions, setShowActions] = useState(false);
+ const [showNotes, setShowNotes] = useState(false);
+
+ const onButtonClick = () => {
+ setShowActions(!showActions);
+ };
- this.state = {
- showActions: false,
- showNotes: false,
+ const onToggleShowNotes = () => {
+ setShowNotes(!showNotes);
};
- }
- public onButtonClick = () => {
- this.setState(prevState => ({
- showActions: !prevState.showActions,
- }));
- };
-
- public onToggleShowNotes = () => {
- this.setState(state => ({ showNotes: !state.showNotes }));
- };
-
- public onClosePopover = () => {
- this.setState({
- showActions: false,
- });
- };
-
- public render() {
- const {
- associateNote,
- createTimeline,
- description,
- getNotesByIds,
- isFavorite,
- isDataInTimeline,
- isDatepickerLocked,
- title,
- noteIds,
- timelineId,
- updateDescription,
- updateIsFavorite,
- updateTitle,
- updateNote,
- usersViewing,
- width,
- } = this.props;
+ const onClosePopover = () => {
+ setShowActions(false);
+ };
const datePickerWidth =
width -
@@ -157,52 +141,52 @@ export class Properties extends React.PureComponent {
return (
datePickerThreshold ? datePickerThreshold : datePickerWidth
+ }
+ description={description}
+ getNotesByIds={getNotesByIds}
+ isDatepickerLocked={isDatepickerLocked}
isFavorite={isFavorite}
- timelineId={timelineId}
- updateIsFavorite={updateIsFavorite}
+ noteIds={noteIds}
+ onToggleShowNotes={onToggleShowNotes}
showDescription={width >= showDescriptionThreshold}
- description={description}
+ showNotes={showNotes}
+ showNotesFromWidth={width >= showNotesThreshold}
+ timelineId={timelineId}
title={title}
- updateTitle={updateTitle}
+ toggleLock={() => {
+ toggleLock({ linkToId: 'timeline' });
+ }}
updateDescription={updateDescription}
- showNotes={this.state.showNotes}
- showNotesFromWidth={width >= showNotesThreshold}
- associateNote={associateNote}
- getNotesByIds={getNotesByIds}
- noteIds={noteIds}
- onToggleShowNotes={this.onToggleShowNotes}
+ updateIsFavorite={updateIsFavorite}
updateNote={updateNote}
- isDatepickerLocked={isDatepickerLocked}
- toggleLock={this.toggleLock}
- datePickerWidth={
- datePickerWidth > datePickerThreshold ? datePickerThreshold : datePickerWidth
- }
+ updateTitle={updateTitle}
/>
0}
- usersViewing={usersViewing}
- description={description}
+ timelineId={timelineId}
updateDescription={updateDescription}
- associateNote={associateNote}
- getNotesByIds={getNotesByIds}
- noteIds={noteIds}
- onToggleShowNotes={this.onToggleShowNotes}
updateNote={updateNote}
+ usersViewing={usersViewing}
/>
);
}
+);
- private toggleLock = () => {
- this.props.toggleLock({ linkToId: 'timeline' });
- };
-}
+Properties.displayName = 'Properties';
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/index.tsx
index 9213ff79ccc50..91113a545821d 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/index.tsx
@@ -45,19 +45,17 @@ interface DispatchProps {
type Props = OwnProps & StateReduxProps & DispatchProps;
-class StatefulSearchOrFilterComponent extends React.PureComponent {
- public render() {
- const {
- applyKqlFilterQuery,
- indexPattern,
- filterQueryDraft,
- isFilterQueryDraftValid,
- kqlMode,
- timelineId,
- setKqlFilterQueryDraft,
- updateKqlMode,
- } = this.props;
-
+const StatefulSearchOrFilterComponent = React.memo(
+ ({
+ applyKqlFilterQuery,
+ filterQueryDraft,
+ indexPattern,
+ isFilterQueryDraftValid,
+ kqlMode,
+ setKqlFilterQueryDraft,
+ timelineId,
+ updateKqlMode,
+ }) => {
const applyFilterQueryFromKueryExpression = (expression: string) =>
applyKqlFilterQuery({
id: timelineId,
@@ -86,13 +84,14 @@ class StatefulSearchOrFilterComponent extends React.PureComponent {
indexPattern={indexPattern}
isFilterQueryDraftValid={isFilterQueryDraftValid}
kqlMode={kqlMode!}
+ setKqlFilterQueryDraft={setFilterQueryDraftFromKueryExpression!}
timelineId={timelineId}
updateKqlMode={updateKqlMode!}
- setKqlFilterQueryDraft={setFilterQueryDraftFromKueryExpression!}
/>
);
}
-}
+);
+StatefulSearchOrFilterComponent.displayName = 'StatefulSearchOrFilterComponent';
const makeMapStateToProps = () => {
const getTimeline = timelineSelectors.getTimelineByIdSelector();
@@ -101,9 +100,9 @@ const makeMapStateToProps = () => {
const mapStateToProps = (state: State, { timelineId }: OwnProps) => {
const timeline: TimelineModel | {} = getTimeline(state, timelineId);
return {
- kqlMode: getOr('filter', 'kqlMode', timeline),
filterQueryDraft: getKqlFilterQueryDraft(state, timelineId),
isFilterQueryDraftValid: isFilterQueryDraftValid(state, timelineId),
+ kqlMode: getOr('filter', 'kqlMode', timeline),
};
};
return mapStateToProps;
diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/use_url_state.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/use_url_state.tsx
index 78c3850057fc1..26826ace6fcfd 100644
--- a/x-pack/legacy/plugins/siem/public/components/url_state/use_url_state.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/url_state/use_url_state.tsx
@@ -33,7 +33,7 @@ import {
} from './types';
function usePrevious(value: PreviousLocationUrlState) {
- const ref = useRef(value);
+ const ref = useRef(value);
useEffect(() => {
ref.current = value;
});
diff --git a/x-pack/legacy/plugins/siem/public/components/with_hover_actions/index.tsx b/x-pack/legacy/plugins/siem/public/components/with_hover_actions/index.tsx
index 972ace6870d14..2569e8d303b69 100644
--- a/x-pack/legacy/plugins/siem/public/components/with_hover_actions/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/with_hover_actions/index.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import * as React from 'react';
+import React, { useState } from 'react';
import { pure } from 'recompose';
import styled from 'styled-components';
@@ -28,10 +28,6 @@ interface Props {
render: (showHoverContent: boolean) => JSX.Element;
}
-interface State {
- showHoverContent: boolean;
-}
-
const HoverActionsPanelContainer = styled.div`
color: ${props => props.theme.eui.textColors.default}
height: 100%;
@@ -67,31 +63,25 @@ WithHoverActionsContainer.displayName = 'WithHoverActionsContainer';
* component also passes `showHoverContent` as a render prop, which
* provides a signal to the content that the user is in a hover state.
*/
-export class WithHoverActions extends React.PureComponent {
- constructor(props: Props) {
- super(props);
-
- this.state = { showHoverContent: false };
- }
-
- public render() {
- const { alwaysShow = false, hoverContent, render } = this.props;
+export const WithHoverActions = React.memo(
+ ({ alwaysShow = false, hoverContent, render }) => {
+ const [showHoverContent, setShowHoverContent] = useState(false);
+ function onMouseEnter() {
+ setShowHoverContent(true);
+ }
+ function onMouseLeave() {
+ setShowHoverContent(false);
+ }
return (
-
- <>{render(this.state.showHoverContent)}>
-
+
+ <>{render(showHoverContent)}>
+
{hoverContent != null ? hoverContent : <>>}
);
}
+);
- private onMouseEnter = () => {
- this.setState({ showHoverContent: true });
- };
-
- private onMouseLeave = () => {
- this.setState({ showHoverContent: false });
- };
-}
+WithHoverActions.displayName = 'WithHoverActions';
diff --git a/x-pack/legacy/plugins/siem/public/containers/hosts/filter.tsx b/x-pack/legacy/plugins/siem/public/containers/hosts/filter.tsx
index 5a5d25e699528..4bacb2f87c458 100644
--- a/x-pack/legacy/plugins/siem/public/containers/hosts/filter.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/hosts/filter.tsx
@@ -4,9 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { isEqual } from 'lodash/fp';
+import React, { useEffect } from 'react';
import memoizeOne from 'memoize-one';
-import React from 'react';
import { connect } from 'react-redux';
import { ActionCreator } from 'typescript-fsa';
import { StaticIndexPattern } from 'ui/index_patterns';
@@ -61,80 +60,72 @@ interface HostsFilterDispatchProps {
export type HostsFilterProps = OwnProps & HostsFilterReduxProps & HostsFilterDispatchProps;
-class HostsFilterComponent extends React.PureComponent {
- private memoizedApplyFilterQueryFromKueryExpression: (expression: string) => void;
- private memoizedSetFilterQueryDraftFromKueryExpression: (expression: string) => void;
-
- constructor(props: HostsFilterProps) {
- super(props);
- this.memoizedApplyFilterQueryFromKueryExpression = memoizeOne(
- this.applyFilterQueryFromKueryExpression
- );
- this.memoizedSetFilterQueryDraftFromKueryExpression = memoizeOne(
- this.setFilterQueryDraftFromKueryExpression
- );
- }
+const HostsFilterComponent = React.memo(
+ ({
+ applyHostsFilterQuery,
+ children,
+ hostsFilterQueryDraft,
+ indexPattern,
+ isHostFilterQueryDraftValid,
+ kueryFilterQuery,
+ setHostsFilterQueryDraft,
+ setQuery,
+ type,
+ }) => {
+ const applyFilterQueryFromKueryExpression = (expression: string) =>
+ applyHostsFilterQuery({
+ filterQuery: {
+ kuery: {
+ kind: 'kuery',
+ expression,
+ },
+ serializedQuery: convertKueryToElasticSearchQuery(expression, indexPattern),
+ },
+ hostsType: type,
+ });
- public componentDidUpdate(prevProps: HostsFilterProps) {
- const { indexPattern, hostsFilterQueryDraft, kueryFilterQuery, setQuery, type } = this.props;
- if (
- setQuery &&
- (!isEqual(prevProps.hostsFilterQueryDraft, hostsFilterQueryDraft) ||
- !isEqual(prevProps.kueryFilterQuery, kueryFilterQuery) ||
- prevProps.type !== type)
- ) {
- setQuery({
- id: 'kql',
- inspect: null,
- loading: false,
- refetch: useUpdateKql({
- indexPattern,
- kueryFilterQuery,
- kueryFilterQueryDraft: hostsFilterQueryDraft,
- storeType: 'hostsType',
- type,
- }),
+ const setFilterQueryDraftFromKueryExpression = (expression: string) =>
+ setHostsFilterQueryDraft({
+ filterQueryDraft: {
+ kind: 'kuery',
+ expression,
+ },
+ hostsType: type,
});
- }
- }
- public render() {
- const { children, hostsFilterQueryDraft, isHostFilterQueryDraftValid } = this.props;
+ const memoizedApplyFilter = memoizeOne(applyFilterQueryFromKueryExpression);
+ const memoizedSetFilter = memoizeOne(setFilterQueryDraftFromKueryExpression);
+ useEffect(() => {
+ if (setQuery) {
+ setQuery({
+ id: 'kql',
+ inspect: null,
+ loading: false,
+ refetch: useUpdateKql({
+ indexPattern,
+ kueryFilterQuery,
+ kueryFilterQueryDraft: hostsFilterQueryDraft,
+ storeType: 'hostsType',
+ type,
+ }),
+ });
+ }
+ }, [hostsFilterQueryDraft, kueryFilterQuery, type]);
return (
<>
{children({
- applyFilterQueryFromKueryExpression: this.memoizedApplyFilterQueryFromKueryExpression,
+ applyFilterQueryFromKueryExpression: memoizedApplyFilter,
filterQueryDraft: hostsFilterQueryDraft,
isFilterQueryDraftValid: isHostFilterQueryDraftValid,
- setFilterQueryDraftFromKueryExpression: this
- .memoizedSetFilterQueryDraftFromKueryExpression,
+ setFilterQueryDraftFromKueryExpression: memoizedSetFilter,
})}
>
);
}
+);
- private applyFilterQueryFromKueryExpression = (expression: string) =>
- this.props.applyHostsFilterQuery({
- filterQuery: {
- kuery: {
- kind: 'kuery',
- expression,
- },
- serializedQuery: convertKueryToElasticSearchQuery(expression, this.props.indexPattern),
- },
- hostsType: this.props.type,
- });
-
- private setFilterQueryDraftFromKueryExpression = (expression: string) =>
- this.props.setHostsFilterQueryDraft({
- filterQueryDraft: {
- kind: 'kuery',
- expression,
- },
- hostsType: this.props.type,
- });
-}
+HostsFilterComponent.displayName = 'HostsFilterComponent';
const makeMapStateToProps = () => {
const getHostsFilterQueryDraft = hostsSelectors.hostsFilterQueryDraft();
diff --git a/x-pack/legacy/plugins/siem/public/containers/hosts/first_last_seen/index.ts b/x-pack/legacy/plugins/siem/public/containers/hosts/first_last_seen/index.ts
index 7d0451adcd18f..042de56fbd99d 100644
--- a/x-pack/legacy/plugins/siem/public/containers/hosts/first_last_seen/index.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/hosts/first_last_seen/index.ts
@@ -36,9 +36,9 @@ export function useFirstLastSeenHostQuery(
apolloClient: ApolloClient
) {
const [loading, updateLoading] = useState(false);
- const [firstSeen, updateFirstSeen] = useState(null);
- const [lastSeen, updateLastSeen] = useState(null);
- const [errorMessage, updateErrorMessage] = useState(null);
+ const [firstSeen, updateFirstSeen] = useState(null);
+ const [lastSeen, updateLastSeen] = useState(null);
+ const [errorMessage, updateErrorMessage] = useState(null);
async function fetchFirstLastSeenHost(signal: AbortSignal) {
updateLoading(true);
diff --git a/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx b/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx
index 9235580563a53..9cf7331441da5 100644
--- a/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
+import React, { useState } from 'react';
import { npStart } from 'ui/new_platform';
import { StaticIndexPattern } from 'ui/index_patterns';
import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public';
@@ -26,78 +26,65 @@ interface KueryAutocompletionCurrentRequest {
cursorPosition: number;
}
-interface KueryAutocompletionLifecycleState {
- // lacking cancellation support in the autocompletion api,
- // this is used to keep older, slower requests from clobbering newer ones
- currentRequest: KueryAutocompletionCurrentRequest | null;
- suggestions: AutocompleteSuggestion[];
-}
-
const getAutocompleteProvider = (language: string) =>
npStart.plugins.data.autocomplete.getProvider(language);
-export class KueryAutocompletion extends React.PureComponent<
- KueryAutocompletionLifecycleProps,
- KueryAutocompletionLifecycleState
-> {
- public readonly state: KueryAutocompletionLifecycleState = {
- currentRequest: null,
- suggestions: [],
- };
-
- public render() {
- const { currentRequest, suggestions } = this.state;
- return this.props.children({
- isLoadingSuggestions: currentRequest !== null,
- loadSuggestions: this.loadSuggestions,
- suggestions,
- });
- }
+export const KueryAutocompletion = React.memo(
+ ({ children, indexPattern }) => {
+ const [currentRequest, setCurrentRequest] = useState(
+ null
+ );
- private loadSuggestions = async (
- expression: string,
- cursorPosition: number,
- maxSuggestions?: number
- ) => {
- const { indexPattern } = this.props;
- const autocompletionProvider = getAutocompleteProvider('kuery');
- const config = {
- get: () => true,
- };
- if (!autocompletionProvider) {
- return;
- }
+ const [suggestions, setSuggestions] = useState([]);
- const getSuggestions = autocompletionProvider({
- config,
- indexPatterns: [indexPattern],
- boolFilter: [],
- });
+ const loadSuggestions = async (
+ expression: string,
+ cursorPosition: number,
+ maxSuggestions?: number
+ ) => {
+ const autocompletionProvider = getAutocompleteProvider('kuery');
+ const config = {
+ get: () => true,
+ };
+ if (!autocompletionProvider) {
+ return;
+ }
- this.setState({
- currentRequest: {
+ const getSuggestions = autocompletionProvider({
+ config,
+ indexPatterns: [indexPattern],
+ boolFilter: [],
+ });
+ const futureRequest = {
expression,
cursorPosition,
- },
- suggestions: [],
- });
+ };
+ setCurrentRequest({
+ expression,
+ cursorPosition,
+ });
+ setSuggestions([]);
+ const newSuggestions = await getSuggestions({
+ query: expression,
+ selectionStart: cursorPosition,
+ selectionEnd: cursorPosition,
+ });
+ if (
+ futureRequest &&
+ futureRequest.expression !== (currentRequest && currentRequest.expression) &&
+ futureRequest.cursorPosition !== (currentRequest && currentRequest.cursorPosition)
+ ) {
+ setCurrentRequest(null);
+ setSuggestions(maxSuggestions ? newSuggestions.slice(0, maxSuggestions) : newSuggestions);
+ }
+ };
- const suggestions = await getSuggestions({
- query: expression,
- selectionStart: cursorPosition,
- selectionEnd: cursorPosition,
+ return children({
+ isLoadingSuggestions: currentRequest !== null,
+ loadSuggestions,
+ suggestions,
});
+ }
+);
- this.setState(state =>
- state.currentRequest &&
- state.currentRequest.expression !== expression &&
- state.currentRequest.cursorPosition !== cursorPosition
- ? state // ignore this result, since a newer request is in flight
- : {
- ...state,
- currentRequest: null,
- suggestions: maxSuggestions ? suggestions.slice(0, maxSuggestions) : suggestions,
- }
- );
- };
-}
+KueryAutocompletion.displayName = 'KueryAutocompletion';
diff --git a/x-pack/legacy/plugins/siem/public/containers/network/filter.tsx b/x-pack/legacy/plugins/siem/public/containers/network/filter.tsx
index cb2fb12a0ca52..6ae2f3ef777e8 100644
--- a/x-pack/legacy/plugins/siem/public/containers/network/filter.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/network/filter.tsx
@@ -4,13 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { isEqual } from 'lodash/fp';
-import React from 'react';
+import React, { useEffect } from 'react';
+import memoizeOne from 'memoize-one';
import { connect } from 'react-redux';
import { ActionCreator } from 'typescript-fsa';
import { StaticIndexPattern } from 'ui/index_patterns';
-import memoizeOne from 'memoize-one';
import { convertKueryToElasticSearchQuery } from '../../lib/keury';
import {
KueryFilterQuery,
@@ -33,13 +32,13 @@ export interface NetworkFilterArgs {
interface OwnProps {
children: (args: NetworkFilterArgs) => React.ReactNode;
indexPattern: StaticIndexPattern;
- type: networkModel.NetworkType;
setQuery?: (params: {
id: string;
inspect: null;
loading: boolean;
refetch: inputsModel.Refetch | inputsModel.RefetchKql;
}) => void;
+ type: networkModel.NetworkType;
}
interface NetworkFilterReduxProps {
@@ -61,80 +60,71 @@ interface NetworkFilterDispatchProps {
export type NetworkFilterProps = OwnProps & NetworkFilterReduxProps & NetworkFilterDispatchProps;
-class NetworkFilterComponent extends React.PureComponent {
- private memoizedApplyFilterQueryFromKueryExpression: (expression: string) => void;
- private memoizedSetFilterQueryDraftFromKueryExpression: (expression: string) => void;
-
- constructor(props: NetworkFilterProps) {
- super(props);
- this.memoizedApplyFilterQueryFromKueryExpression = memoizeOne(
- this.applyFilterQueryFromKueryExpression
- );
- this.memoizedSetFilterQueryDraftFromKueryExpression = memoizeOne(
- this.setFilterQueryDraftFromKueryExpression
- );
- }
-
- public componentDidUpdate(prevProps: NetworkFilterProps) {
- const { indexPattern, networkFilterQueryDraft, kueryFilterQuery, setQuery, type } = this.props;
-
- if (
- setQuery &&
- (!isEqual(prevProps.networkFilterQueryDraft, networkFilterQueryDraft) ||
- !isEqual(prevProps.kueryFilterQuery, kueryFilterQuery) ||
- prevProps.type !== type)
- ) {
- setQuery({
- id: 'kql',
- inspect: null,
- loading: false,
- refetch: useUpdateKql({
- indexPattern,
- kueryFilterQuery,
- kueryFilterQueryDraft: networkFilterQueryDraft,
- storeType: 'networkType',
- type,
- }),
+const NetworkFilterComponent = React.memo(
+ ({
+ applyNetworkFilterQuery,
+ children,
+ indexPattern,
+ isNetworkFilterQueryDraftValid,
+ kueryFilterQuery,
+ networkFilterQueryDraft,
+ setNetworkFilterQueryDraft,
+ setQuery,
+ type,
+ }) => {
+ const applyFilterQueryFromKueryExpression = (expression: string) =>
+ applyNetworkFilterQuery({
+ filterQuery: {
+ kuery: {
+ kind: 'kuery',
+ expression,
+ },
+ serializedQuery: convertKueryToElasticSearchQuery(expression, indexPattern),
+ },
+ networkType: type,
});
- }
- }
-
- public render() {
- const { children, networkFilterQueryDraft, isNetworkFilterQueryDraftValid } = this.props;
+ const setFilterQueryDraftFromKueryExpression = (expression: string) =>
+ setNetworkFilterQueryDraft({
+ filterQueryDraft: {
+ kind: 'kuery',
+ expression,
+ },
+ networkType: type,
+ });
+ const memoizedApplyFilter = memoizeOne(applyFilterQueryFromKueryExpression);
+ const memoizedSetFilter = memoizeOne(setFilterQueryDraftFromKueryExpression);
+
+ useEffect(() => {
+ if (setQuery) {
+ setQuery({
+ id: 'kql',
+ inspect: null,
+ loading: false,
+ refetch: useUpdateKql({
+ indexPattern,
+ kueryFilterQuery,
+ kueryFilterQueryDraft: networkFilterQueryDraft,
+ storeType: 'networkType',
+ type,
+ }),
+ });
+ }
+ }, [networkFilterQueryDraft, kueryFilterQuery, type]);
return (
<>
{children({
- applyFilterQueryFromKueryExpression: this.memoizedApplyFilterQueryFromKueryExpression,
+ applyFilterQueryFromKueryExpression: memoizedApplyFilter,
filterQueryDraft: networkFilterQueryDraft,
isFilterQueryDraftValid: isNetworkFilterQueryDraftValid,
- setFilterQueryDraftFromKueryExpression: this
- .memoizedSetFilterQueryDraftFromKueryExpression,
+ setFilterQueryDraftFromKueryExpression: memoizedSetFilter,
})}
>
);
}
- private applyFilterQueryFromKueryExpression = (expression: string) =>
- this.props.applyNetworkFilterQuery({
- filterQuery: {
- kuery: {
- kind: 'kuery',
- expression,
- },
- serializedQuery: convertKueryToElasticSearchQuery(expression, this.props.indexPattern),
- },
- networkType: this.props.type,
- });
+);
- private setFilterQueryDraftFromKueryExpression = (expression: string) =>
- this.props.setNetworkFilterQueryDraft({
- filterQueryDraft: {
- kind: 'kuery',
- expression,
- },
- networkType: this.props.type,
- });
-}
+NetworkFilterComponent.displayName = 'NetworkFilterComponent';
const makeMapStateToProps = () => {
const getNetworkFilterQueryDraft = networkSelectors.networkFilterQueryDraft();
@@ -142,9 +132,9 @@ const makeMapStateToProps = () => {
const getNetworkKueryFilterQuery = networkSelectors.networkFilterQueryAsKuery();
const mapStateToProps = (state: State, { type }: OwnProps) => {
return {
- networkFilterQueryDraft: getNetworkFilterQueryDraft(state, type),
isNetworkFilterQueryDraftValid: getIsNetworkFilterQueryDraftValid(state, type),
kueryFilterQuery: getNetworkKueryFilterQuery(state, type),
+ networkFilterQueryDraft: getNetworkFilterQueryDraft(state, type),
};
};
return mapStateToProps;
diff --git a/x-pack/legacy/plugins/siem/public/containers/source/index.tsx b/x-pack/legacy/plugins/siem/public/containers/source/index.tsx
index 18b2641a16008..ead483baab43e 100644
--- a/x-pack/legacy/plugins/siem/public/containers/source/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/source/index.tsx
@@ -8,10 +8,10 @@ import { isUndefined } from 'lodash';
import { get, keyBy, pick, set } from 'lodash/fp';
import { Query } from 'react-apollo';
import React from 'react';
+import memoizeOne from 'memoize-one';
import { StaticIndexPattern } from 'ui/index_patterns';
import chrome from 'ui/chrome';
-import memoizeOne from 'memoize-one';
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
import { IndexField, SourceQuery } from '../../graphql/types';
@@ -57,47 +57,8 @@ interface WithSourceProps {
sourceId: string;
}
-export class WithSource extends React.PureComponent {
- private memoizedIndexFields: (title: string, fields: IndexField[]) => StaticIndexPattern;
- private memoizedBrowserFields: (fields: IndexField[]) => BrowserFields;
-
- constructor(props: WithSourceProps) {
- super(props);
- this.memoizedIndexFields = memoizeOne(this.getIndexFields);
- this.memoizedBrowserFields = memoizeOne(this.getBrowserFields);
- }
-
- public render() {
- const { children, sourceId } = this.props;
-
- return (
-
- query={sourceQuery}
- fetchPolicy="cache-first"
- notifyOnNetworkStatusChange
- variables={{
- sourceId,
- defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
- }}
- >
- {({ data }) => {
- return children({
- indicesExist: get('source.status.indicesExist', data),
- browserFields: this.memoizedBrowserFields(get('source.status.indexFields', data)),
- indexPattern: this.memoizedIndexFields(
- chrome
- .getUiSettingsClient()
- .get(DEFAULT_INDEX_KEY)
- .join(),
- get('source.status.indexFields', data)
- ),
- });
- }}
-
- );
- }
-
- private getIndexFields = (title: string, fields: IndexField[]): StaticIndexPattern =>
+export const WithSource = React.memo(({ children, sourceId }) => {
+ const getIndexFields = (title: string, fields: IndexField[]): StaticIndexPattern =>
fields && fields.length > 0
? {
fields: fields.map(field => pick(['name', 'searchable', 'type', 'aggregatable'], field)),
@@ -105,7 +66,7 @@ export class WithSource extends React.PureComponent {
}
: { fields: [], title };
- private getBrowserFields = (fields: IndexField[]): BrowserFields =>
+ const getBrowserFields = (fields: IndexField[]): BrowserFields =>
fields && fields.length > 0
? fields.reduce(
(accumulator: BrowserFields, field: IndexField) =>
@@ -113,7 +74,39 @@ export class WithSource extends React.PureComponent {
{}
)
: {};
-}
+ const getBrowserFieldsMemo: (fields: IndexField[]) => BrowserFields = memoizeOne(
+ getBrowserFields
+ );
+ const getIndexFieldsMemo: (
+ title: string,
+ fields: IndexField[]
+ ) => StaticIndexPattern = memoizeOne(getIndexFields);
+ return (
+
+ query={sourceQuery}
+ fetchPolicy="cache-first"
+ notifyOnNetworkStatusChange
+ variables={{
+ sourceId,
+ defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
+ }}
+ >
+ {({ data }) =>
+ children({
+ indicesExist: get('source.status.indicesExist', data),
+ browserFields: getBrowserFieldsMemo(get('source.status.indexFields', data)),
+ indexPattern: getIndexFieldsMemo(
+ chrome
+ .getUiSettingsClient()
+ .get(DEFAULT_INDEX_KEY)
+ .join(),
+ get('source.status.indexFields', data)
+ ),
+ })
+ }
+
+ );
+});
export const indicesExistOrDataTemporarilyUnavailable = (indicesExist: boolean | undefined) =>
indicesExist || isUndefined(indicesExist);
diff --git a/x-pack/legacy/plugins/siem/public/containers/timeline/all/index.tsx b/x-pack/legacy/plugins/siem/public/containers/timeline/all/index.tsx
index 88bc333b66e97..c3bff998fdefd 100644
--- a/x-pack/legacy/plugins/siem/public/containers/timeline/all/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/timeline/all/index.tsx
@@ -6,9 +6,10 @@
import { getOr } from 'lodash/fp';
import React from 'react';
+import memoizeOne from 'memoize-one';
+
import { Query } from 'react-apollo';
-import memoizeOne from 'memoize-one';
import { OpenTimelineResult } from '../../../components/open_timeline/types';
import {
GetAllTimeline,
@@ -35,19 +36,43 @@ interface OwnProps extends AllTimelinesVariables {
children?: (args: AllTimelinesArgs) => React.ReactNode;
}
-export class AllTimelinesQuery extends React.PureComponent {
- private memoizedAllTimeline: (
- variables: string,
- timelines: TimelineResult[]
- ) => OpenTimelineResult[];
+const getAllTimeline = (variables: string, timelines: TimelineResult[]): OpenTimelineResult[] =>
+ timelines.map(timeline => ({
+ created: timeline.created,
+ description: timeline.description,
+ eventIdToNoteIds:
+ timeline.eventIdToNoteIds != null
+ ? timeline.eventIdToNoteIds.reduce((acc, note) => {
+ if (note.eventId != null) {
+ const notes = getOr([], note.eventId, acc);
+ return { ...acc, [note.eventId]: [...notes, note.noteId] };
+ }
+ return acc;
+ }, {})
+ : null,
+ favorite: timeline.favorite,
+ noteIds: timeline.noteIds,
+ notes:
+ timeline.notes != null
+ ? timeline.notes.map(note => ({ ...note, savedObjectId: note.noteId }))
+ : null,
+ pinnedEventIds:
+ timeline.pinnedEventIds != null
+ ? timeline.pinnedEventIds.reduce(
+ (acc, pinnedEventId) => ({ ...acc, [pinnedEventId]: true }),
+ {}
+ )
+ : null,
+ savedObjectId: timeline.savedObjectId,
+ title: timeline.title,
+ updated: timeline.updated,
+ updatedBy: timeline.updatedBy,
+ }));
- constructor(props: OwnProps) {
- super(props);
- this.memoizedAllTimeline = memoizeOne(this.getAllTimeline);
- }
+export const AllTimelinesQuery = React.memo(
+ ({ children, onlyUserFavorite, pageInfo, search, sort }) => {
+ const memoizedAllTimeline = memoizeOne(getAllTimeline);
- public render() {
- const { children, onlyUserFavorite, pageInfo, search, sort } = this.props;
const variables: GetAllTimeline.Variables = {
onlyUserFavorite,
pageInfo,
@@ -65,7 +90,7 @@ export class AllTimelinesQuery extends React.PureComponent {
return children!({
loading,
totalCount: getOr(0, 'getAllTimeline.totalCount', data),
- timelines: this.memoizedAllTimeline(
+ timelines: memoizedAllTimeline(
JSON.stringify(variables),
getOr([], 'getAllTimeline.timeline', data)
),
@@ -74,41 +99,4 @@ export class AllTimelinesQuery extends React.PureComponent {
);
}
-
- private getAllTimeline = (
- variables: string,
- timelines: TimelineResult[]
- ): OpenTimelineResult[] => {
- return timelines.map(timeline => ({
- created: timeline.created,
- description: timeline.description,
- eventIdToNoteIds:
- timeline.eventIdToNoteIds != null
- ? timeline.eventIdToNoteIds.reduce((acc, note) => {
- if (note.eventId != null) {
- const notes = getOr([], note.eventId, acc);
- return { ...acc, [note.eventId]: [...notes, note.noteId] };
- }
- return acc;
- }, {})
- : null,
- favorite: timeline.favorite,
- noteIds: timeline.noteIds,
- notes:
- timeline.notes != null
- ? timeline.notes.map(note => ({ ...note, savedObjectId: note.noteId }))
- : null,
- pinnedEventIds:
- timeline.pinnedEventIds != null
- ? timeline.pinnedEventIds.reduce(
- (acc, pinnedEventId) => ({ ...acc, [pinnedEventId]: true }),
- {}
- )
- : null,
- savedObjectId: timeline.savedObjectId,
- title: timeline.title,
- updated: timeline.updated,
- updatedBy: timeline.updatedBy,
- }));
- };
-}
+);
diff --git a/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx b/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx
index 9a038a61c9f88..54dd44063f5da 100644
--- a/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx
@@ -21,30 +21,24 @@ export interface EventsArgs {
}
export interface TimelineDetailsProps {
- children?: (args: EventsArgs) => React.ReactNode;
+ children?: (args: EventsArgs) => React.ReactElement;
indexName: string;
eventId: string;
executeQuery: boolean;
sourceId: string;
}
-export class TimelineDetailsComponentQuery extends React.PureComponent {
- private memoizedDetailsEvents: (variables: string, detail: DetailItem[]) => DetailItem[];
+export const TimelineDetailsComponentQuery = React.memo(
+ ({ children, indexName, eventId, executeQuery, sourceId }) => {
+ const getDetailsEvent = (variables: string, detail: DetailItem[]): DetailItem[] => detail;
+ const getDetailsEventMemo = memoizeOne(getDetailsEvent);
- constructor(props: TimelineDetailsProps) {
- super(props);
- this.memoizedDetailsEvents = memoizeOne(this.getDetailsEvent);
- }
-
- public render() {
- const { children, indexName, eventId, executeQuery, sourceId } = this.props;
const variables: GetTimelineDetailsQuery.Variables = {
sourceId,
indexName,
eventId,
defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY),
};
-
return executeQuery ? (
query={timelineDetailsQuery}
@@ -55,7 +49,7 @@ export class TimelineDetailsComponentQuery extends React.PureComponent {
return children!({
loading,
- detailsData: this.memoizedDetailsEvents(
+ detailsData: getDetailsEventMemo(
JSON.stringify(variables),
getOr([], 'source.TimelineDetails.data', data)
),
@@ -66,6 +60,4 @@ export class TimelineDetailsComponentQuery extends React.PureComponent detail;
-}
+);
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 adc5471cc37a7..90eae605de4b7 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
@@ -28,22 +28,18 @@ type OwnProps = TimelinesProps;
export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10;
-export class TimelinesPage extends React.PureComponent {
- public render() {
- return (
- <>
-
-
-
-
-
-
- >
- );
- }
-}
+export const TimelinesPage = React.memo(({ apolloClient }) => (
+ <>
+
+
+
+
+
+
+ >
+));
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 22f861a132063..b3637f80dd42e 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -10260,10 +10260,6 @@
"xpack.siem.kpiNetwork.uniquePrivateIps.sourceChartLabel": "Src.",
"xpack.siem.kpiNetwork.uniquePrivateIps.sourceUnitLabel": "ソース",
"xpack.siem.kpiNetwork.uniquePrivateIps.title": "固有のプライベート IP",
- "xpack.siem.loadMoreTable.loadingButtonLabel": "読み込み中…",
- "xpack.siem.loadMoreTable.loadMoreButtonLabel": "さらに読み込む",
- "xpack.siem.loadMoreTable.rowsButtonLabel": "ページごとの行数",
- "xpack.siem.loadMoreTable.showingSubtitle": "表示中",
"xpack.siem.ml.score.anomalousEntityTitle": "異常エンティティ",
"xpack.siem.ml.table.timestampTitle": "タイムスタンプ",
"xpack.siem.modalAllErrors.close.button": "閉じる",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 68e13c2a436a9..865225f9681e4 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -10262,10 +10262,6 @@
"xpack.siem.kpiNetwork.uniquePrivateIps.sourceChartLabel": "源",
"xpack.siem.kpiNetwork.uniquePrivateIps.sourceUnitLabel": "源",
"xpack.siem.kpiNetwork.uniquePrivateIps.title": "唯一专用 IP",
- "xpack.siem.loadMoreTable.loadingButtonLabel": "正在加载……",
- "xpack.siem.loadMoreTable.loadMoreButtonLabel": "加载更多",
- "xpack.siem.loadMoreTable.rowsButtonLabel": "每页行数",
- "xpack.siem.loadMoreTable.showingSubtitle": "显示",
"xpack.siem.ml.score.anomalousEntityTitle": "异常实体",
"xpack.siem.ml.table.timestampTitle": "时间戳",
"xpack.siem.modalAllErrors.close.button": "关闭",