Skip to content

Commit 7745d36

Browse files
[Security Solution] [Sourcerer] Make use of reselect in sourcerer selectors (#176916)
## Summary This pr should change nothing functionally, but changes the selectors used in components for sourcerer to make use of createSelector and benefit from memoization at all times,
1 parent b207ff4 commit 7745d36

File tree

29 files changed

+644
-598
lines changed

29 files changed

+644
-598
lines changed

x-pack/plugins/security_solution/public/assistant/send_to_timeline/index.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
* 2.0.
66
*/
77

8-
import React, { useCallback, useMemo } from 'react';
8+
import React, { useCallback } from 'react';
99
import { EuiButton, EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
1010
import type { Filter } from '@kbn/es-query';
11-
import { useDispatch } from 'react-redux';
11+
import { useDispatch, useSelector } from 'react-redux';
1212

1313
import { useAssistantContext } from '@kbn/elastic-assistant';
14-
import { useDeepEqualSelector } from '../../common/hooks/use_selector';
1514
import { sourcererSelectors } from '../../common/store';
1615
import { sourcererActions } from '../../common/store/actions';
1716
import { inputsActions } from '../../common/store/inputs';
@@ -63,13 +62,8 @@ export const SendToTimelineButton: React.FunctionComponent<SendToTimelineButtonP
6362

6463
const isEsqlTabInTimelineDisabled = useIsExperimentalFeatureEnabled('timelineEsqlTabDisabled');
6564

66-
const getDataViewsSelector = useMemo(
67-
() => sourcererSelectors.getSourcererDataViewsSelector(),
68-
[]
69-
);
70-
const { defaultDataView, signalIndexName } = useDeepEqualSelector((state) =>
71-
getDataViewsSelector(state)
72-
);
65+
const signalIndexName = useSelector(sourcererSelectors.signalIndexName);
66+
const defaultDataView = useSelector(sourcererSelectors.defaultDataView);
7367

7468
const hasTemplateProviders =
7569
dataProviders && dataProviders.find((provider) => provider.type === 'template');

x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
* 2.0.
66
*/
77

8-
import React, { useMemo, useCallback } from 'react';
8+
import React, { useCallback } from 'react';
99
import { EuiButton, EuiButtonEmpty } from '@elastic/eui';
1010
import type { IconType } from '@elastic/eui';
1111
import type { Filter } from '@kbn/es-query';
12-
import { useDispatch } from 'react-redux';
12+
import { useDispatch, useSelector } from 'react-redux';
1313

1414
import { sourcererSelectors } from '../../../store';
1515
import { InputsModelId } from '../../../store/inputs/constants';
@@ -23,7 +23,6 @@ import { TimelineId } from '../../../../../common/types/timeline';
2323
import { TimelineType } from '../../../../../common/api/timeline';
2424
import { useCreateTimeline } from '../../../../timelines/hooks/use_create_timeline';
2525
import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../../detections/components/alerts_table/translations';
26-
import { useDeepEqualSelector } from '../../../hooks/use_selector';
2726

2827
export interface InvestigateInTimelineButtonProps {
2928
asEmptyButton: boolean;
@@ -49,13 +48,8 @@ export const InvestigateInTimelineButton: React.FunctionComponent<
4948
}) => {
5049
const dispatch = useDispatch();
5150

52-
const getDataViewsSelector = useMemo(
53-
() => sourcererSelectors.getSourcererDataViewsSelector(),
54-
[]
55-
);
56-
const { defaultDataView, signalIndexName } = useDeepEqualSelector((state) =>
57-
getDataViewsSelector(state)
58-
);
51+
const signalIndexName = useSelector(sourcererSelectors.signalIndexName);
52+
const defaultDataView = useSelector(sourcererSelectors.defaultDataView);
5953

6054
const hasTemplateProviders =
6155
dataProviders && dataProviders.find((provider) => provider.type === 'template');

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

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ import {
1515
} from '@elastic/eui';
1616
import type { ChangeEventHandler } from 'react';
1717
import React, { useCallback, useEffect, useMemo, useState } from 'react';
18-
import { useDispatch } from 'react-redux';
18+
import { useDispatch, useSelector } from 'react-redux';
1919

2020
import * as i18n from './translations';
2121
import type { sourcererModel } from '../../store/sourcerer';
2222
import { sourcererActions, sourcererSelectors } from '../../store/sourcerer';
23-
import { useDeepEqualSelector } from '../../hooks/use_selector';
2423
import type { SourcererUrlState } from '../../store/sourcerer/model';
24+
import type { State } from '../../store';
2525
import type { ModifiedTypes } from './use_pick_index_patterns';
2626
import { SourcererScopeName } from '../../store/sourcerer/model';
2727
import { usePickIndexPatterns } from './use_pick_index_patterns';
@@ -129,17 +129,18 @@ export const Sourcerer = React.memo<SourcererComponentProps>(({ scope: scopeId }
129129
const isDefaultSourcerer = scopeId === SourcererScopeName.default;
130130
const updateUrlParam = useUpdateUrlParam<SourcererUrlState>(URL_PARAM_KEY.sourcerer);
131131

132-
const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []);
133-
const {
134-
defaultDataView,
135-
kibanaDataViews,
136-
signalIndexName,
137-
sourcererScope: {
138-
selectedDataViewId,
139-
selectedPatterns,
140-
missingPatterns: sourcererMissingPatterns,
141-
},
142-
} = useDeepEqualSelector((state) => sourcererScopeSelector(state, scopeId));
132+
const signalIndexName = useSelector(sourcererSelectors.signalIndexName);
133+
const defaultDataView = useSelector(sourcererSelectors.defaultDataView);
134+
const kibanaDataViews = useSelector(sourcererSelectors.kibanaDataViews);
135+
const selectedDataViewId = useSelector((state: State) => {
136+
return sourcererSelectors.sourcererScopeSelectedDataViewId(state, scopeId);
137+
});
138+
const selectedPatterns = useSelector((state: State) => {
139+
return sourcererSelectors.sourcererScopeSelectedPatterns(state, scopeId);
140+
});
141+
const sourcererMissingPatterns = useSelector((state: State) => {
142+
return sourcererSelectors.sourcererScopeMissingPatterns(state, scopeId);
143+
});
143144
const { pollForSignalIndex } = useSignalHelpers();
144145

145146
useEffect(() => {

x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
9-
import { useDispatch } from 'react-redux';
9+
import { useDispatch, useSelector } from 'react-redux';
1010
import { i18n } from '@kbn/i18n';
1111
import { matchPath } from 'react-router-dom';
1212
import { sourcererActions, sourcererSelectors } from '../../store/sourcerer';
@@ -36,6 +36,7 @@ import { useAppToasts } from '../../hooks/use_app_toasts';
3636
import { createSourcererDataView } from './create_sourcerer_data_view';
3737
import { getDataViewStateFromIndexFields, useDataView } from '../source/use_data_view';
3838
import { useFetchIndex } from '../source';
39+
import type { State } from '../../store';
3940
import { useInitializeUrlParam, useUpdateUrlParam } from '../../utils/global_query_string';
4041
import { URL_PARAM_KEY } from '../../hooks/use_url_state';
4142
import { sortWithExcludesAtEnd } from '../../../../common/utils/sourcerer';
@@ -54,14 +55,8 @@ export const useInitSourcerer = (
5455
const { loading: loadingSignalIndex, isSignalIndexExists, signalIndexName } = useUserInfo();
5556
const updateUrlParam = useUpdateUrlParam<SourcererUrlState>(URL_PARAM_KEY.sourcerer);
5657

57-
const getDataViewsSelector = useMemo(
58-
() => sourcererSelectors.getSourcererDataViewsSelector(),
59-
[]
60-
);
61-
const { defaultDataView, signalIndexName: signalIndexNameSourcerer } = useDeepEqualSelector(
62-
(state) => getDataViewsSelector(state)
63-
);
64-
58+
const signalIndexNameSourcerer = useSelector(sourcererSelectors.signalIndexName);
59+
const defaultDataView = useSelector(sourcererSelectors.defaultDataView);
6560
const { addError, addWarning } = useAppToasts();
6661

6762
useEffect(() => {
@@ -83,19 +78,29 @@ export const useInitSourcerer = (
8378
getTimelineSelector(state, TimelineId.active)
8479
);
8580

86-
const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []);
87-
const {
88-
sourcererScope: { selectedDataViewId: scopeDataViewId, selectedPatterns, missingPatterns },
89-
} = useDeepEqualSelector((state) => sourcererScopeSelector(state, scopeId));
81+
const scopeDataViewId = useSelector((state: State) => {
82+
return sourcererSelectors.sourcererScopeSelectedDataViewId(state, scopeId);
83+
});
84+
const selectedPatterns = useSelector((state: State) => {
85+
return sourcererSelectors.sourcererScopeSelectedPatterns(state, scopeId);
86+
});
87+
const missingPatterns = useSelector((state: State) => {
88+
return sourcererSelectors.sourcererScopeMissingPatterns(state, scopeId);
89+
});
9090

91-
const {
92-
selectedDataView: timelineSelectedDataView,
93-
sourcererScope: {
94-
selectedDataViewId: timelineDataViewId,
95-
selectedPatterns: timelineSelectedPatterns,
96-
missingPatterns: timelineMissingPatterns,
97-
},
98-
} = useDeepEqualSelector((state) => sourcererScopeSelector(state, SourcererScopeName.timeline));
91+
const kibanaDataViews = useSelector(sourcererSelectors.kibanaDataViews);
92+
const timelineDataViewId = useSelector((state: State) => {
93+
return sourcererSelectors.sourcererScopeSelectedDataViewId(state, SourcererScopeName.timeline);
94+
});
95+
const timelineSelectedPatterns = useSelector((state: State) => {
96+
return sourcererSelectors.sourcererScopeSelectedPatterns(state, SourcererScopeName.timeline);
97+
});
98+
const timelineMissingPatterns = useSelector((state: State) => {
99+
return sourcererSelectors.sourcererScopeMissingPatterns(state, SourcererScopeName.timeline);
100+
});
101+
const timelineSelectedDataView = useMemo(() => {
102+
return kibanaDataViews.find((dataView) => dataView.id === timelineDataViewId);
103+
}, [kibanaDataViews, timelineDataViewId]);
99104

100105
const { indexFieldsSearch } = useDataView();
101106

@@ -387,26 +392,23 @@ export const useInitSourcerer = (
387392
export const useSourcererDataView = (
388393
scopeId: SourcererScopeName = SourcererScopeName.default
389394
): SelectedDataView => {
390-
const { getDataViewsSelector, getSourcererDataViewSelector, getScopeSelector } = useMemo(
391-
() => ({
392-
getDataViewsSelector: sourcererSelectors.getSourcererDataViewsSelector(),
393-
getSourcererDataViewSelector: sourcererSelectors.sourcererDataViewSelector(),
394-
getScopeSelector: sourcererSelectors.scopeIdSelector(),
395-
}),
396-
[]
397-
);
398-
const {
399-
defaultDataView,
400-
signalIndexName,
401-
selectedDataView,
402-
sourcererScope: { missingPatterns, selectedPatterns: scopeSelectedPatterns, loading },
403-
}: sourcererSelectors.SourcererScopeSelector = useDeepEqualSelector((state) => {
404-
const sourcererScope = getScopeSelector(state, scopeId);
405-
return {
406-
...getDataViewsSelector(state),
407-
selectedDataView: getSourcererDataViewSelector(state, sourcererScope.selectedDataViewId),
408-
sourcererScope,
409-
};
395+
const kibanaDataViews = useSelector(sourcererSelectors.kibanaDataViews);
396+
const signalIndexName = useSelector(sourcererSelectors.signalIndexName);
397+
const defaultDataView = useSelector(sourcererSelectors.defaultDataView);
398+
const selectedDataViewId = useSelector((state: State) => {
399+
return sourcererSelectors.sourcererScopeSelectedDataViewId(state, scopeId);
400+
});
401+
const selectedDataView = useMemo(() => {
402+
return kibanaDataViews.find((dataView) => dataView.id === selectedDataViewId);
403+
}, [kibanaDataViews, selectedDataViewId]);
404+
const loading = useSelector((state: State) => {
405+
return sourcererSelectors.sourcererScopeIsLoading(state, scopeId);
406+
});
407+
const scopeSelectedPatterns = useSelector((state: State) => {
408+
return sourcererSelectors.sourcererScopeSelectedPatterns(state, scopeId);
409+
});
410+
const missingPatterns = useSelector((state: State) => {
411+
return sourcererSelectors.sourcererScopeMissingPatterns(state, scopeId);
410412
});
411413

412414
const selectedPatterns = useMemo(

x-pack/plugins/security_solution/public/common/containers/sourcerer/use_signal_helpers.tsx

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

88
import { useCallback, useMemo, useRef } from 'react';
99
import { i18n } from '@kbn/i18n';
10-
import { useDispatch } from 'react-redux';
10+
import { useDispatch, useSelector } from 'react-redux';
1111
import { sourcererSelectors } from '../../store';
12-
import { useDeepEqualSelector } from '../../hooks/use_selector';
1312
import { useSourcererDataView } from '.';
1413
import { SourcererScopeName } from '../../store/sourcerer/model';
1514
import { useDataView } from '../source/use_data_view';
@@ -33,17 +32,8 @@ export const useSignalHelpers = (): {
3332
data: { dataViews },
3433
} = useKibana().services;
3534

36-
const getDefaultDataViewSelector = useMemo(
37-
() => sourcererSelectors.defaultDataViewSelector(),
38-
[]
39-
);
40-
const getSignalIndexNameSelector = useMemo(
41-
() => sourcererSelectors.signalIndexNameSelector(),
42-
[]
43-
);
44-
const signalIndexNameSourcerer = useDeepEqualSelector(getSignalIndexNameSelector);
45-
const defaultDataView = useDeepEqualSelector(getDefaultDataViewSelector);
46-
35+
const signalIndexNameSourcerer = useSelector(sourcererSelectors.signalIndexName);
36+
const defaultDataView = useSelector(sourcererSelectors.defaultDataView);
4737
const signalIndexNeedsInit = useMemo(
4838
() => !defaultDataView.title.includes(`${signalIndexNameSourcerer}`),
4939
[defaultDataView.title, signalIndexNameSourcerer]

x-pack/plugins/security_solution/public/common/hooks/use_get_field_spec.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,21 @@
55
* 2.0.
66
*/
77

8-
import { useCallback } from 'react';
8+
import { useCallback, useMemo } from 'react';
9+
import { useSelector } from 'react-redux';
910
import type { SourcererScopeName } from '../store/sourcerer/model';
10-
import { getSelectedDataviewSelector } from '../store/sourcerer/selectors';
11-
import { useDeepEqualSelector } from './use_selector';
12-
13-
// Calls it from the module scope due to non memoized selectors https://github.com/elastic/kibana/issues/159315
14-
const selectedDataviewSelector = getSelectedDataviewSelector();
11+
import { sourcererSelectors } from '../store/sourcerer';
12+
import type { State } from '../store';
1513

1614
export const useGetFieldSpec = (scopeId: SourcererScopeName) => {
17-
const dataView = useDeepEqualSelector((state) => selectedDataviewSelector(state, scopeId));
18-
15+
const kibanaDataViews = useSelector(sourcererSelectors.kibanaDataViews);
16+
const selectedDataViewId = useSelector((state: State) =>
17+
sourcererSelectors.sourcererScopeSelectedDataViewId(state, scopeId)
18+
);
19+
const dataView = useMemo(
20+
() => kibanaDataViews.find((dv) => dv.id === selectedDataViewId),
21+
[kibanaDataViews, selectedDataViewId]
22+
);
1923
return useCallback(
2024
(fieldName: string) => {
2125
const fields = dataView?.fields;

x-pack/plugins/security_solution/public/common/store/reducer.test.ts renamed to x-pack/plugins/security_solution/public/common/store/reducer.test.tsx

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,47 @@
44
* 2.0; you may not use this file except in compliance with the Elastic License
55
* 2.0.
66
*/
7-
7+
import React from 'react';
88
import { parseExperimentalConfigValue } from '../../../common/experimental_features';
99
import type { SecuritySubPlugins } from '../../app/types';
1010
import { createInitialState } from './reducer';
11-
import { mockIndexPattern, mockSourcererState } from '../mock';
11+
import { mockIndexPattern, mockSourcererState, TestProviders, createMockStore } from '../mock';
1212
import { useSourcererDataView } from '../containers/sourcerer';
13-
import { useDeepEqualSelector } from '../hooks/use_selector';
1413
import { renderHook } from '@testing-library/react-hooks';
1514
import { initialGroupingState } from './grouping/reducer';
1615
import { initialAnalyzerState } from '../../resolver/store/helpers';
1716

1817
jest.mock('../hooks/use_selector');
19-
jest.mock('../lib/kibana', () => ({
20-
KibanaServices: {
21-
get: jest.fn(() => ({ uiSettings: { get: () => ({ from: 'now-24h', to: 'now' }) } })),
22-
},
23-
}));
18+
jest.mock('../lib/kibana', () => {
19+
const original = jest.requireActual('../lib/kibana');
20+
return {
21+
...original,
22+
useKibana: () => ({
23+
...original.useKibana(),
24+
services: {
25+
...original.useKibana().services,
26+
upselling: {
27+
...original.useKibana().services.upselling,
28+
featureUsage: {
29+
...original.useKibana().services.upselling.featureUsage,
30+
hasShown: jest.fn(),
31+
},
32+
},
33+
},
34+
}),
35+
KibanaServices: {
36+
get: jest.fn(() => ({ uiSettings: { get: () => ({ from: 'now-24h', to: 'now' }) } })),
37+
},
38+
};
39+
});
2440
jest.mock('../containers/source', () => ({
2541
useFetchIndex: () => [
2642
false,
2743
{ indexes: [], indicesExist: true, indexPatterns: mockIndexPattern },
2844
],
2945
}));
3046

47+
// TODO: this is more of a hook test, a reducer is a pure function and should not need hooks and context to test.
3148
describe('createInitialState', () => {
3249
describe('sourcerer -> default -> indicesExist', () => {
3350
const mockPluginState = {} as Omit<
@@ -53,15 +70,13 @@ describe('createInitialState', () => {
5370
analyzer: initialAnalyzerState,
5471
}
5572
);
56-
beforeEach(() => {
57-
(useDeepEqualSelector as jest.Mock).mockImplementation((cb) => cb(initState));
58-
});
59-
afterEach(() => {
60-
(useDeepEqualSelector as jest.Mock).mockClear();
61-
});
6273

6374
test('indicesExist should be TRUE if patternList is NOT empty', async () => {
64-
const { result } = renderHook(() => useSourcererDataView());
75+
const { result } = renderHook(() => useSourcererDataView(), {
76+
wrapper: ({ children }) => (
77+
<TestProviders store={createMockStore(initState)}>{children}</TestProviders>
78+
),
79+
});
6580
expect(result.current.indicesExist).toEqual(true);
6681
});
6782

@@ -93,8 +108,11 @@ describe('createInitialState', () => {
93108
analyzer: initialAnalyzerState,
94109
}
95110
);
96-
(useDeepEqualSelector as jest.Mock).mockImplementation((cb) => cb(state));
97-
const { result } = renderHook(() => useSourcererDataView());
111+
const { result } = renderHook(() => useSourcererDataView(), {
112+
wrapper: ({ children }) => (
113+
<TestProviders store={createMockStore(state)}>{children}</TestProviders>
114+
),
115+
});
98116
expect(result.current.indicesExist).toEqual(false);
99117
});
100118
});

0 commit comments

Comments
 (0)