From 8fb48898eba924df1f8e4b631be713c4492704f5 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Wed, 11 Sep 2019 12:18:10 -0400 Subject: [PATCH] [SIEM] Inject/apply KQL changed in refresh button (#45065) (#45389) * wip to apply kql when refresh on hosts page * refactor to have less re-render * add network and timeline page * fix/add unit testing * from review remove any effect from render * clean up + review II * review II + bug fixes * review III --- .../event_details/event_details.tsx | 6 +- .../events_viewer/events_viewer.tsx | 66 +++--- .../components/notes/note_cards/index.tsx | 5 +- .../super_date_picker/index.test.tsx | 3 +- .../components/super_date_picker/index.tsx | 197 +++++++++++------- .../super_date_picker/selectors.test.ts | 18 +- .../components/super_date_picker/selectors.ts | 10 +- .../body/column_headers/actions/index.tsx | 5 +- .../body/column_headers/header/index.tsx | 5 +- .../timeline/body/renderers/row_renderer.tsx | 6 +- .../data_providers/providers.test.tsx | 15 +- .../timeline/expandable_event/index.tsx | 5 +- .../timeline/fetch_kql_timeline.tsx | 79 +++++++ .../components/timeline/refetch_timeline.tsx | 42 ++-- .../public/components/timeline/timeline.tsx | 78 +++---- .../components/timeline/timeline_context.tsx | 39 +++- .../public/containers/global_time/index.tsx | 2 +- .../siem/public/containers/hosts/filter.tsx | 127 +++++++---- .../siem/public/containers/network/filter.tsx | 129 ++++++++---- .../siem/public/pages/hosts/details/index.tsx | 2 +- .../plugins/siem/public/pages/hosts/hosts.tsx | 6 +- .../plugins/siem/public/pages/hosts/kql.tsx | 12 +- .../siem/public/pages/network/ip_details.tsx | 30 +-- .../plugins/siem/public/pages/network/kql.tsx | 12 +- .../siem/public/pages/network/network.tsx | 23 +- .../plugins/siem/public/store/inputs/model.ts | 17 +- .../siem/public/store/timeline/selectors.ts | 12 ++ .../public/utils/kql/use_update_kql.test.tsx | 156 ++++++++++++++ .../siem/public/utils/kql/use_update_kql.tsx | 97 +++++++++ 29 files changed, 876 insertions(+), 328 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/fetch_kql_timeline.tsx create mode 100644 x-pack/legacy/plugins/siem/public/utils/kql/use_update_kql.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/utils/kql/use_update_kql.tsx diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/event_details.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/event_details.tsx index cc4d5969ab69..821ec2048d5a 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/event_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/event_details.tsx @@ -5,7 +5,7 @@ */ import { EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui'; -import React, { useContext } from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; @@ -16,7 +16,7 @@ import { OnUpdateColumns } from '../timeline/events'; import { EventFieldsBrowser } from './event_fields_browser'; import { JsonView } from './json_view'; import * as i18n from './translations'; -import { TimelineWidthContext } from '../timeline/timeline_context'; +import { useTimelineWidthContext } from '../timeline/timeline_context'; export type View = 'table-view' | 'json-view'; @@ -51,7 +51,7 @@ export const EventDetails = React.memo( timelineId, toggleColumn, }) => { - const width = useContext(TimelineWidthContext); + const width = useTimelineWidthContext(); const tabs: EuiTabbedContentTab[] = [ { id: 'table-view', diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx index 881713cca1a5..dfb6af198426 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx @@ -23,9 +23,8 @@ import { DataProvider } from '../timeline/data_providers/data_provider'; import { OnChangeItemsPerPage } from '../timeline/events'; import { Footer, footerHeight } from '../timeline/footer'; import { combineQueries } from '../timeline/helpers'; -import { TimelineRefetch } from '../timeline/refetch_timeline'; import { isCompactFooter } from '../timeline/timeline'; -import { TimelineContext, TimelineWidthContext } from '../timeline/timeline_context'; +import { ManageTimelineContext } from '../timeline/timeline_context'; import { EventsViewerHeader } from './events_viewer_header'; @@ -115,7 +114,6 @@ export const EventsViewer = React.memo( style={{ height: '0px', width: '100%' }} /> - {combinedQueries != null ? ( c.id)} @@ -138,7 +136,7 @@ export const EventsViewer = React.memo( refetch, totalCount = 0, }) => ( - + <> ( />
- - - -
- - + + +
+
-
+ )}
) : null} diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.tsx index 3b76f6c9540f..8ea0f8ee8bb2 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.tsx @@ -8,12 +8,11 @@ import { EuiFlexGroup, EuiPanel } from '@elastic/eui'; import * as React from 'react'; import styled from 'styled-components'; -import { useContext } from 'react'; import { Note } from '../../../lib/note'; import { AddNote } from '../add_note'; import { AssociateNote, GetNewNoteId, UpdateNote } from '../helpers'; import { NoteCard } from '../note_card'; -import { TimelineWidthContext } from '../../timeline/timeline_context'; +import { useTimelineWidthContext } from '../../timeline/timeline_context'; const AddNoteContainer = styled.div``; @@ -30,7 +29,7 @@ interface NoteCardsCompProps { } const NoteCardsComp = React.memo(({ children }) => { - const width = useContext(TimelineWidthContext); + const width = useTimelineWidthContext(); // Passing the styles directly to the component because the width is // being calculated and is recommended by Styled Components for performance diff --git a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx index 4cf5b4e9026e..5c2ae38ed4b6 100644 --- a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx @@ -265,6 +265,7 @@ describe('SIEM Super Date Picker', () => { const wrapperFixedEuiFieldSearch = wrapper.find( 'input[data-test-subj="superDatePickerRefreshIntervalInput"]' ); + wrapperFixedEuiFieldSearch.simulate('change', { target: { value: '2' } }); wrapper.update(); @@ -456,7 +457,7 @@ describe('SIEM Super Date Picker', () => { }, ]; const props2 = mapStateToProps(clone, { id: 'global' }); - expect(props1.refetch).not.toBe(props2.refetch); + expect(props1.queries).not.toBe(props2.queries); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx index d73b0268c4d8..c68306c8368f 100644 --- a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx @@ -15,8 +15,8 @@ import { import { getOr, take } from 'lodash/fp'; import React, { Component } from 'react'; import { connect } from 'react-redux'; -import { ActionCreator } from 'typescript-fsa'; +import { Dispatch } from 'redux'; import { inputsModel, State } from '../../store'; import { inputsActions, timelineActions } from '../../store/actions'; import { InputsModelId } from '../../store/inputs/constants'; @@ -29,7 +29,8 @@ import { fromStrSelector, toStrSelector, isLoadingSelector, - refetchSelector, + queriesSelector, + kqlQuerySelector, } from './selectors'; import { InputsRange, Policy } from '../../store/inputs/model'; @@ -60,29 +61,36 @@ interface SuperDatePickerStateRedux { start: number; end: number; isLoading: boolean; - refetch: inputsModel.Refetch[]; + queries: inputsModel.GlobalGraphqlQuery[]; + kqlQuery: inputsModel.GlobalKqlQuery; } +interface UpdateReduxTime extends OnTimeChangeProps { + id: InputsModelId; + kql?: inputsModel.GlobalKqlQuery | undefined; + timelineId?: string; +} + +interface ReturnUpdateReduxTime { + kqlHaveBeenUpdated: boolean; +} + +type DispatchUpdateReduxTime = ({ + end, + id, + isQuickSelection, + kql, + start, + timelineId, +}: UpdateReduxTime) => ReturnUpdateReduxTime; + interface SuperDatePickerDispatchProps { - setAbsoluteSuperDatePicker: ActionCreator<{ - id: InputsModelId; - from: number; - to: number; - timelineId?: string; - }>; - setRelativeSuperDatePicker: ActionCreator<{ - id: InputsModelId; - fromStr: string; - from: number; - to: number; - toStr: string; - timelineId?: string; - }>; - startAutoReload: ActionCreator<{ id: InputsModelId }>; - stopAutoReload: ActionCreator<{ id: InputsModelId }>; - setDuration: ActionCreator<{ id: InputsModelId; duration: number }>; - updateTimelineRange: ActionCreator<{ id: string; start: number; end: number }>; + startAutoReload: ({ id }: { id: InputsModelId }) => void; + stopAutoReload: ({ id }: { id: InputsModelId }) => void; + setDuration: ({ id, duration }: { id: InputsModelId; duration: number }) => void; + updateReduxTime: DispatchUpdateReduxTime; } + interface OwnProps { id: InputsModelId; disabled?: boolean; @@ -139,27 +147,30 @@ export const SuperDatePickerComponent = class extends Component< ); } private onRefresh = ({ start, end, refreshInterval }: OnRefreshProps): void => { - this.updateReduxTime({ - start, + const { kqlHaveBeenUpdated } = this.props.updateReduxTime({ end, - isQuickSelection: this.state.isQuickSelection, + id: this.props.id, isInvalid: false, + isQuickSelection: this.state.isQuickSelection, + kql: this.props.kqlQuery, + start, + timelineId: this.props.timelineId, }); - const currentStart = this.formatDate(start); + const currentStart = formatDate(start); const currentEnd = this.state.isQuickSelection - ? this.formatDate(end, { roundUp: true }) - : this.formatDate(end); + ? formatDate(end, { roundUp: true }) + : formatDate(end); if ( - !this.state.isQuickSelection || - (this.props.start === currentStart && this.props.end === currentEnd) + !kqlHaveBeenUpdated && + (!this.state.isQuickSelection || + (this.props.start === currentStart && this.props.end === currentEnd)) ) { - this.refetchQuery(this.props.refetch); + this.refetchQuery(this.props.queries); } }; private onRefreshChange = ({ isPaused, refreshInterval }: OnRefreshChangeProps): void => { const { id, duration, policy, stopAutoReload, startAutoReload } = this.props; - if (duration !== refreshInterval) { this.props.setDuration({ id, duration: refreshInterval }); } @@ -174,27 +185,25 @@ export const SuperDatePickerComponent = class extends Component< !isPaused && (!this.state.isQuickSelection || (this.state.isQuickSelection && this.props.toStr !== 'now')) ) { - this.refetchQuery(this.props.refetch); + this.refetchQuery(this.props.queries); } }; - private refetchQuery = (query: inputsModel.Refetch[]) => { - query.forEach((refetch: inputsModel.Refetch) => refetch()); - }; - - private formatDate = ( - date: string, - options?: { - roundUp?: boolean; - } - ) => { - const momentDate = dateMath.parse(date, options); - return momentDate != null && momentDate.isValid() ? momentDate.valueOf() : 0; + private refetchQuery = (queries: inputsModel.GlobalGraphqlQuery[]) => { + queries.forEach(q => q.refetch && (q.refetch as inputsModel.Refetch)()); }; private onTimeChange = ({ start, end, isQuickSelection, isInvalid }: OnTimeChangeProps) => { if (!isInvalid) { - this.updateReduxTime({ start, end, isQuickSelection, isInvalid }); + this.props.updateReduxTime({ + end, + id: this.props.id, + isInvalid, + isQuickSelection, + kql: this.props.kqlQuery, + start, + timelineId: this.props.timelineId, + }); this.setState((prevState: SuperDatePickerState) => { const recentlyUsedRanges = [ { start, end }, @@ -214,40 +223,66 @@ export const SuperDatePickerComponent = class extends Component< }); } }; +}; + +const formatDate = ( + date: string, + options?: { + roundUp?: boolean; + } +) => { + const momentDate = dateMath.parse(date, options); + return momentDate != null && momentDate.isValid() ? momentDate.valueOf() : 0; +}; - private updateReduxTime = ({ start, end, isQuickSelection }: OnTimeChangeProps) => { - const { - id, - setAbsoluteSuperDatePicker, - setRelativeSuperDatePicker, - timelineId, - updateTimelineRange, - } = this.props; - const fromDate = this.formatDate(start); - let toDate = this.formatDate(end, { roundUp: true }); - if (isQuickSelection) { - setRelativeSuperDatePicker({ +const dispatchUpdateReduxTime = (dispatch: Dispatch) => ({ + end, + id, + isQuickSelection, + kql, + start, + timelineId, +}: UpdateReduxTime): ReturnUpdateReduxTime => { + const fromDate = formatDate(start); + let toDate = formatDate(end, { roundUp: true }); + if (isQuickSelection) { + dispatch( + inputsActions.setRelativeRangeDatePicker({ id, fromStr: start, toStr: end, from: fromDate, to: toDate, - }); - } else { - toDate = this.formatDate(end); - setAbsoluteSuperDatePicker({ + }) + ); + } else { + toDate = formatDate(end); + dispatch( + inputsActions.setAbsoluteRangeDatePicker({ id, - from: this.formatDate(start), - to: this.formatDate(end), - }); - } - if (timelineId != null) { - updateTimelineRange({ + from: formatDate(start), + to: formatDate(end), + }) + ); + } + if (timelineId != null) { + dispatch( + timelineActions.updateRange({ id: timelineId, start: fromDate, end: toDate, - }); - } + }) + ); + } + + if (kql) { + return { + kqlHaveBeenUpdated: kql.refetch(dispatch), + }; + } + + return { + kqlHaveBeenUpdated: false, }; }; @@ -260,7 +295,8 @@ export const makeMapStateToProps = () => { const getFromStrSelector = fromStrSelector(); const getToStrSelector = toStrSelector(); const getIsLoadingSelector = isLoadingSelector(); - const getRefetchQuerySelector = refetchSelector(); + const getQueriesSelector = queriesSelector(); + const getKqlQuerySelector = kqlQuerySelector(); return (state: State, { id }: OwnProps) => { const inputsRange: InputsRange = getOr({}, `inputs.${id}`, state); return { @@ -272,19 +308,22 @@ export const makeMapStateToProps = () => { fromStr: getFromStrSelector(inputsRange), toStr: getToStrSelector(inputsRange), isLoading: getIsLoadingSelector(inputsRange), - refetch: getRefetchQuerySelector(inputsRange), + queries: getQueriesSelector(inputsRange), + kqlQuery: getKqlQuerySelector(inputsRange), }; }; }; +const mapDispatchToProps = (dispatch: Dispatch) => ({ + startAutoReload: ({ id }: { id: InputsModelId }) => + dispatch(inputsActions.startAutoReload({ id })), + stopAutoReload: ({ id }: { id: InputsModelId }) => dispatch(inputsActions.stopAutoReload({ id })), + setDuration: ({ id, duration }: { id: InputsModelId; duration: number }) => + dispatch(inputsActions.setDuration({ id, duration })), + updateReduxTime: dispatchUpdateReduxTime(dispatch), +}); + export const SuperDatePicker = connect( makeMapStateToProps, - { - setAbsoluteSuperDatePicker: inputsActions.setAbsoluteRangeDatePicker, - setRelativeSuperDatePicker: inputsActions.setRelativeRangeDatePicker, - startAutoReload: inputsActions.startAutoReload, - stopAutoReload: inputsActions.stopAutoReload, - setDuration: inputsActions.setDuration, - updateTimelineRange: timelineActions.updateRange, - } + mapDispatchToProps )(SuperDatePickerComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/super_date_picker/selectors.test.ts b/x-pack/legacy/plugins/siem/public/components/super_date_picker/selectors.test.ts index 194e77075fb5..2e42ed791f3d 100644 --- a/x-pack/legacy/plugins/siem/public/components/super_date_picker/selectors.test.ts +++ b/x-pack/legacy/plugins/siem/public/components/super_date_picker/selectors.test.ts @@ -13,7 +13,7 @@ import { fromStrSelector, toStrSelector, isLoadingSelector, - refetchSelector, + queriesSelector, } from './selectors'; import { InputsRange, AbsoluteTimeRange, RelativeTimeRange } from '../../store/inputs/model'; import { cloneDeep } from 'lodash/fp'; @@ -45,7 +45,7 @@ describe('selectors', () => { const getFromStrSelector = fromStrSelector(); const getToStrSelector = toStrSelector(); const getIsLoadingSelector = isLoadingSelector(); - const getRefetchSelector = refetchSelector(); + const getQueriesSelector = queriesSelector(); beforeEach(() => { absoluteTime = { @@ -363,22 +363,22 @@ describe('selectors', () => { }); }); - describe('#refetchSelector', () => { + describe('#queriesSelector', () => { test('returns the same reference given the same identical input twice', () => { - const result1 = getRefetchSelector(inputState); - const result2 = getRefetchSelector(inputState); + const result1 = getQueriesSelector(inputState); + const result2 = getQueriesSelector(inputState); expect(result1).toBe(result2); }); test('DOES NOT return the same reference given different input twice but with different deep copies since the query is not a primitive', () => { const clone = cloneDeep(inputState); - const result1 = getRefetchSelector(inputState); - const result2 = getRefetchSelector(clone); + const result1 = getQueriesSelector(inputState); + const result2 = getQueriesSelector(clone); expect(result1).not.toBe(result2); }); test('returns a different reference even if the contents are the same since query is an array and not a primitive', () => { - const result1 = getRefetchSelector(inputState); + const result1 = getQueriesSelector(inputState); const change: InputsRange = { ...inputState, query: [ @@ -392,7 +392,7 @@ describe('selectors', () => { }, ], }; - const result2 = getRefetchSelector(change); + const result2 = getQueriesSelector(change); expect(result1).not.toBe(result2); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/super_date_picker/selectors.ts b/x-pack/legacy/plugins/siem/public/components/super_date_picker/selectors.ts index b0ebefb98ea3..7f2acd17ce79 100644 --- a/x-pack/legacy/plugins/siem/public/components/super_date_picker/selectors.ts +++ b/x-pack/legacy/plugins/siem/public/components/super_date_picker/selectors.ts @@ -61,8 +61,14 @@ export const isLoadingSelector = () => query => query.some(i => i.loading === true) ); -export const refetchSelector = () => +export const queriesSelector = () => createSelector( getQuery, - query => query.map(i => i.refetch) + query => query.filter(q => q.id !== 'kql') + ); + +export const kqlQuerySelector = () => + createSelector( + getQuery, + query => query.find(q => q.id === 'kql') ); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx index f35f5c922f07..6e3409c46e4f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx @@ -9,14 +9,13 @@ import * as React from 'react'; import { pure } from 'recompose'; import styled from 'styled-components'; -import { useContext } from 'react'; import { OnColumnRemoved } from '../../../events'; import { Sort } from '../../sort'; import { SortIndicator } from '../../sort/sort_indicator'; import { ColumnHeader } from '../column_header'; import { getSortDirection } from '../header/helpers'; import * as i18n from '../translations'; -import { TimelineContext } from '../../../timeline_context'; +import { useTimelineContext } from '../../../timeline_context'; const CLOSE_BUTTON_SIZE = 25; // px const SORT_INDICATOR_SIZE = 25; // px @@ -68,7 +67,7 @@ export const CloseButton = pure<{ CloseButton.displayName = 'CloseButton'; export const Actions = React.memo(({ header, onColumnRemoved, show, sort }) => { - const isLoading = useContext(TimelineContext); + const isLoading = useTimelineContext(); return ( (({ children, onClick, isResizing }) => { - const isLoading = useContext(TimelineContext); + const isLoading = useTimelineContext(); return ( (({ children }) => { - const width = useContext(TimelineWidthContext); + const width = useTimelineWidthContext(); // Passing the styles directly to the component because the width is // being calculated and is recommended by Styled Components for performance diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx index c2c4c30db3c5..8a99ed7417a6 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx @@ -17,6 +17,7 @@ import { getDraggableId, Providers } from './providers'; import { DELETE_CLASS_NAME, ENABLE_CLASS_NAME, EXCLUDE_CLASS_NAME } from './provider_item_actions'; describe('Providers', () => { + const mockTimelineContext: boolean = true; describe('rendering', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( @@ -95,7 +96,7 @@ describe('Providers', () => { const mockOnDataProviderRemoved = jest.fn(); const wrapper = mount( - + { const mockOnDataProviderRemoved = jest.fn(); const wrapper = mount( - + { const mockOnToggleDataProviderEnabled = jest.fn(); const wrapper = mount( - + { const wrapper = mount( - + { const wrapper = mount( - + { const wrapper = mount( - + { const wrapper = mount( - + ` ${({ hideExpandButton }) => @@ -52,7 +51,7 @@ export const ExpandableEvent = React.memo( toggleColumn, onUpdateColumns, }) => { - const width = useContext(TimelineWidthContext); + const width = useTimelineWidthContext(); // Passing the styles directly to the component of LazyAccordion because the width is // being calculated and is recommended by Styled Components for performance // https://github.com/styled-components/styled-components/issues/134#issuecomment-312415291 diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/fetch_kql_timeline.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/fetch_kql_timeline.tsx new file mode 100644 index 000000000000..e5f50c332a7c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/fetch_kql_timeline.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { memo, useEffect } from 'react'; +import { connect } from 'react-redux'; +import { ActionCreator } from 'typescript-fsa'; +import { StaticIndexPattern } from 'ui/index_patterns'; + +import { inputsModel, KueryFilterQuery, timelineSelectors, State } from '../../store'; +import { inputsActions } from '../../store/actions'; +import { InputsModelId } from '../../store/inputs/constants'; +import { useUpdateKql } from '../../utils/kql/use_update_kql'; + +interface TimelineKqlFetchRedux { + kueryFilterQuery: KueryFilterQuery | null; + kueryFilterQueryDraft: KueryFilterQuery | null; +} + +interface TimelineKqlFetchDispatch { + setTimelineQuery: ActionCreator<{ + id: string; + inputId: InputsModelId; + inspect: inputsModel.InspectQuery | null; + loading: boolean; + refetch: inputsModel.Refetch | inputsModel.RefetchKql | null; + }>; +} + +export interface TimelineKqlFetchProps { + id: string; + indexPattern: StaticIndexPattern; + inputId: InputsModelId; +} + +type OwnProps = TimelineKqlFetchProps & TimelineKqlFetchRedux & TimelineKqlFetchDispatch; + +const TimelineKqlFetchComponent = memo( + ({ id, indexPattern, inputId, kueryFilterQuery, kueryFilterQueryDraft, setTimelineQuery }) => { + useEffect(() => { + setTimelineQuery({ + id: 'kql', + inputId, + inspect: null, + loading: false, + refetch: useUpdateKql({ + indexPattern, + kueryFilterQuery, + kueryFilterQueryDraft, + storeType: 'timelineType', + type: null, + timelineId: id, + }), + }); + }, [kueryFilterQueryDraft, kueryFilterQuery, id]); + return null; + } +); + +const makeMapStateToProps = () => { + const getTimelineKueryFilterQueryDraft = timelineSelectors.getKqlFilterQueryDraftSelector(); + const getTimelineKueryFilterQuery = timelineSelectors.getKqlFilterKuerySelector(); + const mapStateToProps = (state: State, { id }: TimelineKqlFetchProps) => { + return { + kueryFilterQuery: getTimelineKueryFilterQuery(state, id), + kueryFilterQueryDraft: getTimelineKueryFilterQueryDraft(state, id), + }; + }; + return mapStateToProps; +}; + +export const TimelineKqlFetch = connect( + makeMapStateToProps, + { + setTimelineQuery: inputsActions.setQuery, + } +)(TimelineKqlFetchComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/refetch_timeline.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/refetch_timeline.tsx index 658ae0bb3ffd..ee818597268c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/refetch_timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/refetch_timeline.tsx @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { memo, useEffect } from 'react'; import { connect } from 'react-redux'; +import { compose } from 'redux'; import { ActionCreator } from 'typescript-fsa'; import { inputsModel } from '../../store'; @@ -18,36 +19,35 @@ interface TimelineRefetchDispatch { inputId: InputsModelId; inspect: inputsModel.InspectQuery | null; loading: boolean; - refetch: inputsModel.Refetch; + refetch: inputsModel.Refetch | inputsModel.RefetchKql | null; }>; } -interface TimelineRefetchProps { - children: React.ReactNode; +export interface TimelineRefetchProps { id: string; + inputId: InputsModelId; inspect: inputsModel.InspectQuery | null; loading: boolean; - refetch: inputsModel.Refetch; + refetch: inputsModel.Refetch | null; } -type OwnProps = TimelineRefetchDispatch & TimelineRefetchProps; +type OwnProps = TimelineRefetchProps & TimelineRefetchDispatch; -class TimelineRefetchComponent extends React.PureComponent { - public componentDidUpdate(prevProps: OwnProps) { - const { loading, id, inspect, refetch } = this.props; - if (prevProps.loading !== loading) { - this.props.setTimelineQuery({ id, inputId: 'timeline', inspect, loading, refetch }); - } - } +const TimelineRefetchComponent = memo( + ({ children, id, inputId, inspect, loading, refetch, setTimelineQuery }) => { + useEffect(() => { + setTimelineQuery({ id, inputId, inspect, loading, refetch }); + }, [loading, refetch, inspect]); - public render() { - return <>{this.props.children}; + return null; } -} +); -export const TimelineRefetch = connect( - null, - { - setTimelineQuery: inputsActions.setQuery, - } +export const TimelineRefetch = compose>( + connect( + null, + { + setTimelineQuery: inputsActions.setQuery, + } + ) )(TimelineRefetchComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx index 2a31402fbd32..ded0209cef35 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx @@ -34,7 +34,8 @@ import { Footer, footerHeight } from './footer'; import { TimelineHeader } from './header'; import { calculateBodyHeight, combineQueries } from './helpers'; import { TimelineRefetch } from './refetch_timeline'; -import { TimelineContext, TimelineWidthContext } from './timeline_context'; +import { ManageTimelineContext } from './timeline_context'; +import { TimelineKqlFetch } from './fetch_kql_timeline'; const WrappedByAutoSizer = styled.div` width: 100%; @@ -147,7 +148,7 @@ export const Timeline = React.memo( sort={sort} /> - + {combinedQueries != null ? ( ( getUpdatedAt, refetch, }) => ( - - - - -