mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* [Infra UI] Convert node detail page time range to date strings * remvoing setInterval test * making time parsing more robust. * removing extra code * fixing io-ts import
This commit is contained in:
parent
2c60eeb8c7
commit
1ce9d993f9
9 changed files with 94 additions and 125 deletions
|
@ -8,11 +8,12 @@ import { EuiPageContentBody, EuiTitle } from '@elastic/eui';
|
|||
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
import { InfraMetricData, InfraTimerangeInput } from '../../graphql/types';
|
||||
import { InfraMetricData } from '../../graphql/types';
|
||||
import { InfraMetricLayout, InfraMetricLayoutSection } from '../../pages/metrics/layouts/types';
|
||||
import { NoData } from '../empty_states';
|
||||
import { InfraLoadingPanel } from '../loading';
|
||||
import { Section } from './section';
|
||||
import { MetricsTimeInput } from '../../containers/metrics/with_metrics_time';
|
||||
|
||||
interface Props {
|
||||
metrics: InfraMetricData[];
|
||||
|
@ -21,7 +22,7 @@ interface Props {
|
|||
refetch: () => void;
|
||||
nodeId: string;
|
||||
label: string;
|
||||
onChangeRangeTime?: (time: InfraTimerangeInput) => void;
|
||||
onChangeRangeTime?: (time: MetricsTimeInput) => void;
|
||||
isLiveStreaming?: boolean;
|
||||
stopLiveStreaming?: () => void;
|
||||
intl: InjectedIntl;
|
||||
|
|
|
@ -5,14 +5,15 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { InfraMetricData, InfraTimerangeInput } from '../../graphql/types';
|
||||
import { InfraMetricData } from '../../graphql/types';
|
||||
import { InfraMetricLayoutSection } from '../../pages/metrics/layouts/types';
|
||||
import { sections } from './sections';
|
||||
import { MetricsTimeInput } from '../../containers/metrics/with_metrics_time';
|
||||
|
||||
interface Props {
|
||||
section: InfraMetricLayoutSection;
|
||||
metrics: InfraMetricData[];
|
||||
onChangeRangeTime?: (time: InfraTimerangeInput) => void;
|
||||
onChangeRangeTime?: (time: MetricsTimeInput) => void;
|
||||
crosshairValue?: number;
|
||||
onCrosshairUpdate?: (crosshairValue: number) => void;
|
||||
isLiveStreaming?: boolean;
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from '@elastic/charts';
|
||||
import { EuiPageContentBody, EuiTitle } from '@elastic/eui';
|
||||
import { InfraMetricLayoutSection } from '../../../pages/metrics/layouts/types';
|
||||
import { InfraMetricData, InfraTimerangeInput } from '../../../graphql/types';
|
||||
import { InfraMetricData } from '../../../graphql/types';
|
||||
import { getChartTheme } from '../../metrics_explorer/helpers/get_chart_theme';
|
||||
import { InfraFormatterType } from '../../../lib/lib';
|
||||
import { SeriesChart } from './series_chart';
|
||||
|
@ -32,11 +32,12 @@ import {
|
|||
} from './helpers';
|
||||
import { ErrorMessage } from './error_message';
|
||||
import { useKibanaUiSetting } from '../../../utils/use_kibana_ui_setting';
|
||||
import { MetricsTimeInput } from '../../../containers/metrics/with_metrics_time';
|
||||
|
||||
interface Props {
|
||||
section: InfraMetricLayoutSection;
|
||||
metric: InfraMetricData;
|
||||
onChangeRangeTime?: (time: InfraTimerangeInput) => void;
|
||||
onChangeRangeTime?: (time: MetricsTimeInput) => void;
|
||||
isLiveStreaming?: boolean;
|
||||
stopLiveStreaming?: () => void;
|
||||
intl: InjectedIntl;
|
||||
|
@ -60,8 +61,8 @@ export const ChartSection = injectI18n(
|
|||
stopLiveStreaming();
|
||||
}
|
||||
onChangeRangeTime({
|
||||
from,
|
||||
to,
|
||||
from: moment(from).toISOString(),
|
||||
to: moment(to).toISOString(),
|
||||
interval: '>=1m',
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,28 +7,26 @@
|
|||
import React from 'react';
|
||||
import { MetricsTimeControls } from './time_controls';
|
||||
import { mount } from 'enzyme';
|
||||
import moment from 'moment';
|
||||
import { InfraTimerangeInput } from '../../graphql/types';
|
||||
import DateMath from '@elastic/datemath';
|
||||
import { MetricsTimeInput } from '../../containers/metrics/with_metrics_time';
|
||||
|
||||
describe('MetricsTimeControls', () => {
|
||||
it('should set a valid from and to value for Today', () => {
|
||||
const currentTimeRange = {
|
||||
from: moment()
|
||||
.subtract(15, 'm')
|
||||
.valueOf(),
|
||||
to: moment().valueOf(),
|
||||
from: 'now-15m',
|
||||
to: 'now',
|
||||
interval: '>=1m',
|
||||
};
|
||||
const handleTimeChange = jest.fn().mockImplementation((time: InfraTimerangeInput) => void 0);
|
||||
const handleTimeChange = jest.fn().mockImplementation((time: MetricsTimeInput) => void 0);
|
||||
const handleRefreshChange = jest.fn().mockImplementation((refreshInterval: number) => void 0);
|
||||
const handleAutoReload = jest.fn().mockImplementation((isAutoReloading: boolean) => void 0);
|
||||
const handleOnRefresh = jest.fn().mockImplementation(() => void 0);
|
||||
const component = mount(
|
||||
<MetricsTimeControls
|
||||
currentTimeRange={currentTimeRange}
|
||||
onChangeTimeRange={handleTimeChange}
|
||||
setRefreshInterval={handleRefreshChange}
|
||||
setAutoReload={handleAutoReload}
|
||||
onRefresh={handleOnRefresh}
|
||||
/>
|
||||
);
|
||||
component
|
||||
|
@ -41,12 +39,7 @@ describe('MetricsTimeControls', () => {
|
|||
.simulate('click');
|
||||
expect(handleTimeChange.mock.calls.length).toBe(1);
|
||||
const timeRangeInput = handleTimeChange.mock.calls[0][0];
|
||||
const expectedFrom = DateMath.parse('now/d');
|
||||
const expectedTo = DateMath.parse('now/d', { roundUp: true });
|
||||
if (!expectedFrom || !expectedTo) {
|
||||
throw new Error('This should never happen!');
|
||||
}
|
||||
expect(timeRangeInput.from).toBe(expectedFrom.valueOf());
|
||||
expect(timeRangeInput.to).toBe(expectedTo.valueOf());
|
||||
expect(timeRangeInput.from).toBe('now/d');
|
||||
expect(timeRangeInput.to).toBe('now/d');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,22 +4,19 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import dateMath from '@elastic/datemath';
|
||||
import { EuiSuperDatePicker, OnRefreshChangeProps, OnTimeChangeProps } from '@elastic/eui';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import euiStyled from '../../../../../common/eui_styled_components';
|
||||
import { InfraTimerangeInput } from '../../graphql/types';
|
||||
|
||||
const EuiSuperDatePickerAbsoluteFormat = 'YYYY-MM-DDTHH:mm:ss.sssZ';
|
||||
import { MetricsTimeInput } from '../../containers/metrics/with_metrics_time';
|
||||
|
||||
interface MetricsTimeControlsProps {
|
||||
currentTimeRange: InfraTimerangeInput;
|
||||
currentTimeRange: MetricsTimeInput;
|
||||
isLiveStreaming?: boolean;
|
||||
refreshInterval?: number | null;
|
||||
onChangeTimeRange: (time: InfraTimerangeInput) => void;
|
||||
onChangeTimeRange: (time: MetricsTimeInput) => void;
|
||||
setRefreshInterval: (refreshInterval: number) => void;
|
||||
setAutoReload: (isAutoReloading: boolean) => void;
|
||||
onRefresh: () => void;
|
||||
}
|
||||
|
||||
export class MetricsTimeControls extends React.Component<MetricsTimeControlsProps> {
|
||||
|
@ -28,28 +25,24 @@ export class MetricsTimeControls extends React.Component<MetricsTimeControlsProp
|
|||
return (
|
||||
<MetricsTimeControlsContainer>
|
||||
<EuiSuperDatePicker
|
||||
start={moment(currentTimeRange.from).format(EuiSuperDatePickerAbsoluteFormat)}
|
||||
end={moment(currentTimeRange.to).format(EuiSuperDatePickerAbsoluteFormat)}
|
||||
start={currentTimeRange.from}
|
||||
end={currentTimeRange.to}
|
||||
isPaused={!isLiveStreaming}
|
||||
refreshInterval={refreshInterval ? refreshInterval : 0}
|
||||
onTimeChange={this.handleTimeChange}
|
||||
onRefreshChange={this.handleRefreshChange}
|
||||
onRefresh={this.props.onRefresh}
|
||||
/>
|
||||
</MetricsTimeControlsContainer>
|
||||
);
|
||||
}
|
||||
|
||||
private handleTimeChange = ({ start, end }: OnTimeChangeProps) => {
|
||||
const parsedStart = dateMath.parse(start);
|
||||
const parsedEnd = dateMath.parse(end, { roundUp: true });
|
||||
|
||||
if (parsedStart && parsedEnd) {
|
||||
this.props.onChangeTimeRange({
|
||||
from: parsedStart.valueOf(),
|
||||
to: parsedEnd.valueOf(),
|
||||
interval: '>=1m',
|
||||
});
|
||||
}
|
||||
this.props.onChangeTimeRange({
|
||||
from: start,
|
||||
to: end,
|
||||
interval: '>=1m',
|
||||
});
|
||||
};
|
||||
|
||||
private handleRefreshChange = ({ isPaused, refreshInterval }: OnRefreshChangeProps) => {
|
||||
|
|
|
@ -22,8 +22,8 @@ describe('useMetricsTime hook', () => {
|
|||
const { act, getLastHookValue } = mountHook(() => useMetricsTime());
|
||||
|
||||
const timeRange = {
|
||||
from: 12345,
|
||||
to: 123456,
|
||||
from: 'now-15m',
|
||||
to: 'now',
|
||||
interval: '>=2m',
|
||||
};
|
||||
|
||||
|
@ -36,10 +36,6 @@ describe('useMetricsTime hook', () => {
|
|||
});
|
||||
|
||||
describe('AutoReloading state', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
it('has a default value', () => {
|
||||
const { getLastHookValue } = mountHook(() => useMetricsTime().isAutoReloading);
|
||||
expect(getLastHookValue()).toBe(false);
|
||||
|
@ -54,49 +50,5 @@ describe('useMetricsTime hook', () => {
|
|||
|
||||
expect(getLastHookValue().isAutoReloading).toBe(true);
|
||||
});
|
||||
|
||||
it('sets up an interval when turned on', () => {
|
||||
const { act } = mountHook(() => useMetricsTime());
|
||||
const refreshInterval = 10000;
|
||||
|
||||
act(({ setAutoReload, setRefreshInterval }) => {
|
||||
setRefreshInterval(refreshInterval);
|
||||
setAutoReload(true);
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
|
||||
expect(setInterval).toHaveBeenCalledTimes(1);
|
||||
expect(setInterval).toHaveBeenLastCalledWith(expect.any(Function), refreshInterval);
|
||||
});
|
||||
|
||||
it('updates the time range by RANGE each interval', () => {
|
||||
const { act, getLastHookValue } = mountHook(() => useMetricsTime());
|
||||
const from = 100;
|
||||
const to = 300;
|
||||
const RANGE = 200;
|
||||
|
||||
act(({ setAutoReload, setTimeRange }) => {
|
||||
setAutoReload(true);
|
||||
|
||||
setTimeRange({
|
||||
from,
|
||||
to,
|
||||
interval: '>=1m',
|
||||
});
|
||||
});
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(6000);
|
||||
});
|
||||
|
||||
const timeRange = getLastHookValue().timeRange;
|
||||
expect(timeRange.from).toBeGreaterThan(from);
|
||||
expect(timeRange.to).toBeGreaterThan(to);
|
||||
const newRange = timeRange.to - timeRange.from;
|
||||
// The following two assertions allow 5ms of leniency, rather than expect(newRange).toBe(RANGE),
|
||||
// due to failures in CI that don't happen locally.
|
||||
expect(newRange).toBeGreaterThanOrEqual(RANGE);
|
||||
expect(newRange).toBeLessThanOrEqual(RANGE + 5);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,8 +10,8 @@ import {
|
|||
InfraMetric,
|
||||
InfraMetricData,
|
||||
InfraNodeType,
|
||||
InfraTimerangeInput,
|
||||
MetricsQuery,
|
||||
InfraTimerangeInput,
|
||||
} from '../../graphql/types';
|
||||
import { InfraMetricLayout } from '../../pages/metrics/layouts/types';
|
||||
import { metricsQuery } from './metrics.gql_query';
|
||||
|
|
|
@ -5,57 +5,67 @@
|
|||
*/
|
||||
|
||||
import createContainer from 'constate-latest';
|
||||
import React, { useContext, useState, useMemo, useCallback } from 'react';
|
||||
import { isNumber } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { InfraTimerangeInput } from '../../graphql/types';
|
||||
import { useInterval } from '../../hooks/use_interval';
|
||||
import dateMath from '@elastic/datemath';
|
||||
import * as rt from 'io-ts';
|
||||
import { replaceStateKeyInQueryString, UrlStateContainer } from '../../utils/url_state';
|
||||
import { InfraTimerangeInput } from '../../graphql/types';
|
||||
|
||||
export interface MetricsTimeInput {
|
||||
from: string;
|
||||
to: string;
|
||||
interval: string;
|
||||
}
|
||||
|
||||
interface MetricsTimeState {
|
||||
timeRange: InfraTimerangeInput;
|
||||
setTimeRange: (timeRange: InfraTimerangeInput) => void;
|
||||
timeRange: MetricsTimeInput;
|
||||
parsedTimeRange: InfraTimerangeInput;
|
||||
setTimeRange: (timeRange: MetricsTimeInput) => void;
|
||||
refreshInterval: number;
|
||||
setRefreshInterval: (refreshInterval: number) => void;
|
||||
isAutoReloading: boolean;
|
||||
setAutoReload: (isAutoReloading: boolean) => void;
|
||||
lastRefresh: number;
|
||||
triggerRefresh: () => void;
|
||||
}
|
||||
|
||||
export const useMetricsTime = () => {
|
||||
const [isAutoReloading, setAutoReload] = useState(false);
|
||||
const [refreshInterval, setRefreshInterval] = useState(5000);
|
||||
const [lastRefresh, setLastRefresh] = useState<number>(moment().valueOf());
|
||||
const [timeRange, setTimeRange] = useState({
|
||||
from: moment()
|
||||
.subtract(1, 'hour')
|
||||
.valueOf(),
|
||||
to: moment().valueOf(),
|
||||
from: 'now-1h',
|
||||
to: 'now',
|
||||
interval: '>=1m',
|
||||
});
|
||||
|
||||
const setTimeRangeToNow = useCallback(() => {
|
||||
const range = timeRange.to - timeRange.from;
|
||||
const nowInMs = moment().valueOf();
|
||||
setTimeRange({
|
||||
from: nowInMs - range,
|
||||
to: nowInMs,
|
||||
interval: '>=1m',
|
||||
});
|
||||
}, [timeRange.from, timeRange.to]);
|
||||
|
||||
useInterval(setTimeRangeToNow, isAutoReloading ? refreshInterval : null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAutoReloading) {
|
||||
setTimeRangeToNow();
|
||||
}
|
||||
}, [isAutoReloading]);
|
||||
const parsedFrom = dateMath.parse(timeRange.from);
|
||||
const parsedTo = dateMath.parse(timeRange.to, { roundUp: true });
|
||||
const parsedTimeRange = useMemo(
|
||||
() => ({
|
||||
...timeRange,
|
||||
from:
|
||||
(parsedFrom && parsedFrom.valueOf()) ||
|
||||
moment()
|
||||
.subtract(1, 'hour')
|
||||
.valueOf(),
|
||||
to: (parsedTo && parsedTo.valueOf()) || moment().valueOf(),
|
||||
}),
|
||||
[parsedFrom, parsedTo, lastRefresh]
|
||||
);
|
||||
|
||||
return {
|
||||
timeRange,
|
||||
setTimeRange,
|
||||
parsedTimeRange,
|
||||
refreshInterval,
|
||||
setRefreshInterval,
|
||||
isAutoReloading,
|
||||
setAutoReload,
|
||||
lastRefresh,
|
||||
triggerRefresh: useCallback(() => setLastRefresh(moment().valueOf()), [setLastRefresh]),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -141,8 +151,23 @@ const mapToUrlState = (value: any): MetricsTimeUrlState | undefined =>
|
|||
}
|
||||
: undefined;
|
||||
|
||||
const mapToTimeUrlState = (value: any) =>
|
||||
value && (typeof value.to === 'number' && typeof value.from === 'number') ? value : undefined;
|
||||
const MetricsTimeRT = rt.type({
|
||||
from: rt.union([rt.string, rt.number]),
|
||||
to: rt.union([rt.string, rt.number]),
|
||||
interval: rt.string,
|
||||
});
|
||||
|
||||
const mapToTimeUrlState = (value: any) => {
|
||||
const result = MetricsTimeRT.decode(value);
|
||||
if (result.isRight()) {
|
||||
const to = isNumber(result.value.to) ? moment(result.value.to).toISOString() : result.value.to;
|
||||
const from = isNumber(result.value.from)
|
||||
? moment(result.value.from).toISOString()
|
||||
: result.value.from;
|
||||
return { ...result.value, from, to };
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const mapToAutoReloadUrlState = (value: any) => (typeof value === 'boolean' ? value : undefined);
|
||||
|
||||
|
@ -155,7 +180,7 @@ export const replaceMetricTimeInQueryString = (from: number, to: number) =>
|
|||
autoReload: false,
|
||||
time: {
|
||||
interval: '>=1m',
|
||||
from,
|
||||
to,
|
||||
from: moment(from).toISOString(),
|
||||
to: moment(to).toISOString(),
|
||||
},
|
||||
});
|
||||
|
|
|
@ -33,7 +33,7 @@ import {
|
|||
WithMetricsTime,
|
||||
WithMetricsTimeUrlState,
|
||||
} from '../../containers/metrics/with_metrics_time';
|
||||
import { InfraNodeType, InfraTimerangeInput } from '../../graphql/types';
|
||||
import { InfraNodeType } from '../../graphql/types';
|
||||
import { Error, ErrorPageBody } from '../error';
|
||||
import { layoutCreators } from './layouts';
|
||||
import { InfraMetricLayoutSection } from './layouts/types';
|
||||
|
@ -132,11 +132,13 @@ export const MetricDetail = withMetricPageProviders(
|
|||
<WithMetricsTime>
|
||||
{({
|
||||
timeRange,
|
||||
parsedTimeRange,
|
||||
setTimeRange,
|
||||
refreshInterval,
|
||||
setRefreshInterval,
|
||||
isAutoReloading,
|
||||
setAutoReload,
|
||||
triggerRefresh,
|
||||
}) => (
|
||||
<ColumnarPage>
|
||||
<Header
|
||||
|
@ -159,7 +161,7 @@ export const MetricDetail = withMetricPageProviders(
|
|||
<WithMetrics
|
||||
layouts={filteredLayouts}
|
||||
sourceId={sourceId}
|
||||
timerange={timeRange as InfraTimerangeInput}
|
||||
timerange={parsedTimeRange}
|
||||
nodeType={nodeType}
|
||||
nodeId={nodeId}
|
||||
cloudId={cloudId}
|
||||
|
@ -223,6 +225,7 @@ export const MetricDetail = withMetricPageProviders(
|
|||
setRefreshInterval={setRefreshInterval}
|
||||
onChangeTimeRange={setTimeRange}
|
||||
setAutoReload={setAutoReload}
|
||||
onRefresh={triggerRefresh}
|
||||
/>
|
||||
</MetricsTitleTimeRangeContainer>
|
||||
</EuiPageHeaderSection>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue