From 24ebd2aeb10fadec454e49c2fde623bc8129ab0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Fri, 6 Sep 2019 01:13:43 +0200 Subject: [PATCH] [Logs UI] Preserve relative dates of Logs Analysis datepicker (#44706) (#44914) Backports the following commits to 7.4: - [Logs UI] Preserve relative dates of Logs Analysis datepicker (#44706) --- .../log_analysis_results_url_state.tsx | 37 ++-- .../logs/analysis/page_results_content.tsx | 197 +++++++++--------- 2 files changed, 123 insertions(+), 111 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx index 8ae644a497e19..2171103c349e0 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx @@ -4,14 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import moment from 'moment'; import { useEffect } from 'react'; import * as rt from 'io-ts'; import { useUrlState } from '../../../utils/use_url_state'; -import { timeRangeRT } from '../../../../common/http_api/shared/time_range'; -const autoRefreshRT = rt.union([rt.boolean, rt.undefined]); -const urlTimeRangeRT = rt.union([timeRangeRT, rt.undefined]); +const autoRefreshRT = rt.union([ + rt.type({ + interval: rt.number, + isPaused: rt.boolean, + }), + rt.undefined, +]); + +export const stringTimeRangeRT = rt.type({ + startTime: rt.string, + endTime: rt.string, +}); +export type StringTimeRange = rt.TypeOf; + +const urlTimeRangeRT = rt.union([stringTimeRangeRT, rt.undefined]); const TIME_RANGE_URL_STATE_KEY = 'timeRange'; const AUTOREFRESH_URL_STATE_KEY = 'autoRefresh'; @@ -19,11 +30,8 @@ const AUTOREFRESH_URL_STATE_KEY = 'autoRefresh'; export const useLogAnalysisResultsUrlState = () => { const [timeRange, setTimeRange] = useUrlState({ defaultState: { - startTime: moment - .utc() - .subtract(2, 'weeks') - .valueOf(), - endTime: moment.utc().valueOf(), + startTime: 'now-2w', + endTime: 'now', }, decodeUrlState: (value: unknown) => urlTimeRangeRT.decode(value).getOrElse(undefined), encodeUrlState: urlTimeRangeRT.encode, @@ -34,21 +42,24 @@ export const useLogAnalysisResultsUrlState = () => { setTimeRange(timeRange); }, []); - const [autoRefreshEnabled, setAutoRefresh] = useUrlState({ - defaultState: false, + const [autoRefresh, setAutoRefresh] = useUrlState({ + defaultState: { + isPaused: false, + interval: 30000, + }, decodeUrlState: (value: unknown) => autoRefreshRT.decode(value).getOrElse(undefined), encodeUrlState: autoRefreshRT.encode, urlStateKey: AUTOREFRESH_URL_STATE_KEY, }); useEffect(() => { - setAutoRefresh(autoRefreshEnabled); + setAutoRefresh(autoRefresh); }, []); return { timeRange, setTimeRange, - autoRefreshEnabled, + autoRefresh, setAutoRefresh, }; }; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index 9e106427efdd7..0ec027958cc33 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -4,12 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useMemo, useState } from 'react'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; +import datemath from '@elastic/datemath'; import { - EuiSuperDatePicker, + EuiBadge, EuiFlexGroup, EuiFlexItem, EuiPage, @@ -17,31 +14,25 @@ import { EuiPageContent, EuiPageContentBody, EuiPanel, - EuiBadge, + EuiSuperDatePicker, } from '@elastic/eui'; -import dateMath from '@elastic/datemath'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment'; +import React, { useCallback, useMemo, useState } from 'react'; import euiStyled from '../../../../../../common/eui_styled_components'; -import { useTrackPageview } from '../../../hooks/use_track_metric'; -import { useInterval } from '../../../hooks/use_interval'; -import { useLogAnalysisResults } from '../../../containers/logs/log_analysis'; -import { useLogAnalysisResultsUrlState } from '../../../containers/logs/log_analysis'; +import { TimeRange } from '../../../../common/http_api/shared/time_range'; +import { bucketSpan } from '../../../../common/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; -import { LogRateResults } from './sections/log_rate'; +import { + StringTimeRange, + useLogAnalysisResults, + useLogAnalysisResultsUrlState, +} from '../../../containers/logs/log_analysis'; +import { useTrackPageview } from '../../../hooks/use_track_metric'; import { FirstUseCallout } from './first_use'; - -const DATE_PICKER_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; - -const getLoadingState = () => { - return ( - - ); -}; +import { LogRateResults } from './sections/log_rate'; export const AnalysisResultsContent = ({ sourceId, @@ -54,26 +45,15 @@ export const AnalysisResultsContent = ({ useTrackPageview({ app: 'infra_logs', path: 'analysis_results', delay: 15000 }); const { - timeRange, - setTimeRange, - autoRefreshEnabled, + timeRange: selectedTimeRange, + setTimeRange: setSelectedTimeRange, + autoRefresh, setAutoRefresh, } = useLogAnalysisResultsUrlState(); - const [refreshInterval, setRefreshInterval] = useState(300000); - - const setTimeRangeToNow = useCallback(() => { - const range = timeRange.endTime - timeRange.startTime; - const nowInMs = moment() - .utc() - .valueOf(); - setTimeRange({ - startTime: nowInMs - range, - endTime: nowInMs, - }); - }, [timeRange.startTime, timeRange.endTime, setTimeRange]); - - useInterval(setTimeRangeToNow, autoRefreshEnabled ? refreshInterval : null); + const [queryTimeRange, setQueryTimeRange] = useState( + stringToNumericTimeRange(selectedTimeRange) + ); const bucketDuration = useMemo(() => { // This function takes the current time range in ms, @@ -83,33 +63,52 @@ export const AnalysisResultsContent = ({ // 900000 (15 minutes) to it, so that we don't end up with // jaggy bucket boundaries between the ML buckets and our // aggregation buckets. - const msRange = timeRange.endTime - timeRange.startTime; + const msRange = moment(queryTimeRange.endTime).diff(moment(queryTimeRange.startTime)); const bucketIntervalInMs = msRange / 200; - const bucketSpan = 900000; // TODO: Pull this from 'common' when setup hook PR is merged const result = bucketSpan * Math.round(bucketIntervalInMs / bucketSpan); const roundedResult = parseInt(Number(result).toFixed(0), 10); return roundedResult < bucketSpan ? bucketSpan : roundedResult; - }, [timeRange]); + }, [queryTimeRange.startTime, queryTimeRange.endTime]); + const { isLoading, logEntryRate } = useLogAnalysisResults({ sourceId, - startTime: timeRange.startTime, - endTime: timeRange.endTime, + startTime: queryTimeRange.startTime, + endTime: queryTimeRange.endTime, bucketDuration, }); const hasResults = useMemo(() => logEntryRate && logEntryRate.histogramBuckets.length > 0, [ logEntryRate, ]); - const handleTimeRangeChange = useCallback( - ({ start, end }: { start: string; end: string }) => { - const parsedStart = dateMath.parse(start); - const parsedEnd = dateMath.parse(end); - setTimeRange({ - startTime: - !parsedStart || !parsedStart.isValid() ? timeRange.startTime : parsedStart.valueOf(), - endTime: !parsedEnd || !parsedEnd.isValid() ? timeRange.endTime : parsedEnd.valueOf(), + + const handleQueryTimeRangeChange = useCallback( + ({ start: startTime, end: endTime }: { start: string; end: string }) => { + setQueryTimeRange(stringToNumericTimeRange({ startTime, endTime })); + }, + [setQueryTimeRange] + ); + + const handleSelectedTimeRangeChange = useCallback( + (selectedTime: { start: string; end: string; isInvalid: boolean }) => { + if (selectedTime.isInvalid) { + return; + } + setSelectedTimeRange({ + startTime: selectedTime.start, + endTime: selectedTime.end, + }); + handleQueryTimeRangeChange(selectedTime); + }, + [setSelectedTimeRange, handleQueryTimeRangeChange] + ); + + const handleAutoRefreshChange = useCallback( + ({ isPaused, refreshInterval: interval }: { isPaused: boolean; refreshInterval: number }) => { + setAutoRefresh({ + isPaused, + interval, }); }, - [setTimeRange, timeRange] + [setAutoRefresh] ); const anomaliesDetected = useMemo(() => { @@ -117,18 +116,10 @@ export const AnalysisResultsContent = ({ return null; } else { if (logEntryRate.histogramBuckets && logEntryRate.histogramBuckets.length) { - return logEntryRate.histogramBuckets.reduce((acc: any, bucket) => { - if (bucket.anomalies.length > 0) { - return ( - acc + - bucket.anomalies.reduce((anomalyAcc: any, anomaly) => { - return anomalyAcc + 1; - }, 0) - ); - } else { - return acc; - } - }, 0); + return logEntryRate.histogramBuckets.reduce( + (acc, bucket) => acc + bucket.anomalies.length, + 0 + ); } else { return null; } @@ -138,7 +129,11 @@ export const AnalysisResultsContent = ({ return ( <> {isLoading && !logEntryRate ? ( - <>{getLoadingState()} + ) : ( <> @@ -148,41 +143,33 @@ export const AnalysisResultsContent = ({ {anomaliesDetected !== null ? ( - <> - - - {anomaliesDetected} - - ), - number: anomaliesDetected, - }} - /> - - + + + {anomaliesDetected} + + ), + number: anomaliesDetected, + }} + /> + ) : null} { - if (isPaused) { - setAutoRefresh(false); - } else { - setRefreshInterval(interval); - setAutoRefresh(true); - } - }} + start={selectedTimeRange.startTime} + end={selectedTimeRange.endTime} + onTimeChange={handleSelectedTimeRangeChange} + isPaused={autoRefresh.isPaused} + refreshInterval={autoRefresh.interval} + onRefreshChange={handleAutoRefreshChange} + onRefresh={handleQueryTimeRangeChange} /> @@ -196,7 +183,7 @@ export const AnalysisResultsContent = ({ @@ -208,6 +195,20 @@ export const AnalysisResultsContent = ({ ); }; +const stringToNumericTimeRange = (timeRange: StringTimeRange): TimeRange => ({ + startTime: moment( + datemath.parse(timeRange.startTime, { + momentInstance: moment, + }) + ).valueOf(), + endTime: moment( + datemath.parse(timeRange.endTime, { + momentInstance: moment, + roundUp: true, + }) + ).valueOf(), +}); + const ExpandingPage = euiStyled(EuiPage)` flex: 1 0 0%; `;