mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
25c3d5ea67
commit
81ef6e06bc
53 changed files with 836 additions and 702 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 ? (
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue