mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 03:01:21 -04:00
[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,
This commit is contained in:
parent
b207ff4534
commit
7745d36703
29 changed files with 633 additions and 587 deletions
|
@ -5,13 +5,12 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { EuiButton, EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
|
import { EuiButton, EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
|
||||||
import type { Filter } from '@kbn/es-query';
|
import type { Filter } from '@kbn/es-query';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { useAssistantContext } from '@kbn/elastic-assistant';
|
import { useAssistantContext } from '@kbn/elastic-assistant';
|
||||||
import { useDeepEqualSelector } from '../../common/hooks/use_selector';
|
|
||||||
import { sourcererSelectors } from '../../common/store';
|
import { sourcererSelectors } from '../../common/store';
|
||||||
import { sourcererActions } from '../../common/store/actions';
|
import { sourcererActions } from '../../common/store/actions';
|
||||||
import { inputsActions } from '../../common/store/inputs';
|
import { inputsActions } from '../../common/store/inputs';
|
||||||
|
@ -63,13 +62,8 @@ export const SendToTimelineButton: React.FunctionComponent<SendToTimelineButtonP
|
||||||
|
|
||||||
const isEsqlTabInTimelineDisabled = useIsExperimentalFeatureEnabled('timelineEsqlTabDisabled');
|
const isEsqlTabInTimelineDisabled = useIsExperimentalFeatureEnabled('timelineEsqlTabDisabled');
|
||||||
|
|
||||||
const getDataViewsSelector = useMemo(
|
const signalIndexName = useSelector(sourcererSelectors.signalIndexName);
|
||||||
() => sourcererSelectors.getSourcererDataViewsSelector(),
|
const defaultDataView = useSelector(sourcererSelectors.defaultDataView);
|
||||||
[]
|
|
||||||
);
|
|
||||||
const { defaultDataView, signalIndexName } = useDeepEqualSelector((state) =>
|
|
||||||
getDataViewsSelector(state)
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasTemplateProviders =
|
const hasTemplateProviders =
|
||||||
dataProviders && dataProviders.find((provider) => provider.type === 'template');
|
dataProviders && dataProviders.find((provider) => provider.type === 'template');
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useMemo, useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { EuiButton, EuiButtonEmpty } from '@elastic/eui';
|
import { EuiButton, EuiButtonEmpty } from '@elastic/eui';
|
||||||
import type { IconType } from '@elastic/eui';
|
import type { IconType } from '@elastic/eui';
|
||||||
import type { Filter } from '@kbn/es-query';
|
import type { Filter } from '@kbn/es-query';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { sourcererSelectors } from '../../../store';
|
import { sourcererSelectors } from '../../../store';
|
||||||
import { InputsModelId } from '../../../store/inputs/constants';
|
import { InputsModelId } from '../../../store/inputs/constants';
|
||||||
|
@ -23,7 +23,6 @@ import { TimelineId } from '../../../../../common/types/timeline';
|
||||||
import { TimelineType } from '../../../../../common/api/timeline';
|
import { TimelineType } from '../../../../../common/api/timeline';
|
||||||
import { useCreateTimeline } from '../../../../timelines/hooks/use_create_timeline';
|
import { useCreateTimeline } from '../../../../timelines/hooks/use_create_timeline';
|
||||||
import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../../detections/components/alerts_table/translations';
|
import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../../detections/components/alerts_table/translations';
|
||||||
import { useDeepEqualSelector } from '../../../hooks/use_selector';
|
|
||||||
|
|
||||||
export interface InvestigateInTimelineButtonProps {
|
export interface InvestigateInTimelineButtonProps {
|
||||||
asEmptyButton: boolean;
|
asEmptyButton: boolean;
|
||||||
|
@ -49,13 +48,8 @@ export const InvestigateInTimelineButton: React.FunctionComponent<
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const getDataViewsSelector = useMemo(
|
const signalIndexName = useSelector(sourcererSelectors.signalIndexName);
|
||||||
() => sourcererSelectors.getSourcererDataViewsSelector(),
|
const defaultDataView = useSelector(sourcererSelectors.defaultDataView);
|
||||||
[]
|
|
||||||
);
|
|
||||||
const { defaultDataView, signalIndexName } = useDeepEqualSelector((state) =>
|
|
||||||
getDataViewsSelector(state)
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasTemplateProviders =
|
const hasTemplateProviders =
|
||||||
dataProviders && dataProviders.find((provider) => provider.type === 'template');
|
dataProviders && dataProviders.find((provider) => provider.type === 'template');
|
||||||
|
|
|
@ -15,13 +15,13 @@ import {
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import type { ChangeEventHandler } from 'react';
|
import type { ChangeEventHandler } from 'react';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import * as i18n from './translations';
|
import * as i18n from './translations';
|
||||||
import type { sourcererModel } from '../../store/sourcerer';
|
import type { sourcererModel } from '../../store/sourcerer';
|
||||||
import { sourcererActions, sourcererSelectors } from '../../store/sourcerer';
|
import { sourcererActions, sourcererSelectors } from '../../store/sourcerer';
|
||||||
import { useDeepEqualSelector } from '../../hooks/use_selector';
|
|
||||||
import type { SourcererUrlState } from '../../store/sourcerer/model';
|
import type { SourcererUrlState } from '../../store/sourcerer/model';
|
||||||
|
import type { State } from '../../store';
|
||||||
import type { ModifiedTypes } from './use_pick_index_patterns';
|
import type { ModifiedTypes } from './use_pick_index_patterns';
|
||||||
import { SourcererScopeName } from '../../store/sourcerer/model';
|
import { SourcererScopeName } from '../../store/sourcerer/model';
|
||||||
import { usePickIndexPatterns } from './use_pick_index_patterns';
|
import { usePickIndexPatterns } from './use_pick_index_patterns';
|
||||||
|
@ -129,17 +129,18 @@ export const Sourcerer = React.memo<SourcererComponentProps>(({ scope: scopeId }
|
||||||
const isDefaultSourcerer = scopeId === SourcererScopeName.default;
|
const isDefaultSourcerer = scopeId === SourcererScopeName.default;
|
||||||
const updateUrlParam = useUpdateUrlParam<SourcererUrlState>(URL_PARAM_KEY.sourcerer);
|
const updateUrlParam = useUpdateUrlParam<SourcererUrlState>(URL_PARAM_KEY.sourcerer);
|
||||||
|
|
||||||
const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []);
|
const signalIndexName = useSelector(sourcererSelectors.signalIndexName);
|
||||||
const {
|
const defaultDataView = useSelector(sourcererSelectors.defaultDataView);
|
||||||
defaultDataView,
|
const kibanaDataViews = useSelector(sourcererSelectors.kibanaDataViews);
|
||||||
kibanaDataViews,
|
const selectedDataViewId = useSelector((state: State) => {
|
||||||
signalIndexName,
|
return sourcererSelectors.sourcererScopeSelectedDataViewId(state, scopeId);
|
||||||
sourcererScope: {
|
});
|
||||||
selectedDataViewId,
|
const selectedPatterns = useSelector((state: State) => {
|
||||||
selectedPatterns,
|
return sourcererSelectors.sourcererScopeSelectedPatterns(state, scopeId);
|
||||||
missingPatterns: sourcererMissingPatterns,
|
});
|
||||||
},
|
const sourcererMissingPatterns = useSelector((state: State) => {
|
||||||
} = useDeepEqualSelector((state) => sourcererScopeSelector(state, scopeId));
|
return sourcererSelectors.sourcererScopeMissingPatterns(state, scopeId);
|
||||||
|
});
|
||||||
const { pollForSignalIndex } = useSignalHelpers();
|
const { pollForSignalIndex } = useSignalHelpers();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { matchPath } from 'react-router-dom';
|
import { matchPath } from 'react-router-dom';
|
||||||
import { sourcererActions, sourcererSelectors } from '../../store/sourcerer';
|
import { sourcererActions, sourcererSelectors } from '../../store/sourcerer';
|
||||||
|
@ -36,6 +36,7 @@ import { useAppToasts } from '../../hooks/use_app_toasts';
|
||||||
import { createSourcererDataView } from './create_sourcerer_data_view';
|
import { createSourcererDataView } from './create_sourcerer_data_view';
|
||||||
import { getDataViewStateFromIndexFields, useDataView } from '../source/use_data_view';
|
import { getDataViewStateFromIndexFields, useDataView } from '../source/use_data_view';
|
||||||
import { useFetchIndex } from '../source';
|
import { useFetchIndex } from '../source';
|
||||||
|
import type { State } from '../../store';
|
||||||
import { useInitializeUrlParam, useUpdateUrlParam } from '../../utils/global_query_string';
|
import { useInitializeUrlParam, useUpdateUrlParam } from '../../utils/global_query_string';
|
||||||
import { URL_PARAM_KEY } from '../../hooks/use_url_state';
|
import { URL_PARAM_KEY } from '../../hooks/use_url_state';
|
||||||
import { sortWithExcludesAtEnd } from '../../../../common/utils/sourcerer';
|
import { sortWithExcludesAtEnd } from '../../../../common/utils/sourcerer';
|
||||||
|
@ -54,14 +55,8 @@ export const useInitSourcerer = (
|
||||||
const { loading: loadingSignalIndex, isSignalIndexExists, signalIndexName } = useUserInfo();
|
const { loading: loadingSignalIndex, isSignalIndexExists, signalIndexName } = useUserInfo();
|
||||||
const updateUrlParam = useUpdateUrlParam<SourcererUrlState>(URL_PARAM_KEY.sourcerer);
|
const updateUrlParam = useUpdateUrlParam<SourcererUrlState>(URL_PARAM_KEY.sourcerer);
|
||||||
|
|
||||||
const getDataViewsSelector = useMemo(
|
const signalIndexNameSourcerer = useSelector(sourcererSelectors.signalIndexName);
|
||||||
() => sourcererSelectors.getSourcererDataViewsSelector(),
|
const defaultDataView = useSelector(sourcererSelectors.defaultDataView);
|
||||||
[]
|
|
||||||
);
|
|
||||||
const { defaultDataView, signalIndexName: signalIndexNameSourcerer } = useDeepEqualSelector(
|
|
||||||
(state) => getDataViewsSelector(state)
|
|
||||||
);
|
|
||||||
|
|
||||||
const { addError, addWarning } = useAppToasts();
|
const { addError, addWarning } = useAppToasts();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -83,19 +78,29 @@ export const useInitSourcerer = (
|
||||||
getTimelineSelector(state, TimelineId.active)
|
getTimelineSelector(state, TimelineId.active)
|
||||||
);
|
);
|
||||||
|
|
||||||
const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []);
|
const scopeDataViewId = useSelector((state: State) => {
|
||||||
const {
|
return sourcererSelectors.sourcererScopeSelectedDataViewId(state, scopeId);
|
||||||
sourcererScope: { selectedDataViewId: scopeDataViewId, selectedPatterns, missingPatterns },
|
});
|
||||||
} = useDeepEqualSelector((state) => sourcererScopeSelector(state, scopeId));
|
const selectedPatterns = useSelector((state: State) => {
|
||||||
|
return sourcererSelectors.sourcererScopeSelectedPatterns(state, scopeId);
|
||||||
|
});
|
||||||
|
const missingPatterns = useSelector((state: State) => {
|
||||||
|
return sourcererSelectors.sourcererScopeMissingPatterns(state, scopeId);
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const kibanaDataViews = useSelector(sourcererSelectors.kibanaDataViews);
|
||||||
selectedDataView: timelineSelectedDataView,
|
const timelineDataViewId = useSelector((state: State) => {
|
||||||
sourcererScope: {
|
return sourcererSelectors.sourcererScopeSelectedDataViewId(state, SourcererScopeName.timeline);
|
||||||
selectedDataViewId: timelineDataViewId,
|
});
|
||||||
selectedPatterns: timelineSelectedPatterns,
|
const timelineSelectedPatterns = useSelector((state: State) => {
|
||||||
missingPatterns: timelineMissingPatterns,
|
return sourcererSelectors.sourcererScopeSelectedPatterns(state, SourcererScopeName.timeline);
|
||||||
},
|
});
|
||||||
} = useDeepEqualSelector((state) => sourcererScopeSelector(state, SourcererScopeName.timeline));
|
const timelineMissingPatterns = useSelector((state: State) => {
|
||||||
|
return sourcererSelectors.sourcererScopeMissingPatterns(state, SourcererScopeName.timeline);
|
||||||
|
});
|
||||||
|
const timelineSelectedDataView = useMemo(() => {
|
||||||
|
return kibanaDataViews.find((dataView) => dataView.id === timelineDataViewId);
|
||||||
|
}, [kibanaDataViews, timelineDataViewId]);
|
||||||
|
|
||||||
const { indexFieldsSearch } = useDataView();
|
const { indexFieldsSearch } = useDataView();
|
||||||
|
|
||||||
|
@ -387,26 +392,23 @@ export const useInitSourcerer = (
|
||||||
export const useSourcererDataView = (
|
export const useSourcererDataView = (
|
||||||
scopeId: SourcererScopeName = SourcererScopeName.default
|
scopeId: SourcererScopeName = SourcererScopeName.default
|
||||||
): SelectedDataView => {
|
): SelectedDataView => {
|
||||||
const { getDataViewsSelector, getSourcererDataViewSelector, getScopeSelector } = useMemo(
|
const kibanaDataViews = useSelector(sourcererSelectors.kibanaDataViews);
|
||||||
() => ({
|
const signalIndexName = useSelector(sourcererSelectors.signalIndexName);
|
||||||
getDataViewsSelector: sourcererSelectors.getSourcererDataViewsSelector(),
|
const defaultDataView = useSelector(sourcererSelectors.defaultDataView);
|
||||||
getSourcererDataViewSelector: sourcererSelectors.sourcererDataViewSelector(),
|
const selectedDataViewId = useSelector((state: State) => {
|
||||||
getScopeSelector: sourcererSelectors.scopeIdSelector(),
|
return sourcererSelectors.sourcererScopeSelectedDataViewId(state, scopeId);
|
||||||
}),
|
});
|
||||||
[]
|
const selectedDataView = useMemo(() => {
|
||||||
);
|
return kibanaDataViews.find((dataView) => dataView.id === selectedDataViewId);
|
||||||
const {
|
}, [kibanaDataViews, selectedDataViewId]);
|
||||||
defaultDataView,
|
const loading = useSelector((state: State) => {
|
||||||
signalIndexName,
|
return sourcererSelectors.sourcererScopeIsLoading(state, scopeId);
|
||||||
selectedDataView,
|
});
|
||||||
sourcererScope: { missingPatterns, selectedPatterns: scopeSelectedPatterns, loading },
|
const scopeSelectedPatterns = useSelector((state: State) => {
|
||||||
}: sourcererSelectors.SourcererScopeSelector = useDeepEqualSelector((state) => {
|
return sourcererSelectors.sourcererScopeSelectedPatterns(state, scopeId);
|
||||||
const sourcererScope = getScopeSelector(state, scopeId);
|
});
|
||||||
return {
|
const missingPatterns = useSelector((state: State) => {
|
||||||
...getDataViewsSelector(state),
|
return sourcererSelectors.sourcererScopeMissingPatterns(state, scopeId);
|
||||||
selectedDataView: getSourcererDataViewSelector(state, sourcererScope.selectedDataViewId),
|
|
||||||
sourcererScope,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedPatterns = useMemo(
|
const selectedPatterns = useMemo(
|
||||||
|
|
|
@ -7,9 +7,8 @@
|
||||||
|
|
||||||
import { useCallback, useMemo, useRef } from 'react';
|
import { useCallback, useMemo, useRef } from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { sourcererSelectors } from '../../store';
|
import { sourcererSelectors } from '../../store';
|
||||||
import { useDeepEqualSelector } from '../../hooks/use_selector';
|
|
||||||
import { useSourcererDataView } from '.';
|
import { useSourcererDataView } from '.';
|
||||||
import { SourcererScopeName } from '../../store/sourcerer/model';
|
import { SourcererScopeName } from '../../store/sourcerer/model';
|
||||||
import { useDataView } from '../source/use_data_view';
|
import { useDataView } from '../source/use_data_view';
|
||||||
|
@ -33,17 +32,8 @@ export const useSignalHelpers = (): {
|
||||||
data: { dataViews },
|
data: { dataViews },
|
||||||
} = useKibana().services;
|
} = useKibana().services;
|
||||||
|
|
||||||
const getDefaultDataViewSelector = useMemo(
|
const signalIndexNameSourcerer = useSelector(sourcererSelectors.signalIndexName);
|
||||||
() => sourcererSelectors.defaultDataViewSelector(),
|
const defaultDataView = useSelector(sourcererSelectors.defaultDataView);
|
||||||
[]
|
|
||||||
);
|
|
||||||
const getSignalIndexNameSelector = useMemo(
|
|
||||||
() => sourcererSelectors.signalIndexNameSelector(),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const signalIndexNameSourcerer = useDeepEqualSelector(getSignalIndexNameSelector);
|
|
||||||
const defaultDataView = useDeepEqualSelector(getDefaultDataViewSelector);
|
|
||||||
|
|
||||||
const signalIndexNeedsInit = useMemo(
|
const signalIndexNeedsInit = useMemo(
|
||||||
() => !defaultDataView.title.includes(`${signalIndexNameSourcerer}`),
|
() => !defaultDataView.title.includes(`${signalIndexNameSourcerer}`),
|
||||||
[defaultDataView.title, signalIndexNameSourcerer]
|
[defaultDataView.title, signalIndexNameSourcerer]
|
||||||
|
|
|
@ -5,17 +5,21 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import type { SourcererScopeName } from '../store/sourcerer/model';
|
import type { SourcererScopeName } from '../store/sourcerer/model';
|
||||||
import { getSelectedDataviewSelector } from '../store/sourcerer/selectors';
|
import { sourcererSelectors } from '../store/sourcerer';
|
||||||
import { useDeepEqualSelector } from './use_selector';
|
import type { State } from '../store';
|
||||||
|
|
||||||
// Calls it from the module scope due to non memoized selectors https://github.com/elastic/kibana/issues/159315
|
|
||||||
const selectedDataviewSelector = getSelectedDataviewSelector();
|
|
||||||
|
|
||||||
export const useGetFieldSpec = (scopeId: SourcererScopeName) => {
|
export const useGetFieldSpec = (scopeId: SourcererScopeName) => {
|
||||||
const dataView = useDeepEqualSelector((state) => selectedDataviewSelector(state, scopeId));
|
const kibanaDataViews = useSelector(sourcererSelectors.kibanaDataViews);
|
||||||
|
const selectedDataViewId = useSelector((state: State) =>
|
||||||
|
sourcererSelectors.sourcererScopeSelectedDataViewId(state, scopeId)
|
||||||
|
);
|
||||||
|
const dataView = useMemo(
|
||||||
|
() => kibanaDataViews.find((dv) => dv.id === selectedDataViewId),
|
||||||
|
[kibanaDataViews, selectedDataViewId]
|
||||||
|
);
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(fieldName: string) => {
|
(fieldName: string) => {
|
||||||
const fields = dataView?.fields;
|
const fields = dataView?.fields;
|
||||||
|
|
|
@ -4,23 +4,39 @@
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
import React from 'react';
|
||||||
import { parseExperimentalConfigValue } from '../../../common/experimental_features';
|
import { parseExperimentalConfigValue } from '../../../common/experimental_features';
|
||||||
import type { SecuritySubPlugins } from '../../app/types';
|
import type { SecuritySubPlugins } from '../../app/types';
|
||||||
import { createInitialState } from './reducer';
|
import { createInitialState } from './reducer';
|
||||||
import { mockIndexPattern, mockSourcererState } from '../mock';
|
import { mockIndexPattern, mockSourcererState, TestProviders, createMockStore } from '../mock';
|
||||||
import { useSourcererDataView } from '../containers/sourcerer';
|
import { useSourcererDataView } from '../containers/sourcerer';
|
||||||
import { useDeepEqualSelector } from '../hooks/use_selector';
|
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import { initialGroupingState } from './grouping/reducer';
|
import { initialGroupingState } from './grouping/reducer';
|
||||||
import { initialAnalyzerState } from '../../resolver/store/helpers';
|
import { initialAnalyzerState } from '../../resolver/store/helpers';
|
||||||
|
|
||||||
jest.mock('../hooks/use_selector');
|
jest.mock('../hooks/use_selector');
|
||||||
jest.mock('../lib/kibana', () => ({
|
jest.mock('../lib/kibana', () => {
|
||||||
|
const original = jest.requireActual('../lib/kibana');
|
||||||
|
return {
|
||||||
|
...original,
|
||||||
|
useKibana: () => ({
|
||||||
|
...original.useKibana(),
|
||||||
|
services: {
|
||||||
|
...original.useKibana().services,
|
||||||
|
upselling: {
|
||||||
|
...original.useKibana().services.upselling,
|
||||||
|
featureUsage: {
|
||||||
|
...original.useKibana().services.upselling.featureUsage,
|
||||||
|
hasShown: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
KibanaServices: {
|
KibanaServices: {
|
||||||
get: jest.fn(() => ({ uiSettings: { get: () => ({ from: 'now-24h', to: 'now' }) } })),
|
get: jest.fn(() => ({ uiSettings: { get: () => ({ from: 'now-24h', to: 'now' }) } })),
|
||||||
},
|
},
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
jest.mock('../containers/source', () => ({
|
jest.mock('../containers/source', () => ({
|
||||||
useFetchIndex: () => [
|
useFetchIndex: () => [
|
||||||
false,
|
false,
|
||||||
|
@ -28,6 +44,7 @@ jest.mock('../containers/source', () => ({
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// TODO: this is more of a hook test, a reducer is a pure function and should not need hooks and context to test.
|
||||||
describe('createInitialState', () => {
|
describe('createInitialState', () => {
|
||||||
describe('sourcerer -> default -> indicesExist', () => {
|
describe('sourcerer -> default -> indicesExist', () => {
|
||||||
const mockPluginState = {} as Omit<
|
const mockPluginState = {} as Omit<
|
||||||
|
@ -53,15 +70,13 @@ describe('createInitialState', () => {
|
||||||
analyzer: initialAnalyzerState,
|
analyzer: initialAnalyzerState,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
beforeEach(() => {
|
|
||||||
(useDeepEqualSelector as jest.Mock).mockImplementation((cb) => cb(initState));
|
|
||||||
});
|
|
||||||
afterEach(() => {
|
|
||||||
(useDeepEqualSelector as jest.Mock).mockClear();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('indicesExist should be TRUE if patternList is NOT empty', async () => {
|
test('indicesExist should be TRUE if patternList is NOT empty', async () => {
|
||||||
const { result } = renderHook(() => useSourcererDataView());
|
const { result } = renderHook(() => useSourcererDataView(), {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<TestProviders store={createMockStore(initState)}>{children}</TestProviders>
|
||||||
|
),
|
||||||
|
});
|
||||||
expect(result.current.indicesExist).toEqual(true);
|
expect(result.current.indicesExist).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -93,8 +108,11 @@ describe('createInitialState', () => {
|
||||||
analyzer: initialAnalyzerState,
|
analyzer: initialAnalyzerState,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
(useDeepEqualSelector as jest.Mock).mockImplementation((cb) => cb(state));
|
const { result } = renderHook(() => useSourcererDataView(), {
|
||||||
const { result } = renderHook(() => useSourcererDataView());
|
wrapper: ({ children }) => (
|
||||||
|
<TestProviders store={createMockStore(state)}>{children}</TestProviders>
|
||||||
|
),
|
||||||
|
});
|
||||||
expect(result.current.indicesExist).toEqual(false);
|
expect(result.current.indicesExist).toEqual(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -4,108 +4,93 @@
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import type { DataViewSpec } from '@kbn/data-views-plugin/common';
|
|
||||||
import type { State } from '../types';
|
import type { State } from '../types';
|
||||||
import type {
|
import type { SourcererModel } from './model';
|
||||||
SourcererDataView,
|
import { SourcererScopeName } from './model';
|
||||||
SourcererModel,
|
|
||||||
SourcererScope,
|
|
||||||
SourcererScopeName,
|
|
||||||
} from './model';
|
|
||||||
export const sourcererKibanaDataViewsSelector = ({
|
|
||||||
sourcerer,
|
|
||||||
}: State): SourcererModel['kibanaDataViews'] => sourcerer.kibanaDataViews;
|
|
||||||
|
|
||||||
export const sourcererSignalIndexNameSelector = ({ sourcerer }: State): string | null =>
|
const SOURCERER_SCOPE_MAX_SIZE = Object.keys(SourcererScopeName).length;
|
||||||
sourcerer.signalIndexName;
|
|
||||||
|
|
||||||
export const sourcererDefaultDataViewSelector = ({
|
const selectSourcerer = (state: State): SourcererModel => state.sourcerer;
|
||||||
sourcerer,
|
|
||||||
}: State): SourcererModel['defaultDataView'] => sourcerer.defaultDataView;
|
|
||||||
|
|
||||||
export const dataViewSelector = (
|
export const sourcererScopes = createSelector(
|
||||||
{ sourcerer }: State,
|
selectSourcerer,
|
||||||
id: string | null
|
(sourcerer) => sourcerer.sourcererScopes
|
||||||
): SourcererDataView | undefined =>
|
);
|
||||||
sourcerer.kibanaDataViews.find((dataView) => dataView.id === id);
|
|
||||||
|
|
||||||
export const sourcererScopeIdSelector = (
|
export const sourcererScope = createSelector(
|
||||||
{ sourcerer }: State,
|
sourcererScopes,
|
||||||
scopeId: SourcererScopeName
|
(state: State, scopeId: SourcererScopeName) => scopeId,
|
||||||
): SourcererScope => sourcerer.sourcererScopes[scopeId];
|
(scopes, scopeId) => scopes[scopeId],
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
maxSize: SOURCERER_SCOPE_MAX_SIZE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const scopeIdSelector = () => createSelector(sourcererScopeIdSelector, (scope) => scope);
|
export const sourcererScopeIsLoading = createSelector(sourcererScope, (scope) => scope.loading, {
|
||||||
|
memoizeOptions: {
|
||||||
|
maxSize: SOURCERER_SCOPE_MAX_SIZE,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const kibanaDataViewsSelector = () =>
|
export const sourcererScopeSelectedDataViewId = createSelector(
|
||||||
createSelector(sourcererKibanaDataViewsSelector, (dataViews) => dataViews);
|
sourcererScope,
|
||||||
|
(scope) => scope.selectedDataViewId,
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
maxSize: SOURCERER_SCOPE_MAX_SIZE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const signalIndexNameSelector = () =>
|
export const sourcererScopeSelectedPatterns = createSelector(
|
||||||
createSelector(sourcererSignalIndexNameSelector, (signalIndexName) => signalIndexName);
|
sourcererScope,
|
||||||
|
(scope) => scope.selectedPatterns,
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
maxSize: SOURCERER_SCOPE_MAX_SIZE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const defaultDataViewSelector = () =>
|
export const sourcererScopeMissingPatterns = createSelector(
|
||||||
createSelector(sourcererDefaultDataViewSelector, (dataViews) => dataViews);
|
sourcererScope,
|
||||||
|
(scope) => scope.missingPatterns,
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
maxSize: SOURCERER_SCOPE_MAX_SIZE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const sourcererDataViewSelector = () =>
|
export const kibanaDataViews = createSelector(
|
||||||
createSelector(dataViewSelector, (dataView) => dataView);
|
selectSourcerer,
|
||||||
|
(sourcerer) => sourcerer.kibanaDataViews,
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
maxSize: SOURCERER_SCOPE_MAX_SIZE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export interface SourcererScopeSelector extends Omit<SourcererModel, 'sourcererScopes'> {
|
export const defaultDataView = createSelector(
|
||||||
selectedDataView: SourcererDataView | undefined;
|
selectSourcerer,
|
||||||
sourcererScope: SourcererScope;
|
(sourcerer) => sourcerer.defaultDataView,
|
||||||
}
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
maxSize: SOURCERER_SCOPE_MAX_SIZE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const getSourcererDataViewsSelector = () => {
|
export const signalIndexName = createSelector(
|
||||||
const getKibanaDataViewsSelector = kibanaDataViewsSelector();
|
selectSourcerer,
|
||||||
const getDefaultDataViewSelector = defaultDataViewSelector();
|
(sourcerer) => sourcerer.signalIndexName,
|
||||||
const getSignalIndexNameSelector = signalIndexNameSelector();
|
{
|
||||||
return (state: State): Omit<SourcererModel, 'sourcererScopes'> => {
|
memoizeOptions: {
|
||||||
const kibanaDataViews = getKibanaDataViewsSelector(state);
|
maxSize: SOURCERER_SCOPE_MAX_SIZE,
|
||||||
const defaultDataView = getDefaultDataViewSelector(state);
|
},
|
||||||
const signalIndexName = getSignalIndexNameSelector(state);
|
}
|
||||||
|
);
|
||||||
return {
|
|
||||||
defaultDataView,
|
|
||||||
kibanaDataViews,
|
|
||||||
signalIndexName,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attn Future Developer
|
|
||||||
* Access sourcererScope.selectedPatterns from
|
|
||||||
* hook useSourcererDataView in `common/containers/sourcerer/index`
|
|
||||||
* in order to get exclude patterns for searches
|
|
||||||
* Access sourcererScope.selectedPatterns
|
|
||||||
* from this function for display purposes only
|
|
||||||
* */
|
|
||||||
export const getSourcererScopeSelector = () => {
|
|
||||||
const getDataViewsSelector = getSourcererDataViewsSelector();
|
|
||||||
const getSourcererDataViewSelector = sourcererDataViewSelector();
|
|
||||||
const getScopeSelector = scopeIdSelector();
|
|
||||||
|
|
||||||
return (state: State, scopeId: SourcererScopeName): SourcererScopeSelector => {
|
|
||||||
const dataViews = getDataViewsSelector(state);
|
|
||||||
const scope = getScopeSelector(state, scopeId);
|
|
||||||
const selectedDataView = getSourcererDataViewSelector(state, scope.selectedDataViewId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...dataViews,
|
|
||||||
selectedDataView,
|
|
||||||
sourcererScope: scope,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSelectedDataviewSelector = () => {
|
|
||||||
const getSourcererDataViewSelector = sourcererDataViewSelector();
|
|
||||||
const getScopeSelector = scopeIdSelector();
|
|
||||||
|
|
||||||
return (state: State, scopeId: SourcererScopeName): DataViewSpec | undefined => {
|
|
||||||
const scope = getScopeSelector(state, scopeId);
|
|
||||||
const selectedDataView = getSourcererDataViewSelector(state, scope.selectedDataViewId);
|
|
||||||
|
|
||||||
return selectedDataView?.dataView;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
|
@ -7,14 +7,11 @@
|
||||||
|
|
||||||
import { render, waitFor } from '@testing-library/react';
|
import { render, waitFor } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import * as redux from 'react-redux';
|
|
||||||
|
|
||||||
import '../../../../common/mock/match_media';
|
import '../../../../common/mock/match_media';
|
||||||
import { TestProviders } from '../../../../common/mock';
|
import { TestProviders, mockGlobalState, createMockStore } from '../../../../common/mock';
|
||||||
|
|
||||||
import { EmbeddedMapComponent } from './embedded_map';
|
import { EmbeddedMapComponent } from './embedded_map';
|
||||||
import { createEmbeddable } from './create_embeddable';
|
import { createEmbeddable } from './create_embeddable';
|
||||||
import { useSourcererDataView } from '../../../../common/containers/sourcerer';
|
|
||||||
import { getLayerList } from './map_config';
|
import { getLayerList } from './map_config';
|
||||||
import { useIsFieldInIndexPattern } from '../../../containers/fields';
|
import { useIsFieldInIndexPattern } from '../../../containers/fields';
|
||||||
import { buildTimeRangeFilter } from '../../../../detections/components/alerts_table/helpers';
|
import { buildTimeRangeFilter } from '../../../../detections/components/alerts_table/helpers';
|
||||||
|
@ -55,17 +52,47 @@ jest.mock('@kbn/embeddable-plugin/public', () => ({
|
||||||
EmbeddablePanel: jest.fn().mockReturnValue(<div data-test-subj="EmbeddablePanel" />),
|
EmbeddablePanel: jest.fn().mockReturnValue(<div data-test-subj="EmbeddablePanel" />),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const mockUseSourcererDataView = useSourcererDataView as jest.Mock;
|
|
||||||
const mockCreateEmbeddable = createEmbeddable as jest.Mock;
|
const mockCreateEmbeddable = createEmbeddable as jest.Mock;
|
||||||
const mockUseIsFieldInIndexPattern = useIsFieldInIndexPattern as jest.Mock;
|
const mockUseIsFieldInIndexPattern = useIsFieldInIndexPattern as jest.Mock;
|
||||||
const mockGetStorage = jest.fn();
|
const mockGetStorage = jest.fn();
|
||||||
const mockSetStorage = jest.fn();
|
const mockSetStorage = jest.fn();
|
||||||
const setQuery: jest.Mock = jest.fn();
|
const setQuery: jest.Mock = jest.fn();
|
||||||
const filebeatDataView = { id: '6f1eeb50-023d-11eb-bcb6-6ba0578012a9', title: 'filebeat-*' };
|
const filebeatDataView = {
|
||||||
const packetbeatDataView = { id: '28995490-023d-11eb-bcb6-6ba0578012a9', title: 'packetbeat-*' };
|
id: '6f1eeb50-023d-11eb-bcb6-6ba0578012a9',
|
||||||
const mockSelector = {
|
title: 'filebeat-*',
|
||||||
kibanaDataViews: [filebeatDataView, packetbeatDataView],
|
browserFields: {},
|
||||||
|
fields: {},
|
||||||
|
loading: false,
|
||||||
|
patternList: ['filebeat-*'],
|
||||||
|
dataView: {
|
||||||
|
id: '6f1eeb50-023d-11eb-bcb6-6ba0578012a9',
|
||||||
|
fields: {},
|
||||||
|
},
|
||||||
|
runtimeMappings: {},
|
||||||
|
indexFields: [],
|
||||||
};
|
};
|
||||||
|
const packetbeatDataView = {
|
||||||
|
id: '28995490-023d-11eb-bcb6-6ba0578012a9',
|
||||||
|
title: 'packetbeat-*',
|
||||||
|
browserFields: {},
|
||||||
|
fields: {},
|
||||||
|
loading: false,
|
||||||
|
patternList: ['packetbeat-*'],
|
||||||
|
dataView: {
|
||||||
|
id: '28995490-023d-11eb-bcb6-6ba0578012a9',
|
||||||
|
fields: {},
|
||||||
|
},
|
||||||
|
runtimeMappings: {},
|
||||||
|
indexFields: [],
|
||||||
|
};
|
||||||
|
const mockState = {
|
||||||
|
...mockGlobalState,
|
||||||
|
sourcerer: {
|
||||||
|
...mockGlobalState.sourcerer,
|
||||||
|
kibanaDataViews: [filebeatDataView, packetbeatDataView],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const defaultMockStore = createMockStore(mockState);
|
||||||
const mockUpdateInput = jest.fn();
|
const mockUpdateInput = jest.fn();
|
||||||
const embeddableValue = {
|
const embeddableValue = {
|
||||||
destroyed: false,
|
destroyed: false,
|
||||||
|
@ -106,8 +133,6 @@ describe('EmbeddedMapComponent', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setQuery.mockClear();
|
setQuery.mockClear();
|
||||||
mockGetStorage.mockReturnValue(true);
|
mockGetStorage.mockReturnValue(true);
|
||||||
jest.spyOn(redux, 'useSelector').mockReturnValue(mockSelector);
|
|
||||||
mockUseSourcererDataView.mockReturnValue({ selectedPatterns: ['filebeat-*', 'auditbeat-*'] });
|
|
||||||
mockCreateEmbeddable.mockResolvedValue(embeddableValue);
|
mockCreateEmbeddable.mockResolvedValue(embeddableValue);
|
||||||
mockUseIsFieldInIndexPattern.mockReturnValue(() => true);
|
mockUseIsFieldInIndexPattern.mockReturnValue(() => true);
|
||||||
|
|
||||||
|
@ -121,7 +146,7 @@ describe('EmbeddedMapComponent', () => {
|
||||||
|
|
||||||
test('renders', async () => {
|
test('renders', async () => {
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<TestProviders>
|
<TestProviders store={defaultMockStore}>
|
||||||
<EmbeddedMapComponent {...testProps} />
|
<EmbeddedMapComponent {...testProps} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
@ -132,7 +157,7 @@ describe('EmbeddedMapComponent', () => {
|
||||||
|
|
||||||
test('calls updateInput with time range filter', async () => {
|
test('calls updateInput with time range filter', async () => {
|
||||||
render(
|
render(
|
||||||
<TestProviders>
|
<TestProviders store={defaultMockStore}>
|
||||||
<EmbeddedMapComponent {...testProps} />
|
<EmbeddedMapComponent {...testProps} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
@ -146,7 +171,7 @@ describe('EmbeddedMapComponent', () => {
|
||||||
|
|
||||||
test('renders EmbeddablePanel from embeddable plugin', async () => {
|
test('renders EmbeddablePanel from embeddable plugin', async () => {
|
||||||
const { getByTestId, queryByTestId } = render(
|
const { getByTestId, queryByTestId } = render(
|
||||||
<TestProviders>
|
<TestProviders store={defaultMockStore}>
|
||||||
<EmbeddedMapComponent {...testProps} />
|
<EmbeddedMapComponent {...testProps} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
@ -159,13 +184,17 @@ describe('EmbeddedMapComponent', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders IndexPatternsMissingPrompt', async () => {
|
test('renders IndexPatternsMissingPrompt', async () => {
|
||||||
jest.spyOn(redux, 'useSelector').mockReturnValue({
|
const state = {
|
||||||
...mockSelector,
|
...mockGlobalState,
|
||||||
|
sourcerer: {
|
||||||
|
...mockGlobalState.sourcerer,
|
||||||
kibanaDataViews: [],
|
kibanaDataViews: [],
|
||||||
});
|
},
|
||||||
|
};
|
||||||
|
const store = createMockStore(state);
|
||||||
|
|
||||||
const { getByTestId, queryByTestId } = render(
|
const { getByTestId, queryByTestId } = render(
|
||||||
<TestProviders>
|
<TestProviders store={store}>
|
||||||
<EmbeddedMapComponent {...testProps} />
|
<EmbeddedMapComponent {...testProps} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
@ -180,7 +209,7 @@ describe('EmbeddedMapComponent', () => {
|
||||||
mockCreateEmbeddable.mockResolvedValue(null);
|
mockCreateEmbeddable.mockResolvedValue(null);
|
||||||
|
|
||||||
const { getByTestId, queryByTestId } = render(
|
const { getByTestId, queryByTestId } = render(
|
||||||
<TestProviders>
|
<TestProviders store={defaultMockStore}>
|
||||||
<EmbeddedMapComponent {...testProps} />
|
<EmbeddedMapComponent {...testProps} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
@ -194,7 +223,7 @@ describe('EmbeddedMapComponent', () => {
|
||||||
test('map hidden on close', async () => {
|
test('map hidden on close', async () => {
|
||||||
mockGetStorage.mockReturnValue(false);
|
mockGetStorage.mockReturnValue(false);
|
||||||
const { getByTestId, queryByTestId } = render(
|
const { getByTestId, queryByTestId } = render(
|
||||||
<TestProviders>
|
<TestProviders store={defaultMockStore}>
|
||||||
<EmbeddedMapComponent {...testProps} />
|
<EmbeddedMapComponent {...testProps} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
@ -210,7 +239,7 @@ describe('EmbeddedMapComponent', () => {
|
||||||
|
|
||||||
test('map visible on open', async () => {
|
test('map visible on open', async () => {
|
||||||
const { getByTestId, queryByTestId } = render(
|
const { getByTestId, queryByTestId } = render(
|
||||||
<TestProviders>
|
<TestProviders store={defaultMockStore}>
|
||||||
<EmbeddedMapComponent {...testProps} />
|
<EmbeddedMapComponent {...testProps} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
@ -225,8 +254,16 @@ describe('EmbeddedMapComponent', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('On mount, selects existing Kibana data views that match any selected index pattern', async () => {
|
test('On mount, selects existing Kibana data views that match any selected index pattern', async () => {
|
||||||
|
const state = {
|
||||||
|
...mockGlobalState,
|
||||||
|
sourcerer: {
|
||||||
|
...mockGlobalState.sourcerer,
|
||||||
|
kibanaDataViews: [filebeatDataView],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const store = createMockStore(state);
|
||||||
render(
|
render(
|
||||||
<TestProviders>
|
<TestProviders store={store}>
|
||||||
<EmbeddedMapComponent {...testProps} />
|
<EmbeddedMapComponent {...testProps} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
@ -237,11 +274,16 @@ describe('EmbeddedMapComponent', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('On rerender with new selected patterns, selects existing Kibana data views that match any selected index pattern', async () => {
|
test('On rerender with new selected patterns, selects existing Kibana data views that match any selected index pattern', async () => {
|
||||||
mockUseSourcererDataView.mockReturnValue({
|
const state = {
|
||||||
selectedPatterns: ['filebeat-*', 'auditbeat-*'],
|
...mockGlobalState,
|
||||||
});
|
sourcerer: {
|
||||||
|
...mockGlobalState.sourcerer,
|
||||||
|
kibanaDataViews: [filebeatDataView],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const store = createMockStore(state);
|
||||||
const { rerender } = render(
|
const { rerender } = render(
|
||||||
<TestProviders>
|
<TestProviders store={store}>
|
||||||
<EmbeddedMapComponent {...testProps} />
|
<EmbeddedMapComponent {...testProps} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
@ -249,11 +291,8 @@ describe('EmbeddedMapComponent', () => {
|
||||||
const dataViewArg = (getLayerList as jest.Mock).mock.calls[0][0];
|
const dataViewArg = (getLayerList as jest.Mock).mock.calls[0][0];
|
||||||
expect(dataViewArg).toEqual([filebeatDataView]);
|
expect(dataViewArg).toEqual([filebeatDataView]);
|
||||||
});
|
});
|
||||||
mockUseSourcererDataView.mockReturnValue({
|
|
||||||
selectedPatterns: ['filebeat-*', 'packetbeat-*'],
|
|
||||||
});
|
|
||||||
rerender(
|
rerender(
|
||||||
<TestProviders>
|
<TestProviders store={defaultMockStore}>
|
||||||
<EmbeddedMapComponent {...testProps} />
|
<EmbeddedMapComponent {...testProps} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
import { EuiAccordion, EuiLink, EuiText } from '@elastic/eui';
|
import { EuiAccordion, EuiLink, EuiText } from '@elastic/eui';
|
||||||
import React, { useCallback, useEffect, useState, useMemo } from 'react';
|
import React, { useCallback, useEffect, useState, useMemo } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import { createHtmlPortalNode, InPortal } from 'react-reverse-portal';
|
import { createHtmlPortalNode, InPortal } from 'react-reverse-portal';
|
||||||
import styled, { css } from 'styled-components';
|
import styled, { css } from 'styled-components';
|
||||||
import type { Filter, Query } from '@kbn/es-query';
|
import type { Filter, Query } from '@kbn/es-query';
|
||||||
|
@ -32,10 +33,9 @@ import * as i18n from './translations';
|
||||||
import { useKibana } from '../../../../common/lib/kibana';
|
import { useKibana } from '../../../../common/lib/kibana';
|
||||||
import { getLayerList } from './map_config';
|
import { getLayerList } from './map_config';
|
||||||
import { sourcererSelectors } from '../../../../common/store/sourcerer';
|
import { sourcererSelectors } from '../../../../common/store/sourcerer';
|
||||||
|
import type { State } from '../../../../common/store';
|
||||||
import type { SourcererDataView } from '../../../../common/store/sourcerer/model';
|
import type { SourcererDataView } from '../../../../common/store/sourcerer/model';
|
||||||
import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
|
import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
|
||||||
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
|
|
||||||
import { useSourcererDataView } from '../../../../common/containers/sourcerer';
|
|
||||||
|
|
||||||
export const NETWORK_MAP_VISIBLE = 'network_map_visbile';
|
export const NETWORK_MAP_VISIBLE = 'network_map_visbile';
|
||||||
|
|
||||||
|
@ -119,12 +119,10 @@ export const EmbeddedMapComponent = ({
|
||||||
|
|
||||||
const { addError } = useAppToasts();
|
const { addError } = useAppToasts();
|
||||||
|
|
||||||
const getDataViewsSelector = useMemo(
|
const kibanaDataViews = useSelector(sourcererSelectors.kibanaDataViews);
|
||||||
() => sourcererSelectors.getSourcererDataViewsSelector(),
|
const selectedPatterns = useSelector((state: State) => {
|
||||||
[]
|
return sourcererSelectors.sourcererScopeSelectedPatterns(state, SourcererScopeName.default);
|
||||||
);
|
});
|
||||||
const { kibanaDataViews } = useDeepEqualSelector((state) => getDataViewsSelector(state));
|
|
||||||
const { selectedPatterns } = useSourcererDataView(SourcererScopeName.default);
|
|
||||||
|
|
||||||
const isFieldInIndexPattern = useIsFieldInIndexPattern();
|
const isFieldInIndexPattern = useIsFieldInIndexPattern();
|
||||||
|
|
||||||
|
@ -250,7 +248,6 @@ export const EmbeddedMapComponent = ({
|
||||||
() => buildTimeRangeFilter(startDate, endDate),
|
() => buildTimeRangeFilter(startDate, endDate),
|
||||||
[startDate, endDate]
|
[startDate, endDate]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (embeddable != null) {
|
if (embeddable != null) {
|
||||||
// pass time range as filter instead of via timeRange param
|
// pass time range as filter instead of via timeRange param
|
||||||
|
|
|
@ -5,11 +5,9 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
|
|
||||||
import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
|
import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
|
||||||
import { sourcererActions } from '../../../../common/store/sourcerer';
|
import { sourcererActions } from '../../../../common/store/sourcerer';
|
||||||
import {
|
import {
|
||||||
|
@ -33,13 +31,8 @@ export interface Filter {
|
||||||
export const useNavigateToTimeline = () => {
|
export const useNavigateToTimeline = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const getDataViewsSelector = useMemo(
|
const signalIndexName = useSelector(sourcererSelectors.signalIndexName);
|
||||||
() => sourcererSelectors.getSourcererDataViewsSelector(),
|
const defaultDataView = useSelector(sourcererSelectors.defaultDataView);
|
||||||
[]
|
|
||||||
);
|
|
||||||
const { defaultDataView, signalIndexName } = useDeepEqualSelector((state) =>
|
|
||||||
getDataViewsSelector(state)
|
|
||||||
);
|
|
||||||
|
|
||||||
const clearTimeline = useCreateTimeline({
|
const clearTimeline = useCreateTimeline({
|
||||||
timelineId: TimelineId.active,
|
timelineId: TimelineId.active,
|
||||||
|
|
|
@ -109,6 +109,16 @@ export class Simulator {
|
||||||
this.store = createMockStore(
|
this.store = createMockStore(
|
||||||
{
|
{
|
||||||
...mockGlobalState,
|
...mockGlobalState,
|
||||||
|
sourcerer: {
|
||||||
|
...mockGlobalState.sourcerer,
|
||||||
|
sourcererScopes: {
|
||||||
|
...mockGlobalState.sourcerer.sourcererScopes,
|
||||||
|
analyzer: {
|
||||||
|
...mockGlobalState.sourcerer.sourcererScopes.default,
|
||||||
|
selectedPatterns: indices,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
analyzer: {
|
analyzer: {
|
||||||
[resolverComponentInstanceID]: EMPTY_RESOLVER,
|
[resolverComponentInstanceID]: EMPTY_RESOLVER,
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,6 +17,7 @@ import type { SideEffectSimulator, ResolverProps } from '../../types';
|
||||||
import { ResolverWithoutProviders } from '../../view/resolver_without_providers';
|
import { ResolverWithoutProviders } from '../../view/resolver_without_providers';
|
||||||
import { SideEffectContext } from '../../view/side_effect_context';
|
import { SideEffectContext } from '../../view/side_effect_context';
|
||||||
import type { State } from '../../../common/store/types';
|
import type { State } from '../../../common/store/types';
|
||||||
|
import { TestProviders } from '../../../common/mock';
|
||||||
|
|
||||||
enableMapSet();
|
enableMapSet();
|
||||||
|
|
||||||
|
@ -93,6 +94,7 @@ export const MockResolver = React.memo((props: MockResolverProps) => {
|
||||||
}, [props.rasterWidth, props.rasterHeight, props.sideEffectSimulator.controls, resolverElement]);
|
}, [props.rasterWidth, props.rasterHeight, props.sideEffectSimulator.controls, resolverElement]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<TestProviders>
|
||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<Router history={props.history}>
|
<Router history={props.history}>
|
||||||
<KibanaContextProvider services={props.coreStart}>
|
<KibanaContextProvider services={props.coreStart}>
|
||||||
|
@ -111,5 +113,6 @@ export const MockResolver = React.memo((props: MockResolverProps) => {
|
||||||
</KibanaContextProvider>
|
</KibanaContextProvider>
|
||||||
</Router>
|
</Router>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
|
</TestProviders>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,11 +20,15 @@ jest.mock('react-redux', () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mockRef = {
|
||||||
|
current: null,
|
||||||
|
};
|
||||||
|
|
||||||
describe('TimelineBottomBar', () => {
|
describe('TimelineBottomBar', () => {
|
||||||
test('should render all components for bottom bar', () => {
|
test('should render all components for bottom bar', () => {
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<TestProviders>
|
<TestProviders>
|
||||||
<TimelineBottomBar show={false} timelineId={TimelineId.test} />
|
<TimelineBottomBar show={false} timelineId={TimelineId.test} openToggleRef={mockRef} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -38,7 +42,7 @@ describe('TimelineBottomBar', () => {
|
||||||
test('should not render the event count badge if timeline is open', () => {
|
test('should not render the event count badge if timeline is open', () => {
|
||||||
const { queryByTestId } = render(
|
const { queryByTestId } = render(
|
||||||
<TestProviders>
|
<TestProviders>
|
||||||
<TimelineBottomBar show={true} timelineId={TimelineId.test} />
|
<TimelineBottomBar show={true} timelineId={TimelineId.test} openToggleRef={mockRef} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -50,7 +54,7 @@ describe('TimelineBottomBar', () => {
|
||||||
|
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<TestProviders>
|
<TestProviders>
|
||||||
<TimelineBottomBar show={true} timelineId={TimelineId.test} />
|
<TimelineBottomBar show={true} timelineId={TimelineId.test} openToggleRef={mockRef} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -26,12 +26,15 @@ interface TimelineBottomBarProps {
|
||||||
* True if the timeline modal is open
|
* True if the timeline modal is open
|
||||||
*/
|
*/
|
||||||
show: boolean;
|
show: boolean;
|
||||||
|
|
||||||
|
openToggleRef: React.MutableRefObject<null | HTMLAnchorElement | HTMLButtonElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders the bottom bar for timeline displayed or most of the pages within Security Solution.
|
* This component renders the bottom bar for timeline displayed or most of the pages within Security Solution.
|
||||||
*/
|
*/
|
||||||
export const TimelineBottomBar = React.memo<TimelineBottomBarProps>(({ show, timelineId }) => {
|
export const TimelineBottomBar = React.memo<TimelineBottomBarProps>(
|
||||||
|
({ show, timelineId, openToggleRef }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const openTimeline = useCallback(
|
const openTimeline = useCallback(
|
||||||
|
@ -55,6 +58,7 @@ export const TimelineBottomBar = React.memo<TimelineBottomBarProps>(({ show, tim
|
||||||
aria-label={i18n.OPEN_TIMELINE_BUTTON(title)}
|
aria-label={i18n.OPEN_TIMELINE_BUTTON(title)}
|
||||||
onClick={openTimeline}
|
onClick={openTimeline}
|
||||||
data-test-subj="timeline-bottom-bar-title-button"
|
data-test-subj="timeline-bottom-bar-title-button"
|
||||||
|
ref={openToggleRef}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</EuiLink>
|
</EuiLink>
|
||||||
|
@ -70,6 +74,7 @@ export const TimelineBottomBar = React.memo<TimelineBottomBarProps>(({ show, tim
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</EuiPanel>
|
</EuiPanel>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
TimelineBottomBar.displayName = 'TimelineBottomBar';
|
TimelineBottomBar.displayName = 'TimelineBottomBar';
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, act } from '@testing-library/react';
|
import { render, act } from '@testing-library/react';
|
||||||
|
import type { Store } from 'redux';
|
||||||
import type { UseFieldBrowserOptionsProps, UseFieldBrowserOptions, FieldEditorActionsRef } from '.';
|
import type { UseFieldBrowserOptionsProps, UseFieldBrowserOptions, FieldEditorActionsRef } from '.';
|
||||||
import { useFieldBrowserOptions } from '.';
|
import { useFieldBrowserOptions } from '.';
|
||||||
import type { Start } from '@kbn/data-view-field-editor-plugin/public/mocks';
|
import type { Start } from '@kbn/data-view-field-editor-plugin/public/mocks';
|
||||||
|
@ -27,22 +28,6 @@ let mockIndexPatternFieldEditor: Start;
|
||||||
jest.mock('../../../common/lib/kibana');
|
jest.mock('../../../common/lib/kibana');
|
||||||
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
||||||
|
|
||||||
const defaultDataviewState: {
|
|
||||||
missingPatterns: string[];
|
|
||||||
selectedDataViewId: string | null;
|
|
||||||
} = {
|
|
||||||
missingPatterns: [],
|
|
||||||
selectedDataViewId: 'security-solution',
|
|
||||||
};
|
|
||||||
const mockScopeIdSelector = jest.fn(() => defaultDataviewState);
|
|
||||||
jest.mock('../../../common/store', () => {
|
|
||||||
const original = jest.requireActual('../../../common/store');
|
|
||||||
return {
|
|
||||||
...original,
|
|
||||||
sourcererSelectors: { scopeIdSelector: () => mockScopeIdSelector },
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const mockIndexFieldsSearch = jest.fn();
|
const mockIndexFieldsSearch = jest.fn();
|
||||||
jest.mock('../../../common/containers/source/use_data_view', () => ({
|
jest.mock('../../../common/containers/source/use_data_view', () => ({
|
||||||
useDataView: () => ({
|
useDataView: () => ({
|
||||||
|
@ -57,8 +42,10 @@ const mockOnHide = jest.fn();
|
||||||
const runAllPromises = () => new Promise(setImmediate);
|
const runAllPromises = () => new Promise(setImmediate);
|
||||||
|
|
||||||
// helper function to render the hook
|
// helper function to render the hook
|
||||||
const renderUseFieldBrowserOptions = (props: Partial<UseFieldBrowserOptionsProps> = {}) =>
|
const renderUseFieldBrowserOptions = (
|
||||||
renderHook<UseFieldBrowserOptionsProps, ReturnType<UseFieldBrowserOptions>>(
|
props: Partial<UseFieldBrowserOptionsProps & { store?: Store }> = {}
|
||||||
|
) =>
|
||||||
|
renderHook<UseFieldBrowserOptionsProps & { store?: Store }, ReturnType<UseFieldBrowserOptions>>(
|
||||||
() =>
|
() =>
|
||||||
useFieldBrowserOptions({
|
useFieldBrowserOptions({
|
||||||
sourcererScope: SourcererScopeName.default,
|
sourcererScope: SourcererScopeName.default,
|
||||||
|
@ -67,7 +54,12 @@ const renderUseFieldBrowserOptions = (props: Partial<UseFieldBrowserOptionsProps
|
||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
wrapper: TestProviders,
|
wrapper: ({ children, store }) => {
|
||||||
|
if (store) {
|
||||||
|
return <TestProviders store={store}>{children}</TestProviders>;
|
||||||
|
}
|
||||||
|
return <TestProviders>{children}</TestProviders>;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -104,7 +96,6 @@ describe('useFieldBrowserOptions', () => {
|
||||||
...useKibanaMock().services.application.capabilities,
|
...useKibanaMock().services.application.capabilities,
|
||||||
indexPatterns: { save: true },
|
indexPatterns: { save: true },
|
||||||
};
|
};
|
||||||
mockScopeIdSelector.mockReturnValue(defaultDataviewState);
|
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -137,13 +128,6 @@ describe('useFieldBrowserOptions', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return the button when a dataView is not present', () => {
|
|
||||||
mockScopeIdSelector.mockReturnValue({ missingPatterns: [], selectedDataViewId: null });
|
|
||||||
const { result } = renderUseFieldBrowserOptions();
|
|
||||||
|
|
||||||
expect(result.current.createFieldButton).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call onHide when button is pressed', async () => {
|
it('should call onHide when button is pressed', async () => {
|
||||||
useKibanaMock().services.data.dataViews.get = () => Promise.resolve({} as DataView);
|
useKibanaMock().services.data.dataViews.get = () => Promise.resolve({} as DataView);
|
||||||
const { result } = await renderUpdatedUseFieldBrowserOptions();
|
const { result } = await renderUpdatedUseFieldBrowserOptions();
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import type { MutableRefObject } from 'react';
|
import type { MutableRefObject } from 'react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import type { DataViewField, DataView } from '@kbn/data-views-plugin/common';
|
import type { DataViewField, DataView } from '@kbn/data-views-plugin/common';
|
||||||
import type {
|
import type {
|
||||||
CreateFieldComponent,
|
CreateFieldComponent,
|
||||||
|
@ -14,9 +15,9 @@ import type {
|
||||||
} from '@kbn/triggers-actions-ui-plugin/public/types';
|
} from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||||
import type { ColumnHeaderOptions } from '../../../../common/types';
|
import type { ColumnHeaderOptions } from '../../../../common/types';
|
||||||
import { useDataView } from '../../../common/containers/source/use_data_view';
|
import { useDataView } from '../../../common/containers/source/use_data_view';
|
||||||
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
|
|
||||||
import { useKibana } from '../../../common/lib/kibana';
|
import { useKibana } from '../../../common/lib/kibana';
|
||||||
import { sourcererSelectors } from '../../../common/store';
|
import { sourcererSelectors } from '../../../common/store';
|
||||||
|
import type { State } from '../../../common/store';
|
||||||
import type { SourcererScopeName } from '../../../common/store/sourcerer/model';
|
import type { SourcererScopeName } from '../../../common/store/sourcerer/model';
|
||||||
import { defaultColumnHeaderType } from '../timeline/body/column_headers/default_headers';
|
import { defaultColumnHeaderType } from '../timeline/body/column_headers/default_headers';
|
||||||
import { DEFAULT_COLUMN_MIN_WIDTH } from '../timeline/body/constants';
|
import { DEFAULT_COLUMN_MIN_WIDTH } from '../timeline/body/constants';
|
||||||
|
@ -57,11 +58,12 @@ export const useFieldBrowserOptions: UseFieldBrowserOptions = ({
|
||||||
dataViewFieldEditor,
|
dataViewFieldEditor,
|
||||||
data: { dataViews },
|
data: { dataViews },
|
||||||
} = useKibana().services;
|
} = useKibana().services;
|
||||||
|
const missingPatterns = useSelector((state: State) => {
|
||||||
const scopeIdSelector = useMemo(() => sourcererSelectors.scopeIdSelector(), []);
|
return sourcererSelectors.sourcererScopeMissingPatterns(state, sourcererScope);
|
||||||
const { missingPatterns, selectedDataViewId } = useDeepEqualSelector((state) =>
|
});
|
||||||
scopeIdSelector(state, sourcererScope)
|
const selectedDataViewId = useSelector((state: State) => {
|
||||||
);
|
return sourcererSelectors.sourcererScopeSelectedDataViewId(state, sourcererScope);
|
||||||
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let ignore = false;
|
let ignore = false;
|
||||||
const fetchAndSetDataView = async (dataViewId: string) => {
|
const fetchAndSetDataView = async (dataViewId: string) => {
|
||||||
|
|
|
@ -10,9 +10,9 @@ import React from 'react';
|
||||||
import { NewTimelineButton } from './new_timeline_button';
|
import { NewTimelineButton } from './new_timeline_button';
|
||||||
import { TimelineId } from '../../../../../common/types';
|
import { TimelineId } from '../../../../../common/types';
|
||||||
import { timelineActions } from '../../../store';
|
import { timelineActions } from '../../../store';
|
||||||
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
|
|
||||||
import { useDiscoverInTimelineContext } from '../../../../common/components/discover_in_timeline/use_discover_in_timeline_context';
|
import { useDiscoverInTimelineContext } from '../../../../common/components/discover_in_timeline/use_discover_in_timeline_context';
|
||||||
import { defaultHeaders } from '../../timeline/body/column_headers/default_headers';
|
import { defaultHeaders } from '../../timeline/body/column_headers/default_headers';
|
||||||
|
import { TestProviders } from '../../../../common/mock';
|
||||||
|
|
||||||
jest.mock('../../../../common/components/discover_in_timeline/use_discover_in_timeline_context');
|
jest.mock('../../../../common/components/discover_in_timeline/use_discover_in_timeline_context');
|
||||||
jest.mock('../../../../common/hooks/use_selector');
|
jest.mock('../../../../common/hooks/use_selector');
|
||||||
|
@ -26,11 +26,11 @@ jest.mock('react-redux', () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderNewTimelineButton = () => render(<NewTimelineButton timelineId={TimelineId.test} />);
|
const renderNewTimelineButton = () =>
|
||||||
|
render(<NewTimelineButton timelineId={TimelineId.test} />, { wrapper: TestProviders });
|
||||||
|
|
||||||
describe('NewTimelineButton', () => {
|
describe('NewTimelineButton', () => {
|
||||||
it('should render 2 options in the popover when clicking on the button', async () => {
|
it('should render 2 options in the popover when clicking on the button', async () => {
|
||||||
(useDeepEqualSelector as jest.Mock).mockReturnValue({});
|
|
||||||
(useDiscoverInTimelineContext as jest.Mock).mockReturnValue({});
|
(useDiscoverInTimelineContext as jest.Mock).mockReturnValue({});
|
||||||
|
|
||||||
const { getByTestId, getByText } = renderNewTimelineButton();
|
const { getByTestId, getByText } = renderNewTimelineButton();
|
||||||
|
@ -52,12 +52,8 @@ describe('NewTimelineButton', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call the correct action with clicking on the new timeline button', () => {
|
it('should call the correct action with clicking on the new timeline button', () => {
|
||||||
const dataViewId = 'dataViewId';
|
const dataViewId = '';
|
||||||
const selectedPatterns = ['selectedPatterns'];
|
const selectedPatterns: string[] = [];
|
||||||
(useDeepEqualSelector as jest.Mock).mockReturnValue({
|
|
||||||
id: dataViewId,
|
|
||||||
patternList: selectedPatterns,
|
|
||||||
});
|
|
||||||
(useDiscoverInTimelineContext as jest.Mock).mockReturnValue({
|
(useDiscoverInTimelineContext as jest.Mock).mockReturnValue({
|
||||||
resetDiscoverAppState: jest.fn(),
|
resetDiscoverAppState: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -41,10 +41,13 @@ jest.mock('react-redux', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const timelineId = 'timeline-1';
|
const timelineId = 'timeline-1';
|
||||||
|
const mockRef = {
|
||||||
|
current: null,
|
||||||
|
};
|
||||||
const renderTimelineModalHeader = () =>
|
const renderTimelineModalHeader = () =>
|
||||||
render(
|
render(
|
||||||
<TestProviders>
|
<TestProviders>
|
||||||
<TimelineModalHeader timelineId={timelineId} />
|
<TimelineModalHeader timelineId={timelineId} openToggleRef={mockRef} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -62,12 +62,14 @@ interface FlyoutHeaderPanelProps {
|
||||||
* Id of the timeline to be displayed within the modal
|
* Id of the timeline to be displayed within the modal
|
||||||
*/
|
*/
|
||||||
timelineId: string;
|
timelineId: string;
|
||||||
|
openToggleRef: React.MutableRefObject<null | HTMLAnchorElement | HTMLButtonElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component rendered at the top of the timeline modal. It contains the timeline title, all the action buttons (save, open, favorite...) and the close button
|
* Component rendered at the top of the timeline modal. It contains the timeline title, all the action buttons (save, open, favorite...) and the close button
|
||||||
*/
|
*/
|
||||||
export const TimelineModalHeader = React.memo<FlyoutHeaderPanelProps>(({ timelineId }) => {
|
export const TimelineModalHeader = React.memo<FlyoutHeaderPanelProps>(
|
||||||
|
({ timelineId, openToggleRef }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { browserFields, indexPattern } = useSourcererDataView(SourcererScopeName.timeline);
|
const { browserFields, indexPattern } = useSourcererDataView(SourcererScopeName.timeline);
|
||||||
const { cases, uiSettings } = useKibana().services;
|
const { cases, uiSettings } = useKibana().services;
|
||||||
|
@ -78,8 +80,8 @@ export const TimelineModalHeader = React.memo<FlyoutHeaderPanelProps>(({ timelin
|
||||||
const isDataInTimeline = useSelector((state: State) => selectDataInTimeline(state, timelineId));
|
const isDataInTimeline = useSelector((state: State) => selectDataInTimeline(state, timelineId));
|
||||||
const kqlQueryObj = useSelector((state: State) => selectKqlQuery(state, timelineId));
|
const kqlQueryObj = useSelector((state: State) => selectKqlQuery(state, timelineId));
|
||||||
|
|
||||||
const { activeTab, dataProviders, timelineType, filters, kqlMode } = useSelector((state: State) =>
|
const { activeTab, dataProviders, timelineType, filters, kqlMode } = useSelector(
|
||||||
selectTimelineById(state, timelineId)
|
(state: State) => selectTimelineById(state, timelineId)
|
||||||
);
|
);
|
||||||
|
|
||||||
const combinedQueries = useMemo(
|
const combinedQueries = useMemo(
|
||||||
|
@ -98,9 +100,12 @@ export const TimelineModalHeader = React.memo<FlyoutHeaderPanelProps>(({ timelin
|
||||||
const isInspectDisabled = !isDataInTimeline || combinedQueries?.filterQuery === undefined;
|
const isInspectDisabled = !isDataInTimeline || combinedQueries?.filterQuery === undefined;
|
||||||
|
|
||||||
const closeTimeline = useCallback(() => {
|
const closeTimeline = useCallback(() => {
|
||||||
|
if (openToggleRef.current != null) {
|
||||||
|
openToggleRef.current.focus();
|
||||||
|
}
|
||||||
createHistoryEntry();
|
createHistoryEntry();
|
||||||
dispatch(timelineActions.showTimeline({ id: timelineId, show: false }));
|
dispatch(timelineActions.showTimeline({ id: timelineId, show: false }));
|
||||||
}, [dispatch, timelineId]);
|
}, [dispatch, timelineId, openToggleRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TimelinePanel
|
<TimelinePanel
|
||||||
|
@ -186,6 +191,7 @@ export const TimelineModalHeader = React.memo<FlyoutHeaderPanelProps>(({ timelin
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</TimelinePanel>
|
</TimelinePanel>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
TimelineModalHeader.displayName = 'TimelineModalHeader';
|
TimelineModalHeader.displayName = 'TimelineModalHeader';
|
||||||
|
|
|
@ -21,10 +21,14 @@ jest.mock('../../../common/store/selectors', () => ({
|
||||||
inputsSelectors: { timelineFullScreenSelector: () => mockIsFullScreen() },
|
inputsSelectors: { timelineFullScreenSelector: () => mockIsFullScreen() },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const mockRef = {
|
||||||
|
current: null,
|
||||||
|
};
|
||||||
|
|
||||||
const renderTimelineModal = () =>
|
const renderTimelineModal = () =>
|
||||||
render(
|
render(
|
||||||
<TestProviders>
|
<TestProviders>
|
||||||
<TimelineModal timelineId={TimelineId.test} />
|
<TimelineModal timelineId={TimelineId.test} openToggleRef={mockRef} />
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -33,14 +33,17 @@ interface TimelineModalProps {
|
||||||
* If true the timeline modal will be visible
|
* If true the timeline modal will be visible
|
||||||
*/
|
*/
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
|
openToggleRef: React.MutableRefObject<null | HTMLAnchorElement | HTMLButtonElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the timeline modal. Internally this is using an EuiPortal.
|
* Renders the timeline modal. Internally this is using an EuiPortal.
|
||||||
*/
|
*/
|
||||||
export const TimelineModal = React.memo<TimelineModalProps>(({ timelineId, visible = true }) => {
|
export const TimelineModal = React.memo<TimelineModalProps>(
|
||||||
|
({ timelineId, openToggleRef, visible = true }) => {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const isFullScreen = useShallowEqualSelector(inputsSelectors.timelineFullScreenSelector) ?? false;
|
const isFullScreen =
|
||||||
|
useShallowEqualSelector(inputsSelectors.timelineFullScreenSelector) ?? false;
|
||||||
|
|
||||||
const styles = usePaneStyles();
|
const styles = usePaneStyles();
|
||||||
const wrapperClassName = classNames('timeline-portal-overlay-mask', styles, {
|
const wrapperClassName = classNames('timeline-portal-overlay-mask', styles, {
|
||||||
|
@ -48,7 +51,10 @@ export const TimelineModal = React.memo<TimelineModalProps>(({ timelineId, visib
|
||||||
'timeline-portal-overlay-mask--hidden': !visible,
|
'timeline-portal-overlay-mask--hidden': !visible,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sibling: HTMLDivElement | null = useMemo(() => (!visible ? ref?.current : null), [visible]);
|
const sibling: HTMLDivElement | null = useMemo(
|
||||||
|
() => (!visible ? ref?.current : null),
|
||||||
|
[visible]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-test-subj="timeline-portal-ref" ref={ref}>
|
<div data-test-subj="timeline-portal-ref" ref={ref}>
|
||||||
|
@ -63,6 +69,7 @@ export const TimelineModal = React.memo<TimelineModalProps>(({ timelineId, visib
|
||||||
renderCellValue={DefaultCellRenderer}
|
renderCellValue={DefaultCellRenderer}
|
||||||
rowRenderers={defaultRowRenderers}
|
rowRenderers={defaultRowRenderers}
|
||||||
timelineId={timelineId}
|
timelineId={timelineId}
|
||||||
|
openToggleRef={openToggleRef}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -70,6 +77,7 @@ export const TimelineModal = React.memo<TimelineModalProps>(({ timelineId, visib
|
||||||
{visible && <OverflowHiddenGlobalStyles />}
|
{visible && <OverflowHiddenGlobalStyles />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
TimelineModal.displayName = 'TimelineModal';
|
TimelineModal.displayName = 'TimelineModal';
|
||||||
|
|
|
@ -10,10 +10,10 @@ import React from 'react';
|
||||||
import { NewTimelineButton } from '.';
|
import { NewTimelineButton } from '.';
|
||||||
import { TimelineId } from '../../../../common/types';
|
import { TimelineId } from '../../../../common/types';
|
||||||
import { timelineActions } from '../../store';
|
import { timelineActions } from '../../store';
|
||||||
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
|
|
||||||
import { useDiscoverInTimelineContext } from '../../../common/components/discover_in_timeline/use_discover_in_timeline_context';
|
import { useDiscoverInTimelineContext } from '../../../common/components/discover_in_timeline/use_discover_in_timeline_context';
|
||||||
import { defaultHeaders } from '../timeline/body/column_headers/default_headers';
|
import { defaultHeaders } from '../timeline/body/column_headers/default_headers';
|
||||||
import { TimelineType } from '../../../../common/api/timeline';
|
import { TimelineType } from '../../../../common/api/timeline';
|
||||||
|
import { TestProviders } from '../../../common/mock';
|
||||||
|
|
||||||
jest.mock('../../../common/components/discover_in_timeline/use_discover_in_timeline_context');
|
jest.mock('../../../common/components/discover_in_timeline/use_discover_in_timeline_context');
|
||||||
jest.mock('../../../common/hooks/use_selector');
|
jest.mock('../../../common/hooks/use_selector');
|
||||||
|
@ -27,15 +27,12 @@ jest.mock('react-redux', () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderNewTimelineButton = (type: TimelineType) => render(<NewTimelineButton type={type} />);
|
const renderNewTimelineButton = (type: TimelineType) =>
|
||||||
|
render(<NewTimelineButton type={type} />, { wrapper: TestProviders });
|
||||||
|
|
||||||
describe('NewTimelineButton', () => {
|
describe('NewTimelineButton', () => {
|
||||||
const dataViewId = 'dataViewId';
|
const dataViewId = '';
|
||||||
const selectedPatterns = ['selectedPatterns'];
|
const selectedPatterns: string[] = [];
|
||||||
(useDeepEqualSelector as jest.Mock).mockReturnValue({
|
|
||||||
id: dataViewId,
|
|
||||||
patternList: selectedPatterns,
|
|
||||||
});
|
|
||||||
(useDiscoverInTimelineContext as jest.Mock).mockReturnValue({
|
(useDiscoverInTimelineContext as jest.Mock).mockReturnValue({
|
||||||
resetDiscoverAppState: jest.fn(),
|
resetDiscoverAppState: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -70,6 +70,9 @@ jest.mock('react-router-dom', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockDispatch = jest.fn();
|
const mockDispatch = jest.fn();
|
||||||
|
const mockRef = {
|
||||||
|
current: null,
|
||||||
|
};
|
||||||
|
|
||||||
jest.mock('react-redux', () => {
|
jest.mock('react-redux', () => {
|
||||||
const actual = jest.requireActual('react-redux');
|
const actual = jest.requireActual('react-redux');
|
||||||
|
@ -95,6 +98,7 @@ describe('StatefulTimeline', () => {
|
||||||
renderCellValue: DefaultCellRenderer,
|
renderCellValue: DefaultCellRenderer,
|
||||||
rowRenderers: defaultRowRenderers,
|
rowRenderers: defaultRowRenderers,
|
||||||
timelineId: TimelineId.test,
|
timelineId: TimelineId.test,
|
||||||
|
openToggleRef: mockRef,
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import { pick } from 'lodash/fp';
|
import { pick } from 'lodash/fp';
|
||||||
import { EuiProgress } from '@elastic/eui';
|
import { EuiProgress } from '@elastic/eui';
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, createContext } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, createContext } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { isTab } from '@kbn/timelines-plugin/public';
|
import { isTab } from '@kbn/timelines-plugin/public';
|
||||||
|
@ -22,6 +22,7 @@ import { TimelineModalHeader } from '../modal/header';
|
||||||
import type { TimelineId, RowRenderer, TimelineTabs } from '../../../../common/types/timeline';
|
import type { TimelineId, RowRenderer, TimelineTabs } from '../../../../common/types/timeline';
|
||||||
import { TimelineType } from '../../../../common/api/timeline';
|
import { TimelineType } from '../../../../common/api/timeline';
|
||||||
import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector';
|
import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector';
|
||||||
|
import type { State } from '../../../common/store';
|
||||||
import { EVENTS_COUNT_BUTTON_CLASS_NAME, onTimelineTabKeyPressed } from './helpers';
|
import { EVENTS_COUNT_BUTTON_CLASS_NAME, onTimelineTabKeyPressed } from './helpers';
|
||||||
import * as i18n from './translations';
|
import * as i18n from './translations';
|
||||||
import { TabsContent } from './tabs_content';
|
import { TabsContent } from './tabs_content';
|
||||||
|
@ -50,6 +51,7 @@ export interface Props {
|
||||||
renderCellValue: (props: CellValueElementProps) => React.ReactNode;
|
renderCellValue: (props: CellValueElementProps) => React.ReactNode;
|
||||||
rowRenderers: RowRenderer[];
|
rowRenderers: RowRenderer[];
|
||||||
timelineId: TimelineId;
|
timelineId: TimelineId;
|
||||||
|
openToggleRef: React.MutableRefObject<null | HTMLAnchorElement | HTMLButtonElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimelineSavingProgressComponent: React.FC<{ timelineId: TimelineId }> = ({ timelineId }) => {
|
const TimelineSavingProgressComponent: React.FC<{ timelineId: TimelineId }> = ({ timelineId }) => {
|
||||||
|
@ -67,15 +69,17 @@ const StatefulTimelineComponent: React.FC<Props> = ({
|
||||||
renderCellValue,
|
renderCellValue,
|
||||||
rowRenderers,
|
rowRenderers,
|
||||||
timelineId,
|
timelineId,
|
||||||
|
openToggleRef,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const containerElement = useRef<HTMLDivElement | null>(null);
|
const containerElement = useRef<HTMLDivElement | null>(null);
|
||||||
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
|
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
|
||||||
const scopeIdSelector = useMemo(() => sourcererSelectors.scopeIdSelector(), []);
|
const selectedPatternsSourcerer = useSelector((state: State) => {
|
||||||
const {
|
return sourcererSelectors.sourcererScopeSelectedPatterns(state, SourcererScopeName.timeline);
|
||||||
selectedPatterns: selectedPatternsSourcerer,
|
});
|
||||||
selectedDataViewId: selectedDataViewIdSourcerer,
|
const selectedDataViewIdSourcerer = useSelector((state: State) => {
|
||||||
} = useDeepEqualSelector((state) => scopeIdSelector(state, SourcererScopeName.timeline));
|
return sourcererSelectors.sourcererScopeSelectedDataViewId(state, SourcererScopeName.timeline);
|
||||||
|
});
|
||||||
const {
|
const {
|
||||||
dataViewId: selectedDataViewIdTimeline,
|
dataViewId: selectedDataViewIdTimeline,
|
||||||
indexNames: selectedPatternsTimeline,
|
indexNames: selectedPatternsTimeline,
|
||||||
|
@ -233,7 +237,7 @@ const StatefulTimelineComponent: React.FC<Props> = ({
|
||||||
$isVisible={!timelineFullScreen}
|
$isVisible={!timelineFullScreen}
|
||||||
data-test-subj="timeline-hide-show-container"
|
data-test-subj="timeline-hide-show-container"
|
||||||
>
|
>
|
||||||
<TimelineModalHeader timelineId={timelineId} />
|
<TimelineModalHeader timelineId={timelineId} openToggleRef={openToggleRef} />
|
||||||
</HideShowContainer>
|
</HideShowContainer>
|
||||||
|
|
||||||
<TabsContent
|
<TabsContent
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useDeepEqualSelector } from '../../common/hooks/use_selector';
|
import { useDeepEqualSelector } from '../../common/hooks/use_selector';
|
||||||
import {
|
import {
|
||||||
|
@ -47,11 +48,7 @@ export function useTimelineDataFilters(isActiveTimelines: boolean) {
|
||||||
return getEndSelector(state.inputs.global);
|
return getEndSelector(state.inputs.global);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const getDefaultDataViewSelector = useMemo(
|
const defaultDataView = useSelector(sourcererSelectors.defaultDataView);
|
||||||
() => sourcererSelectors.defaultDataViewSelector(),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const defaultDataView = useDeepEqualSelector(getDefaultDataViewSelector);
|
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const { selectedPatterns: nonTimelinePatterns } = useSourcererDataView(
|
const { selectedPatterns: nonTimelinePatterns } = useSourcererDataView(
|
||||||
getScopeFromPath(pathname)
|
getScopeFromPath(pathname)
|
||||||
|
|
|
@ -4,15 +4,12 @@
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
import React from 'react';
|
||||||
import type { RenderHookResult } from '@testing-library/react-hooks';
|
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import type { UseCreateTimelineParams } from './use_create_timeline';
|
|
||||||
import { useCreateTimeline } from './use_create_timeline';
|
import { useCreateTimeline } from './use_create_timeline';
|
||||||
import type { TimeRange } from '../../common/store/inputs/model';
|
import type { TimeRange } from '../../common/store/inputs/model';
|
||||||
import { TimelineType } from '../../../common/api/timeline';
|
import { TimelineType } from '../../../common/api/timeline';
|
||||||
import { TimelineId } from '../../../common/types';
|
import { TimelineId } from '../../../common/types';
|
||||||
import { useDeepEqualSelector } from '../../common/hooks/use_selector';
|
|
||||||
import { useDiscoverInTimelineContext } from '../../common/components/discover_in_timeline/use_discover_in_timeline_context';
|
import { useDiscoverInTimelineContext } from '../../common/components/discover_in_timeline/use_discover_in_timeline_context';
|
||||||
import { timelineActions } from '../store';
|
import { timelineActions } from '../store';
|
||||||
import { inputsActions } from '../../common/store/inputs';
|
import { inputsActions } from '../../common/store/inputs';
|
||||||
|
@ -21,87 +18,90 @@ import { appActions } from '../../common/store/app';
|
||||||
import { defaultHeaders } from '../components/timeline/body/column_headers/default_headers';
|
import { defaultHeaders } from '../components/timeline/body/column_headers/default_headers';
|
||||||
import { SourcererScopeName } from '../../common/store/sourcerer/model';
|
import { SourcererScopeName } from '../../common/store/sourcerer/model';
|
||||||
import { InputsModelId } from '../../common/store/inputs/constants';
|
import { InputsModelId } from '../../common/store/inputs/constants';
|
||||||
|
import { TestProviders, mockGlobalState } from '../../common/mock';
|
||||||
|
|
||||||
jest.mock('../../common/components/discover_in_timeline/use_discover_in_timeline_context');
|
jest.mock('../../common/components/discover_in_timeline/use_discover_in_timeline_context');
|
||||||
jest.mock('../../common/hooks/use_selector');
|
jest.mock('../../common/containers/use_global_time', () => {
|
||||||
jest.mock('react-redux', () => {
|
|
||||||
const original = jest.requireActual('react-redux');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...original,
|
useGlobalTime: jest.fn().mockReturnValue({
|
||||||
useSelector: jest.fn(),
|
from: '2022-04-05T12:00:00.000Z',
|
||||||
useDispatch: () => jest.fn(),
|
to: '2022-04-08T12:00:00.000Z',
|
||||||
|
setQuery: () => jest.fn(),
|
||||||
|
deleteQuery: () => jest.fn(),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
jest.mock('../../common/lib/kibana');
|
||||||
|
|
||||||
describe('useCreateTimeline', () => {
|
describe('useCreateTimeline', () => {
|
||||||
let hookResult: RenderHookResult<
|
|
||||||
UseCreateTimelineParams,
|
|
||||||
(options?: { timeRange?: TimeRange }) => void
|
|
||||||
>;
|
|
||||||
|
|
||||||
const resetDiscoverAppState = jest.fn();
|
const resetDiscoverAppState = jest.fn();
|
||||||
(useDiscoverInTimelineContext as jest.Mock).mockReturnValue({ resetDiscoverAppState });
|
(useDiscoverInTimelineContext as jest.Mock).mockReturnValue({ resetDiscoverAppState });
|
||||||
|
|
||||||
it('should return a function', () => {
|
it('should return a function', () => {
|
||||||
(useDeepEqualSelector as jest.Mock).mockReturnValue({});
|
const hookResult = renderHook(
|
||||||
|
() => useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineType.default }),
|
||||||
hookResult = renderHook(() =>
|
{
|
||||||
useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineType.default })
|
wrapper: ({ children }) => <TestProviders>{children}</TestProviders>,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(hookResult.result.current).toEqual(expect.any(Function));
|
expect(hookResult.result.current).toEqual(expect.any(Function));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should dispatch correct actions when calling the returned function', () => {
|
it('should dispatch correct actions when calling the returned function', () => {
|
||||||
const dataViewId = 'dataViewId';
|
|
||||||
const selectedPatterns = ['selectedPatterns'];
|
|
||||||
(useDeepEqualSelector as jest.Mock).mockReturnValue({
|
|
||||||
id: dataViewId,
|
|
||||||
patternList: selectedPatterns,
|
|
||||||
});
|
|
||||||
|
|
||||||
const createTimeline = jest.spyOn(timelineActions, 'createTimeline');
|
const createTimeline = jest.spyOn(timelineActions, 'createTimeline');
|
||||||
const setSelectedDataView = jest.spyOn(sourcererActions, 'setSelectedDataView');
|
const setSelectedDataView = jest.spyOn(sourcererActions, 'setSelectedDataView');
|
||||||
const addLinkTo = jest.spyOn(inputsActions, 'addLinkTo');
|
const addLinkTo = jest.spyOn(inputsActions, 'addLinkTo');
|
||||||
const addNotes = jest.spyOn(appActions, 'addNotes');
|
const addNotes = jest.spyOn(appActions, 'addNotes');
|
||||||
|
|
||||||
hookResult = renderHook(() =>
|
const hookResult = renderHook(
|
||||||
useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineType.default })
|
() => useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineType.default }),
|
||||||
|
{
|
||||||
|
wrapper: ({ children }) => <TestProviders>{children}</TestProviders>,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(hookResult.result.current).toEqual(expect.any(Function));
|
expect(hookResult.result.current).toEqual(expect.any(Function));
|
||||||
|
|
||||||
hookResult.result.current();
|
hookResult.result.current();
|
||||||
|
expect(createTimeline.mock.calls[0][0].id).toEqual(TimelineId.test);
|
||||||
expect(createTimeline).toHaveBeenCalledWith({
|
expect(createTimeline.mock.calls[0][0].timelineType).toEqual(TimelineType.default);
|
||||||
columns: defaultHeaders,
|
expect(createTimeline.mock.calls[0][0].columns).toEqual(defaultHeaders);
|
||||||
dataViewId,
|
expect(createTimeline.mock.calls[0][0].dataViewId).toEqual(
|
||||||
id: TimelineId.test,
|
mockGlobalState.sourcerer.defaultDataView.id
|
||||||
indexNames: selectedPatterns,
|
);
|
||||||
show: true,
|
expect(createTimeline.mock.calls[0][0].indexNames).toEqual(
|
||||||
timelineType: 'default',
|
expect.arrayContaining(
|
||||||
updated: undefined,
|
mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline].selectedPatterns
|
||||||
});
|
)
|
||||||
expect(setSelectedDataView).toHaveBeenCalledWith({
|
);
|
||||||
id: SourcererScopeName.timeline,
|
expect(createTimeline.mock.calls[0][0].show).toEqual(true);
|
||||||
selectedDataViewId: dataViewId,
|
expect(createTimeline.mock.calls[0][0].updated).toEqual(undefined);
|
||||||
selectedPatterns,
|
expect(setSelectedDataView.mock.calls[0][0].id).toEqual(SourcererScopeName.timeline);
|
||||||
});
|
expect(setSelectedDataView.mock.calls[0][0].selectedDataViewId).toEqual(
|
||||||
|
mockGlobalState.sourcerer.defaultDataView.id
|
||||||
|
);
|
||||||
|
expect(setSelectedDataView.mock.calls[0][0].selectedPatterns).toEqual(
|
||||||
|
expect.arrayContaining(
|
||||||
|
mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline].selectedPatterns
|
||||||
|
)
|
||||||
|
);
|
||||||
expect(addLinkTo).toHaveBeenCalledWith([InputsModelId.global, InputsModelId.timeline]);
|
expect(addLinkTo).toHaveBeenCalledWith([InputsModelId.global, InputsModelId.timeline]);
|
||||||
expect(addNotes).toHaveBeenCalledWith({ notes: [] });
|
expect(addNotes).toHaveBeenCalledWith({ notes: [] });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run the onClick method if provided', () => {
|
it('should run the onClick method if provided', () => {
|
||||||
(useDeepEqualSelector as jest.Mock).mockReturnValue({});
|
|
||||||
|
|
||||||
const onClick = jest.fn();
|
const onClick = jest.fn();
|
||||||
hookResult = renderHook(() =>
|
const hookResult = renderHook(
|
||||||
|
() =>
|
||||||
useCreateTimeline({
|
useCreateTimeline({
|
||||||
timelineId: TimelineId.test,
|
timelineId: TimelineId.test,
|
||||||
timelineType: TimelineType.default,
|
timelineType: TimelineType.default,
|
||||||
onClick,
|
onClick,
|
||||||
})
|
}),
|
||||||
|
{
|
||||||
|
wrapper: ({ children }) => <TestProviders>{children}</TestProviders>,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
hookResult.result.current();
|
hookResult.result.current();
|
||||||
|
@ -111,13 +111,14 @@ describe('useCreateTimeline', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should dispatch removeLinkTo action if absolute timeRange is passed to callback', () => {
|
it('should dispatch removeLinkTo action if absolute timeRange is passed to callback', () => {
|
||||||
(useDeepEqualSelector as jest.Mock).mockReturnValue({});
|
|
||||||
|
|
||||||
const removeLinkTo = jest.spyOn(inputsActions, 'removeLinkTo');
|
const removeLinkTo = jest.spyOn(inputsActions, 'removeLinkTo');
|
||||||
const setAbsoluteRangeDatePicker = jest.spyOn(inputsActions, 'setAbsoluteRangeDatePicker');
|
const setAbsoluteRangeDatePicker = jest.spyOn(inputsActions, 'setAbsoluteRangeDatePicker');
|
||||||
|
|
||||||
hookResult = renderHook(() =>
|
const hookResult = renderHook(
|
||||||
useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineType.default })
|
() => useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineType.default }),
|
||||||
|
{
|
||||||
|
wrapper: ({ children }) => <TestProviders>{children}</TestProviders>,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const timeRange: TimeRange = { kind: 'absolute', from: '', to: '' };
|
const timeRange: TimeRange = { kind: 'absolute', from: '', to: '' };
|
||||||
|
@ -131,12 +132,13 @@ describe('useCreateTimeline', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should dispatch removeLinkTo action if relative timeRange is passed to callback', () => {
|
it('should dispatch removeLinkTo action if relative timeRange is passed to callback', () => {
|
||||||
(useDeepEqualSelector as jest.Mock).mockReturnValue({});
|
|
||||||
|
|
||||||
const setRelativeRangeDatePicker = jest.spyOn(inputsActions, 'setRelativeRangeDatePicker');
|
const setRelativeRangeDatePicker = jest.spyOn(inputsActions, 'setRelativeRangeDatePicker');
|
||||||
|
|
||||||
hookResult = renderHook(() =>
|
const hookResult = renderHook(
|
||||||
useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineType.default })
|
() => useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineType.default }),
|
||||||
|
{
|
||||||
|
wrapper: ({ children }) => <TestProviders>{children}</TestProviders>,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const timeRange: TimeRange = { kind: 'relative', fromStr: '', toStr: '', from: '', to: '' };
|
const timeRange: TimeRange = { kind: 'relative', fromStr: '', toStr: '', from: '', to: '' };
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { InputsModelId } from '../../common/store/inputs/constants';
|
import { InputsModelId } from '../../common/store/inputs/constants';
|
||||||
import { defaultHeaders } from '../components/timeline/body/column_headers/default_headers';
|
import { defaultHeaders } from '../components/timeline/body/column_headers/default_headers';
|
||||||
import { timelineActions } from '../store';
|
import { timelineActions } from '../store';
|
||||||
|
@ -47,9 +47,9 @@ export const useCreateTimeline = ({
|
||||||
onClick,
|
onClick,
|
||||||
}: UseCreateTimelineParams): ((options?: { timeRange?: TimeRange }) => void) => {
|
}: UseCreateTimelineParams): ((options?: { timeRange?: TimeRange }) => void) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const defaultDataViewSelector = useMemo(() => sourcererSelectors.defaultDataViewSelector(), []);
|
const { id: dataViewId, patternList: selectedPatterns } = useSelector(
|
||||||
const { id: dataViewId, patternList: selectedPatterns } =
|
sourcererSelectors.defaultDataView
|
||||||
useDeepEqualSelector(defaultDataViewSelector);
|
) ?? { id: '', patternList: [] };
|
||||||
|
|
||||||
const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen();
|
const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen();
|
||||||
const globalTimeRange = useDeepEqualSelector(inputsSelectors.globalTimeRangeSelector);
|
const globalTimeRange = useDeepEqualSelector(inputsSelectors.globalTimeRangeSelector);
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EuiFocusTrap, EuiWindowEvent, keys } from '@elastic/eui';
|
import { EuiFocusTrap, EuiWindowEvent, keys } from '@elastic/eui';
|
||||||
import React, { useMemo, useCallback } from 'react';
|
import React, { useMemo, useCallback, useRef } from 'react';
|
||||||
import type { AppLeaveHandler } from '@kbn/core/public';
|
import type { AppLeaveHandler } from '@kbn/core/public';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { TimelineModal } from '../components/modal';
|
import { TimelineModal } from '../components/modal';
|
||||||
|
@ -38,7 +38,7 @@ export const TimelineWrapper: React.FC<TimelineWrapperProps> = React.memo(
|
||||||
const getTimelineShowStatus = useMemo(() => getTimelineShowStatusByIdSelector(), []);
|
const getTimelineShowStatus = useMemo(() => getTimelineShowStatusByIdSelector(), []);
|
||||||
const { show } = useDeepEqualSelector((state) => getTimelineShowStatus(state, timelineId));
|
const { show } = useDeepEqualSelector((state) => getTimelineShowStatus(state, timelineId));
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const openToggleRef = useRef(null);
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
dispatch(timelineActions.showTimeline({ id: timelineId, show: false }));
|
dispatch(timelineActions.showTimeline({ id: timelineId, show: false }));
|
||||||
}, [dispatch, timelineId]);
|
}, [dispatch, timelineId]);
|
||||||
|
@ -58,9 +58,9 @@ export const TimelineWrapper: React.FC<TimelineWrapperProps> = React.memo(
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EuiFocusTrap disabled={!show}>
|
<EuiFocusTrap disabled={!show}>
|
||||||
<TimelineModal timelineId={timelineId} visible={show} />
|
<TimelineModal timelineId={timelineId} visible={show} openToggleRef={openToggleRef} />
|
||||||
</EuiFocusTrap>
|
</EuiFocusTrap>
|
||||||
<TimelineBottomBar show={show} timelineId={timelineId} />
|
<TimelineBottomBar show={show} timelineId={timelineId} openToggleRef={openToggleRef} />
|
||||||
<EuiWindowEvent event="keydown" handler={onKeyDown} />
|
<EuiWindowEvent event="keydown" handler={onKeyDown} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue