[Uptime]Make uptime ping histogram bar clickable to improve filtering (#79054)
This commit is contained in:
parent
d5b8a95694
commit
8120e7e7bc
|
@ -66,6 +66,7 @@ export const MonitorSummaryType = t.intersection([
|
|||
}),
|
||||
t.partial({
|
||||
histogram: HistogramType,
|
||||
minInterval: t.number,
|
||||
}),
|
||||
]);
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ export interface GetPingHistogramParams {
|
|||
|
||||
export interface HistogramResult {
|
||||
histogram: HistogramDataPoint[];
|
||||
interval: string;
|
||||
minInterval: number;
|
||||
}
|
||||
|
||||
export interface HistogramQueryResult {
|
||||
|
|
|
@ -131,6 +131,7 @@ exports[`MonitorBarSeries component shallow renders a series when there are down
|
|||
},
|
||||
]
|
||||
}
|
||||
minInterval={10}
|
||||
/>
|
||||
</ContextProvider>
|
||||
</ContextProvider>
|
||||
|
|
|
@ -273,7 +273,7 @@ exports[`PingHistogram component shallow renders the component without errors 1`
|
|||
"y": 1,
|
||||
},
|
||||
],
|
||||
"interval": "1s",
|
||||
"minInterval": 60,
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -31,6 +31,7 @@ describe('MonitorBarSeries component', () => {
|
|||
up: 0,
|
||||
},
|
||||
],
|
||||
minInterval: 10,
|
||||
};
|
||||
histogramSeries = [
|
||||
{ timestamp: 1580387868000, up: 0, down: 5 },
|
||||
|
@ -192,14 +193,16 @@ describe('MonitorBarSeries component', () => {
|
|||
});
|
||||
|
||||
it('shallow renders nothing if the data series is null', () => {
|
||||
const component = shallowWithRouter(<MonitorBarSeries histogramSeries={null} />);
|
||||
const component = shallowWithRouter(
|
||||
<MonitorBarSeries histogramSeries={null} minInterval={5} />
|
||||
);
|
||||
expect(component).toEqual({});
|
||||
});
|
||||
|
||||
it('renders if the data series is present', () => {
|
||||
const component = renderWithRouter(
|
||||
<MountWithReduxProvider>
|
||||
<MonitorBarSeries histogramSeries={histogramSeries} />
|
||||
<MonitorBarSeries histogramSeries={histogramSeries} minInterval={5} />
|
||||
</MountWithReduxProvider>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
|
|
|
@ -44,7 +44,7 @@ describe('PingHistogram component', () => {
|
|||
{ x: 1581068989000, downCount: 3, upCount: 36, y: 1 },
|
||||
{ x: 1581069019000, downCount: 1, upCount: 11, y: 1 },
|
||||
],
|
||||
interval: '1s',
|
||||
minInterval: 60,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { getDateRangeFromChartElement } from '../utils';
|
||||
import { XYChartElementEvent } from '@elastic/charts';
|
||||
|
||||
describe('Chart utils', () => {
|
||||
it('get date range from chart element should add 100 miliseconds', () => {
|
||||
const elementData = [{ x: 1548697920000, y: 4 }];
|
||||
const dr = getDateRangeFromChartElement(elementData as XYChartElementEvent, 1000);
|
||||
expect(dr).toStrictEqual({
|
||||
dateRangeStart: '2019-01-28T17:52:00.000Z',
|
||||
dateRangeEnd: '2019-01-28T17:52:01.000Z',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -13,6 +13,8 @@ import {
|
|||
Position,
|
||||
timeFormatter,
|
||||
BrushEndListener,
|
||||
XYChartElementEvent,
|
||||
ElementClickListener,
|
||||
} from '@elastic/charts';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useContext } from 'react';
|
||||
|
@ -23,12 +25,15 @@ import { HistogramPoint } from '../../../../common/runtime_types';
|
|||
import { getChartDateLabel, seriesHasDownValues } from '../../../lib/helper';
|
||||
import { useUrlParams } from '../../../hooks';
|
||||
import { UptimeThemeContext } from '../../../contexts';
|
||||
import { getDateRangeFromChartElement } from './utils';
|
||||
|
||||
export interface MonitorBarSeriesProps {
|
||||
/**
|
||||
* The timeseries data to display.
|
||||
*/
|
||||
histogramSeries: HistogramPoint[] | null;
|
||||
|
||||
minInterval: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -36,7 +41,7 @@ export interface MonitorBarSeriesProps {
|
|||
* so we will only render the series component if there are down counts for the selected monitor.
|
||||
* @param props - the values for the monitor this chart visualizes
|
||||
*/
|
||||
export const MonitorBarSeries = ({ histogramSeries }: MonitorBarSeriesProps) => {
|
||||
export const MonitorBarSeries = ({ histogramSeries, minInterval }: MonitorBarSeriesProps) => {
|
||||
const {
|
||||
colors: { danger },
|
||||
chartTheme,
|
||||
|
@ -55,14 +60,23 @@ export const MonitorBarSeries = ({ histogramSeries }: MonitorBarSeriesProps) =>
|
|||
});
|
||||
};
|
||||
|
||||
const onBarClicked: ElementClickListener = ([elementData]) => {
|
||||
updateUrlParams(getDateRangeFromChartElement(elementData as XYChartElementEvent, minInterval));
|
||||
};
|
||||
|
||||
const id = 'downSeries';
|
||||
|
||||
return seriesHasDownValues(histogramSeries) ? (
|
||||
<div style={{ height: 50, width: '100%', maxWidth: '1200px', marginRight: 15 }}>
|
||||
<Chart>
|
||||
<Settings
|
||||
xDomain={{ min: absoluteDateRangeStart, max: absoluteDateRangeEnd }}
|
||||
xDomain={{
|
||||
minInterval,
|
||||
min: absoluteDateRangeStart,
|
||||
max: absoluteDateRangeEnd,
|
||||
}}
|
||||
onBrushEnd={onBrushEnd}
|
||||
onElementClick={onBarClicked}
|
||||
{...chartTheme}
|
||||
/>
|
||||
<Axis
|
||||
|
|
|
@ -12,6 +12,8 @@ import {
|
|||
Settings,
|
||||
timeFormatter,
|
||||
BrushEndListener,
|
||||
XYChartElementEvent,
|
||||
ElementClickListener,
|
||||
} from '@elastic/charts';
|
||||
import { EuiTitle, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -25,6 +27,7 @@ import { UptimeThemeContext } from '../../../contexts';
|
|||
import { HistogramResult } from '../../../../common/runtime_types';
|
||||
import { useUrlParams } from '../../../hooks';
|
||||
import { ChartEmptyState } from './chart_empty_state';
|
||||
import { getDateRangeFromChartElement } from './utils';
|
||||
|
||||
export interface PingHistogramComponentProps {
|
||||
/**
|
||||
|
@ -79,7 +82,7 @@ export const PingHistogramComponent: React.FC<PingHistogramComponentProps> = ({
|
|||
/>
|
||||
);
|
||||
} else {
|
||||
const { histogram } = data;
|
||||
const { histogram, minInterval } = data;
|
||||
|
||||
const downSpecId = i18n.translate('xpack.uptime.snapshotHistogram.series.downLabel', {
|
||||
defaultMessage: 'Down',
|
||||
|
@ -100,6 +103,12 @@ export const PingHistogramComponent: React.FC<PingHistogramComponentProps> = ({
|
|||
});
|
||||
};
|
||||
|
||||
const onBarClicked: ElementClickListener = ([elementData]) => {
|
||||
updateUrlParams(
|
||||
getDateRangeFromChartElement(elementData as XYChartElementEvent, minInterval)
|
||||
);
|
||||
};
|
||||
|
||||
const barData: BarPoint[] = [];
|
||||
|
||||
histogram.forEach(({ x, upCount, downCount }) => {
|
||||
|
@ -125,11 +134,13 @@ export const PingHistogramComponent: React.FC<PingHistogramComponentProps> = ({
|
|||
<Chart>
|
||||
<Settings
|
||||
xDomain={{
|
||||
minInterval,
|
||||
min: absoluteStartDate,
|
||||
max: absoluteEndDate,
|
||||
}}
|
||||
showLegend={false}
|
||||
onBrushEnd={onBrushEnd}
|
||||
onElementClick={onBarClicked}
|
||||
{...chartTheme}
|
||||
/>
|
||||
<Axis
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { XYChartElementEvent } from '@elastic/charts';
|
||||
import moment from 'moment';
|
||||
|
||||
export const getDateRangeFromChartElement = (
|
||||
elementData: XYChartElementEvent,
|
||||
minInterval: number
|
||||
) => {
|
||||
const startRange = (elementData as XYChartElementEvent)[0].x;
|
||||
|
||||
return {
|
||||
dateRangeStart: moment(startRange).toISOString(),
|
||||
dateRangeEnd: moment(startRange).add(minInterval, 'ms').toISOString(),
|
||||
};
|
||||
};
|
|
@ -139,8 +139,8 @@ export const MonitorListComponent: ({
|
|||
mobileOptions: {
|
||||
show: false,
|
||||
},
|
||||
render: (histogramSeries: HistogramPoint[] | null) => (
|
||||
<MonitorBarSeries histogramSeries={histogramSeries} />
|
||||
render: (histogramSeries: HistogramPoint[] | null, summary: MonitorSummary) => (
|
||||
<MonitorBarSeries histogramSeries={histogramSeries} minInterval={summary.minInterval!} />
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ Object {
|
|||
"y": 1,
|
||||
},
|
||||
],
|
||||
"interval": "1m",
|
||||
"minInterval": 36000,
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -48,7 +48,7 @@ Object {
|
|||
"y": 1,
|
||||
},
|
||||
],
|
||||
"interval": "1h",
|
||||
"minInterval": 36000,
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -62,7 +62,7 @@ Object {
|
|||
"y": 1,
|
||||
},
|
||||
],
|
||||
"interval": "10s",
|
||||
"minInterval": 36000,
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -82,6 +82,6 @@ Object {
|
|||
"y": 1,
|
||||
},
|
||||
],
|
||||
"interval": "1m",
|
||||
"minInterval": 36000,
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -140,8 +140,8 @@ describe('getPingHistogram', () => {
|
|||
const result = await getPingHistogram({
|
||||
callES: mockEsClient,
|
||||
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
|
||||
from: '1234',
|
||||
to: '5678',
|
||||
from: 'now-15m',
|
||||
to: 'now',
|
||||
filters: JSON.stringify(searchFilter),
|
||||
monitorId: undefined,
|
||||
});
|
||||
|
|
|
@ -68,13 +68,21 @@ export const getMonitorStates: UMElasticsearchQueryFn<
|
|||
const iterator = new MonitorSummaryIterator(queryContext);
|
||||
const page = await iterator.nextPage(size);
|
||||
|
||||
const minInterval = getHistogramInterval(
|
||||
queryContext.dateRangeStart,
|
||||
queryContext.dateRangeEnd,
|
||||
12
|
||||
);
|
||||
|
||||
const histograms = await getHistogramForMonitors(
|
||||
queryContext,
|
||||
page.monitorSummaries.map((s) => s.monitor_id)
|
||||
page.monitorSummaries.map((s) => s.monitor_id),
|
||||
minInterval
|
||||
);
|
||||
|
||||
page.monitorSummaries.forEach((s) => {
|
||||
s.histogram = histograms[s.monitor_id];
|
||||
s.minInterval = minInterval;
|
||||
});
|
||||
|
||||
return {
|
||||
|
@ -86,7 +94,8 @@ export const getMonitorStates: UMElasticsearchQueryFn<
|
|||
|
||||
export const getHistogramForMonitors = async (
|
||||
queryContext: QueryContext,
|
||||
monitorIds: string[]
|
||||
monitorIds: string[],
|
||||
minInterval: number
|
||||
): Promise<{ [key: string]: Histogram }> => {
|
||||
const params = {
|
||||
index: queryContext.heartbeatIndices,
|
||||
|
@ -122,9 +131,7 @@ export const getHistogramForMonitors = async (
|
|||
field: '@timestamp',
|
||||
// 12 seems to be a good size for performance given
|
||||
// long monitor lists of up to 100 on the overview page
|
||||
fixed_interval:
|
||||
getHistogramInterval(queryContext.dateRangeStart, queryContext.dateRangeEnd, 12) +
|
||||
'ms',
|
||||
fixed_interval: minInterval + 'ms',
|
||||
missing: 0,
|
||||
},
|
||||
aggs: {
|
||||
|
|
|
@ -37,6 +37,8 @@ export const getPingHistogram: UMElasticsearchQueryFn<
|
|||
}
|
||||
const filter = getFilterClause(from, to, additionalFilters);
|
||||
|
||||
const minInterval = getHistogramInterval(from, to, QUERY.DEFAULT_BUCKET_COUNT);
|
||||
|
||||
const params = {
|
||||
index: dynamicSettings.heartbeatIndices,
|
||||
body: {
|
||||
|
@ -50,8 +52,7 @@ export const getPingHistogram: UMElasticsearchQueryFn<
|
|||
timeseries: {
|
||||
date_histogram: {
|
||||
field: '@timestamp',
|
||||
fixed_interval:
|
||||
bucketSize || getHistogramInterval(from, to, QUERY.DEFAULT_BUCKET_COUNT) + 'ms',
|
||||
fixed_interval: bucketSize || minInterval + 'ms',
|
||||
missing: 0,
|
||||
},
|
||||
aggs: {
|
||||
|
@ -76,7 +77,6 @@ export const getPingHistogram: UMElasticsearchQueryFn<
|
|||
};
|
||||
|
||||
const result = await callES('search', params);
|
||||
const interval = result.aggregations?.timeseries?.interval;
|
||||
const buckets: HistogramQueryResult[] = result?.aggregations?.timeseries?.buckets ?? [];
|
||||
const histogram = buckets.map((bucket) => {
|
||||
const x: number = bucket.key;
|
||||
|
@ -91,6 +91,6 @@ export const getPingHistogram: UMElasticsearchQueryFn<
|
|||
});
|
||||
return {
|
||||
histogram,
|
||||
interval,
|
||||
minInterval,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -54,4 +54,4 @@
|
|||
},
|
||||
"docId": "h5toHm0B0I9WX_CznN_V",
|
||||
"timestamp": "2019-09-11T03:40:34.371Z"
|
||||
}
|
||||
}
|
|
@ -156,5 +156,6 @@
|
|||
"upCount": 93,
|
||||
"y": 1
|
||||
}
|
||||
]
|
||||
],
|
||||
"minInterval": 22801
|
||||
}
|
|
@ -156,5 +156,6 @@
|
|||
"upCount": 93,
|
||||
"y": 1
|
||||
}
|
||||
]
|
||||
],
|
||||
"minInterval": 22801
|
||||
}
|
|
@ -156,5 +156,6 @@
|
|||
"upCount": 1,
|
||||
"y": 1
|
||||
}
|
||||
]
|
||||
],
|
||||
"minInterval": 22801
|
||||
}
|
Loading…
Reference in a new issue