[SIEM] Add react/display-name eslint rule (#53107) (#54159)

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
patrykkopycinski 2020-01-08 00:10:31 +01:00 committed by Ryland Herrick
parent 25c3d5ea67
commit 81ef6e06bc
53 changed files with 836 additions and 702 deletions

View file

@ -756,6 +756,7 @@ module.exports = {
// 'prefer-template': 'warn',
'react/boolean-prop-naming': 'error',
'react/button-has-type': 'error',
'react/display-name': 'error',
'react/forbid-dom-props': 'error',
'react/no-access-state-in-setstate': 'error',
// This style will be turned on after most bugs are fixed

View file

@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { createHashHistory } from 'history';
import React, { memo, FC } from 'react';
import { createHashHistory, History } from 'history';
import React, { memo, useMemo, FC } from 'react';
import { ApolloProvider } from 'react-apollo';
import { Store } from 'redux';
import { Provider as ReduxStoreProvider } from 'react-redux';
import { ThemeProvider } from 'styled-components';
@ -23,7 +24,7 @@ import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
import { DEFAULT_DARK_MODE } from '../../common/constants';
import { ErrorToastDispatcher } from '../components/error_toast_dispatcher';
import { compose } from '../lib/compose/kibana_compose';
import { AppFrontendLibs } from '../lib/lib';
import { AppFrontendLibs, AppApolloClient } from '../lib/lib';
import { StartCore, StartPlugins } from './plugin';
import { PageRouter } from '../routes';
import { createStore } from '../store/store';
@ -32,48 +33,70 @@ import { MlCapabilitiesProvider } from '../components/ml/permissions/ml_capabili
import { ApolloClientContext } from '../utils/apollo_context';
const StartApp: FC<AppFrontendLibs> = memo(libs => {
interface AppPluginRootComponentProps {
apolloClient: AppApolloClient;
history: History;
store: Store;
theme: any; // eslint-disable-line @typescript-eslint/no-explicit-any
}
const AppPluginRootComponent: React.FC<AppPluginRootComponentProps> = ({
theme,
store,
apolloClient,
history,
}) => (
<EuiErrorBoundary>
<I18nContext>
<ManageGlobalToaster>
<ReduxStoreProvider store={store}>
<ApolloProvider client={apolloClient}>
<ApolloClientContext.Provider value={apolloClient}>
<ThemeProvider theme={theme}>
<MlCapabilitiesProvider>
<PageRouter history={history} />
</MlCapabilitiesProvider>
</ThemeProvider>
<ErrorToastDispatcher />
<GlobalToaster />
</ApolloClientContext.Provider>
</ApolloProvider>
</ReduxStoreProvider>
</ManageGlobalToaster>
</I18nContext>
</EuiErrorBoundary>
);
const AppPluginRoot = memo(AppPluginRootComponent);
const StartAppComponent: FC<AppFrontendLibs> = libs => {
const history = createHashHistory();
const libs$ = new BehaviorSubject(libs);
const store = createStore(undefined, libs$.pipe(pluck('apolloClient')));
const [darkMode] = useUiSetting$<boolean>(DEFAULT_DARK_MODE);
const theme = useMemo(
() => ({
eui: darkMode ? euiDarkVars : euiLightVars,
darkMode,
}),
[darkMode]
);
const AppPluginRoot = memo(() => {
const [darkMode] = useUiSetting$<boolean>(DEFAULT_DARK_MODE);
return (
<EuiErrorBoundary>
<I18nContext>
<ManageGlobalToaster>
<ReduxStoreProvider store={store}>
<ApolloProvider client={libs.apolloClient}>
<ApolloClientContext.Provider value={libs.apolloClient}>
<ThemeProvider
theme={() => ({
eui: darkMode ? euiDarkVars : euiLightVars,
darkMode,
})}
>
<MlCapabilitiesProvider>
<PageRouter history={history} />
</MlCapabilitiesProvider>
</ThemeProvider>
<ErrorToastDispatcher />
<GlobalToaster />
</ApolloClientContext.Provider>
</ApolloProvider>
</ReduxStoreProvider>
</ManageGlobalToaster>
</I18nContext>
</EuiErrorBoundary>
);
});
return <AppPluginRoot />;
});
return (
<AppPluginRoot store={store} apolloClient={libs.apolloClient} history={history} theme={theme} />
);
};
const StartApp = memo(StartAppComponent);
export const ROOT_ELEMENT_ID = 'react-siem-root';
export const SiemApp = memo<{ core: StartCore; plugins: StartPlugins }>(({ core, plugins }) => (
interface SiemAppComponentProps {
core: StartCore;
plugins: StartPlugins;
}
const SiemAppComponent: React.FC<SiemAppComponentProps> = ({ core, plugins }) => (
<KibanaContextProvider
services={{
appName: 'siem',
@ -84,4 +107,6 @@ export const SiemApp = memo<{ core: StartCore; plugins: StartPlugins }>(({ core,
>
<StartApp {...compose()} />
</KibanaContextProvider>
));
);
export const SiemApp = memo(SiemAppComponent);

View file

@ -51,33 +51,31 @@ const defaultAlertsFilters: esFilters.Filter[] = [
},
];
export const AlertsTable = React.memo(
({
endDate,
startDate,
pageFilters = [],
}: {
endDate: number;
startDate: number;
pageFilters?: esFilters.Filter[];
}) => {
const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]);
return (
<StatefulEventsViewer
pageFilters={alertsFilter}
defaultModel={alertsDefaultModel}
end={endDate}
id={ALERTS_TABLE_ID}
start={startDate}
timelineTypeContext={useMemo(
() => ({
documentType: i18n.ALERTS_DOCUMENT_TYPE,
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
title: i18n.ALERTS_TABLE_TITLE,
}),
[]
)}
/>
);
}
);
interface Props {
endDate: number;
startDate: number;
pageFilters?: esFilters.Filter[];
}
const AlertsTableComponent: React.FC<Props> = ({ endDate, startDate, pageFilters = [] }) => {
const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]);
return (
<StatefulEventsViewer
pageFilters={alertsFilter}
defaultModel={alertsDefaultModel}
end={endDate}
id={ALERTS_TABLE_ID}
start={startDate}
timelineTypeContext={useMemo(
() => ({
documentType: i18n.ALERTS_DOCUMENT_TYPE,
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
title: i18n.ALERTS_TABLE_TITLE,
}),
[]
)}
/>
);
};
export const AlertsTable = React.memo(AlertsTableComponent);

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import {
EuiCheckbox,
EuiFlexGroup,

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import {
EuiIcon,
EuiFlexGroup,

View file

@ -102,138 +102,138 @@ 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 const FieldsBrowser = React.memo<Props>(
({
browserFields,
columnHeaders,
filteredBrowserFields,
isEventViewer,
isSearching,
onCategorySelected,
onFieldSelected,
onHideFieldBrowser,
onSearchInputChange,
onOutsideClick,
onUpdateColumns,
searchInput,
selectedCategoryId,
timelineId,
toggleColumn,
width,
}) => {
/** Focuses the input that filters the field browser */
const focusInput = () => {
const elements = document.getElementsByClassName(
getFieldBrowserSearchInputClassName(timelineId)
const FieldsBrowserComponent: React.FC<Props> = ({
browserFields,
columnHeaders,
filteredBrowserFields,
isEventViewer,
isSearching,
onCategorySelected,
onFieldSelected,
onHideFieldBrowser,
onSearchInputChange,
onOutsideClick,
onUpdateColumns,
searchInput,
selectedCategoryId,
timelineId,
toggleColumn,
width,
}) => {
/** Focuses the input that filters the field browser */
const focusInput = () => {
const elements = document.getElementsByClassName(
getFieldBrowserSearchInputClassName(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 */
const onInputChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
onSearchInputChange(event.target.value);
},
[onSearchInputChange]
);
const selectFieldAndHide = useCallback(
(fieldId: string) => {
if (onFieldSelected != null) {
onFieldSelected(fieldId);
}
onHideFieldBrowser();
},
[onFieldSelected, onHideFieldBrowser]
);
const scrollViews = () => {
if (selectedCategoryId !== '') {
const categoryPaneTitles = document.getElementsByClassName(
getCategoryPaneCategoryClassName({
categoryId: selectedCategoryId,
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 */
const onInputChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
onSearchInputChange(event.target.value);
},
[onSearchInputChange]
);
const selectFieldAndHide = useCallback(
(fieldId: string) => {
if (onFieldSelected != null) {
onFieldSelected(fieldId);
}
onHideFieldBrowser();
},
[onFieldSelected, onHideFieldBrowser]
);
const 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();
}
if (categoryPaneTitles.length > 0) {
categoryPaneTitles[0].scrollIntoView();
}
focusInput(); // always re-focus the input to enable additional filtering
};
const fieldPaneTitles = document.getElementsByClassName(
getFieldBrowserCategoryTitleClassName({
categoryId: selectedCategoryId,
timelineId,
})
);
useEffect(() => {
scrollViews();
}, [selectedCategoryId, timelineId]);
if (fieldPaneTitles.length > 0) {
fieldPaneTitles[0].scrollIntoView();
}
}
return (
<EuiOutsideClickDetector
data-test-subj="outside-click-detector"
onOutsideClick={onFieldSelected != null ? noop : onOutsideClick}
isDisabled={false}
>
<FieldsBrowserContainer data-test-subj="fields-browser-container" width={width}>
<Header
data-test-subj="header"
filteredBrowserFields={filteredBrowserFields}
isEventViewer={isEventViewer}
isSearching={isSearching}
onOutsideClick={onOutsideClick}
onSearchInputChange={onInputChange}
onUpdateColumns={onUpdateColumns}
searchInput={searchInput}
timelineId={timelineId}
/>
focusInput(); // always re-focus the input to enable additional filtering
};
<PanesFlexGroup alignItems="flexStart" gutterSize="none" justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<CategoriesPane
browserFields={browserFields}
data-test-subj="left-categories-pane"
filteredBrowserFields={filteredBrowserFields}
width={CATEGORY_PANE_WIDTH}
onCategorySelected={onCategorySelected}
onUpdateColumns={onUpdateColumns}
selectedCategoryId={selectedCategoryId}
timelineId={timelineId}
/>
</EuiFlexItem>
useEffect(() => {
scrollViews();
}, [selectedCategoryId, timelineId]);
<EuiFlexItem grow={false}>
<FieldsPane
columnHeaders={columnHeaders}
data-test-subj="fields-pane"
filteredBrowserFields={filteredBrowserFields}
onCategorySelected={onCategorySelected}
onFieldSelected={selectFieldAndHide}
onUpdateColumns={onUpdateColumns}
searchInput={searchInput}
selectedCategoryId={selectedCategoryId}
timelineId={timelineId}
toggleColumn={toggleColumn}
width={FIELDS_PANE_WIDTH}
/>
</EuiFlexItem>
</PanesFlexGroup>
</FieldsBrowserContainer>
</EuiOutsideClickDetector>
);
}
);
return (
<EuiOutsideClickDetector
data-test-subj="outside-click-detector"
onOutsideClick={onFieldSelected != null ? noop : onOutsideClick}
isDisabled={false}
>
<FieldsBrowserContainer data-test-subj="fields-browser-container" width={width}>
<Header
data-test-subj="header"
filteredBrowserFields={filteredBrowserFields}
isEventViewer={isEventViewer}
isSearching={isSearching}
onOutsideClick={onOutsideClick}
onSearchInputChange={onInputChange}
onUpdateColumns={onUpdateColumns}
searchInput={searchInput}
timelineId={timelineId}
/>
<PanesFlexGroup alignItems="flexStart" gutterSize="none" justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<CategoriesPane
browserFields={browserFields}
data-test-subj="left-categories-pane"
filteredBrowserFields={filteredBrowserFields}
width={CATEGORY_PANE_WIDTH}
onCategorySelected={onCategorySelected}
onUpdateColumns={onUpdateColumns}
selectedCategoryId={selectedCategoryId}
timelineId={timelineId}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<FieldsPane
columnHeaders={columnHeaders}
data-test-subj="fields-pane"
filteredBrowserFields={filteredBrowserFields}
onCategorySelected={onCategorySelected}
onFieldSelected={selectFieldAndHide}
onUpdateColumns={onUpdateColumns}
searchInput={searchInput}
selectedCategoryId={selectedCategoryId}
timelineId={timelineId}
toggleColumn={toggleColumn}
width={FIELDS_PANE_WIDTH}
/>
</EuiFlexItem>
</PanesFlexGroup>
</FieldsBrowserContainer>
</EuiOutsideClickDetector>
);
};
export const FieldsBrowser = React.memo(FieldsBrowserComponent);

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import { EuiCheckbox, EuiIcon, EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { uniqBy } from 'lodash/fp';
import * as React from 'react';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import { EuiLink, EuiTableRow, EuiTableRowCell, EuiText, EuiToolTip } from '@elastic/eui';
import * as React from 'react';
import ReactMarkdown from 'react-markdown';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import { shallow } from 'enzyme';
import * as React from 'react';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
import { Columns } from '../../paginated_table';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import chrome from 'ui/chrome';
import React, { useEffect, useState } from 'react';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import * as React from 'react';
import { EuiTableDataType } from '@elastic/eui';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import * as React from 'react';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import { EuiButtonIcon, EuiLink } from '@elastic/eui';
import { omit } from 'lodash/fp';
import * as React from 'react';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import * as React from 'react';
import { defaultToEmptyTag } from '../../empty_value';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import { EuiIcon, EuiToolTip } from '@elastic/eui';
import * as React from 'react';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import { has } from 'lodash/fp';
import React, { useCallback } from 'react';
import { connect } from 'react-redux';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import React, { useCallback } from 'react';
import { connect } from 'react-redux';
import { ActionCreator } from 'typescript-fsa';

View file

@ -146,6 +146,8 @@ export const KpiNetworkBaseComponent = React.memo<{
);
});
KpiNetworkBaseComponent.displayName = 'KpiNetworkBaseComponent';
export const KpiNetworkComponent = React.memo<KpiNetworkProps>(
({ data, from, id, loading, to, narrowDateRange }) => {
return loading ? (

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import React from 'react';
import numeral from '@elastic/numeral';
import { NetworkHttpEdges, NetworkHttpFields, NetworkHttpItem } from '../../../../graphql/types';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import React from 'react';
import moment from 'moment';
import { TlsNode } from '../../../../graphql/types';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
import { mount, ReactWrapper } from 'enzyme';
import toJson from 'enzyme-to-json';

View file

@ -9,7 +9,7 @@ import uuid from 'uuid';
import VisibilitySensor from 'react-visibility-sensor';
import { BrowserFields } from '../../../../containers/source';
import { TimelineDetailsComponentQuery } from '../../../../containers/timeline/details';
import { TimelineDetailsQuery } from '../../../../containers/timeline/details';
import { TimelineItem, DetailItem, TimelineNonEcsData } from '../../../../graphql/types';
import { requestIdleCallbackViaScheduler } from '../../../../lib/helpers/scheduler';
import { Note } from '../../../../lib/note';
@ -91,7 +91,7 @@ interface AttributesProps {
children: React.ReactNode;
}
const Attributes = React.memo<AttributesProps>(({ children }) => {
const AttributesComponent: React.FC<AttributesProps> = ({ children }) => {
const width = useTimelineWidthContext();
// Passing the styles directly to the component because the width is
@ -106,187 +106,187 @@ const Attributes = React.memo<AttributesProps>(({ children }) => {
{children}
</EventsTrSupplement>
);
});
};
export const StatefulEvent = React.memo<Props>(
({
actionsColumnWidth,
addNoteToEvent,
browserFields,
columnHeaders,
columnRenderers,
event,
eventIdToNoteIds,
getNotesByIds,
isEventViewer = false,
isEventPinned = false,
loadingEventIds,
maxDelay = 0,
onColumnResized,
onPinEvent,
onRowSelected,
onUnPinEvent,
onUpdateColumns,
rowRenderers,
selectedEventIds,
showCheckboxes,
timelineId,
toggleColumn,
updateNote,
}) => {
const [expanded, setExpanded] = useState<{ [eventId: string]: boolean }>({});
const [initialRender, setInitialRender] = useState(false);
const [showNotes, setShowNotes] = useState<{ [eventId: string]: boolean }>({});
const Attributes = React.memo(AttributesComponent);
const divElement = useRef<HTMLDivElement | null>(null);
const StatefulEventComponent: React.FC<Props> = ({
actionsColumnWidth,
addNoteToEvent,
browserFields,
columnHeaders,
columnRenderers,
event,
eventIdToNoteIds,
getNotesByIds,
isEventViewer = false,
isEventPinned = false,
loadingEventIds,
maxDelay = 0,
onColumnResized,
onPinEvent,
onRowSelected,
onUnPinEvent,
onUpdateColumns,
rowRenderers,
selectedEventIds,
showCheckboxes,
timelineId,
toggleColumn,
updateNote,
}) => {
const [expanded, setExpanded] = useState<{ [eventId: string]: boolean }>({});
const [initialRender, setInitialRender] = useState(false);
const [showNotes, setShowNotes] = useState<{ [eventId: string]: boolean }>({});
const onToggleShowNotes = useCallback(() => {
const eventId = event._id;
setShowNotes({ ...showNotes, [eventId]: !showNotes[eventId] });
}, [event, showNotes]);
const divElement = useRef<HTMLDivElement | null>(null);
const onToggleExpanded = useCallback(() => {
const eventId = event._id;
setExpanded({
...expanded,
[eventId]: !expanded[eventId],
});
}, [event, expanded]);
const onToggleShowNotes = useCallback(() => {
const eventId = event._id;
setShowNotes({ ...showNotes, [eventId]: !showNotes[eventId] });
}, [event, showNotes]);
const associateNote = useCallback(
(noteId: string) => {
addNoteToEvent({ eventId: event._id, noteId });
if (!isEventPinned) {
onPinEvent(event._id); // pin the event, because it has notes
const onToggleExpanded = useCallback(() => {
const eventId = event._id;
setExpanded({
...expanded,
[eventId]: !expanded[eventId],
});
}, [event, expanded]);
const associateNote = useCallback(
(noteId: string) => {
addNoteToEvent({ eventId: event._id, noteId });
if (!isEventPinned) {
onPinEvent(event._id); // pin the event, because it has notes
}
},
[addNoteToEvent, event, isEventPinned, onPinEvent]
);
/**
* 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.
*/
useEffect(() => {
let _isMounted = true;
requestIdleCallbackViaScheduler(
() => {
if (!initialRender && _isMounted) {
setInitialRender(true);
}
},
[addNoteToEvent, event, isEventPinned, onPinEvent]
{ timeout: maxDelay }
);
return () => {
_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.
*/
useEffect(() => {
let _isMounted = true;
// Number of current columns plus one for actions.
const columnCount = columnHeaders.length + 1;
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 useEffect() for when it schedules the first
// time this stateful component should be rendered.
if (!initialRender) {
return <SkeletonRow cellCount={columnCount} />;
}
return (
<VisibilitySensor
partialVisibility={true}
scrollCheck={true}
offset={{ top: TOP_OFFSET, bottom: BOTTOM_OFFSET }}
>
{({ isVisible }) => {
if (isVisible) {
return (
<TimelineDetailsComponentQuery
sourceId="default"
indexName={event._index!}
eventId={event._id}
executeQuery={!!expanded[event._id]}
>
{({ detailsData, loading }) => (
<EventsTrGroup
className={STATEFUL_EVENT_CSS_CLASS_NAME}
data-test-subj="event"
ref={c => {
if (c != null) {
divElement.current = c;
}
}}
>
{getRowRenderer(event.ecs, rowRenderers).renderRow({
browserFields,
data: event.ecs,
children: (
<StatefulEventChild
actionsColumnWidth={actionsColumnWidth}
addNoteToEvent={addNoteToEvent}
associateNote={associateNote}
columnHeaders={columnHeaders}
columnRenderers={columnRenderers}
data={event.data}
eventIdToNoteIds={eventIdToNoteIds}
expanded={!!expanded[event._id]}
getNotesByIds={getNotesByIds}
id={event._id}
isEventPinned={isEventPinned}
isEventViewer={isEventViewer}
loading={loading}
loadingEventIds={loadingEventIds}
onColumnResized={onColumnResized}
onPinEvent={onPinEvent}
onRowSelected={onRowSelected}
onToggleExpanded={onToggleExpanded}
onToggleShowNotes={onToggleShowNotes}
onUnPinEvent={onUnPinEvent}
selectedEventIds={selectedEventIds}
showCheckboxes={showCheckboxes}
showNotes={!!showNotes[event._id]}
timelineId={timelineId}
updateNote={updateNote}
/>
),
timelineId,
})}
<Attributes>
<ExpandableEvent
browserFields={browserFields}
columnHeaders={columnHeaders}
event={detailsData || emptyDetails}
forceExpand={!!expanded[event._id] && !loading}
id={event._id}
onUpdateColumns={onUpdateColumns}
timelineId={timelineId}
toggleColumn={toggleColumn}
/>
</Attributes>
</EventsTrGroup>
)}
</TimelineDetailsComponentQuery>
);
} else {
// Height place holder for visibility detection as well as re-rendering sections.
const 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.
// https://github.com/styled-components/styled-components/issues/134#issuecomment-312415291
return <SkeletonRow cellCount={columnCount} style={{ height }} />;
}
}}
</VisibilitySensor>
);
// If we are not ready to render yet, just return null
// see useEffect() for when it schedules the first
// time this stateful component should be rendered.
if (!initialRender) {
return <SkeletonRow cellCount={columnCount} />;
}
);
StatefulEvent.displayName = 'StatefulEvent';
return (
<VisibilitySensor
partialVisibility={true}
scrollCheck={true}
offset={{ top: TOP_OFFSET, bottom: BOTTOM_OFFSET }}
>
{({ isVisible }) => {
if (isVisible) {
return (
<TimelineDetailsQuery
sourceId="default"
indexName={event._index!}
eventId={event._id}
executeQuery={!!expanded[event._id]}
>
{({ detailsData, loading }) => (
<EventsTrGroup
className={STATEFUL_EVENT_CSS_CLASS_NAME}
data-test-subj="event"
ref={c => {
if (c != null) {
divElement.current = c;
}
}}
>
{getRowRenderer(event.ecs, rowRenderers).renderRow({
browserFields,
data: event.ecs,
children: (
<StatefulEventChild
actionsColumnWidth={actionsColumnWidth}
addNoteToEvent={addNoteToEvent}
associateNote={associateNote}
columnHeaders={columnHeaders}
columnRenderers={columnRenderers}
data={event.data}
eventIdToNoteIds={eventIdToNoteIds}
expanded={!!expanded[event._id]}
getNotesByIds={getNotesByIds}
id={event._id}
isEventPinned={isEventPinned}
isEventViewer={isEventViewer}
loading={loading}
loadingEventIds={loadingEventIds}
onColumnResized={onColumnResized}
onPinEvent={onPinEvent}
onRowSelected={onRowSelected}
onToggleExpanded={onToggleExpanded}
onToggleShowNotes={onToggleShowNotes}
onUnPinEvent={onUnPinEvent}
selectedEventIds={selectedEventIds}
showCheckboxes={showCheckboxes}
showNotes={!!showNotes[event._id]}
timelineId={timelineId}
updateNote={updateNote}
/>
),
timelineId,
})}
<Attributes>
<ExpandableEvent
browserFields={browserFields}
columnHeaders={columnHeaders}
event={detailsData || emptyDetails}
forceExpand={!!expanded[event._id] && !loading}
id={event._id}
onUpdateColumns={onUpdateColumns}
timelineId={timelineId}
toggleColumn={toggleColumn}
/>
</Attributes>
</EventsTrGroup>
)}
</TimelineDetailsQuery>
);
} else {
// Height place holder for visibility detection as well as re-rendering sections.
const 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.
// https://github.com/styled-components/styled-components/issues/134#issuecomment-312415291
return <SkeletonRow cellCount={columnCount} style={{ height }} />;
}
}}
</VisibilitySensor>
);
};
export const StatefulEvent = React.memo(StatefulEventComponent);

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import { IconType } from '@elastic/eui';
import { get } from 'lodash/fp';
import React from 'react';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import * as React from 'react';
import { TimelineNonEcsData } from '../../../../graphql/types';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import { get } from 'lodash/fp';
import React from 'react';
import styled from 'styled-components';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import React from 'react';
import { RowRenderer } from './row_renderer';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import { get } from 'lodash/fp';
import React from 'react';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import { get } from 'lodash/fp';
import React from 'react';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import { get } from 'lodash/fp';
import React from 'react';

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { memo, useEffect } from 'react';
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { ActionCreator } from 'typescript-fsa';
@ -33,18 +33,23 @@ export interface TimelineRefetchProps {
type OwnProps = TimelineRefetchProps & TimelineRefetchDispatch;
const TimelineRefetchComponent = memo<OwnProps>(
({ id, inputId, inspect, loading, refetch, setTimelineQuery }) => {
useEffect(() => {
setTimelineQuery({ id, inputId, inspect, loading, refetch });
}, [id, inputId, loading, refetch, inspect]);
const TimelineRefetchComponent: React.FC<OwnProps> = ({
id,
inputId,
inspect,
loading,
refetch,
setTimelineQuery,
}) => {
useEffect(() => {
setTimelineQuery({ id, inputId, inspect, loading, refetch });
}, [id, inputId, loading, refetch, inspect]);
return null;
}
);
return null;
};
export const TimelineRefetch = compose<React.ComponentClass<TimelineRefetchProps>>(
connect(null, {
setTimelineQuery: inputsActions.setQuery,
})
)(TimelineRefetchComponent);
)(React.memo(TimelineRefetchComponent));

View file

@ -45,30 +45,35 @@ interface ManageTimelineContextProps {
// todo we need to refactor this as more complex context/reducer with useReducer
// to avoid so many Context, at least the separation of code is there now
export const ManageTimelineContext = memo<ManageTimelineContextProps>(
({ children, loading, width, type = initTimelineType }) => {
const [myLoading, setLoading] = useState(initTimelineContext);
const [myWidth, setWidth] = useState(initTimelineWidth);
const [myType, setType] = useState(initTimelineType);
const ManageTimelineContextComponent: React.FC<ManageTimelineContextProps> = ({
children,
loading,
width,
type = initTimelineType,
}) => {
const [myLoading, setLoading] = useState(initTimelineContext);
const [myWidth, setWidth] = useState(initTimelineWidth);
const [myType, setType] = useState(initTimelineType);
useEffect(() => {
setLoading(loading);
}, [loading]);
useEffect(() => {
setLoading(loading);
}, [loading]);
useEffect(() => {
setType(type);
}, [type]);
useEffect(() => {
setType(type);
}, [type]);
useEffect(() => {
setWidth(width);
}, [width]);
useEffect(() => {
setWidth(width);
}, [width]);
return (
<TimelineContext.Provider value={myLoading}>
<TimelineWidthContext.Provider value={myWidth}>
<TimelineTypeContext.Provider value={myType}>{children}</TimelineTypeContext.Provider>
</TimelineWidthContext.Provider>
</TimelineContext.Provider>
);
}
);
return (
<TimelineContext.Provider value={myLoading}>
<TimelineWidthContext.Provider value={myWidth}>
<TimelineTypeContext.Provider value={myType}>{children}</TimelineTypeContext.Provider>
</TimelineWidthContext.Provider>
</TimelineContext.Provider>
);
};
export const ManageTimelineContext = memo(ManageTimelineContextComponent);

View file

@ -19,16 +19,12 @@ import { dispatchUpdateTimeline } from '../open_timeline/helpers';
import { dispatchSetInitialStateFromUrl } from './initialize_redux_by_url';
import { makeMapStateToProps } from './helpers';
export const UrlStateContainer = React.memo<UrlStateContainerPropTypes>(
(props: UrlStateContainerPropTypes) => {
useUrlStateHooks(props);
return null;
},
(prevProps, nextProps) =>
prevProps.pathName === nextProps.pathName && isEqual(prevProps.urlState, nextProps.urlState)
);
UrlStateContainer.displayName = 'UrlStateContainer';
export const UrlStateContainer: React.FC<UrlStateContainerPropTypes> = (
props: UrlStateContainerPropTypes
) => {
useUrlStateHooks(props);
return null;
};
const mapDispatchToProps = (dispatch: Dispatch) => ({
setInitialStateFromUrl: dispatchSetInitialStateFromUrl(dispatch),
@ -39,13 +35,21 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
export const UrlStateRedux = compose<React.ComponentClass<UrlStateProps & RouteSpyState>>(
connect(makeMapStateToProps, mapDispatchToProps)
)(UrlStateContainer);
)(
React.memo(
UrlStateContainer,
(prevProps, nextProps) =>
prevProps.pathName === nextProps.pathName && isEqual(prevProps.urlState, nextProps.urlState)
)
);
export const UseUrlState = React.memo<UrlStateProps>(props => {
const UseUrlStateComponent: React.FC<UrlStateProps> = props => {
const [routeProps] = useRouteSpy();
const urlStateReduxProps: RouteSpyState & UrlStateProps = {
...routeProps,
...props,
};
return <UrlStateRedux {...urlStateReduxProps} />;
});
};
export const UseUrlState = React.memo(UseUrlStateComponent);

View file

@ -47,33 +47,38 @@ interface OwnProps {
type GlobalTimeProps = OwnProps & GlobalTimeReduxState & GlobalTimeDispatch;
export const GlobalTimeComponent = React.memo(
({ children, deleteAllQuery, deleteOneQuery, from, to, setGlobalQuery }: GlobalTimeProps) => {
const [isInitializing, setIsInitializing] = useState(true);
export const GlobalTimeComponent: React.FC<GlobalTimeProps> = ({
children,
deleteAllQuery,
deleteOneQuery,
from,
to,
setGlobalQuery,
}) => {
const [isInitializing, setIsInitializing] = useState(true);
useEffect(() => {
if (isInitializing) {
setIsInitializing(false);
}
return () => {
deleteAllQuery({ id: 'global' });
};
}, []);
useEffect(() => {
if (isInitializing) {
setIsInitializing(false);
}
return () => {
deleteAllQuery({ id: 'global' });
};
}, []);
return (
<>
{children({
isInitializing,
from,
to,
setQuery: ({ id, inspect, loading, refetch }: SetQuery) =>
setGlobalQuery({ inputId: 'global', id, inspect, loading, refetch }),
deleteQuery: ({ id }: { id: string }) => deleteOneQuery({ inputId: 'global', id }),
})}
</>
);
}
);
return (
<>
{children({
isInitializing,
from,
to,
setQuery: ({ id, inspect, loading, refetch }: SetQuery) =>
setGlobalQuery({ inputId: 'global', id, inspect, loading, refetch }),
deleteQuery: ({ id }: { id: string }) => deleteOneQuery({ inputId: 'global', id }),
})}
</>
);
};
const mapStateToProps = (state: State) => {
const timerange: inputsModel.TimeRange = inputsSelectors.globalTimeRangeSelector(state);
@ -87,4 +92,4 @@ export const GlobalTime = connect(mapStateToProps, {
deleteAllQuery: inputsActions.deleteAllQuery,
deleteOneQuery: inputsActions.deleteOneQuery,
setGlobalQuery: inputsActions.setQuery,
})(GlobalTimeComponent);
})(React.memo(GlobalTimeComponent));

View file

@ -71,32 +71,38 @@ const getAllTimeline = memoizeOne(
}))
);
export const AllTimelinesQuery = React.memo<OwnProps>(
({ children, onlyUserFavorite, pageInfo, search, sort }) => {
const variables: GetAllTimeline.Variables = {
onlyUserFavorite,
pageInfo,
search,
sort,
};
return (
<Query<GetAllTimeline.Query, GetAllTimeline.Variables>
query={allTimelinesQuery}
fetchPolicy="network-only"
notifyOnNetworkStatusChange
variables={variables}
>
{({ data, loading }) => {
return children!({
loading,
totalCount: getOr(0, 'getAllTimeline.totalCount', data),
timelines: getAllTimeline(
JSON.stringify(variables),
getOr([], 'getAllTimeline.timeline', data)
),
});
}}
</Query>
);
}
);
const AllTimelinesQueryComponent: React.FC<OwnProps> = ({
children,
onlyUserFavorite,
pageInfo,
search,
sort,
}) => {
const variables: GetAllTimeline.Variables = {
onlyUserFavorite,
pageInfo,
search,
sort,
};
return (
<Query<GetAllTimeline.Query, GetAllTimeline.Variables>
query={allTimelinesQuery}
fetchPolicy="network-only"
notifyOnNetworkStatusChange
variables={variables}
>
{({ data, loading }) =>
children!({
loading,
totalCount: getOr(0, 'getAllTimeline.totalCount', data),
timelines: getAllTimeline(
JSON.stringify(variables),
getOr([], 'getAllTimeline.timeline', data)
),
})
}
</Query>
);
};
export const AllTimelinesQuery = React.memo(AllTimelinesQueryComponent);

View file

@ -32,33 +32,39 @@ const getDetailsEvent = memoizeOne(
(variables: string, detail: DetailItem[]): DetailItem[] => detail
);
export const TimelineDetailsComponentQuery = React.memo<TimelineDetailsProps>(
({ children, indexName, eventId, executeQuery, sourceId }) => {
const variables: GetTimelineDetailsQuery.Variables = {
sourceId,
indexName,
eventId,
defaultIndex: useUiSetting<string[]>(DEFAULT_INDEX_KEY),
};
return executeQuery ? (
<Query<GetTimelineDetailsQuery.Query, GetTimelineDetailsQuery.Variables>
query={timelineDetailsQuery}
fetchPolicy="network-only"
notifyOnNetworkStatusChange
variables={variables}
>
{({ data, loading, refetch }) => {
return children!({
loading,
detailsData: getDetailsEvent(
JSON.stringify(variables),
getOr([], 'source.TimelineDetails.data', data)
),
});
}}
</Query>
) : (
children!({ loading: false, detailsData: null })
);
}
);
const TimelineDetailsQueryComponent: React.FC<TimelineDetailsProps> = ({
children,
indexName,
eventId,
executeQuery,
sourceId,
}) => {
const variables: GetTimelineDetailsQuery.Variables = {
sourceId,
indexName,
eventId,
defaultIndex: useUiSetting<string[]>(DEFAULT_INDEX_KEY),
};
return executeQuery ? (
<Query<GetTimelineDetailsQuery.Query, GetTimelineDetailsQuery.Variables>
query={timelineDetailsQuery}
fetchPolicy="network-only"
notifyOnNetworkStatusChange
variables={variables}
>
{({ data, loading, refetch }) =>
children!({
loading,
detailsData: getDetailsEvent(
JSON.stringify(variables),
getOr([], 'source.TimelineDetails.data', data)
),
})
}
</Query>
) : (
children!({ loading: false, detailsData: null })
);
};
export const TimelineDetailsQuery = React.memo(TimelineDetailsQueryComponent);

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import React from 'react';
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';

View file

@ -61,26 +61,33 @@ Object.defineProperty(window, 'localStorage', {
const MockKibanaContextProvider = createKibanaContextProviderMock();
/** A utility for wrapping children in the providers required to run most tests */
export const TestProviders = React.memo<Props>(
({ children, store = createStore(state, apolloClientObservable), onDragEnd = jest.fn() }) => (
<I18nProvider>
<MockKibanaContextProvider>
<ApolloProvider client={apolloClient}>
<ReduxStoreProvider store={store}>
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
</ThemeProvider>
</ReduxStoreProvider>
</ApolloProvider>
</MockKibanaContextProvider>
</I18nProvider>
)
const TestProvidersComponent: React.FC<Props> = ({
children,
store = createStore(state, apolloClientObservable),
onDragEnd = jest.fn(),
}) => (
<I18nProvider>
<MockKibanaContextProvider>
<ApolloProvider client={apolloClient}>
<ReduxStoreProvider store={store}>
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
</ThemeProvider>
</ReduxStoreProvider>
</ApolloProvider>
</MockKibanaContextProvider>
</I18nProvider>
);
export const TestProviderWithoutDragAndDrop = React.memo<Props>(
({ children, store = createStore(state, apolloClientObservable) }) => (
<I18nProvider>
<ReduxStoreProvider store={store}>{children}</ReduxStoreProvider>
</I18nProvider>
)
export const TestProviders = React.memo(TestProvidersComponent);
const TestProviderWithoutDragAndDropComponent: React.FC<Props> = ({
children,
store = createStore(state, apolloClientObservable),
}) => (
<I18nProvider>
<ReduxStoreProvider store={store}>{children}</ReduxStoreProvider>
</I18nProvider>
);
export const TestProviderWithoutDragAndDrop = React.memo(TestProviderWithoutDragAndDropComponent);

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import { EuiBasicTableColumn, EuiIconTip, EuiLink, EuiTextColor } from '@elastic/eui';
import { DefaultItemIconButtonAction } from '@elastic/eui/src/components/basic_table/action_types';
import React from 'react';

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import React from 'react';

View file

@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiFilterButton, EuiFilterGroup } from '@elastic/eui';
import React, { useCallback, useState } from 'react';
import * as i18n from '../translations';
@ -11,41 +12,41 @@ export const FILTER_OPEN = 'open';
export const FILTER_CLOSED = 'closed';
export type SignalFilterOption = typeof FILTER_OPEN | typeof FILTER_CLOSED;
export const SignalsTableFilterGroup = React.memo(
({
onFilterGroupChanged,
}: {
onFilterGroupChanged: (filterGroup: SignalFilterOption) => void;
}) => {
const [filterGroup, setFilterGroup] = useState(FILTER_OPEN);
interface Props {
onFilterGroupChanged: (filterGroup: SignalFilterOption) => void;
}
const onClickOpenFilterCallback = useCallback(() => {
setFilterGroup(FILTER_OPEN);
onFilterGroupChanged(FILTER_OPEN);
}, [setFilterGroup, onFilterGroupChanged]);
const SignalsTableFilterGroupComponent: React.FC<Props> = ({ onFilterGroupChanged }) => {
const [filterGroup, setFilterGroup] = useState(FILTER_OPEN);
const onClickCloseFilterCallback = useCallback(() => {
setFilterGroup(FILTER_CLOSED);
onFilterGroupChanged(FILTER_CLOSED);
}, [setFilterGroup, onFilterGroupChanged]);
const onClickOpenFilterCallback = useCallback(() => {
setFilterGroup(FILTER_OPEN);
onFilterGroupChanged(FILTER_OPEN);
}, [setFilterGroup, onFilterGroupChanged]);
return (
<EuiFilterGroup>
<EuiFilterButton
hasActiveFilters={filterGroup === FILTER_OPEN}
onClick={onClickOpenFilterCallback}
withNext
>
{i18n.OPEN_SIGNALS}
</EuiFilterButton>
const onClickCloseFilterCallback = useCallback(() => {
setFilterGroup(FILTER_CLOSED);
onFilterGroupChanged(FILTER_CLOSED);
}, [setFilterGroup, onFilterGroupChanged]);
<EuiFilterButton
hasActiveFilters={filterGroup === FILTER_CLOSED}
onClick={onClickCloseFilterCallback}
>
{i18n.CLOSED_SIGNALS}
</EuiFilterButton>
</EuiFilterGroup>
);
}
);
return (
<EuiFilterGroup>
<EuiFilterButton
hasActiveFilters={filterGroup === FILTER_OPEN}
onClick={onClickOpenFilterCallback}
withNext
>
{i18n.OPEN_SIGNALS}
</EuiFilterButton>
<EuiFilterButton
hasActiveFilters={filterGroup === FILTER_CLOSED}
onClick={onClickCloseFilterCallback}
>
{i18n.CLOSED_SIGNALS}
</EuiFilterButton>
</EuiFilterGroup>
);
};
export const SignalsTableFilterGroup = React.memo(SignalsTableFilterGroupComponent);

View file

@ -33,103 +33,100 @@ interface SignalsUtilityBarProps {
updateSignalsStatus: UpdateSignalsStatus;
}
export const SignalsUtilityBar = React.memo<SignalsUtilityBarProps>(
({
areEventsLoading,
clearSelection,
totalCount,
selectedEventIds,
isFilteredToOpen,
selectAll,
showClearSelection,
updateSignalsStatus,
sendSignalsToTimeline,
}) => {
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
const SignalsUtilityBarComponent: React.FC<SignalsUtilityBarProps> = ({
areEventsLoading,
clearSelection,
totalCount,
selectedEventIds,
isFilteredToOpen,
selectAll,
showClearSelection,
updateSignalsStatus,
sendSignalsToTimeline,
}) => {
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
const getBatchItemsPopoverContent = useCallback(
(closePopover: () => void) => (
<EuiContextMenuPanel
items={getBatchItems(
areEventsLoading,
showClearSelection,
selectedEventIds,
updateSignalsStatus,
sendSignalsToTimeline,
closePopover,
isFilteredToOpen
)}
/>
),
[
areEventsLoading,
selectedEventIds,
updateSignalsStatus,
sendSignalsToTimeline,
isFilteredToOpen,
]
);
const getBatchItemsPopoverContent = useCallback(
(closePopover: () => void) => (
<EuiContextMenuPanel
items={getBatchItems(
areEventsLoading,
showClearSelection,
selectedEventIds,
updateSignalsStatus,
sendSignalsToTimeline,
closePopover,
isFilteredToOpen
)}
/>
),
[
areEventsLoading,
selectedEventIds,
updateSignalsStatus,
sendSignalsToTimeline,
isFilteredToOpen,
]
);
const formattedTotalCount = numeral(totalCount).format(defaultNumberFormat);
const formattedSelectedEventsCount = numeral(Object.keys(selectedEventIds).length).format(
defaultNumberFormat
);
const formattedTotalCount = numeral(totalCount).format(defaultNumberFormat);
const formattedSelectedEventsCount = numeral(Object.keys(selectedEventIds).length).format(
defaultNumberFormat
);
return (
<>
<UtilityBar>
<UtilityBarSection>
<UtilityBarGroup>
<UtilityBarText>
{i18n.SHOWING_SIGNALS(formattedTotalCount, totalCount)}
</UtilityBarText>
</UtilityBarGroup>
return (
<>
<UtilityBar>
<UtilityBarSection>
<UtilityBarGroup>
<UtilityBarText>{i18n.SHOWING_SIGNALS(formattedTotalCount, totalCount)}</UtilityBarText>
</UtilityBarGroup>
<UtilityBarGroup>
{totalCount > 0 && (
<>
<UtilityBarText>
{i18n.SELECTED_SIGNALS(
showClearSelection ? formattedTotalCount : formattedSelectedEventsCount,
showClearSelection ? totalCount : Object.keys(selectedEventIds).length
)}
</UtilityBarText>
<UtilityBarGroup>
{totalCount > 0 && (
<>
<UtilityBarText>
{i18n.SELECTED_SIGNALS(
showClearSelection ? formattedTotalCount : formattedSelectedEventsCount,
showClearSelection ? totalCount : Object.keys(selectedEventIds).length
)}
</UtilityBarText>
<UtilityBarAction
iconSide="right"
iconType="arrowDown"
popoverContent={getBatchItemsPopoverContent}
>
{i18n.BATCH_ACTIONS}
</UtilityBarAction>
<UtilityBarAction
iconSide="right"
iconType="arrowDown"
popoverContent={getBatchItemsPopoverContent}
>
{i18n.BATCH_ACTIONS}
</UtilityBarAction>
<UtilityBarAction
iconType="listAdd"
onClick={() => {
if (!showClearSelection) {
selectAll();
} else {
clearSelection();
}
}}
>
{showClearSelection
? i18n.CLEAR_SELECTION
: i18n.SELECT_ALL_SIGNALS(formattedTotalCount, totalCount)}
</UtilityBarAction>
</>
)}
</UtilityBarGroup>
</UtilityBarSection>
</UtilityBar>
</>
);
},
(prevProps, nextProps) => {
return (
prevProps.selectedEventIds === nextProps.selectedEventIds &&
prevProps.totalCount === nextProps.totalCount &&
prevProps.showClearSelection === nextProps.showClearSelection
);
}
<UtilityBarAction
iconType="listAdd"
onClick={() => {
if (!showClearSelection) {
selectAll();
} else {
clearSelection();
}
}}
>
{showClearSelection
? i18n.CLEAR_SELECTION
: i18n.SELECT_ALL_SIGNALS(formattedTotalCount, totalCount)}
</UtilityBarAction>
</>
)}
</UtilityBarGroup>
</UtilityBarSection>
</UtilityBar>
</>
);
};
export const SignalsUtilityBar = React.memo(
SignalsUtilityBarComponent,
(prevProps, nextProps) =>
prevProps.selectedEventIds === nextProps.selectedEventIds &&
prevProps.totalCount === nextProps.totalCount &&
prevProps.showClearSelection === nextProps.showClearSelection
);

View file

@ -23,7 +23,7 @@ export const sampleChartOptions = [
{ text: 'Top users', value: 'users' },
];
export const SignalsCharts = memo(() => (
const SignalsChartsComponent = () => (
<EuiPanel>
<HeaderSection title="Signal detection frequency">
<EuiSelect
@ -36,4 +36,6 @@ export const SignalsCharts = memo(() => (
<HistogramSignals />
</EuiPanel>
));
);
export const SignalsCharts = memo(SignalsChartsComponent);

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable react/display-name */
import {
EuiTableActionsColumnType,
EuiBasicTableColumn,

View file

@ -5,7 +5,7 @@
*/
import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import React, { memo } from 'react';
import React from 'react';
import { RuleStatusIcon, RuleStatusIconProps } from '../status_icon';
@ -13,7 +13,7 @@ interface AccordionTitleProps extends RuleStatusIconProps {
title: string;
}
export const AccordionTitle = memo<AccordionTitleProps>(({ name, title, type }) => (
const AccordionTitleComponent: React.FC<AccordionTitleProps> = ({ name, title, type }) => (
<EuiFlexGroup alignItems="center" gutterSize="m" responsive={false}>
<EuiFlexItem grow={false}>
<RuleStatusIcon name={name} type={type} />
@ -24,4 +24,6 @@ export const AccordionTitle = memo<AccordionTitleProps>(({ name, title, type })
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
));
);
export const AccordionTitle = React.memo(AccordionTitleComponent);

View file

@ -16,7 +16,7 @@ interface Props {
valueLabel?: string;
}
export const FilterLabel = memo<Props>(({ filter, valueLabel }) => {
const FilterLabelComponent: React.FC<Props> = ({ filter, valueLabel }) => {
const prefixText = filter.meta.negate
? ` ${i18n.translate('xpack.siem.detectionEngine.createRule.filterLabel.negatedFilterPrefix', {
defaultMessage: 'NOT ',
@ -90,4 +90,6 @@ export const FilterLabel = memo<Props>(({ filter, valueLabel }) => {
</>
);
}
});
};
export const FilterLabel = memo(FilterLabelComponent);

View file

@ -68,34 +68,35 @@ const MyEuiTextArea = styled(EuiTextArea)`
height: 80px;
`;
export const StepRuleDescription = memo<StepRuleDescriptionProps>(
({ data, direction = 'row', indexPatterns, schema }) => {
const kibana = useKibana();
const [filterManager] = useState<FilterManager>(new FilterManager(kibana.services.uiSettings));
const StepRuleDescriptionComponent: React.FC<StepRuleDescriptionProps> = ({
data,
direction = 'row',
indexPatterns,
schema,
}) => {
const kibana = useKibana();
const [filterManager] = useState<FilterManager>(new FilterManager(kibana.services.uiSettings));
const keys = Object.keys(schema);
const listItems = keys.reduce(
(acc: ListItems[], key: string) => [
...acc,
...buildListItems(data, pick(key, schema), filterManager, indexPatterns),
],
[]
);
return (
<EuiFlexGroup gutterSize="none" direction={direction} justifyContent="spaceAround">
{chunk(Math.ceil(listItems.length / 2), listItems).map((chunckListItems, index) => (
<EuiFlexItemWidth
direction={direction}
key={`description-step-rule-${index}`}
grow={false}
>
<EuiDescriptionList listItems={chunckListItems} compressed />
</EuiFlexItemWidth>
))}
</EuiFlexGroup>
);
}
);
const keys = Object.keys(schema);
const listItems = keys.reduce(
(acc: ListItems[], key: string) => [
...acc,
...buildListItems(data, pick(key, schema), filterManager, indexPatterns),
],
[]
);
return (
<EuiFlexGroup gutterSize="none" direction={direction} justifyContent="spaceAround">
{chunk(Math.ceil(listItems.length / 2), listItems).map((chunckListItems, index) => (
<EuiFlexItemWidth direction={direction} key={`description-step-rule-${index}`} grow={false}>
<EuiDescriptionList listItems={chunckListItems} compressed />
</EuiFlexItemWidth>
))}
</EuiFlexGroup>
);
};
export const StepRuleDescription = memo(StepRuleDescriptionComponent);
interface ListItems {
title: NonNullable<ReactNode>;

View file

@ -25,7 +25,7 @@ const RuleStatusIconStyled = styled.div`
}
`;
export const RuleStatusIcon = memo<RuleStatusIconProps>(({ name, type }) => {
const RuleStatusIconComponent: React.FC<RuleStatusIconProps> = ({ name, type }) => {
const theme = useEuiTheme();
const color = type === 'passive' ? theme.euiColorLightestShade : theme.euiColorDarkestShade;
return (
@ -34,4 +34,6 @@ export const RuleStatusIcon = memo<RuleStatusIconProps>(({ name, type }) => {
{type === 'valid' ? <EuiIcon type="check" color={theme.euiColorEmptyShade} size="l" /> : null}
</RuleStatusIconStyled>
);
});
};
export const RuleStatusIcon = memo(RuleStatusIconComponent);

View file

@ -17,15 +17,15 @@ interface StepPanelProps {
}
const MyPanel = styled(EuiPanel)`
poistion: relative;
position: relative;
`;
export const StepPanel = memo<StepPanelProps>(({ children, loading, title }) => {
return (
<MyPanel>
{loading && <EuiProgress size="xs" color="accent" position="absolute" />}
<HeaderSection title={title} />
{children}
</MyPanel>
);
});
const StepPanelComponent: React.FC<StepPanelProps> = ({ children, loading, title }) => (
<MyPanel>
{loading && <EuiProgress size="xs" color="accent" position="absolute" />}
<HeaderSection title={title} />
{children}
</MyPanel>
);
export const StepPanel = memo(StepPanelComponent);

View file

@ -17,7 +17,6 @@ import * as i18n from './translations';
const TimelinesContainer = styled.div`
width: 100%;
`;
TimelinesContainer.displayName = 'TimelinesContainer';
interface TimelinesProps<TCache = object> {
apolloClient: ApolloClient<TCache>;
@ -27,7 +26,7 @@ type OwnProps = TimelinesProps;
export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10;
export const TimelinesPage = React.memo<OwnProps>(({ apolloClient }) => (
const TimelinesPageComponent: React.FC<OwnProps> = ({ apolloClient }) => (
<>
<WrapperPage>
<HeaderPage border title={i18n.PAGE_TITLE} />
@ -44,4 +43,6 @@ export const TimelinesPage = React.memo<OwnProps>(({ apolloClient }) => (
<SpyRoute />
</>
));
);
export const TimelinesPage = React.memo(TimelinesPageComponent);

View file

@ -16,7 +16,7 @@ interface RouterProps {
history: History;
}
export const PageRouter: FC<RouterProps> = memo(({ history }) => (
const PageRouterComponent: FC<RouterProps> = ({ history }) => (
<ManageRoutesSpy>
<Router history={history}>
<Switch>
@ -25,4 +25,6 @@ export const PageRouter: FC<RouterProps> = memo(({ history }) => (
</Switch>
</Router>
</ManageRoutesSpy>
));
);
export const PageRouter = memo(PageRouterComponent);

View file

@ -4,12 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { memo, useReducer } from 'react';
import React, { FC, memo, useReducer } from 'react';
import { ManageRoutesSpyProps, RouteSpyState, RouteSpyAction } from './types';
import { RouterSpyStateContext, initRouteSpy } from './helpers';
export const ManageRoutesSpy = memo(({ children }: ManageRoutesSpyProps) => {
const ManageRoutesSpyComponent: FC<ManageRoutesSpyProps> = ({ children }) => {
const reducerSpyRoute = (state: RouteSpyState, action: RouteSpyAction) => {
switch (action.type) {
case 'updateRoute':
@ -28,4 +28,6 @@ export const ManageRoutesSpy = memo(({ children }: ManageRoutesSpyProps) => {
{children}
</RouterSpyStateContext.Provider>
);
});
};
export const ManageRoutesSpy = memo(ManageRoutesSpyComponent);