[Security Solution] Update timeline when sourcerer updates (#118958) (#119035)

Co-authored-by: Steph Milovic <stephanie.milovic@elastic.co>
This commit is contained in:
Kibana Machine 2021-11-18 11:08:07 -05:00 committed by GitHub
parent 25507793a0
commit b5b7662b38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 89 additions and 14 deletions

View file

@ -13,18 +13,26 @@ import { DragDropContextWrapper } from '../../../common/components/drag_and_drop
import '../../../common/mock/match_media';
import { mockBrowserFields, mockDocValueFields } from '../../../common/containers/source/mock';
import { TimelineId } from '../../../../common/types/timeline';
import { mockIndexNames, mockIndexPattern, TestProviders } from '../../../common/mock';
import {
mockGlobalState,
mockIndexNames,
mockIndexPattern,
TestProviders,
} from '../../../common/mock';
import { StatefulTimeline, Props as StatefulTimelineOwnProps } from './index';
import { useTimelineEvents } from '../../containers/index';
import { DefaultCellRenderer } from './cell_rendering/default_cell_renderer';
import { SELECTOR_TIMELINE_GLOBAL_CONTAINER } from './styles';
import { defaultRowRenderers } from './body/renderers';
import { useSourcererDataView } from '../../../common/containers/sourcerer';
jest.mock('../../containers/index', () => ({
useTimelineEvents: jest.fn(),
}));
jest.mock('./tabs_content');
jest.mock('../../../common/lib/kibana');
jest.mock('../../../common/components/url_state/normalize_time_range.ts');
jest.mock('@kbn/i18n/react', () => {
@ -54,21 +62,29 @@ jest.mock('react-router-dom', () => {
useHistory: jest.fn(),
};
});
jest.mock('../../../common/containers/sourcerer', () => {
const originalModule = jest.requireActual('../../../common/containers/sourcerer');
const mockDispatch = jest.fn();
jest.mock('react-redux', () => {
const actual = jest.requireActual('react-redux');
return {
...originalModule,
useSourcererDataView: jest.fn().mockReturnValue({
browserFields: mockBrowserFields,
docValueFields: mockDocValueFields,
loading: false,
indexPattern: mockIndexPattern,
pageInfo: { activePage: 0, querySize: 0 },
selectedPatterns: mockIndexNames,
}),
...actual,
useDispatch: () => mockDispatch,
};
});
const mockUseSourcererDataView: jest.Mock = useSourcererDataView as jest.Mock;
jest.mock('../../../common/containers/sourcerer');
const mockDataView = {
dataViewId: mockGlobalState.timeline.timelineById.test?.dataViewId,
browserFields: mockBrowserFields,
docValueFields: mockDocValueFields,
loading: false,
indexPattern: mockIndexPattern,
pageInfo: { activePage: 0, querySize: 0 },
selectedPatterns: mockGlobalState.timeline.timelineById.test?.indexNames,
};
mockUseSourcererDataView.mockReturnValue(mockDataView);
describe('StatefulTimeline', () => {
const props: StatefulTimelineOwnProps = {
renderCellValue: DefaultCellRenderer,
@ -77,6 +93,7 @@ describe('StatefulTimeline', () => {
};
beforeEach(() => {
jest.clearAllMocks();
(useTimelineEvents as jest.Mock).mockReturnValue([
false,
{
@ -97,6 +114,25 @@ describe('StatefulTimeline', () => {
</TestProviders>
);
expect(wrapper.find('[data-test-subj="timeline"]')).toBeTruthy();
expect(mockDispatch).toBeCalledTimes(1);
});
test('data view updates, updates timeline', () => {
mockUseSourcererDataView.mockReturnValue({ ...mockDataView, selectedPatterns: mockIndexNames });
mount(
<TestProviders>
<StatefulTimeline {...props} />
</TestProviders>
);
expect(mockDispatch).toBeCalledTimes(2);
expect(mockDispatch).toHaveBeenNthCalledWith(2, {
payload: {
id: 'test',
dataViewId: mockDataView.dataViewId,
indexNames: mockIndexNames,
},
type: 'x-pack/security_solution/local/timeline/UPDATE_DATA_VIEW',
});
});
test(`it add attribute data-timeline-id in ${SELECTOR_TIMELINE_GLOBAL_CONTAINER}`, () => {

View file

@ -64,9 +64,16 @@ const StatefulTimelineComponent: React.FC<Props> = ({
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
const { dataViewId, selectedPatterns } = useSourcererDataView(SourcererScopeName.timeline);
const { graphEventId, savedObjectId, timelineType, description } = useDeepEqualSelector((state) =>
const {
dataViewId: dataViewIdCurrent,
indexNames: selectedPatternsCurrent,
graphEventId,
savedObjectId,
timelineType,
description,
} = useDeepEqualSelector((state) =>
pick(
['graphEventId', 'savedObjectId', 'timelineType', 'description'],
['indexNames', 'dataViewId', 'graphEventId', 'savedObjectId', 'timelineType', 'description'],
getTimeline(state, timelineId) ?? timelineDefaults
)
);
@ -88,6 +95,38 @@ const StatefulTimelineComponent: React.FC<Props> = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onDataViewChange = useCallback(() => {
if (
// initial state will get set on create
(dataViewIdCurrent === '' && selectedPatternsCurrent.length === 0) ||
// don't update if no change
(dataViewIdCurrent === dataViewId &&
selectedPatternsCurrent.sort().join() === selectedPatterns.sort().join())
) {
return;
}
dispatch(
timelineActions.updateDataView({
id: timelineId,
dataViewId,
indexNames: selectedPatterns,
})
);
}, [
dataViewId,
dataViewIdCurrent,
dispatch,
selectedPatterns,
selectedPatternsCurrent,
timelineId,
]);
useEffect(() => {
onDataViewChange();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataViewId, selectedPatterns]);
const onSkipFocusBeforeEventsTable = useCallback(() => {
const exitFullScreenButton = containerElement.current?.querySelector<HTMLButtonElement>(
EXIT_FULL_SCREEN_CLASS_NAME