mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Backports the following commits to 7.4: - [Logs UI] Preserve relative dates of Logs Analysis datepicker (#44706)
This commit is contained in:
parent
3445844e8c
commit
24ebd2aeb1
2 changed files with 123 additions and 111 deletions
|
@ -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<typeof stringTimeRangeRT>;
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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 (
|
||||
<LoadingPage
|
||||
message={i18n.translate('xpack.infra.logs.logsAnalysisResults.loadingMessage', {
|
||||
defaultMessage: 'Loading results...',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
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<TimeRange>(
|
||||
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()}</>
|
||||
<LoadingPage
|
||||
message={i18n.translate('xpack.infra.logs.logsAnalysisResults.loadingMessage', {
|
||||
defaultMessage: 'Loading results...',
|
||||
})}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<EuiPage>
|
||||
|
@ -148,41 +143,33 @@ export const AnalysisResultsContent = ({
|
|||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
{anomaliesDetected !== null ? (
|
||||
<>
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logs.analysis.anomaliesDetectedText"
|
||||
defaultMessage="Detected {formattedNumber} {number, plural, one {anomaly} other {anomalies}}"
|
||||
values={{
|
||||
formattedNumber: (
|
||||
<EuiBadge color={anomaliesDetected === 0 ? 'default' : 'warning'}>
|
||||
{anomaliesDetected}
|
||||
</EuiBadge>
|
||||
),
|
||||
number: anomaliesDetected,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</>
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logs.analysis.anomaliesDetectedText"
|
||||
defaultMessage="Detected {formattedNumber} {number, plural, one {anomaly} other {anomalies}}"
|
||||
values={{
|
||||
formattedNumber: (
|
||||
<EuiBadge color={anomaliesDetected === 0 ? 'default' : 'warning'}>
|
||||
{anomaliesDetected}
|
||||
</EuiBadge>
|
||||
),
|
||||
number: anomaliesDetected,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiSuperDatePicker
|
||||
start={moment.utc(timeRange.startTime).format(DATE_PICKER_FORMAT)}
|
||||
end={moment.utc(timeRange.endTime).format(DATE_PICKER_FORMAT)}
|
||||
onTimeChange={handleTimeRangeChange}
|
||||
isPaused={!autoRefreshEnabled}
|
||||
refreshInterval={refreshInterval}
|
||||
onRefreshChange={({ isPaused, refreshInterval: interval }) => {
|
||||
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}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
@ -196,7 +183,7 @@ export const AnalysisResultsContent = ({
|
|||
<LogRateResults
|
||||
isLoading={isLoading}
|
||||
results={logEntryRate}
|
||||
timeRange={timeRange}
|
||||
timeRange={queryTimeRange}
|
||||
/>
|
||||
</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
|
@ -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%;
|
||||
`;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue