From bc47a8c828875bea7cf139314270cf572b16b460 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Tue, 18 Dec 2018 12:06:04 -0500 Subject: [PATCH] Add date range picker above kql (#27281) * Create Date Range picker component * add full functionality for date range picker * clean up * add generic type * remove unused export * review * fix more review * fix type issue * Fix typo * fix refresh on loading more table * Fix loading in load more table * Fix test by adding new state * Move switch theme * Fix rebase issue with date logic --- x-pack/plugins/infra/types/eui.d.ts | 1 + x-pack/plugins/secops/common/graphql/types.ts | 4 +- .../public/components/flyout/index.test.tsx | 14 ++ .../components/load_more_table/index.test.tsx | 2 +- .../components/load_more_table/index.tsx | 29 ++- .../public/components/page/date_picker.tsx | 58 ----- .../secops/public/components/page/footer.tsx | 24 +- .../page/hosts/hosts_table/index.test.tsx | 14 ++ .../uncommon_process_table/index.test.tsx | 14 ++ .../secops/public/components/page/index.tsx | 13 -- .../public/components/page/manage_query.tsx | 34 +++ .../components/page/navigation/index.tsx | 30 --- .../range_date_picker/global_date_button.tsx | 119 ++++++++++ .../components/range_date_picker/index.tsx | 219 ++++++++++++++++++ .../quick_select_popover/commonly_used.tsx | 150 ++++++++++++ .../quick_select_popover/index.tsx | 146 ++++++++++++ .../quick_select_popover/quick_select.tsx | 179 ++++++++++++++ .../quick_select_popover/recently_used.tsx | 77 ++++++ .../quick_select_popover/timer.tsx | 102 ++++++++ .../range_date_picker/update_button.tsx | 43 ++++ .../timeline/header/timeline_header.test.tsx | 14 ++ .../components/timeline/timeline.test.tsx | 14 ++ .../secops/public/containers/events/index.tsx | 27 ++- .../public/containers/global_time/index.tsx | 67 ++++++ .../secops/public/containers/hosts/index.tsx | 29 ++- .../containers/uncommon_processes/index.tsx | 27 ++- .../secops/public/pages/home/index.tsx | 28 +-- .../secops/public/pages/hosts/index.tsx | 126 +++++----- x-pack/plugins/secops/public/store/actions.ts | 2 +- x-pack/plugins/secops/public/store/epic.ts | 11 + x-pack/plugins/secops/public/store/index.ts | 2 + .../secops/public/store/local/actions.ts | 1 + .../plugins/secops/public/store/local/epic.ts | 11 + .../secops/public/store/local/index.ts | 2 + .../public/store/local/inputs/actions.ts | 33 +++ .../secops/public/store/local/inputs/epic.ts | 70 ++++++ .../secops/public/store/local/inputs/index.ts | 14 ++ .../secops/public/store/local/inputs/model.ts | 42 ++++ .../public/store/local/inputs/reducer.ts | 101 ++++++++ .../public/store/local/inputs/selectors.ts | 23 ++ .../secops/public/store/local/model.ts | 7 + .../secops/public/store/local/reducer.ts | 4 + .../secops/public/store/local/selectors.ts | 1 + x-pack/plugins/secops/public/store/model.ts | 7 + .../plugins/secops/public/store/selectors.ts | 8 +- x-pack/plugins/secops/public/store/store.ts | 37 ++- .../secops/server/lib/events/query.dsl.ts | 4 +- .../secops/server/lib/hosts/query.dsl.ts | 4 +- .../lib/uncommon_processes/query.dsl.ts | 4 +- .../build_query/merge_fields_with_hits.ts | 6 +- 50 files changed, 1780 insertions(+), 218 deletions(-) delete mode 100644 x-pack/plugins/secops/public/components/page/date_picker.tsx create mode 100644 x-pack/plugins/secops/public/components/page/manage_query.tsx create mode 100644 x-pack/plugins/secops/public/components/range_date_picker/global_date_button.tsx create mode 100644 x-pack/plugins/secops/public/components/range_date_picker/index.tsx create mode 100644 x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/commonly_used.tsx create mode 100644 x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/index.tsx create mode 100644 x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/quick_select.tsx create mode 100644 x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/recently_used.tsx create mode 100644 x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/timer.tsx create mode 100644 x-pack/plugins/secops/public/components/range_date_picker/update_button.tsx create mode 100644 x-pack/plugins/secops/public/containers/global_time/index.tsx create mode 100644 x-pack/plugins/secops/public/store/epic.ts create mode 100644 x-pack/plugins/secops/public/store/local/epic.ts create mode 100644 x-pack/plugins/secops/public/store/local/inputs/actions.ts create mode 100644 x-pack/plugins/secops/public/store/local/inputs/epic.ts create mode 100644 x-pack/plugins/secops/public/store/local/inputs/index.ts create mode 100644 x-pack/plugins/secops/public/store/local/inputs/model.ts create mode 100644 x-pack/plugins/secops/public/store/local/inputs/reducer.ts create mode 100644 x-pack/plugins/secops/public/store/local/inputs/selectors.ts create mode 100644 x-pack/plugins/secops/public/store/local/model.ts create mode 100644 x-pack/plugins/secops/public/store/model.ts diff --git a/x-pack/plugins/infra/types/eui.d.ts b/x-pack/plugins/infra/types/eui.d.ts index b3285d211f5d..7f20abc696ca 100644 --- a/x-pack/plugins/infra/types/eui.d.ts +++ b/x-pack/plugins/infra/types/eui.d.ts @@ -164,6 +164,7 @@ declare module '@elastic/eui' { disabled?: boolean; isLoading?: boolean; dateFormat?: string; + isCustom?: boolean; }; export const EuiDatePickerRange: React.SFC; diff --git a/x-pack/plugins/secops/common/graphql/types.ts b/x-pack/plugins/secops/common/graphql/types.ts index a5aaf4525ae7..96dbb392b4df 100644 --- a/x-pack/plugins/secops/common/graphql/types.ts +++ b/x-pack/plugins/secops/common/graphql/types.ts @@ -143,7 +143,7 @@ export interface HostsData { pageInfo: PageInfo; } -export interface HostsEdges extends Record { +export interface HostsEdges { host: HostItem; cursor: CursorType; } @@ -172,7 +172,7 @@ export interface UncommonProcessesData { pageInfo: PageInfo; } -export interface UncommonProcessesEdges extends Record { +export interface UncommonProcessesEdges { uncommonProcess: UncommonProcessItem; cursor: CursorType; } diff --git a/x-pack/plugins/secops/public/components/flyout/index.test.tsx b/x-pack/plugins/secops/public/components/flyout/index.test.tsx index 6980a3681e89..136b27b98108 100644 --- a/x-pack/plugins/secops/public/components/flyout/index.test.tsx +++ b/x-pack/plugins/secops/public/components/flyout/index.test.tsx @@ -36,6 +36,20 @@ describe('Flyout', () => { dragAndDrop: { dataProviders: {}, }, + inputs: { + global: { + timerange: { + kind: 'absolute', + from: 0, + to: 1, + }, + query: [], + policy: { + kind: 'manual', + duration: 5000, + }, + }, + }, timeline: { timelineById: { test: { diff --git a/x-pack/plugins/secops/public/components/load_more_table/index.test.tsx b/x-pack/plugins/secops/public/components/load_more_table/index.test.tsx index bf0f5180cccd..e12d067cf2e8 100644 --- a/x-pack/plugins/secops/public/components/load_more_table/index.test.tsx +++ b/x-pack/plugins/secops/public/components/load_more_table/index.test.tsx @@ -99,7 +99,7 @@ describe('Load More Table Component', () => { title={

Hosts

} /> ); - + wrapper.setState({ paginationLoading: true }); expect(wrapper.find('[data-test-subj="LoadingPanelLoadMoreTable"]').exists()).toBeFalsy(); expect( wrapper diff --git a/x-pack/plugins/secops/public/components/load_more_table/index.tsx b/x-pack/plugins/secops/public/components/load_more_table/index.tsx index 70a33bad1ec0..f636180b50ca 100644 --- a/x-pack/plugins/secops/public/components/load_more_table/index.tsx +++ b/x-pack/plugins/secops/public/components/load_more_table/index.tsx @@ -47,6 +47,7 @@ interface BasicTableProps { interface BasicTableState { isPopoverOpen: boolean; + paginationLoading: boolean; } export interface Columns { @@ -62,8 +63,20 @@ export interface Columns { export class LoadMoreTable extends React.PureComponent, BasicTableState> { public readonly state = { isPopoverOpen: false, + paginationLoading: false, }; + public componentDidUpdate(prevProps: BasicTableProps) { + const { paginationLoading } = this.state; + const { loading } = this.props; + if (paginationLoading && prevProps.loading && !loading) { + this.setState({ + ...this.state, + paginationLoading: false, + }); + } + } + public render() { const { columns, @@ -73,12 +86,12 @@ export class LoadMoreTable extends React.PureComponent, Ba loadingTitle, pageOfItems, title, - loadMore, limit, updateLimitPagination, } = this.props; + const { paginationLoading } = this.state; - if (loading && isEmpty(pageOfItems)) { + if (loading && !paginationLoading) { return ( extends React.PureComponent, Ba {loading ? 'Loading...' : 'Load More'} @@ -167,14 +180,24 @@ export class LoadMoreTable extends React.PureComponent, Ba ); } + private loadMore = () => { + this.setState({ + ...this.state, + paginationLoading: true, + }); + this.props.loadMore(); + }; + private onButtonClick = () => { this.setState({ + ...this.state, isPopoverOpen: !this.state.isPopoverOpen, }); }; private closePopover = () => { this.setState({ + ...this.state, isPopoverOpen: false, }); }; diff --git a/x-pack/plugins/secops/public/components/page/date_picker.tsx b/x-pack/plugins/secops/public/components/page/date_picker.tsx deleted file mode 100644 index 8280f023b0c3..000000000000 --- a/x-pack/plugins/secops/public/components/page/date_picker.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 { EuiDatePicker, EuiDatePickerRange } from '@elastic/eui'; -import { first, last, noop } from 'lodash/fp'; -import moment, { Moment } from 'moment'; -import * as React from 'react'; -import { pure } from 'recompose'; -import { getDateRange } from '../timeline/body/mini_map/date_ranges'; - -// TODO: replace this stub -const getDefaultStartDate = () => { - const dates: Date[] = getDateRange('day'); - return moment(first(dates)); -}; - -// TODO: replace this stub -const getDefaultEndDate = () => { - const dates: Date[] = getDateRange('day'); - return moment(last(dates)); -}; - -interface DatePickerProps { - startDate?: Moment; - endDate?: Moment; -} - -export const DatePicker = pure( - ({ startDate = getDefaultStartDate(), endDate = getDefaultEndDate() }) => ( - <> - - } - endDateControl={ - - } - /> - - ) -); diff --git a/x-pack/plugins/secops/public/components/page/footer.tsx b/x-pack/plugins/secops/public/components/page/footer.tsx index a3a274569eb0..69f488bd7d76 100644 --- a/x-pack/plugins/secops/public/components/page/footer.tsx +++ b/x-pack/plugins/secops/public/components/page/footer.tsx @@ -5,6 +5,9 @@ */ import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, // @ts-ignore EuiHeaderLogo, EuiHealth, @@ -14,9 +17,28 @@ import { pure } from 'recompose'; import { FooterContainer } from '.'; import { WhoAmI } from '../../containers/who_am_i'; +import { ThemeSwitcher } from '../theme_switcher'; export const Footer = pure(() => ( - {() => Live data} + + + + {({ appName }) => Live {appName} data} + + + + + + + + + + Add data + + + + + )); diff --git a/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.test.tsx b/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.test.tsx index 35b5ee6599f4..29a7c21b69d2 100644 --- a/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.test.tsx +++ b/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.test.tsx @@ -28,6 +28,20 @@ describe('Load More Table Component', () => { hosts: { limit: 2, }, + inputs: { + global: { + timerange: { + kind: 'absolute', + from: 0, + to: 1, + }, + query: [], + policy: { + kind: 'manual', + duration: 5000, + }, + }, + }, dragAndDrop: { dataProviders: {}, }, diff --git a/x-pack/plugins/secops/public/components/page/hosts/uncommon_process_table/index.test.tsx b/x-pack/plugins/secops/public/components/page/hosts/uncommon_process_table/index.test.tsx index 4e0c736ee1f4..1d188e90f345 100644 --- a/x-pack/plugins/secops/public/components/page/hosts/uncommon_process_table/index.test.tsx +++ b/x-pack/plugins/secops/public/components/page/hosts/uncommon_process_table/index.test.tsx @@ -27,6 +27,20 @@ describe('UncommonProcess Table Component', () => { hosts: { limit: 2, }, + inputs: { + global: { + timerange: { + kind: 'absolute', + from: 0, + to: 1, + }, + query: [], + policy: { + kind: 'manual', + duration: 5000, + }, + }, + }, dragAndDrop: { dataProviders: {}, }, diff --git a/x-pack/plugins/secops/public/components/page/index.tsx b/x-pack/plugins/secops/public/components/page/index.tsx index e122271cf652..017e3acecf07 100644 --- a/x-pack/plugins/secops/public/components/page/index.tsx +++ b/x-pack/plugins/secops/public/components/page/index.tsx @@ -47,19 +47,6 @@ export const FooterContainer = styled.div` width: 100%; `; -export const SubHeader = styled.div` - display: flex; - flex-direction: column; - user-select: none; - width: 100%; -`; - -export const SubHeaderDatePicker = styled.div` - display: flex; - justify-content: flex-end; - margin: 5px 0 5px 0; -`; - export const PaneScrollContainer = styled.div` height: 100%; overflow-y: scroll; diff --git a/x-pack/plugins/secops/public/components/page/manage_query.tsx b/x-pack/plugins/secops/public/components/page/manage_query.tsx new file mode 100644 index 000000000000..782ca23bf7ba --- /dev/null +++ b/x-pack/plugins/secops/public/components/page/manage_query.tsx @@ -0,0 +1,34 @@ +/* + * 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 { omit } from 'lodash/fp'; +import React from 'react'; +import { inputsModel } from '../../store'; + +interface OwnProps { + id: string; + loading: boolean; + refetch: inputsModel.Refetch; + setQuery: (params: { id: string; loading: boolean; refetch: inputsModel.Refetch }) => void; +} + +export function manageQuery(WrappedComponent: React.ComponentClass | React.ComponentType) { + class ManageQuery extends React.PureComponent { + public componentDidUpdate(prevProps: OwnProps) { + const { loading, id, refetch, setQuery } = this.props; + if (prevProps.loading !== loading) { + setQuery({ id, loading, refetch }); + } + } + + public render() { + const otherProps = omit(['id', 'refetch', 'setQuery'], this.props); + return ; + } + } + + return ManageQuery; +} diff --git a/x-pack/plugins/secops/public/components/page/navigation/index.tsx b/x-pack/plugins/secops/public/components/page/navigation/index.tsx index 48cc067f4301..4b39ee601c44 100644 --- a/x-pack/plugins/secops/public/components/page/navigation/index.tsx +++ b/x-pack/plugins/secops/public/components/page/navigation/index.tsx @@ -4,18 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ import { - EuiButton, // @ts-ignore EuiTab, // @ts-ignore EuiTabs, } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { getHostsUrl, getNetworkUrl, getOverviewUrl } from '../../link_to'; -import { ThemeSwitcher } from '../../theme_switcher'; interface NavTab { id: string; @@ -49,30 +46,6 @@ interface NavigationState { selectedTabId: string; } -const AddSources = styled.div` - display: flex; - flex-direction: row; - position: relative; - top: -48px; -`; - -const AddData = pure(() => ( - - - Add data - - - -)); - -const AddDataContainer = styled.div` - display: flex; - flex-direction: row; - height: 0px; - justify-content: flex-end; - width: 100%; -`; - const NavigationContainer = styled.div` display: flex; flex-direction: column; @@ -93,9 +66,6 @@ export class Navigation extends React.PureComponent<{}, NavigationState> { return ( {this.renderTabs()} - - - ); } diff --git a/x-pack/plugins/secops/public/components/range_date_picker/global_date_button.tsx b/x-pack/plugins/secops/public/components/range_date_picker/global_date_button.tsx new file mode 100644 index 000000000000..fd80d55b268f --- /dev/null +++ b/x-pack/plugins/secops/public/components/range_date_picker/global_date_button.tsx @@ -0,0 +1,119 @@ +/* + * 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 { EuiDatePicker, EuiFieldText, EuiFormRow, EuiPopover } from '@elastic/eui'; +import classNames from 'classnames'; +import moment, { Moment } from 'moment'; +import React from 'react'; + +type Position = 'start' | 'end'; + +interface Props { + id: string; + position: Position; + isInvalid: boolean; + needsUpdating?: boolean; + date: Moment; + buttonProps?: string; + buttonOnly?: boolean; + onChange: (date: moment.Moment | null) => void; +} + +interface State { + isPopoverOpen: boolean; +} + +export class GlobalDateButton extends React.PureComponent { + public readonly state = { + isPopoverOpen: false, + }; + + public render() { + const { + id, + position, + isInvalid, + needsUpdating = false, + date, + buttonProps, + buttonOnly, + onChange, + ...rest + } = this.props; + + const { isPopoverOpen } = this.state; + + const classes = classNames([ + 'euiGlobalDatePicker__dateButton', + `euiGlobalDatePicker__dateButton--${position}`, + { + 'euiGlobalDatePicker__dateButton-isSelected': isPopoverOpen, + 'euiGlobalDatePicker__dateButton-isInvalid': isInvalid, + 'euiGlobalDatePicker__dateButton-needsUpdating': needsUpdating, + }, + ]); + + let title = date.format('L LTS'); + if (isInvalid) { + title = `Invalid date: ${title}`; + } else if (needsUpdating) { + title = `Update needed: ${title}`; + } + + const button = ( + + ); + + return buttonOnly ? ( + button + ) : ( + +
+ + + + +
+
+ ); + } + + private togglePopover = () => { + this.setState({ + isPopoverOpen: !this.state.isPopoverOpen, + }); + }; + + private closePopover = () => { + this.setState({ + isPopoverOpen: false, + }); + }; +} diff --git a/x-pack/plugins/secops/public/components/range_date_picker/index.tsx b/x-pack/plugins/secops/public/components/range_date_picker/index.tsx new file mode 100644 index 000000000000..4d208c5cd114 --- /dev/null +++ b/x-pack/plugins/secops/public/components/range_date_picker/index.tsx @@ -0,0 +1,219 @@ +/* + * 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 { EuiDatePickerRange, EuiFlexGroup, EuiFlexItem, EuiFormControlLayout } from '@elastic/eui'; +import { get, getOr, has, isEqual } from 'lodash/fp'; +import moment, { Moment } from 'moment'; +import React from 'react'; +import { connect } from 'react-redux'; +import { inputsActions, inputsModel, State } from '../../store'; +import { GlobalDateButton } from './global_date_button'; +import { QuickSelectPopover } from './quick_select_popover'; +import { UpdateButton } from './update_button'; + +export type DateType = 'relative' | 'absolute'; + +interface RangeDatePickerStateRedux { + from: number; + to: number; + isTimerOn: boolean; + duration: number; + loading: boolean; + refetch: inputsModel.Refetch[]; +} + +interface RangeDatePickerDispatchProps { + setAbsoluteRangeDatePicker: (params: { id: string; from: number; to: number }) => void; + setRelativeRangeDatePicker: ( + params: { id: string; option: string; from: number; to: number } + ) => void; + startAutoReload: (params: { id: string }) => void; + stopAutoReload: (params: { id: string }) => void; + setDuration: (params: { id: string; duration: number }) => void; +} +interface OwnProps { + id: string; + disabled?: boolean; +} + +type RangeDatePickerProps = OwnProps & RangeDatePickerDispatchProps & RangeDatePickerStateRedux; + +interface RecentylUsedBasic { + kind: string; + text: string; +} + +interface RecentylUsedDateRange { + kind: string; + timerange: number[]; +} + +export type RecentlyUsedI = RecentylUsedBasic | RecentylUsedDateRange; + +interface RangeDatePickerState { + recentlyUsed: RecentlyUsedI[]; +} + +class RangeDatePickerComponents extends React.PureComponent< + RangeDatePickerProps, + RangeDatePickerState +> { + public readonly state = { + recentlyUsed: [] as RecentlyUsedI[], + }; + + public render() { + const { recentlyUsed } = this.state; + const { id, loading, disabled = false, from, to, isTimerOn, refetch } = this.props; + + const quickSelectPopover = ( + + ); + + return ( + + + + to : false} + /> + } + endDateControl={ + to : false} + /> + } + /> + + + + + + + ); + } + + private handleChangeFrom = (date: moment.Moment | null) => { + const { id, to } = this.props; + if (date && moment(this.props.from) !== date) { + this.props.setAbsoluteRangeDatePicker({ id, from: date.valueOf(), to }); + this.updateRecentlyUsed({ + kind: 'date-range', + timerange: [date.valueOf(), to], + }); + } + }; + + private handleChangeTo = (date: moment.Moment | null) => { + const { id, from } = this.props; + if (date && moment(this.props.to) !== date) { + this.props.setAbsoluteRangeDatePicker({ id, from, to: date.valueOf() }); + this.updateRecentlyUsed({ + kind: 'date-range', + timerange: [from, date.valueOf()], + }); + } + }; + + private updateRecentlyUsed = (msg?: RecentlyUsedI) => { + const { recentlyUsed } = this.state; + if ( + msg && + recentlyUsed.filter((i: RecentlyUsedI) => { + const timerange = getOr(false, 'timerange', msg); + const text = getOr(false, 'text', msg); + if (timerange && has('timerange', i)) { + return isEqual(timerange, get('timerange', i)); + } else if (text && has('text', i)) { + return text === get('text', i); + } + return false; + }).length === 0 + ) { + recentlyUsed.unshift(msg); + this.setState({ + ...this.state, + recentlyUsed: recentlyUsed.slice(0, 5), + }); + } + }; + + private onChange = (from: Moment, to: Moment, type: DateType, msg?: RecentlyUsedI) => { + const { id } = this.props; + if (type === 'absolute') { + this.props.setAbsoluteRangeDatePicker({ + id, + from: from.valueOf(), + to: to.valueOf(), + }); + } else if (type === 'relative') { + this.props.setRelativeRangeDatePicker({ + id, + option: msg ? msg.kind : '', + from: from.valueOf(), + to: to.valueOf(), + }); + } + this.updateRecentlyUsed(msg); + }; + + private updateTimer = (isTimerOn: boolean, duration: number, durationKind: string) => { + const { id } = this.props; + this.props.setDuration({ + id, + duration: moment + .duration(duration, durationKind as moment.unitOfTime.DurationConstructor) + .asMilliseconds(), + }); + if (isTimerOn) { + this.props.startAutoReload({ id }); + } else { + this.props.stopAutoReload({ id }); + } + }; +} + +const mapStateToProps = (state: State, { id }: OwnProps) => { + const myState = getOr({}, `local.inputs.${id}`, state); + return { + from: get('timerange.from', myState), + to: get('timerange.to', myState), + isTimerOn: get('policy.kind', myState) === 'interval', + duration: get('policy.duration', myState), + loading: myState.query.filter((i: inputsModel.GlobalQuery) => i.loading === true).length > 0, + refetch: myState.query.map((i: inputsModel.GlobalQuery) => i.refetch), + }; +}; + +export const RangeDatePicker = connect( + mapStateToProps, + { + setAbsoluteRangeDatePicker: inputsActions.setAbsoluteRangeDatePicker, + setRelativeRangeDatePicker: inputsActions.setRelativeRangeDatePicker, + startAutoReload: inputsActions.startAutoReload, + stopAutoReload: inputsActions.stopAutoReload, + setDuration: inputsActions.setDuration, + } +)(RangeDatePickerComponents); diff --git a/x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/commonly_used.tsx b/x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/commonly_used.tsx new file mode 100644 index 000000000000..b235f13bc8e7 --- /dev/null +++ b/x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/commonly_used.tsx @@ -0,0 +1,150 @@ +/* + * 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 { EuiFlexGrid, EuiFlexItem, EuiLink, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +import { getOr } from 'lodash/fp'; +import moment, { Moment } from 'moment'; +import React from 'react'; +import { pure } from 'recompose'; +import { DateType, RecentlyUsedI } from '..'; + +export enum DatePickerOptions { + today = 'today', + yesterday = 'yesterday', + thisWeek = 'this-week', + weekToDate = 'week-to-date', + thisMonth = 'this-month', + monthToDate = 'month-to-date', + thisYear = 'this-year', + yearToDate = 'year-to-date', +} + +const commonDates: Array<{ id: DatePickerOptions; label: string }> = [ + { + id: DatePickerOptions.today, + label: 'Today', + }, + { + id: DatePickerOptions.yesterday, + label: 'Yesterday', + }, + { + id: DatePickerOptions.thisWeek, + label: 'This week', + }, + { + id: DatePickerOptions.weekToDate, + label: 'Week to date', + }, + { + id: DatePickerOptions.thisMonth, + label: 'This month', + }, + { + id: DatePickerOptions.monthToDate, + label: 'Month to date', + }, + { + id: DatePickerOptions.thisYear, + label: 'This year', + }, + { + id: DatePickerOptions.yearToDate, + label: 'Year to date', + }, +]; + +interface Props { + setRangeDatePicker: (from: Moment, to: Moment, type: DateType, msg: RecentlyUsedI) => void; +} + +export const CommonlyUsed = pure(({ setRangeDatePicker }) => { + const links = commonDates.map(date => { + return ( + + updateRangeDatePickerByCommonUsed(date.id, setRangeDatePicker)}> + {date.label} + + + ); + }); + + return ( + <> + + Commonly used + + + + + {links} + + + + ); +}); + +export const updateRangeDatePickerByCommonUsed = ( + option: DatePickerOptions, + setRangeDatePicker: (from: Moment, to: Moment, kind: DateType, msg: RecentlyUsedI) => void +) => { + let from = null; + let to = null; + let kind: DateType = 'absolute'; + if (option === DatePickerOptions.today) { + from = moment().startOf('day'); + to = moment() + .startOf('day') + .add(24, 'hour'); + kind = 'absolute'; + } else if (option === DatePickerOptions.yesterday) { + from = moment() + .subtract(1, 'day') + .startOf('day'); + to = moment() + .subtract(1, 'day') + .startOf('day') + .add(24, 'hour'); + kind = 'absolute'; + } else if (option === DatePickerOptions.thisWeek) { + from = moment().startOf('week'); + to = moment() + .startOf('week') + .add(1, 'week'); + kind = 'absolute'; + } else if (option === DatePickerOptions.weekToDate) { + from = moment().startOf('week'); + to = moment(); + kind = 'relative'; + } else if (option === DatePickerOptions.thisMonth) { + from = moment().startOf('month'); + to = moment() + .startOf('month') + .add(1, 'month'); + kind = 'absolute'; + } else if (option === DatePickerOptions.monthToDate) { + from = moment().startOf('month'); + to = moment(); + kind = 'relative'; + } else if (option === DatePickerOptions.thisYear) { + from = moment().startOf('year'); + to = moment() + .startOf('year') + .add(1, 'year'); + kind = 'absolute'; + } else if (option === DatePickerOptions.yearToDate) { + from = moment().startOf('year'); + to = moment(); + kind = 'relative'; + } + if (from && to) { + const text = getOr('', 'label', commonDates.filter(i => i.id === option)[0]); + setRangeDatePicker(from, to, kind, { + kind: option, + text, + }); + } +}; diff --git a/x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/index.tsx b/x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/index.tsx new file mode 100644 index 000000000000..0023d1d734a7 --- /dev/null +++ b/x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/index.tsx @@ -0,0 +1,146 @@ +/* + * 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 { + EuiButtonEmpty, + EuiHorizontalRule, + EuiIcon, + EuiPopover, + EuiPopoverProps, +} from '@elastic/eui'; +import { Moment } from 'moment'; +import React from 'react'; +import { DateType, RecentlyUsedI } from '../index'; +import { CommonlyUsed } from './commonly_used'; +import { QuickSelect } from './quick_select'; +import { MyRecentlyUsed } from './recently_used'; +import { Timer } from './timer'; + +type MyEuiPopoverProps = Pick< + EuiPopoverProps, + 'id' | 'closePopover' | 'button' | 'isOpen' | 'anchorPosition' +> & { + zIndex?: number; +}; + +const MyEuiPopover: React.SFC = EuiPopover; + +interface Props { + disabled: boolean; + recentlyUsed: RecentlyUsedI[]; + isTimerOn: boolean; + onChange: (from: Moment, to: Moment, type: DateType, msg?: RecentlyUsedI) => void; + updateAutoReload: (isTimerOn: boolean, interval: number, intervalType: string) => void; +} + +interface State { + quickSelectTime: number; + quickSelectUnit: string; + duration: number; + durationKind: string; + isPopoverOpen: boolean; +} + +export class QuickSelectPopover extends React.PureComponent { + public readonly state = { + isPopoverOpen: false, + quickSelectTime: 1, + quickSelectUnit: 'hours', + duration: 5, + durationKind: 'minutes', + }; + + public render() { + const { quickSelectTime, quickSelectUnit, duration, durationKind } = this.state; + const { disabled, isTimerOn, recentlyUsed, updateAutoReload } = this.props; + const quickSelectButton = ( + + + + ); + + return ( + +
+ + + + + + + updateAutoReload(isOn, duration, durationKind)} + /> +
+
+ ); + } + + private updateState = ( + stateType: string, + args: React.FormEvent | React.ChangeEvent + ) => { + let value: string | number = args!.currentTarget.value; + + if ((stateType === 'quickSelectTime' || stateType === 'duration') && value !== '') { + value = parseInt(args!.currentTarget.value, 10); + } + this.setState({ + ...this.state, + [stateType]: value, + }); + }; + + private onChange = (from: Moment, to: Moment, type: DateType, msg?: RecentlyUsedI) => { + this.setState( + { + ...this.state, + isPopoverOpen: false, + }, + () => { + this.props.onChange(from, to, type, msg); + } + ); + }; + + private closePopover = () => { + this.setState({ + ...this.state, + isPopoverOpen: false, + }); + }; + + private togglePopover = () => { + this.setState({ + isPopoverOpen: !this.state.isPopoverOpen, + }); + }; +} diff --git a/x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/quick_select.tsx b/x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/quick_select.tsx new file mode 100644 index 000000000000..48ac8db61a85 --- /dev/null +++ b/x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/quick_select.tsx @@ -0,0 +1,179 @@ +/* + * 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 { + EuiButton, + EuiFieldNumber, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSelect, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { getOr } from 'lodash/fp'; +import moment, { Moment } from 'moment'; +import React from 'react'; +import { pure } from 'recompose'; +import { DateType, RecentlyUsedI } from '..'; + +interface Options { + value: string; + text: string; +} + +export const singleLastOptions: Options[] = [ + { + value: 'seconds', + text: 'second', + }, + { + value: 'minutes', + text: 'minute', + }, + { + value: 'hours', + text: 'hour', + }, + { + value: 'days', + text: 'day', + }, + { + value: 'weeks', + text: 'week', + }, + { + value: 'months', + text: 'month', + }, + { + value: 'years', + text: 'year', + }, +]; + +export const pluralLastOptions: Options[] = [ + { + value: 'seconds', + text: 'seconds', + }, + { + value: 'minutes', + text: 'minutes', + }, + { + value: 'hours', + text: 'hours', + }, + { + value: 'days', + text: 'days', + }, + { + value: 'weeks', + text: 'weeks', + }, + { + value: 'months', + text: 'months', + }, + { + value: 'years', + text: 'years', + }, +]; + +interface Props { + quickSelectTime: number; + quickSelectUnit: string; + onChange: ( + stateType: string, + args: React.FormEvent | React.ChangeEvent + ) => void; + setRangeDatePicker: (from: Moment, to: Moment, type: DateType, msg?: RecentlyUsedI) => void; +} + +export const QuickSelect = pure( + ({ setRangeDatePicker, quickSelectTime, quickSelectUnit, onChange }) => ( + <> + + + + Quick select + + + + + + + + Last + + + + + { + onChange('quickSelectTime', arg); + }} + /> + + + + + { + onChange('quickSelectUnit', arg); + }} + /> + + + + + + updateRangeDatePickerByQuickSelect( + quickSelectTime, + quickSelectUnit, + setRangeDatePicker + ) + } + style={{ minWidth: 0 }} + > + Apply + + + + + + ) +); + +export const updateRangeDatePickerByQuickSelect = ( + quickSelectTime: number, + quickSelectUnit: string, + setRangeDatePicker: (from: Moment, to: Moment, type: DateType, msg?: RecentlyUsedI) => void +) => { + const quickSelectUnitStr = + quickSelectTime === 1 + ? getOr('', 'text', singleLastOptions.filter(i => i.value === quickSelectUnit)[0]) + : getOr('', 'text', pluralLastOptions.filter(i => i.value === quickSelectUnit)[0]); + const from = moment().subtract( + quickSelectTime, + quickSelectUnit as moment.unitOfTime.DurationConstructor + ); + const to = moment(); + setRangeDatePicker(from, to, 'relative', { + kind: 'quick-select', + text: `Last ${quickSelectTime} ${quickSelectUnitStr}`, + }); +}; diff --git a/x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/recently_used.tsx b/x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/recently_used.tsx new file mode 100644 index 000000000000..2ab1e95e264b --- /dev/null +++ b/x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/recently_used.tsx @@ -0,0 +1,77 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +import { getOr } from 'lodash/fp'; +import moment, { Moment } from 'moment'; +import React from 'react'; +import { pure } from 'recompose'; +import { DateType, RecentlyUsedI } from '../index'; +import { DatePickerOptions, updateRangeDatePickerByCommonUsed } from './commonly_used'; +import { updateRangeDatePickerByQuickSelect } from './quick_select'; + +interface Props { + recentlyUsed: RecentlyUsedI[]; + setRangeDatePicker: (from: Moment, to: Moment, kind: DateType) => void; +} + +export const MyRecentlyUsed = pure(({ setRangeDatePicker, recentlyUsed }) => { + const links = recentlyUsed.map((date: RecentlyUsedI) => { + let dateRange; + let dateLink = null; + const text = getOr(false, 'text', date); + const timerange = getOr(false, 'timerange', date); + if (text) { + dateLink = ( + updateRangeDatePicker(date.kind, setRangeDatePicker, text)}> + {text} + + ); + } else if (timerange) { + dateRange = `${moment(timerange[0]).format('L LTS')} – ${moment(timerange[1]).format( + 'L LTS' + )}`; + dateLink = ( + setRangeDatePicker(timerange[0], timerange[1], 'absolute')}> + {dateRange} + + ); + } + + return ( + + {dateLink} + + ); + }); + + return ( + <> + + Recently used date ranges + + + + + {links} + + + + ); +}); + +const updateRangeDatePicker = ( + option: string, + setRangeDatePicker: (from: Moment, to: Moment, kind: DateType, msg?: RecentlyUsedI) => void, + text?: string +) => { + if (option === 'quick-select') { + const options = text!.split(' '); + updateRangeDatePickerByQuickSelect(parseInt(options[1], 10), options[2], setRangeDatePicker); + } else { + updateRangeDatePickerByCommonUsed(option as DatePickerOptions, setRangeDatePicker); + } +}; diff --git a/x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/timer.tsx b/x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/timer.tsx new file mode 100644 index 000000000000..c20685f47d52 --- /dev/null +++ b/x-pack/plugins/secops/public/components/range_date_picker/quick_select_popover/timer.tsx @@ -0,0 +1,102 @@ +/* + * 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 { + EuiButton, + EuiFieldNumber, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSelect, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import React from 'react'; +import { pure } from 'recompose'; + +interface Options { + value: string; + text: string; +} + +export const singleLastOptions: Options[] = [ + { + value: 'minutes', + text: 'minute', + }, + { + value: 'hours', + text: 'hour', + }, +]; + +export const pluralLastOptions: Options[] = [ + { + value: 'minutes', + text: 'minutes', + }, + { + value: 'hours', + text: 'hours', + }, +]; + +interface Props { + timerIsOn: boolean; + duration: number; + durationKind: string; + onChange: ( + stateType: string, + args: React.FormEvent | React.ChangeEvent + ) => void; + toggleTimer: (timerIsOn: boolean) => void; +} + +export const Timer = pure(({ onChange, timerIsOn, duration, durationKind, toggleTimer }) => ( + <> + + Refresh every + + + + + + { + onChange('duration', arg); + }} + /> + + + + + { + onChange('durationKind', arg); + }} + /> + + + + + toggleTimer(!timerIsOn)} + style={{ minWidth: 0 }} + > + {timerIsOn ? 'Stop' : 'Start'} + + + + + +)); diff --git a/x-pack/plugins/secops/public/components/range_date_picker/update_button.tsx b/x-pack/plugins/secops/public/components/range_date_picker/update_button.tsx new file mode 100644 index 000000000000..beff06c578d1 --- /dev/null +++ b/x-pack/plugins/secops/public/components/range_date_picker/update_button.tsx @@ -0,0 +1,43 @@ +/* + * 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 { EuiButton } from '@elastic/eui'; +import React from 'react'; +import { pure } from 'recompose'; +import { inputsModel } from '../../store'; + +interface Props { + loading: boolean; + refetch: inputsModel.Refetch[]; +} + +export const UpdateButton = pure(({ loading, refetch }) => { + const color = loading ? 'secondary' : 'primary'; + const icon = 'refresh'; + let text = 'Refresh'; + + if (loading) { + text = 'Updating'; + } + + return ( + refetchQuery(refetch)} + textProps={{ className: 'euiGlobalDatePicker__updateButtonText' }} + > + {text} + + ); +}); + +const refetchQuery = (query: inputsModel.Refetch[]) => { + query.forEach((refetch: inputsModel.Refetch) => refetch()); +}; diff --git a/x-pack/plugins/secops/public/components/timeline/header/timeline_header.test.tsx b/x-pack/plugins/secops/public/components/timeline/header/timeline_header.test.tsx index 6170cf4a8f5e..e6a713df71b6 100644 --- a/x-pack/plugins/secops/public/components/timeline/header/timeline_header.test.tsx +++ b/x-pack/plugins/secops/public/components/timeline/header/timeline_header.test.tsx @@ -23,6 +23,20 @@ describe('Header', () => { notesById: {}, theme: 'dark', }, + inputs: { + global: { + timerange: { + kind: 'absolute', + from: 0, + to: 1, + }, + query: [], + policy: { + kind: 'manual', + duration: 5000, + }, + }, + }, hosts: { limit: 2, }, diff --git a/x-pack/plugins/secops/public/components/timeline/timeline.test.tsx b/x-pack/plugins/secops/public/components/timeline/timeline.test.tsx index d4ef842aeaac..e0c30ce35bf6 100644 --- a/x-pack/plugins/secops/public/components/timeline/timeline.test.tsx +++ b/x-pack/plugins/secops/public/components/timeline/timeline.test.tsx @@ -52,6 +52,20 @@ describe('Timeline', () => { hosts: { limit: 2, }, + inputs: { + global: { + timerange: { + kind: 'absolute', + from: 0, + to: 1, + }, + query: [], + policy: { + kind: 'manual', + duration: 5000, + }, + }, + }, dragAndDrop: { dataProviders: {}, }, diff --git a/x-pack/plugins/secops/public/containers/events/index.tsx b/x-pack/plugins/secops/public/containers/events/index.tsx index 021fb3d0e937..c82fcd9dcca0 100644 --- a/x-pack/plugins/secops/public/containers/events/index.tsx +++ b/x-pack/plugins/secops/public/containers/events/index.tsx @@ -10,16 +10,19 @@ import { Query } from 'react-apollo'; import { pure } from 'recompose'; import { EventItem, GetEventsQuery, KpiItem } from '../../../common/graphql/types'; - +import { inputsModel } from '../../store'; import { eventsQuery } from './index.gql_query'; export interface EventsArgs { + id: string; events?: EventItem[]; kpiEventType?: KpiItem[]; loading: boolean; + refetch: inputsModel.Refetch; } export interface EventsProps { + id?: string; children?: (args: EventsArgs) => React.ReactNode; sourceId: string; startDate: number; @@ -28,7 +31,7 @@ export interface EventsProps { } export const EventsQuery = pure( - ({ children, filterQuery, sourceId, startDate, endDate }) => ( + ({ id = 'eventsQuery', children, filterQuery, sourceId, startDate, endDate }) => ( query={eventsQuery} fetchPolicy="cache-and-network" @@ -38,18 +41,22 @@ export const EventsQuery = pure( sourceId, timerange: { interval: '12h', - from: endDate, - to: startDate, + from: startDate, + to: endDate, }, }} > - {({ data, loading }) => - children!({ + {({ data, loading, refetch }) => { + const events = getOr([], 'source.getEvents.events', data); + const kpiEventType = getOr([], 'source.getEvents.kpiEventType', data); + return children!({ + id, + refetch, loading, - events: getOr([], 'source.getEvents.events', data), - kpiEventType: getOr([], 'source.getEvents.kpiEventType', data), - }) - } + events, + kpiEventType, + }); + }} ) ); diff --git a/x-pack/plugins/secops/public/containers/global_time/index.tsx b/x-pack/plugins/secops/public/containers/global_time/index.tsx new file mode 100644 index 000000000000..e59c99cc6334 --- /dev/null +++ b/x-pack/plugins/secops/public/containers/global_time/index.tsx @@ -0,0 +1,67 @@ +/* + * 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 React from 'react'; +import { connect } from 'react-redux'; +import { pure } from 'recompose'; +import { + globalPolicySelector, + globalTimeRangeSelector, + inputsActions, + inputsModel, + State, +} from '../../store'; + +interface GlobalTimeArgs { + poll: number; + from: number; + to: number; + setQuery: (params: { id: string; loading: boolean; refetch: inputsModel.Refetch }) => void; +} + +interface OwnProps { + children: (args: GlobalTimeArgs) => React.ReactNode; +} + +interface GlobalTimeDispatch { + setQuery: (params: { id: string; loading: boolean; refetch: inputsModel.Refetch }) => void; +} + +interface GlobalTimeReduxState { + from: number; + to: number; + poll: number; +} + +type GlobalTimeProps = OwnProps & GlobalTimeReduxState & GlobalTimeDispatch; + +const GlobalTimeComponent = pure(({ children, poll, from, to, setQuery }) => ( + <> + {children({ + poll, + from, + to, + setQuery, + })} + +)); + +const mapStateToProps = (state: State) => { + const timerange: inputsModel.TimeRange = globalTimeRangeSelector(state); + const policy: inputsModel.Policy = globalPolicySelector(state); + return { + poll: policy.kind === 'interval' && timerange.kind === 'absolute' ? policy.duration : 0, + from: timerange.from, + to: timerange.to, + }; +}; + +export const GlobalTime = connect( + mapStateToProps, + { + setQuery: inputsActions.setQuery, + } +)(GlobalTimeComponent); diff --git a/x-pack/plugins/secops/public/containers/hosts/index.tsx b/x-pack/plugins/secops/public/containers/hosts/index.tsx index d590f14dbfa3..f3cf58d5bccb 100644 --- a/x-pack/plugins/secops/public/containers/hosts/index.tsx +++ b/x-pack/plugins/secops/public/containers/hosts/index.tsx @@ -12,24 +12,27 @@ import { pure } from 'recompose'; import { GetHostsQuery, HostsEdges, PageInfo } from '../../../common/graphql/types'; import { connect } from 'react-redux'; -import { hostsSelector, State } from '../../store'; +import { hostsSelector, inputsModel, State } from '../../store'; import { hostsQuery } from './index.gql_query'; export interface HostsArgs { + id: string; hosts: HostsEdges[]; totalCount: number; pageInfo: PageInfo; loading: boolean; loadMore: (cursor: string) => void; + refetch: inputsModel.Refetch; } export interface OwnProps { + id?: string; children: (args: HostsArgs) => React.ReactNode; sourceId: string; startDate: number; endDate: number; filterQuery?: string; - cursor: string | null; + poll: number; } export interface HostsComponentReduxProps { @@ -39,31 +42,35 @@ export interface HostsComponentReduxProps { type HostsProps = OwnProps & HostsComponentReduxProps; const HostsComponentQuery = pure( - ({ children, filterQuery, sourceId, startDate, endDate, limit = 2, cursor }) => ( + ({ id = 'hostsQuery', children, filterQuery, sourceId, startDate, endDate, limit = 2, poll }) => ( query={hostsQuery} fetchPolicy="cache-and-network" + pollInterval={poll} notifyOnNetworkStatusChange variables={{ sourceId, timerange: { interval: '12h', - from: endDate, - to: startDate, + from: startDate, + to: endDate, }, pagination: { limit, - cursor, + cursor: null, tiebreaker: null, }, filterQuery, }} > - {({ data, loading, fetchMore }) => - children({ + {({ data, loading, fetchMore, refetch }) => { + const hosts = getOr([], 'source.Hosts.edges', data); + return children({ + id, + refetch, loading, totalCount: getOr(0, 'source.Hosts.totalCount', data), - hosts: getOr([], 'source.Hosts.edges', data), + hosts, pageInfo: getOr({}, 'source.Hosts.pageInfo', data), loadMore: (newCursor: string) => fetchMore({ @@ -89,8 +96,8 @@ const HostsComponentQuery = pure( }; }, }), - }) - } + }); + }} ) ); diff --git a/x-pack/plugins/secops/public/containers/uncommon_processes/index.tsx b/x-pack/plugins/secops/public/containers/uncommon_processes/index.tsx index e02d89941506..c156fb5ec3af 100644 --- a/x-pack/plugins/secops/public/containers/uncommon_processes/index.tsx +++ b/x-pack/plugins/secops/public/containers/uncommon_processes/index.tsx @@ -16,24 +16,28 @@ import { } from '../../../common/graphql/types'; import { connect } from 'react-redux'; -import { State } from '../../store'; +import { inputsModel, State } from '../../store'; import { uncommonProcessesQuery } from './index.gql_query'; export interface UncommonProcessesArgs { + id: string; uncommonProcesses: UncommonProcessesEdges[]; totalCount: number; pageInfo: PageInfo; loading: boolean; loadMore: (cursor: string) => void; + refetch: inputsModel.Refetch; } export interface OwnProps { + id?: string; children: (args: UncommonProcessesArgs) => React.ReactNode; sourceId: string; startDate: number; endDate: number; filterQuery?: string; cursor: string | null; + poll: number; } export interface UncommonProcessesComponentReduxProps { @@ -43,17 +47,28 @@ export interface UncommonProcessesComponentReduxProps { type UncommonProcessesProps = OwnProps & UncommonProcessesComponentReduxProps; const UncommonProcessesComponentQuery = pure( - ({ children, filterQuery, sourceId, startDate, endDate, limit, cursor }) => ( + ({ + id = 'uncommonProcessesQuery', + children, + filterQuery, + sourceId, + startDate, + endDate, + limit, + cursor, + poll, + }) => ( query={uncommonProcessesQuery} fetchPolicy="cache-and-network" + pollInterval={poll} notifyOnNetworkStatusChange variables={{ sourceId, timerange: { interval: '12h', - from: endDate, - to: startDate, + from: startDate, + to: endDate, }, pagination: { limit, @@ -63,9 +78,11 @@ const UncommonProcessesComponentQuery = pure( filterQuery, }} > - {({ data, loading, fetchMore }) => + {({ data, loading, fetchMore, refetch }) => children({ + id, loading, + refetch, totalCount: getOr(0, 'source.UncommonProcesses.totalCount', data), uncommonProcesses: getOr([], 'source.UncommonProcesses.edges', data), pageInfo: getOr({}, 'source.UncommonProcesses.pageInfo', data), diff --git a/x-pack/plugins/secops/public/pages/home/index.tsx b/x-pack/plugins/secops/public/pages/home/index.tsx index beed881aeba6..5f398d22074a 100644 --- a/x-pack/plugins/secops/public/pages/home/index.tsx +++ b/x-pack/plugins/secops/public/pages/home/index.tsx @@ -5,7 +5,8 @@ */ import { - EuiHorizontalRule, + EuiFlexGroup, + EuiFlexItem, // @ts-ignore EuiSearchBar, } from '@elastic/eui'; @@ -16,12 +17,11 @@ import { defaultTo, noop } from 'lodash/fp'; import * as React from 'react'; import { connect } from 'react-redux'; import { Redirect, Route, Switch } from 'react-router-dom'; +import { pure } from 'recompose'; import { Dispatch } from 'redux'; -import { ThemeProvider } from 'styled-components'; +import styled, { ThemeProvider } from 'styled-components'; import chrome from 'ui/chrome'; -import { pure } from 'recompose'; -import styled from 'styled-components'; import { AutoSizer } from '../../components/auto_sizer'; import { DragDropContextWrapper } from '../../components/drag_and_drop/drag_drop_context_wrapper'; import { Flyout, flyoutHeaderHeight } from '../../components/flyout'; @@ -33,12 +33,10 @@ import { Pane, PaneHeader, PaneScrollContainer, - SubHeader, - SubHeaderDatePicker, } from '../../components/page'; -import { DatePicker } from '../../components/page/date_picker'; import { Footer } from '../../components/page/footer'; import { Navigation } from '../../components/page/navigation'; +import { RangeDatePicker } from '../../components/range_date_picker'; import { StatefulTimeline } from '../../components/timeline'; import { headers } from '../../components/timeline/body/column_headers/headers'; import { themeSelector } from '../../store/local/app'; @@ -107,17 +105,15 @@ const HomePageComponent = pure(({ theme }) => ( headers={headers} /> + + + + + - - - - - - - @@ -148,3 +144,7 @@ const mapStateToProps = (state: State) => ({ }); export const HomePage = connect(mapStateToProps)(HomePageComponent); + +const MyEuiFlexGroup = styled(EuiFlexGroup)` + margin: 2px 0px; +`; diff --git a/x-pack/plugins/secops/public/pages/hosts/index.tsx b/x-pack/plugins/secops/public/pages/hosts/index.tsx index 2663f41f1ae4..3ba57451836f 100644 --- a/x-pack/plugins/secops/public/pages/hosts/index.tsx +++ b/x-pack/plugins/secops/public/pages/hosts/index.tsx @@ -18,73 +18,89 @@ import { UncommonProcessTable, } from '../../components/page/hosts'; +import { manageQuery } from '../../components/page/manage_query'; import { EventsQuery } from '../../containers/events'; +import { GlobalTime } from '../../containers/global_time'; import { HostsQuery } from '../../containers/hosts'; import { WithSource } from '../../containers/source'; import { UncommonProcessesQuery } from '../../containers/uncommon_processes'; const basePath = chrome.getBasePath(); -// TODO: wire up the date picker to remove the hard-coded start/end dates, which show good data for the KPI event type -const startDate = 1514782800000; -const endDate = 1546318799999; +const HostsTableManage = manageQuery(HostsTable); +const EventsTableManage = manageQuery(EventsTable); +const UncommonProcessTableManage = manageQuery(UncommonProcessTable); export const Hosts = pure(() => ( {({ auditbeatIndicesExist }) => auditbeatIndicesExist || isUndefined(auditbeatIndicesExist) ? ( - <> - - {({ kpiEventType, loading }) => ( - ({ - x: i.count, - y: i.value, - }))} - /> - )} - - - {({ hosts, totalCount, loading, pageInfo, loadMore }) => ( - - )} - - - {({ uncommonProcesses, totalCount, loading, pageInfo, loadMore }) => ( - - )} - - - {({ events, loading }) => ( - - )} - - + + {({ poll, to, from, setQuery }) => ( + <> + + {({ kpiEventType, loading }) => ( + ({ + x: i.count, + y: i.value, + }))} + /> + )} + + + {({ hosts, totalCount, loading, pageInfo, loadMore, id, refetch }) => ( + + )} + + + {({ uncommonProcesses, totalCount, loading, pageInfo, loadMore, id, refetch }) => ( + + )} + + + {({ events, loading, id, refetch }) => ( + + )} + + + )} + ) : ( () => combineEpics(createLocalEpic()); diff --git a/x-pack/plugins/secops/public/store/index.ts b/x-pack/plugins/secops/public/store/index.ts index be890b8054f8..4432b0988deb 100644 --- a/x-pack/plugins/secops/public/store/index.ts +++ b/x-pack/plugins/secops/public/store/index.ts @@ -7,5 +7,7 @@ export * from './actions'; export * from './reducer'; export * from './selectors'; +export * from './epic'; +export * from './model'; export { createStore } from './store'; diff --git a/x-pack/plugins/secops/public/store/local/actions.ts b/x-pack/plugins/secops/public/store/local/actions.ts index b91ecae4c48f..0e38559bb304 100644 --- a/x-pack/plugins/secops/public/store/local/actions.ts +++ b/x-pack/plugins/secops/public/store/local/actions.ts @@ -8,3 +8,4 @@ export { dragAndDropActions } from './drag_and_drop'; export { hostsActions } from './hosts'; export { timelineActions } from './timeline'; export { appActions } from './app'; +export { inputsActions } from './inputs'; diff --git a/x-pack/plugins/secops/public/store/local/epic.ts b/x-pack/plugins/secops/public/store/local/epic.ts new file mode 100644 index 000000000000..46250ea81620 --- /dev/null +++ b/x-pack/plugins/secops/public/store/local/epic.ts @@ -0,0 +1,11 @@ +/* + * 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 { combineEpics } from 'redux-observable'; + +import { createGlobalTimeEpic } from './inputs'; + +export const createLocalEpic = () => combineEpics(createGlobalTimeEpic()); diff --git a/x-pack/plugins/secops/public/store/local/index.ts b/x-pack/plugins/secops/public/store/local/index.ts index 2119b53d43ee..2e56d47df5e7 100644 --- a/x-pack/plugins/secops/public/store/local/index.ts +++ b/x-pack/plugins/secops/public/store/local/index.ts @@ -7,3 +7,5 @@ export * from './actions'; export * from './reducer'; export * from './selectors'; +export * from './epic'; +export * from './model'; diff --git a/x-pack/plugins/secops/public/store/local/inputs/actions.ts b/x-pack/plugins/secops/public/store/local/inputs/actions.ts new file mode 100644 index 000000000000..c0a977f4ad8f --- /dev/null +++ b/x-pack/plugins/secops/public/store/local/inputs/actions.ts @@ -0,0 +1,33 @@ +/* + * 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 actionCreatorFactory from 'typescript-fsa'; +import { Refetch } from './model'; + +const actionCreator = actionCreatorFactory('x-pack/secops/local/inputs'); + +export const setAbsoluteRangeDatePicker = actionCreator<{ + id: string; + from: number; + to: number; +}>('SET_ABSOLUTE_RANGE_DATE_PICKER'); + +export const setRelativeRangeDatePicker = actionCreator<{ + id: string; + option: string; + from: number; + to: number; +}>('SET_RELATIVE_RANGE_DATE_PICKER'); + +export const setDuration = actionCreator<{ id: string; duration: number }>('SET_DURATION'); + +export const startAutoReload = actionCreator<{ id: string }>('START_KQL_AUTO_RELOAD'); + +export const stopAutoReload = actionCreator<{ id: string }>('STOP_KQL_AUTO_RELOAD'); + +export const setQuery = actionCreator<{ id: string; loading: boolean; refetch: Refetch }>( + 'SET_QUERY' +); diff --git a/x-pack/plugins/secops/public/store/local/inputs/epic.ts b/x-pack/plugins/secops/public/store/local/inputs/epic.ts new file mode 100644 index 000000000000..fe567f8fc5f0 --- /dev/null +++ b/x-pack/plugins/secops/public/store/local/inputs/epic.ts @@ -0,0 +1,70 @@ +/* + * 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 { get } from 'lodash/fp'; +import moment from 'moment'; +import { Action } from 'redux'; +import { Epic } from 'redux-observable'; +import { timer } from 'rxjs'; +import { exhaustMap, filter, map, takeUntil, withLatestFrom } from 'rxjs/operators'; + +import { setRelativeRangeDatePicker, startAutoReload, stopAutoReload } from './actions'; +import { Policy, TimeRange } from './model'; + +interface GlobalTimeEpicDependencies { + selectGlobalPolicy: (state: State) => Policy; + selectGlobalTimeRange: (state: State) => TimeRange; +} + +export const createGlobalTimeEpic = (): Epic< + Action, + Action, + State, + GlobalTimeEpicDependencies +> => (action$, state$, { selectGlobalPolicy, selectGlobalTimeRange }) => { + const policy$ = state$.pipe( + map(selectGlobalPolicy), + filter(isNotNull) + ); + + const timerange$ = state$.pipe( + map(selectGlobalTimeRange), + filter(isNotNull) + ); + + return action$.pipe( + filter(startAutoReload.match), + withLatestFrom(policy$, timerange$), + filter(([action, policy, timerange]) => timerange.kind === 'relative'), + exhaustMap(([action, policy, timerange]) => + timer(0, policy.duration).pipe( + map(() => { + const option = get('option', timerange); + if (option === 'quick-select') { + const diff = timerange.to - timerange.from; + return setRelativeRangeDatePicker({ + id: 'global', + option, + to: moment().valueOf(), + from: moment() + .subtract(diff, 'ms') + .valueOf(), + }); + } + return setRelativeRangeDatePicker({ + id: 'global', + option, + to: moment().valueOf(), + from: timerange.from, + }); + }), + takeUntil(action$.pipe(filter(stopAutoReload.match))) + ) + ) + ); +}; + +const isNotNull = (value: T | null): value is T => value !== null; diff --git a/x-pack/plugins/secops/public/store/local/inputs/index.ts b/x-pack/plugins/secops/public/store/local/inputs/index.ts new file mode 100644 index 000000000000..c0fa9e5c855f --- /dev/null +++ b/x-pack/plugins/secops/public/store/local/inputs/index.ts @@ -0,0 +1,14 @@ +/* + * 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 * as inputsActions from './actions'; +import * as inputsModel from './model'; + +export { inputsActions }; +export * from './reducer'; +export * from './selectors'; +export * from './epic'; +export { inputsModel }; diff --git a/x-pack/plugins/secops/public/store/local/inputs/model.ts b/x-pack/plugins/secops/public/store/local/inputs/model.ts new file mode 100644 index 000000000000..fc0e3adc0345 --- /dev/null +++ b/x-pack/plugins/secops/public/store/local/inputs/model.ts @@ -0,0 +1,42 @@ +/* + * 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. + */ + +interface AbsoluteTimeRange { + kind: 'absolute'; + from: number; + to: number; +} + +interface RelativeTimeRange { + kind: 'relative'; + option: 'week-to-date' | 'month-to-date' | 'year-to-date' | 'quick-select'; + from: number; + to: number; +} + +export type TimeRange = AbsoluteTimeRange | RelativeTimeRange; + +export interface Policy { + kind: 'manual' | 'interval'; + duration: number; // in ms +} + +export type Refetch = () => void; +export interface GlobalQuery { + id: string; + loading: boolean; + refetch: null | Refetch; +} + +export interface InputsRange { + timerange: TimeRange; + policy: Policy; + query: GlobalQuery[]; +} + +export interface InputsModel { + global: InputsRange; +} diff --git a/x-pack/plugins/secops/public/store/local/inputs/reducer.ts b/x-pack/plugins/secops/public/store/local/inputs/reducer.ts new file mode 100644 index 000000000000..22238e4b2455 --- /dev/null +++ b/x-pack/plugins/secops/public/store/local/inputs/reducer.ts @@ -0,0 +1,101 @@ +/* + * 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 { get, unionBy } from 'lodash/fp'; +import moment from 'moment'; +import { reducerWithInitialState } from 'typescript-fsa-reducers'; + +import { + setAbsoluteRangeDatePicker, + setDuration, + setQuery, + setRelativeRangeDatePicker, + startAutoReload, + stopAutoReload, +} from './actions'; +import { InputsModel } from './model'; + +export type InputsState = InputsModel; + +export const initialInputsState: InputsState = { + global: { + timerange: { + kind: 'absolute', + from: moment() + .subtract(1, 'hour') + .valueOf(), + to: moment().valueOf(), + }, + query: [], + policy: { + kind: 'manual', + duration: 5000, + }, + }, +}; + +export const inputsReducer = reducerWithInitialState(initialInputsState) + .case(setAbsoluteRangeDatePicker, (state, { id, from, to }) => ({ + ...state, + [id]: { + ...get(id, state), + timerange: { + kind: 'absolute', + from, + to, + }, + }, + })) + .case(setRelativeRangeDatePicker, (state, { id, option, from, to }) => ({ + ...state, + [id]: { + ...get(id, state), + timerange: { + kind: 'relative', + option, + from, + to, + }, + }, + })) + .case(setQuery, (state, { id, loading, refetch }) => ({ + ...state, + global: { + ...state.global, + query: unionBy('id', [{ id, loading, refetch }], state.global.query), + }, + })) + .case(setDuration, (state, { id, duration }) => ({ + ...state, + [id]: { + ...get(id, state), + policy: { + ...get(`${id}.policy`, state), + duration, + }, + }, + })) + .case(startAutoReload, (state, { id }) => ({ + ...state, + [id]: { + ...get(id, state), + policy: { + ...get(`${id}.policy`, state), + kind: 'interval', + }, + }, + })) + .case(stopAutoReload, (state, { id }) => ({ + ...state, + [id]: { + ...get(id, state), + policy: { + ...get(`${id}.policy`, state), + kind: 'manual', + }, + }, + })) + .build(); diff --git a/x-pack/plugins/secops/public/store/local/inputs/selectors.ts b/x-pack/plugins/secops/public/store/local/inputs/selectors.ts new file mode 100644 index 000000000000..0c10e27ccce8 --- /dev/null +++ b/x-pack/plugins/secops/public/store/local/inputs/selectors.ts @@ -0,0 +1,23 @@ +/* + * 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 { createSelector } from 'reselect'; + +import { State } from '../../reducer'; +import { GlobalQuery, Policy, TimeRange } from './model'; + +const selectGlobalTimeRange = (state: State): TimeRange => state.local.inputs.global.timerange; +const selectGlobalPolicy = (state: State): Policy => state.local.inputs.global.policy; +const selectGlobalQuery = (state: State): GlobalQuery[] => state.local.inputs.global.query; + +export const globalTimeRangeSelector = createSelector( + selectGlobalTimeRange, + timerange => timerange +); + +export const globalPolicySelector = createSelector(selectGlobalPolicy, policy => policy); + +export const globalQuery = createSelector(selectGlobalQuery, query => query); diff --git a/x-pack/plugins/secops/public/store/local/model.ts b/x-pack/plugins/secops/public/store/local/model.ts new file mode 100644 index 000000000000..ac079d04911d --- /dev/null +++ b/x-pack/plugins/secops/public/store/local/model.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { inputsModel } from './inputs'; diff --git a/x-pack/plugins/secops/public/store/local/reducer.ts b/x-pack/plugins/secops/public/store/local/reducer.ts index fabfa14abf58..773785b57227 100644 --- a/x-pack/plugins/secops/public/store/local/reducer.ts +++ b/x-pack/plugins/secops/public/store/local/reducer.ts @@ -9,6 +9,7 @@ import { combineReducers } from 'redux'; import { appReducer, AppState, initialAppState } from './app'; import { dragAndDropReducer, DragAndDropState, initialDragAndDropState } from './drag_and_drop'; import { hostsReducer, HostsState, initialHostsState } from './hosts'; +import { initialInputsState, inputsReducer, InputsState } from './inputs'; import { initialTimelineState, timelineReducer, TimelineState } from './timeline'; export interface LocalState { @@ -16,6 +17,7 @@ export interface LocalState { dragAndDrop: DragAndDropState; timeline: TimelineState; hosts: HostsState; + inputs: InputsState; } export const initialLocalState: LocalState = { @@ -23,6 +25,7 @@ export const initialLocalState: LocalState = { dragAndDrop: initialDragAndDropState, timeline: initialTimelineState, hosts: initialHostsState, + inputs: initialInputsState, }; export const localReducer = combineReducers({ @@ -30,4 +33,5 @@ export const localReducer = combineReducers({ dragAndDrop: dragAndDropReducer, timeline: timelineReducer, hosts: hostsReducer, + inputs: inputsReducer, }); diff --git a/x-pack/plugins/secops/public/store/local/selectors.ts b/x-pack/plugins/secops/public/store/local/selectors.ts index d301994bcaa2..630d37a08a16 100644 --- a/x-pack/plugins/secops/public/store/local/selectors.ts +++ b/x-pack/plugins/secops/public/store/local/selectors.ts @@ -7,3 +7,4 @@ export * from './drag_and_drop'; export * from './hosts'; export * from './timeline'; +export * from './inputs'; diff --git a/x-pack/plugins/secops/public/store/model.ts b/x-pack/plugins/secops/public/store/model.ts new file mode 100644 index 000000000000..2873cc67fdaa --- /dev/null +++ b/x-pack/plugins/secops/public/store/model.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { inputsModel } from './local'; diff --git a/x-pack/plugins/secops/public/store/selectors.ts b/x-pack/plugins/secops/public/store/selectors.ts index 03cee8150934..b757a19a9621 100644 --- a/x-pack/plugins/secops/public/store/selectors.ts +++ b/x-pack/plugins/secops/public/store/selectors.ts @@ -4,4 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export { dataProvidersSelector, hostsSelector, timelineByIdSelector } from './local'; +export { + dataProvidersSelector, + hostsSelector, + timelineByIdSelector, + globalTimeRangeSelector, + globalPolicySelector, +} from './local'; diff --git a/x-pack/plugins/secops/public/store/store.ts b/x-pack/plugins/secops/public/store/store.ts index d414e9534eea..bbc7ee5e0554 100644 --- a/x-pack/plugins/secops/public/store/store.ts +++ b/x-pack/plugins/secops/public/store/store.ts @@ -4,10 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AnyAction, applyMiddleware, compose, createStore as createReduxStore, Store } from 'redux'; -import thunk from 'redux-thunk'; +import { + Action, + AnyAction, + applyMiddleware, + compose, + createStore as createReduxStore, + Store, +} from 'redux'; +import { createEpicMiddleware } from 'redux-observable'; -import { initialState, reducer, State } from '.'; +import { + createRootEpic, + globalPolicySelector, + globalTimeRangeSelector, + initialState, + reducer, + State, +} from '.'; declare global { interface Window { @@ -18,5 +32,20 @@ declare global { export const createStore = (state = initialState): Store => { const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; - return createReduxStore(reducer, state, composeEnhancers(applyMiddleware(thunk))); + const middlewareDependencies = { + selectGlobalPolicy: globalPolicySelector, + selectGlobalTimeRange: globalTimeRangeSelector, + }; + + const epicMiddleware = createEpicMiddleware( + { + dependencies: middlewareDependencies, + } + ); + + const store = createReduxStore(reducer, state, composeEnhancers(applyMiddleware(epicMiddleware))); + + epicMiddleware.run(createRootEpic()); + + return store; }; diff --git a/x-pack/plugins/secops/server/lib/events/query.dsl.ts b/x-pack/plugins/secops/server/lib/events/query.dsl.ts index 873096d1c50d..d9bcd9fcb95c 100644 --- a/x-pack/plugins/secops/server/lib/events/query.dsl.ts +++ b/x-pack/plugins/secops/server/lib/events/query.dsl.ts @@ -39,8 +39,8 @@ export const buildQuery = (options: EventsRequestOptions) => { { range: { [options.sourceConfiguration.fields.timestamp]: { - gte: to, - lte: from, + gte: from, + lte: to, }, }, }, diff --git a/x-pack/plugins/secops/server/lib/hosts/query.dsl.ts b/x-pack/plugins/secops/server/lib/hosts/query.dsl.ts index a81e9cb480f0..95d71981fd9e 100644 --- a/x-pack/plugins/secops/server/lib/hosts/query.dsl.ts +++ b/x-pack/plugins/secops/server/lib/hosts/query.dsl.ts @@ -30,8 +30,8 @@ export const buildQuery = (options: HostsRequestOptions) => { { range: { [options.sourceConfiguration.fields.timestamp]: { - gte: to, - lte: from, + gte: from, + lte: to, }, }, }, diff --git a/x-pack/plugins/secops/server/lib/uncommon_processes/query.dsl.ts b/x-pack/plugins/secops/server/lib/uncommon_processes/query.dsl.ts index ac0f0684574f..f20d8bf8cd33 100644 --- a/x-pack/plugins/secops/server/lib/uncommon_processes/query.dsl.ts +++ b/x-pack/plugins/secops/server/lib/uncommon_processes/query.dsl.ts @@ -31,8 +31,8 @@ export const buildQuery = (options: UncommonProcessesRequestOptions) => { { range: { [options.sourceConfiguration.fields.timestamp]: { - gte: to, - lte: from, + gte: from, + lte: to, }, }, }, diff --git a/x-pack/plugins/secops/server/utils/build_query/merge_fields_with_hits.ts b/x-pack/plugins/secops/server/utils/build_query/merge_fields_with_hits.ts index c09743b63d4e..c1d99ab5690c 100644 --- a/x-pack/plugins/secops/server/utils/build_query/merge_fields_with_hits.ts +++ b/x-pack/plugins/secops/server/utils/build_query/merge_fields_with_hits.ts @@ -6,10 +6,10 @@ import { get, has, merge } from 'lodash/fp'; -export const mergeFieldsWithHit = ( +export const mergeFieldsWithHit = ( fieldName: string, propertyName: string, - flattenedFields: Record, + flattenedFields: T, fieldMap: Readonly>, hit: { _source: {} } ) => { @@ -18,7 +18,7 @@ export const mergeFieldsWithHit = ( if (has(esField, hit._source)) { const objectWithProperty = { [propertyName]: { - ...flattenedFields[propertyName], + ...get(propertyName, flattenedFields), ...fieldName .split('.') .reduceRight((obj, next) => ({ [next]: obj }), get(esField, hit._source)),