Skip to content

Commit 1eb8594

Browse files
committed
Move create field memoization to a hook
1 parent 01d2b96 commit 1eb8594

File tree

7 files changed

+102
-103
lines changed

7 files changed

+102
-103
lines changed

x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ jest.mock('../../../timelines/containers', () => ({
3232

3333
jest.mock('../../components/url_state/normalize_time_range.ts');
3434

35-
jest.mock('../../../common/containers/source', () => ({ useIndexFields: () => ({}) }));
36-
3735
const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock;
3836
jest.mock('use-resize-observer/polyfilled');
3937
mockUseResizeObserver.mockImplementation(() => ({}));

x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx

Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { ControlColumnProps, RowRenderer, TimelineId } from '../../../../common/
1616
import { timelineSelectors, timelineActions } from '../../../timelines/store/timeline';
1717
import type { SubsetTimelineModel, TimelineModel } from '../../../timelines/store/timeline/model';
1818
import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
19-
import { Filter, IndexPatternField } from '../../../../../../../src/plugins/data/public';
19+
import { Filter } from '../../../../../../../src/plugins/data/public';
2020
import { InspectButtonContainer } from '../inspect';
2121
import { useGlobalFullScreen } from '../../containers/use_full_screen';
2222
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
@@ -31,11 +31,7 @@ import { defaultControlColumn } from '../../../timelines/components/timeline/bod
3131
import { EventsViewer } from './events_viewer';
3232
import * as i18n from './translations';
3333
import { GraphOverlay } from '../../../timelines/components/graph_overlay';
34-
import { CreateFieldButton } from '../../../timelines/components/create_field_button';
35-
import { CreateFieldComponentType, tGridActions } from '../../../../../timelines/public';
36-
import { useIndexFields } from '../../containers/source';
37-
import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants';
38-
import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers';
34+
import { useCreateFieldButton } from '../../../timelines/components/create_field_button';
3935

4036
const EMPTY_CONTROL_COLUMNS: ControlColumnProps[] = [];
4137
const leadingControlColumns: ControlColumnProps[] = [
@@ -178,37 +174,7 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
178174
}, [id, timelineQuery, globalQuery]);
179175
const bulkActions = useMemo(() => ({ onAlertStatusActionSuccess }), [onAlertStatusActionSuccess]);
180176

181-
const { indexFieldsSearch } = useIndexFields(scopeId, false);
182-
183-
const createFieldComponent = useMemo(() => {
184-
const onCreateField = (field: IndexPatternField) => {
185-
// Fetch updated list of fields
186-
indexFieldsSearch(selectedDataViewId);
187-
// Add the new field to the event table
188-
dispatch(
189-
tGridActions.upsertColumn({
190-
column: {
191-
columnHeaderType: defaultColumnHeaderType,
192-
id: field.name,
193-
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
194-
},
195-
id,
196-
index: 1,
197-
})
198-
);
199-
};
200-
201-
// It receives onClick props from field browser in order to close the modal.
202-
const CreateFieldButtonComponent: CreateFieldComponentType = ({ onClick }) => (
203-
<CreateFieldButton
204-
selectedDataViewId={selectedDataViewId}
205-
onClick={onClick}
206-
onCreateField={onCreateField}
207-
/>
208-
);
209-
210-
return CreateFieldButtonComponent;
211-
}, [selectedDataViewId, dispatch, id, indexFieldsSearch]);
177+
const createFieldComponent = useCreateFieldButton(scopeId, id);
212178

213179
return (
214180
<>

x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.test.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
import { TestProviders } from '../../../common/mock';
1717
import { useKibana } from '../../../common/lib/kibana';
1818
import { DataView } from '../../../../../../../src/plugins/data/common';
19+
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
20+
import { TimelineId } from '../../../../common';
1921

2022
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
2123

@@ -38,7 +40,8 @@ describe('CreateFieldButton', () => {
3840
<CreateFieldButton
3941
selectedDataViewId={'dataViewId'}
4042
onClick={() => undefined}
41-
onCreateField={() => undefined}
43+
sourcererScope={SourcererScopeName.timeline}
44+
timelineId={TimelineId.detectionsPage}
4245
/>,
4346
{
4447
wrapper: TestProviders,
@@ -54,7 +57,8 @@ describe('CreateFieldButton', () => {
5457
<CreateFieldButton
5558
selectedDataViewId={'dataViewId'}
5659
onClick={() => undefined}
57-
onCreateField={() => undefined}
60+
sourcererScope={SourcererScopeName.timeline}
61+
timelineId={TimelineId.detectionsPage}
5862
/>,
5963
{
6064
wrapper: TestProviders,
@@ -74,7 +78,8 @@ describe('CreateFieldButton', () => {
7478
<CreateFieldButton
7579
selectedDataViewId={'dataViewId'}
7680
onClick={onClickParam}
77-
onCreateField={() => undefined}
81+
sourcererScope={SourcererScopeName.timeline}
82+
timelineId={TimelineId.detectionsPage}
7883
/>,
7984
{
8085
wrapper: TestProviders,

x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.tsx

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,46 @@
55
* 2.0.
66
*/
77

8-
import React, { useCallback, useEffect, useState } from 'react';
8+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
99
import { EuiButton } from '@elastic/eui';
1010
import styled from 'styled-components';
1111

12+
import { useDispatch } from 'react-redux';
1213
import { IndexPattern, IndexPatternField } from '../../../../../../../src/plugins/data/public';
1314
import { useKibana } from '../../../common/lib/kibana';
1415

1516
import * as i18n from './translations';
17+
import { CreateFieldComponentType, TimelineId } from '../../../../../timelines/common';
18+
import { tGridActions } from '../../../../../timelines/public';
19+
import { useIndexFields } from '../../../common/containers/source';
20+
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
21+
import { sourcererSelectors } from '../../../common/store';
22+
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
23+
import { SelectedDataView } from '../../../common/store/sourcerer/selectors';
24+
import { DEFAULT_COLUMN_MIN_WIDTH } from '../timeline/body/constants';
25+
import { defaultColumnHeaderType } from '../timeline/body/column_headers/default_headers';
1626

1727
interface CreateFieldButtonProps {
1828
selectedDataViewId: string;
1929
onClick: () => void;
20-
onCreateField: (field: IndexPatternField) => void;
30+
timelineId: TimelineId;
31+
sourcererScope: SourcererScopeName;
2132
}
2233
const StyledButton = styled(EuiButton)`
2334
margin-left: ${({ theme }) => theme.eui.paddingSizes.m};
2435
`;
2536

2637
export const CreateFieldButton = React.memo<CreateFieldButtonProps>(
27-
({ selectedDataViewId, onClick: onClickParam, onCreateField }) => {
38+
({ selectedDataViewId, onClick: onClickParam, sourcererScope, timelineId }) => {
39+
const [dataView, setDataView] = useState<IndexPattern | null>(null);
40+
const dispatch = useDispatch();
41+
42+
const { indexFieldsSearch } = useIndexFields(sourcererScope, false);
2843
const {
2944
indexPatternFieldEditor,
3045
data: { dataViews },
3146
} = useKibana().services;
3247

33-
const [dataView, setDataView] = useState<IndexPattern | null>(null);
34-
3548
useEffect(() => {
3649
dataViews.get(selectedDataViewId).then((dataViewResponse) => {
3750
setDataView(dataViewResponse);
@@ -42,11 +55,35 @@ export const CreateFieldButton = React.memo<CreateFieldButtonProps>(
4255
if (dataView) {
4356
indexPatternFieldEditor?.openEditor({
4457
ctx: { indexPattern: dataView },
45-
onSave: onCreateField,
58+
onSave: (field: IndexPatternField) => {
59+
// Fetch the updated list of fields
60+
indexFieldsSearch(selectedDataViewId);
61+
62+
// Add the new field to the event table
63+
dispatch(
64+
tGridActions.upsertColumn({
65+
column: {
66+
columnHeaderType: defaultColumnHeaderType,
67+
id: field.name,
68+
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
69+
},
70+
id: timelineId,
71+
index: 0,
72+
})
73+
);
74+
},
4675
});
4776
}
4877
onClickParam();
49-
}, [indexPatternFieldEditor, dataView, onClickParam, onCreateField]);
78+
}, [
79+
indexPatternFieldEditor,
80+
dataView,
81+
onClickParam,
82+
indexFieldsSearch,
83+
selectedDataViewId,
84+
dispatch,
85+
timelineId,
86+
]);
5087

5188
if (!indexPatternFieldEditor?.userPermissions.editIndexPattern()) {
5289
return null;
@@ -69,3 +106,33 @@ export const CreateFieldButton = React.memo<CreateFieldButtonProps>(
69106
);
70107

71108
CreateFieldButton.displayName = 'CreateFieldButton';
109+
110+
/**
111+
*
112+
* Returns a memoised 'CreateFieldButton' with only an 'onClick' property.
113+
*/
114+
export const useCreateFieldButton = (
115+
sourcererScope: SourcererScopeName,
116+
timelineId: TimelineId
117+
) => {
118+
const getSelectedDataView = useMemo(() => sourcererSelectors.getSelectedDataViewSelector(), []);
119+
const { dataViewId } = useDeepEqualSelector<SelectedDataView>((state) =>
120+
getSelectedDataView(state, sourcererScope)
121+
);
122+
123+
const createFieldComponent = useMemo(() => {
124+
// It receives onClick props from field browser in order to close the modal.
125+
const CreateFieldButtonComponent: CreateFieldComponentType = ({ onClick }) => (
126+
<CreateFieldButton
127+
selectedDataViewId={dataViewId}
128+
onClick={onClick}
129+
sourcererScope={sourcererScope}
130+
timelineId={timelineId}
131+
/>
132+
);
133+
134+
return CreateFieldButtonComponent;
135+
}, [dataViewId, sourcererScope, timelineId]);
136+
137+
return createFieldComponent;
138+
};

x-pack/plugins/security_solution/public/timelines/components/create_field_button/translations.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
import { i18n } from '@kbn/i18n';
99

10-
export const CREATE_FIELD = i18n.translate('xpack.timelines.fieldBrowser.createFieldButton', {
11-
defaultMessage: 'Create field',
12-
});
10+
export const CREATE_FIELD = i18n.translate(
11+
'xpack.securitySolution.fieldBrowser.createFieldButton',
12+
{
13+
defaultMessage: 'Create field',
14+
}
15+
);

x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@ import {
3232
} from '../../styles';
3333
import { Sort } from '../sort';
3434
import { ColumnHeader } from './column_header';
35-
import { CreateFieldComponentType } from '../../../../../../../timelines/public';
35+
36+
import { SourcererScopeName } from '../../../../../common/store/sourcerer/model';
37+
import { useCreateFieldButton } from '../../../create_field_button';
3638

3739
interface Props {
3840
actionsColumnWidth: number;
3941
browserFields: BrowserFields;
40-
createFieldComponent: CreateFieldComponentType;
4142
columnHeaders: ColumnHeaderOptions[];
4243
isEventViewer?: boolean;
4344
isSelectAllChecked: boolean;
@@ -88,7 +89,6 @@ export const ColumnHeadersComponent = ({
8889
actionsColumnWidth,
8990
browserFields,
9091
columnHeaders,
91-
createFieldComponent,
9292
isEventViewer = false,
9393
isSelectAllChecked,
9494
onSelectAll,
@@ -172,6 +172,11 @@ export const ColumnHeadersComponent = ({
172172
[trailingControlColumns]
173173
);
174174

175+
const createFieldComponent = useCreateFieldButton(
176+
SourcererScopeName.timeline,
177+
timelineId as TimelineId
178+
);
179+
175180
const LeadingHeaderActions = useMemo(() => {
176181
return leadingHeaderCells.map(
177182
(Header: React.ComponentType<HeaderActionProps> | React.ComponentType | undefined, index) => {

x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx

Lines changed: 3 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,14 @@
88
import { noop } from 'lodash/fp';
99
import memoizeOne from 'memoize-one';
1010
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
11-
import { connect, ConnectedProps, useDispatch } from 'react-redux';
11+
import { connect, ConnectedProps } from 'react-redux';
1212
import deepEqual from 'fast-deep-equal';
1313

1414
import {
1515
FIRST_ARIA_INDEX,
1616
ARIA_COLINDEX_ATTRIBUTE,
1717
ARIA_ROWINDEX_ATTRIBUTE,
1818
onKeyDownFocusHandler,
19-
CreateFieldComponentType,
20-
tGridActions,
2119
} from '../../../../../../timelines/public';
2220
import { CellValueElementProps } from '../cell_rendering';
2321
import { DEFAULT_COLUMN_MIN_WIDTH } from './constants';
@@ -29,9 +27,9 @@ import {
2927
TimelineId,
3028
TimelineTabs,
3129
} from '../../../../../common/types/timeline';
32-
import { BrowserFields, useIndexFields } from '../../../../common/containers/source';
30+
import { BrowserFields } from '../../../../common/containers/source';
3331
import { TimelineItem } from '../../../../../common/search_strategy/timeline';
34-
import { inputsModel, sourcererSelectors, State } from '../../../../common/store';
32+
import { inputsModel, State } from '../../../../common/store';
3533
import { TimelineModel } from '../../../store/timeline/model';
3634
import { timelineDefaults } from '../../../store/timeline/defaults';
3735
import { timelineActions, timelineSelectors } from '../../../store/timeline';
@@ -45,11 +43,6 @@ import { ColumnHeaders } from './column_headers';
4543
import { Events } from './events';
4644
import { DEFAULT_ICON_BUTTON_WIDTH } from '../helpers';
4745
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
48-
import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
49-
import { CreateFieldButton } from '../../create_field_button';
50-
import { SelectedDataView } from '../../../../common/store/sourcerer/selectors';
51-
import { IndexPatternField } from '../../../../../../../../src/plugins/data/public';
52-
import { defaultColumnHeaderType } from './column_headers/default_headers';
5346

5447
interface OwnProps {
5548
activePage: number;
@@ -231,43 +224,6 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
231224
[columnHeaders.length, containerRef, data.length]
232225
);
233226

234-
const getSelectedDataView = useMemo(() => sourcererSelectors.getSelectedDataViewSelector(), []);
235-
const { dataViewId } = useDeepEqualSelector<SelectedDataView>((state) =>
236-
getSelectedDataView(state, SourcererScopeName.timeline)
237-
);
238-
239-
const dispatch = useDispatch();
240-
const { indexFieldsSearch } = useIndexFields(SourcererScopeName.timeline, false);
241-
242-
const createFieldComponent = useMemo(() => {
243-
const onCreateField = (field: IndexPatternField) => {
244-
// Fetch updated list of fields
245-
indexFieldsSearch(dataViewId);
246-
// Add the new field to the event table
247-
dispatch(
248-
tGridActions.upsertColumn({
249-
column: {
250-
columnHeaderType: defaultColumnHeaderType,
251-
id: field.name,
252-
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
253-
},
254-
id,
255-
index: 0,
256-
})
257-
);
258-
};
259-
// It receives onClick props from field browser in order to close the modal.
260-
const CreateFieldButtonComponent: CreateFieldComponentType = ({ onClick }) => (
261-
<CreateFieldButton
262-
selectedDataViewId={dataViewId}
263-
onClick={onClick}
264-
onCreateField={onCreateField}
265-
/>
266-
);
267-
268-
return CreateFieldButtonComponent;
269-
}, [dataViewId, dispatch, id, indexFieldsSearch]);
270-
271227
return (
272228
<>
273229
<TimelineBody data-test-subj="timeline-body" ref={containerRef}>
@@ -284,7 +240,6 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
284240
actionsColumnWidth={actionsColumnWidth}
285241
browserFields={browserFields}
286242
columnHeaders={columnHeaders}
287-
createFieldComponent={createFieldComponent}
288243
isEventViewer={isEventViewer}
289244
isSelectAllChecked={isSelectAllChecked}
290245
onSelectAll={onSelectAll}

0 commit comments

Comments
 (0)