[Uptime] Improve monitor charts query (#30561) (#32343)

* Refactor chart querying.

* Fix monitor chart query.

* Refactor several inline computations to helper functions. Improve schema naming.

* Move unit conversion to client, remove bare conversion values.

* Add API tests for monitor charts.

* Add test for conversion function.

* Add type annotations to latest schema additions.

* Fix typo.

* Refactor based on PR feedback, add comments asked for in PR feedback.

* Rename fields in schema, update tests. Extract monitor charts to functional component and add unit test.
This commit is contained in:
Justin Kambic 2019-03-05 14:42:50 -05:00 committed by GitHub
parent d7f95fee0d
commit 507e7139e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1135 additions and 389 deletions

View file

@ -183,11 +183,7 @@
"defaultValue": null
}
],
"type": {
"kind": "LIST",
"name": null,
"ofType": { "kind": "OBJECT", "name": "MonitorChartEntry", "ofType": null }
},
"type": { "kind": "OBJECT", "name": "MonitorChart", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
@ -1575,11 +1571,11 @@
{
"kind": "OBJECT",
"name": "LatestMonitor",
"description": "",
"description": "Represents the latest recorded information about a monitor.",
"fields": [
{
"name": "id",
"description": "",
"description": "The ID of the monitor represented by this data.",
"args": [],
"type": {
"kind": "NON_NULL",
@ -1591,7 +1587,7 @@
},
{
"name": "ping",
"description": "",
"description": "Information from the latest document.",
"args": [],
"type": { "kind": "OBJECT", "name": "Ping", "ofType": null },
"isDeprecated": false,
@ -1599,7 +1595,7 @@
},
{
"name": "upSeries",
"description": "",
"description": "Buckets of recent up count status data.",
"args": [],
"type": {
"kind": "LIST",
@ -1611,7 +1607,7 @@
},
{
"name": "downSeries",
"description": "",
"description": "Buckets of recent down count status data.",
"args": [],
"type": {
"kind": "LIST",
@ -1793,86 +1789,94 @@
},
{
"kind": "OBJECT",
"name": "MonitorChartEntry",
"description": "",
"name": "MonitorChart",
"description": "The data used to populate the monitor charts.",
"fields": [
{
"name": "maxContent",
"description": "",
"name": "durationArea",
"description": "The max and min values for the monitor duration.",
"args": [],
"type": { "kind": "OBJECT", "name": "DataPoint", "ofType": null },
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "OBJECT", "name": "MonitorDurationAreaPoint", "ofType": null }
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "maxResponse",
"description": "",
"name": "durationLine",
"description": "The average values for the monitor duration.",
"args": [],
"type": { "kind": "OBJECT", "name": "DataPoint", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "maxValidate",
"description": "",
"args": [],
"type": { "kind": "OBJECT", "name": "DataPoint", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "maxTotal",
"description": "",
"args": [],
"type": { "kind": "OBJECT", "name": "DataPoint", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "maxWriteRequest",
"description": "",
"args": [],
"type": { "kind": "OBJECT", "name": "DataPoint", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "maxTcpRtt",
"description": "",
"args": [],
"type": { "kind": "OBJECT", "name": "DataPoint", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "maxDuration",
"description": "",
"args": [],
"type": { "kind": "OBJECT", "name": "DataPoint", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "minDuration",
"description": "",
"args": [],
"type": { "kind": "OBJECT", "name": "DataPoint", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "avgDuration",
"description": "",
"args": [],
"type": { "kind": "OBJECT", "name": "DataPoint", "ofType": null },
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "MonitorDurationAveragePoint",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "status",
"description": "",
"description": "The counts of up/down checks for the monitor.",
"args": [],
"type": { "kind": "OBJECT", "name": "StatusData", "ofType": null },
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "OBJECT", "name": "StatusData", "ofType": null }
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "statusMaxCount",
"description": "The maximum status doc count in this chart.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "Int", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "durationMaxValue",
"description": "The maximum duration value in this chart.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "Int", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
}
@ -1884,20 +1888,32 @@
},
{
"kind": "OBJECT",
"name": "DataPoint",
"description": "",
"name": "MonitorDurationAreaPoint",
"description": "Represents a monitor's duration performance in microseconds at a point in time.",
"fields": [
{
"name": "x",
"description": "",
"description": "The timeseries value for this point in time.",
"args": [],
"type": { "kind": "SCALAR", "name": "UnsignedInteger", "ofType": null },
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "UnsignedInteger", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "y",
"description": "",
"name": "yMin",
"description": "The min duration value in microseconds at this time.",
"args": [],
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "yMax",
"description": "The max duration value in microseconds at this point.",
"args": [],
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
"isDeprecated": false,
@ -1921,20 +1937,55 @@
},
{
"kind": "OBJECT",
"name": "StatusData",
"description": "",
"name": "MonitorDurationAveragePoint",
"description": "Represents the average monitor duration ms at a point in time.",
"fields": [
{
"name": "x",
"description": "",
"description": "The timeseries value for this point.",
"args": [],
"type": { "kind": "SCALAR", "name": "UnsignedInteger", "ofType": null },
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "UnsignedInteger", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "y",
"description": "The average duration ms for the monitor.",
"args": [],
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "StatusData",
"description": "Represents a bucket of monitor status information.",
"fields": [
{
"name": "x",
"description": "The timeseries point for this status data.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "UnsignedInteger", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "up",
"description": "",
"description": "The value of up counts for this point.",
"args": [],
"type": { "kind": "SCALAR", "name": "Int", "ofType": null },
"isDeprecated": false,
@ -1942,7 +1993,7 @@
},
{
"name": "down",
"description": "",
"description": "The value for down counts for this point.",
"args": [],
"type": { "kind": "SCALAR", "name": "Int", "ofType": null },
"isDeprecated": false,
@ -1950,7 +2001,7 @@
},
{
"name": "total",
"description": "",
"description": "The total down counts for this point.",
"args": [],
"type": { "kind": "SCALAR", "name": "Int", "ofType": null },
"isDeprecated": false,
@ -2854,6 +2905,33 @@
}
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "DataPoint",
"description": "",
"fields": [
{
"name": "x",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "UnsignedInteger", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "y",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
}
],
"directives": [

View file

@ -24,7 +24,7 @@ export interface Query {
getSnapshot?: Snapshot | null;
getMonitorChartsData?: (MonitorChartEntry | null)[] | null;
getMonitorChartsData?: MonitorChart | null;
getLatestMonitors: Ping[];
@ -307,14 +307,15 @@ export interface DocCount {
export interface LatestMonitorsResult {
monitors?: LatestMonitor[] | null;
}
/** Represents the latest recorded information about a monitor. */
export interface LatestMonitor {
/** The ID of the monitor represented by this data. */
id: MonitorKey;
/** Information from the latest document. */
ping?: Ping | null;
/** Buckets of recent up count status data. */
upSeries?: (MonitorSeriesPoint | null)[] | null;
/** Buckets of recent down count status data. */
downSeries?: (MonitorSeriesPoint | null)[] | null;
}
@ -351,42 +352,44 @@ export interface HistogramDataPoint {
y?: UnsignedInteger | null;
}
export interface MonitorChartEntry {
maxContent?: DataPoint | null;
maxResponse?: DataPoint | null;
maxValidate?: DataPoint | null;
maxTotal?: DataPoint | null;
maxWriteRequest?: DataPoint | null;
maxTcpRtt?: DataPoint | null;
maxDuration?: DataPoint | null;
minDuration?: DataPoint | null;
avgDuration?: DataPoint | null;
status?: StatusData | null;
/** The data used to populate the monitor charts. */
export interface MonitorChart {
/** The max and min values for the monitor duration. */
durationArea: MonitorDurationAreaPoint[];
/** The average values for the monitor duration. */
durationLine: MonitorDurationAveragePoint[];
/** The counts of up/down checks for the monitor. */
status: StatusData[];
/** The maximum status doc count in this chart. */
statusMaxCount: number;
/** The maximum duration value in this chart. */
durationMaxValue: number;
}
export interface DataPoint {
x?: UnsignedInteger | null;
/** Represents a monitor's duration performance in microseconds at a point in time. */
export interface MonitorDurationAreaPoint {
/** The timeseries value for this point in time. */
x: UnsignedInteger;
/** The min duration value in microseconds at this time. */
yMin?: number | null;
/** The max duration value in microseconds at this point. */
yMax?: number | null;
}
/** Represents the average monitor duration ms at a point in time. */
export interface MonitorDurationAveragePoint {
/** The timeseries value for this point. */
x: UnsignedInteger;
/** The average duration ms for the monitor. */
y?: number | null;
}
/** Represents a bucket of monitor status information. */
export interface StatusData {
x?: UnsignedInteger | null;
/** The timeseries point for this status data. */
x: UnsignedInteger;
/** The value of up counts for this point. */
up?: number | null;
/** The value for down counts for this point. */
down?: number | null;
/** The total down counts for this point. */
total?: number | null;
}
@ -424,6 +427,12 @@ export interface MonitorPageTitle {
name?: string | null;
}
export interface DataPoint {
x?: UnsignedInteger | null;
y?: number | null;
}
// ====================================================
// Arguments
// ====================================================

View file

@ -0,0 +1,355 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MonitorCharts component renders the component without errors 1`] = `
<Fragment>
<EuiFlexGroup
alignItems="stretch"
component="div"
direction="row"
gutterSize="l"
justifyContent="flexStart"
responsive={true}
wrap={false}
>
<EuiFlexItem
component="div"
grow={true}
>
<EuiTitle
size="xs"
textTransform="none"
>
<h4>
<FormattedMessage
defaultMessage="Monitor Duration ms"
description="The 'ms' is an abbreviation for milliseconds."
id="xpack.uptime.monitorCharts.monitorDuration.titleLabel"
values={Object {}}
/>
</h4>
</EuiTitle>
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
style={
Object {
"maxHeight": 220,
"maxWidth": 520,
}
}
>
<FlexibleEuiSeriesChart
crosshairValue={150}
height={200}
margins={
Object {
"bottom": 40,
"left": 60,
"right": 40,
"top": 10,
}
}
onCrosshairUpdate={[MockFunction]}
width={500}
xCrosshairFormat="YYYY-MM-DD hh:mmZ"
xType="time"
yDomain={
Array [
0,
75,
]
}
>
<EuiAreaSeries
color="secondaryColor"
curve="curveBasis"
data={
Array [
Object {
"x": 1548697620000,
"y": 3120,
"y0": 106,
},
Object {
"x": 1548697920000,
"y": 3955,
"y0": 122,
},
Object {
"x": 1548698220000,
"y": 3705,
"y0": 118,
},
Object {
"x": 1548698520000,
"y": 6669,
"y0": 123,
},
Object {
"x": 1548698820000,
"y": 3956,
"y0": 117,
},
Object {
"x": 1548699120000,
"y": 4045,
"y0": 122,
},
Object {
"x": 1548699420000,
"y": 3683,
"y0": 120,
},
Object {
"x": 1548699720000,
"y": 3701,
"y0": 115,
},
Object {
"x": 1548700020000,
"y": 3632,
"y0": 112,
},
Object {
"x": 1548700320000,
"y": 3801,
"y0": 105,
},
Object {
"x": 1548700620000,
"y": 3925,
"y0": 124,
},
]
}
fillOpacity={1}
lineSize={1}
name="Duration range"
/>
<EuiLineSeries
borderOpacity={1}
color="primaryColor"
curve="linear"
data={
Array [
Object {
"x": 1548697620000,
"y": 744,
},
Object {
"x": 1548697920000,
"y": 767,
},
Object {
"x": 1548698220000,
"y": 787,
},
Object {
"x": 1548698520000,
"y": 781,
},
Object {
"x": 1548698820000,
"y": 742,
},
Object {
"x": 1548699120000,
"y": 759,
},
Object {
"x": 1548699420000,
"y": 738,
},
Object {
"x": 1548699720000,
"y": 729,
},
Object {
"x": 1548700020000,
"y": 720,
},
Object {
"x": 1548700320000,
"y": 769,
},
Object {
"x": 1548700620000,
"y": 741,
},
]
}
lineMarkSize={0}
lineSize={1}
name="Mean duration"
showLineMarks={false}
/>
</FlexibleEuiSeriesChart>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
>
<EuiTitle
size="xs"
textTransform="none"
>
<h4>
<FormattedMessage
defaultMessage="Check status"
id="xpack.uptime.monitorCharts.checkStatus.title"
values={Object {}}
/>
</h4>
</EuiTitle>
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
style={
Object {
"maxHeight": 220,
"maxWidth": 520,
}
}
>
<FlexibleEuiSeriesChart
crosshairValue={150}
height={200}
margins={
Object {
"bottom": 40,
"left": 60,
"right": 40,
"top": 10,
}
}
onCrosshairUpdate={[MockFunction]}
stackBy="y"
width={500}
xCrosshairFormat="YYYY-MM-DD hh:mmZ"
xType="time"
yDomain={
Array [
0,
75,
]
}
>
<EuiAreaSeries
color="primaryColor"
curve="curveBasis"
data={
Array [
Object {
"x": 1548697620000,
"y": 74,
},
Object {
"x": 1548697920000,
"y": 75,
},
Object {
"x": 1548698220000,
"y": 75,
},
Object {
"x": 1548698520000,
"y": 73,
},
Object {
"x": 1548698820000,
"y": 75,
},
Object {
"x": 1548699120000,
"y": 74,
},
Object {
"x": 1548699420000,
"y": 75,
},
Object {
"x": 1548699720000,
"y": 75,
},
Object {
"x": 1548700020000,
"y": 75,
},
Object {
"x": 1548700320000,
"y": 75,
},
Object {
"x": 1548700620000,
"y": 75,
},
]
}
fillOpacity={1}
lineSize={1}
name="Up count"
/>
<EuiAreaSeries
color="dangerColor"
curve="linear"
data={
Array [
Object {
"x": 1548697620000,
"y": null,
},
Object {
"x": 1548697920000,
"y": null,
},
Object {
"x": 1548698220000,
"y": null,
},
Object {
"x": 1548698520000,
"y": null,
},
Object {
"x": 1548698820000,
"y": null,
},
Object {
"x": 1548699120000,
"y": null,
},
Object {
"x": 1548699420000,
"y": null,
},
Object {
"x": 1548699720000,
"y": null,
},
Object {
"x": 1548700020000,
"y": null,
},
Object {
"x": 1548700320000,
"y": null,
},
Object {
"x": 1548700620000,
"y": null,
},
]
}
fillOpacity={1}
lineSize={1}
name="Down count"
/>
</FlexibleEuiSeriesChart>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
`;

View file

@ -0,0 +1,73 @@
/*
* 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 { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { MonitorCharts } from '../monitor_charts';
describe('MonitorCharts component', () => {
const chartResponse = {
monitorChartsData: {
durationArea: [
{ x: 1548697620000, yMin: 106421, yMax: 3120392 },
{ x: 1548697920000, yMin: 121653, yMax: 3955186 },
{ x: 1548698220000, yMin: 118224, yMax: 3705359 },
{ x: 1548698520000, yMin: 123345, yMax: 6669234 },
{ x: 1548698820000, yMin: 117268, yMax: 3955729 },
{ x: 1548699120000, yMin: 122110, yMax: 4045216 },
{ x: 1548699420000, yMin: 120015, yMax: 3682859 },
{ x: 1548699720000, yMin: 114751, yMax: 3701297 },
{ x: 1548700020000, yMin: 111949, yMax: 3632224 },
{ x: 1548700320000, yMin: 105126, yMax: 3801401 },
{ x: 1548700620000, yMin: 123639, yMax: 3925269 },
],
durationLine: [
{ x: 1548697620000, y: 743928.2027027027 },
{ x: 1548697920000, y: 766840.0133333333 },
{ x: 1548698220000, y: 786970.8266666667 },
{ x: 1548698520000, y: 781064.7808219178 },
{ x: 1548698820000, y: 741563.04 },
{ x: 1548699120000, y: 759354.6756756756 },
{ x: 1548699420000, y: 737533.3866666667 },
{ x: 1548699720000, y: 728669.0266666666 },
{ x: 1548700020000, y: 719951.64 },
{ x: 1548700320000, y: 769181.7866666666 },
{ x: 1548700620000, y: 740805.2666666667 },
],
status: [
{ x: 1548697620000, up: 74, down: null, total: 74 },
{ x: 1548697920000, up: 75, down: null, total: 75 },
{ x: 1548698220000, up: 75, down: null, total: 75 },
{ x: 1548698520000, up: 73, down: null, total: 73 },
{ x: 1548698820000, up: 75, down: null, total: 75 },
{ x: 1548699120000, up: 74, down: null, total: 74 },
{ x: 1548699420000, up: 75, down: null, total: 75 },
{ x: 1548699720000, up: 75, down: null, total: 75 },
{ x: 1548700020000, up: 75, down: null, total: 75 },
{ x: 1548700320000, up: 75, down: null, total: 75 },
{ x: 1548700620000, up: 75, down: null, total: 75 },
],
statusMaxCount: 75,
durationMaxValue: 6669234,
},
};
it('renders the component without errors', () => {
const component = shallowWithIntl(
<MonitorCharts
checkDomainLimits={[0, 75]}
crosshairLocation={150}
danger="dangerColor"
durationDomainLimits={[0, 75]}
monitorChartData={chartResponse.monitorChartsData}
primary="primaryColor"
secondary="secondaryColor"
updateCrosshairLocation={jest.fn()}
/>
);
expect(component).toMatchSnapshot();
});
});

View file

@ -9,6 +9,7 @@ export { EmptyStatusBar } from './empty_status_bar';
export { ErrorList } from './error_list';
export { FilterBar } from './filter_bar';
export { FilterBarLoading } from './filter_bar_loading';
export { MonitorCharts } from './monitor_charts';
export { MonitorList } from './monitor_list';
export { MonitorPageTitle } from './monitor_page_title';
export { MonitorStatusBar } from './monitor_status_bar';

View file

@ -0,0 +1,147 @@
/*
* 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 {
// @ts-ignore missing typings
EuiAreaSeries,
EuiFlexGroup,
EuiFlexItem,
// @ts-ignore missing typings
EuiLineSeries,
EuiPanel,
// @ts-ignore missing typings
EuiSeriesChart,
// @ts-ignore missing typings
EuiSeriesChartUtils,
// @ts-ignore missing typings
EuiSpacer,
// @ts-ignore missing typings
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Fragment } from 'react';
import { MonitorChart } from '../../../common/graphql/types';
import { convertMicrosecondsToMilliseconds as microsToMillis } from '../../lib/helper';
interface MonitorChartsProps {
checkDomainLimits: number[];
crosshairLocation: number;
danger: string;
durationDomainLimits: number[];
monitorChartData: MonitorChart;
primary: string;
secondary: string;
updateCrosshairLocation: (crosshairLocation: number) => void;
}
export const MonitorCharts = ({
checkDomainLimits,
crosshairLocation,
danger,
durationDomainLimits,
monitorChartData: { durationArea, durationLine, status },
primary,
secondary,
updateCrosshairLocation,
}: MonitorChartsProps) => (
<Fragment>
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="xs">
<h4>
<FormattedMessage
id="xpack.uptime.monitorCharts.monitorDuration.titleLabel"
defaultMessage="Monitor Duration ms"
description="The 'ms' is an abbreviation for milliseconds."
/>
</h4>
</EuiTitle>
<EuiPanel style={{ maxWidth: 520, maxHeight: 220 }}>
<EuiSeriesChart
margins={{ left: 60, right: 40, top: 10, bottom: 40 }}
width={500}
height={200}
xType={EuiSeriesChartUtils.SCALE.TIME}
xCrosshairFormat="YYYY-MM-DD hh:mmZ"
yDomain={durationDomainLimits}
crosshairValue={crosshairLocation}
onCrosshairUpdate={updateCrosshairLocation}
>
<EuiAreaSeries
color={secondary}
name={i18n.translate(
'xpack.uptime.monitorCharts.monitorDuration.series.durationRangeLabel',
{
defaultMessage: 'Duration range',
}
)}
data={durationArea.map(({ x, yMin, yMax }) => ({
x,
y0: microsToMillis(yMin),
y: microsToMillis(yMax),
}))}
curve="curveBasis"
/>
<EuiLineSeries
color={primary}
name={i18n.translate(
'xpack.uptime.monitorCharts.monitorDuration.series.meanDurationLabel',
{
defaultMessage: 'Mean duration',
}
)}
data={durationLine.map(({ x, y }) => ({
x,
y: microsToMillis(y),
}))}
/>
</EuiSeriesChart>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle size="xs">
<h4>
<FormattedMessage
id="xpack.uptime.monitorCharts.checkStatus.title"
defaultMessage="Check status"
/>
</h4>
</EuiTitle>
<EuiPanel style={{ maxWidth: 520, maxHeight: 220 }}>
<EuiSeriesChart
margins={{ left: 60, right: 40, top: 10, bottom: 40 }}
width={500}
height={200}
xType={EuiSeriesChartUtils.SCALE.TIME}
xCrosshairFormat="YYYY-MM-DD hh:mmZ"
stackBy="y"
crosshairValue={crosshairLocation}
onCrosshairUpdate={updateCrosshairLocation}
yDomain={checkDomainLimits}
>
<EuiAreaSeries
name={i18n.translate('xpack.uptime.monitorCharts.checkStatus.series.upCountLabel', {
defaultMessage: 'Up count',
})}
data={status.map(({ x, up }) => ({ x, y: up }))}
curve="curveBasis"
color={primary}
/>
<EuiAreaSeries
name={i18n.translate('xpack.uptime.monitorCharts.checkStatus.series.downCountLabel', {
defaultMessage: 'Down count',
})}
data={status.map(({ x, down }) => ({ x, y: down }))}
color={danger}
/>
</EuiSeriesChart>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
);

View file

@ -11,7 +11,7 @@ import moment from 'moment';
import React from 'react';
interface Props {
duration?: number;
duration?: number | null;
url?: string;
status?: string;
timestamp?: string;
@ -50,19 +50,21 @@ export const MonitorStatusBar = ({ timestamp, url, duration, status }: Props) =>
</EuiLink>
</EuiFlexItem>
</EuiFlexItem>
<EuiFlexItem
aria-label={i18n.translate('xpack.uptime.monitorStatusBar.durationTextAriaLabel', {
defaultMessage: 'Monitor duration in milliseconds',
})}
grow={false}
>
<FormattedMessage
id="xpack.uptime.monitorStatusBar.healthStatus.durationInMillisecondsMessage"
values={{ duration: duration ? duration : 0 }}
defaultMessage="{duration}ms"
description="The 'ms' is an abbreviation for 'milliseconds'."
/>
</EuiFlexItem>
{!!duration && (
<EuiFlexItem
aria-label={i18n.translate('xpack.uptime.monitorStatusBar.durationTextAriaLabel', {
defaultMessage: 'Monitor duration in milliseconds',
})}
grow={false}
>
<FormattedMessage
id="xpack.uptime.monitorStatusBar.healthStatus.durationInMillisecondsMessage"
values={{ duration }}
defaultMessage="{duration}ms"
description="The 'ms' is an abbreviation for 'milliseconds'."
/>
</EuiFlexItem>
)}
<EuiFlexItem
aria-label={i18n.translate('xpack.uptime.monitorStatusBar.timestampFromNowTextAriaLabel', {
defaultMessage: 'Time since last check',

View file

@ -23,6 +23,7 @@ import { get } from 'lodash';
import moment from 'moment';
import React, { Fragment } from 'react';
import { Ping, PingResults } from '../../../common/graphql/types';
import { convertMicrosecondsToMilliseconds as microsToMillis } from '../../lib/helper';
interface PingListProps {
loading: boolean;
@ -88,7 +89,7 @@ export const PingList = ({
defaultMessage: 'Duration ms',
description: 'The "ms" in the default message is an abbreviation for milliseconds',
}),
render: (duration: number) => duration / 1000,
render: (duration: number) => microsToMillis(duration),
},
{
field: 'error.type',

View file

@ -6,22 +6,19 @@
import gql from 'graphql-tag';
export const createGetMonitorChartsQueryString = `
export const getMonitorChartsQueryString = `
query MonitorCharts($dateRangeStart: String!, $dateRangeEnd: String!, $monitorId: String!) {
monitorChartsData: getMonitorChartsData(
monitorId: $monitorId
dateRangeStart: $dateRangeStart
dateRangeEnd: $dateRangeEnd
) {
minDuration {
durationArea {
x
y
yMin
yMax
}
maxDuration {
x
y
}
avgDuration {
durationLine {
x
y
}
@ -31,10 +28,12 @@ query MonitorCharts($dateRangeStart: String!, $dateRangeEnd: String!, $monitorId
down
total
}
statusMaxCount
durationMaxValue
}
}
`;
export const createGetMonitorChartsQuery = gql`
${createGetMonitorChartsQueryString}
${getMonitorChartsQueryString}
`;

View file

@ -4,28 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import {
// @ts-ignore missing typings
EuiAreaSeries,
EuiFlexGroup,
EuiFlexItem,
// @ts-ignore missing typings
EuiLineSeries,
EuiPanel,
// @ts-ignore missing typings
EuiSeriesChart,
// @ts-ignore missing typings
EuiSeriesChartUtils,
// @ts-ignore missing typings
EuiSpacer,
// @ts-ignore missing typings
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Fragment } from 'react';
import React from 'react';
import { Query } from 'react-apollo';
import { MonitorChart } from '../../../../common/graphql/types';
import { convertMicrosecondsToMilliseconds as microsToMillis } from '../../../lib/helper';
import { UptimeCommonProps } from '../../../uptime_app';
import { MonitorCharts } from '../../functional';
import { createGetMonitorChartsQuery } from './get_monitor_charts';
interface MonitorChartsProps {
@ -72,125 +57,27 @@ export class MonitorChartsQuery extends React.Component<Props, MonitorChartsStat
});
}
// TODO: this should not exist in the UI, update the GQL resolver/schema to return
// an object that contains these series already shaped in the way required by the visualizations.
const { monitorChartsData } = data;
const avgDurationSeries: any[] = [];
const areaDurationSeries: any[] = [];
const downSeries: any[] = [];
const upSeries: any[] = [];
const checksSeries: any[] = [];
monitorChartsData.forEach(({ avgDuration, maxDuration, minDuration, status }: any) => {
avgDurationSeries.push(avgDuration);
areaDurationSeries.push({ x: minDuration.x, y0: minDuration.y, y: maxDuration.y });
downSeries.push({ x: status.x, y: status.down });
upSeries.push({ x: status.x, y: status.up });
checksSeries.push({ x: status.x, y: status.total });
});
const {
monitorChartsData,
monitorChartsData: { durationMaxValue, statusMaxCount },
}: { monitorChartsData: MonitorChart } = data;
// As above, we are building a domain size for the chart to use.
// Without this code the chart could render data outside of the field.
const checksDomain = upSeries.concat(downSeries).map(({ y }) => y);
const checkDomainLimits = [0, Math.max(...checksDomain)];
const durationDomain = avgDurationSeries.concat(areaDurationSeries);
const durationDomainLimits = [0, Math.max(...durationDomain.map(({ y }) => y))];
const durationMax = microsToMillis(durationMaxValue);
// These limits provide domain sizes for the charts
const checkDomainLimits = [0, statusMaxCount];
const durationDomainLimits = [0, durationMax ? durationMax : 0];
return (
<Fragment>
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="xs">
<h4>
<FormattedMessage
id="xpack.uptime.monitorCharts.monitorDuration.titleLabel"
defaultMessage="Monitor Duration ms"
description="The 'ms' is an abbreviation for milliseconds."
/>
</h4>
</EuiTitle>
<EuiPanel style={{ maxWidth: 520, maxHeight: 220 }}>
<EuiSeriesChart
margins={{ left: 60, right: 40, top: 10, bottom: 40 }}
width={500}
height={200}
xType={EuiSeriesChartUtils.SCALE.TIME}
xCrosshairFormat="YYYY-MM-DD hh:mmZ"
yDomain={durationDomainLimits}
crosshairValue={this.state.crosshairLocation}
onCrosshairUpdate={this.updateCrosshairLocation}
>
<EuiAreaSeries
color={secondary}
name={i18n.translate(
'xpack.uptime.monitorCharts.monitorDuration.series.durationRangeLabel',
{
defaultMessage: 'Duration range',
}
)}
data={areaDurationSeries}
curve="curveBasis"
/>
<EuiLineSeries
color={primary}
name={i18n.translate(
'xpack.uptime.monitorCharts.monitorDuration.series.meanDurationLabel',
{
defaultMessage: 'Mean duration',
}
)}
data={avgDurationSeries}
/>
</EuiSeriesChart>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle size="xs">
<h4>
<FormattedMessage
id="xpack.uptime.monitorCharts.checkStatus.title"
defaultMessage="Check status"
/>
</h4>
</EuiTitle>
<EuiPanel style={{ maxWidth: 520, maxHeight: 220 }}>
<EuiSeriesChart
margins={{ left: 60, right: 40, top: 10, bottom: 40 }}
width={500}
height={200}
xType={EuiSeriesChartUtils.SCALE.TIME}
xCrosshairFormat="YYYY-MM-DD hh:mmZ"
stackBy="y"
crosshairValue={this.state.crosshairLocation}
onCrosshairUpdate={this.updateCrosshairLocation}
yDomain={checkDomainLimits}
>
<EuiAreaSeries
name={i18n.translate(
'xpack.uptime.monitorCharts.checkStatus.series.upCountLabel',
{
defaultMessage: 'Up count',
}
)}
data={upSeries}
curve="curveBasis"
color={primary}
/>
<EuiAreaSeries
name={i18n.translate(
'xpack.uptime.monitorCharts.checkStatus.series.downCountLabel',
{
defaultMessage: 'Down count',
}
)}
data={downSeries}
color={danger}
/>
</EuiSeriesChart>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
<MonitorCharts
checkDomainLimits={checkDomainLimits}
crosshairLocation={this.state.crosshairLocation}
danger={danger}
durationDomainLimits={durationDomainLimits}
monitorChartData={monitorChartsData}
primary={primary}
secondary={secondary}
updateCrosshairLocation={this.updateCrosshairLocation}
/>
);
}}
</Query>

View file

@ -1,24 +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 { formatDuration } from '../format_duration';
describe('formatDuration', () => {
it('returns 0 for undefined', () => {
const result = formatDuration(undefined);
expect(result).toEqual(0);
});
it('returns 0 for NaN', () => {
const result = formatDuration(NaN);
expect(result).toEqual(0);
});
it('returns duration value in ms', () => {
const duration = 320000; // microseconds
expect(formatDuration(duration)).toEqual(320);
});
});

View file

@ -1,13 +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.
*/
export const formatDuration = (duration: number | undefined): number => {
if (duration === undefined) {
return 0;
}
// TODO: formatting should not be performed this way, remove bare number
return isNaN(duration) ? 0 : duration / 1000;
};

View file

@ -10,9 +10,9 @@ import { get } from 'lodash';
import React from 'react';
import { Query } from 'react-apollo';
import { Ping } from 'x-pack/plugins/uptime/common/graphql/types';
import { convertMicrosecondsToMilliseconds as microsToMillis } from '../../../lib/helper';
import { UptimeCommonProps } from '../../../uptime_app';
import { EmptyStatusBar, MonitorStatusBar } from '../../functional';
import { formatDuration } from './format_duration';
import { getMonitorStatusBarQuery } from './get_monitor_status_bar';
interface MonitorStatusBarProps {
@ -63,16 +63,11 @@ export const MonitorStatusBarQuery = ({
}
const { monitor, timestamp, url } = monitorStatus[0];
const status = get(monitor, 'status', undefined);
const duration = parseInt(get(monitor, 'duration.us'), 10);
const duration = microsToMillis(get(monitor, 'duration.us', null));
const full = get(url, 'full', undefined);
return (
<MonitorStatusBar
duration={formatDuration(duration)}
status={status}
timestamp={timestamp}
url={full}
/>
<MonitorStatusBar duration={duration} status={status} timestamp={timestamp} url={full} />
);
}}
</Query>

View file

@ -0,0 +1,21 @@
/*
* 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 { convertMicrosecondsToMilliseconds } from '../convert_measurements';
describe('convertMicrosecondsToMilliseconds', () => {
it('converts microseconds to millis', () => {
const microValue = 3425342;
const result = convertMicrosecondsToMilliseconds(microValue);
expect(result).toEqual(3425);
});
it('returns null for null parameter', () => {
expect(convertMicrosecondsToMilliseconds(null)).toBeNull();
});
it('returns undefined for undefined parameter', () => {
expect(convertMicrosecondsToMilliseconds(undefined)).toBeUndefined();
});
});

View file

@ -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.
*/
const NUM_MICROSECONDS_IN_MILLISECOND = 1000;
/**
* This simply converts microseconds to milliseconds. People tend to prefer ms to us
* when visualizaing request duration times.
*/
export const convertMicrosecondsToMilliseconds = (microseconds: number | null | undefined) =>
microseconds ? Math.round(microseconds / NUM_MICROSECONDS_IN_MILLISECOND) : microseconds;

View file

@ -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 { convertMicrosecondsToMilliseconds } from './convert_measurements';

View file

@ -15,6 +15,7 @@ import {
GetMonitorPageTitleQueryArgs,
GetMonitorsQueryArgs,
GetSnapshotQueryArgs,
MonitorChart,
MonitorPageTitle,
Ping,
Snapshot,
@ -113,8 +114,8 @@ export const createMonitorsResolvers: CreateUMGraphQLResolvers = (
resolver,
{ monitorId, dateRangeStart, dateRangeEnd },
{ req }
): Promise<any> {
return libs.monitors.getMonitorChartsData(req, monitorId, dateRangeStart, dateRangeEnd);
): Promise<MonitorChart> {
return await libs.monitors.getMonitorChartsData(req, monitorId, dateRangeStart, dateRangeEnd);
},
async getLatestMonitors(
resolver,

View file

@ -35,24 +35,30 @@ export const monitorsSchema = gql`
y: Float
}
"Represents a bucket of monitor status information."
type StatusData {
x: UnsignedInteger
"The timeseries point for this status data."
x: UnsignedInteger!
"The value of up counts for this point."
up: Int
"The value for down counts for this point."
down: Int
"The total down counts for this point."
total: Int
}
type MonitorChartEntry {
maxContent: DataPoint
maxResponse: DataPoint
maxValidate: DataPoint
maxTotal: DataPoint
maxWriteRequest: DataPoint
maxTcpRtt: DataPoint
maxDuration: DataPoint
minDuration: DataPoint
avgDuration: DataPoint
status: StatusData
"The data used to populate the monitor charts."
type MonitorChart {
"The max and min values for the monitor duration."
durationArea: [MonitorDurationAreaPoint!]!
"The average values for the monitor duration."
durationLine: [MonitorDurationAveragePoint!]!
"The counts of up/down checks for the monitor."
status: [StatusData!]!
"The maximum status doc count in this chart."
statusMaxCount: Int!
"The maximum duration value in this chart."
durationMaxValue: Int!
}
type MonitorKey {
@ -65,10 +71,33 @@ export const monitorsSchema = gql`
y: Int
}
"Represents a monitor's duration performance in microseconds at a point in time."
type MonitorDurationAreaPoint {
"The timeseries value for this point in time."
x: UnsignedInteger!
"The min duration value in microseconds at this time."
yMin: Float
"The max duration value in microseconds at this point."
yMax: Float
}
"Represents the average monitor duration ms at a point in time."
type MonitorDurationAveragePoint {
"The timeseries value for this point."
x: UnsignedInteger!
"The average duration ms for the monitor."
y: Float
}
"Represents the latest recorded information about a monitor."
type LatestMonitor {
"The ID of the monitor represented by this data."
id: MonitorKey!
"Information from the latest document."
ping: Ping
"Buckets of recent up count status data."
upSeries: [MonitorSeriesPoint]
"Buckets of recent down count status data."
downSeries: [MonitorSeriesPoint]
}
@ -104,7 +133,7 @@ export const monitorsSchema = gql`
monitorId: String!
dateRangeStart: String!
dateRangeEnd: String!
): [MonitorChartEntry]
): MonitorChart
getLatestMonitors(dateRangeStart: String!, dateRangeEnd: String!, monitorId: String): [Ping!]!

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { MonitorPageTitle } from 'x-pack/plugins/uptime/common/graphql/types';
import { MonitorChart, MonitorPageTitle } from '../../../../common/graphql/types';
export interface UMMonitorsAdapter {
getMonitorChartsData(
@ -12,7 +12,7 @@ export interface UMMonitorsAdapter {
monitorId: string,
dateRangeStart: string,
dateRangeEnd: string
): Promise<any>;
): Promise<MonitorChart>;
getMonitors(
request: any,
dateRangeStart: string,

View file

@ -10,21 +10,16 @@ import {
ErrorListItem,
FilterBar,
LatestMonitor,
MonitorChart,
MonitorKey,
MonitorPageTitle,
MonitorSeriesPoint,
Ping,
} from '../../../../common/graphql/types';
import { getFilteredQuery, getFilteredQueryAndStatusFilter } from '../../helper';
import { dropLatestBucket, getFilteredQuery, getFilteredQueryAndStatusFilter } from '../../helper';
import { DatabaseAdapter } from '../database';
import { UMMonitorsAdapter } from './adapter_types';
// the values for these charts are stored as μs, but should be displayed as ms
const formatChartValue = (time: any, chartPoint: any) => ({
x: time,
y: chartPoint.value === null ? null : chartPoint.value / 1000,
});
const formatStatusBuckets = (time: any, buckets: any, docCount: any) => {
let up = null;
let down = null;
@ -62,7 +57,7 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
monitorId: string,
dateRangeStart: string,
dateRangeEnd: string
): Promise<any> {
): Promise<MonitorChart> {
const params = {
index: INDEX_NAMES.HEARTBEAT,
body: {
@ -79,15 +74,9 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
timeseries: {
auto_date_histogram: {
field: '@timestamp',
buckets: 50,
buckets: 25,
},
aggs: {
max_content: { max: { field: 'http.rtt.content.us' } },
max_response: { max: { field: 'http.rtt.response_header.us' } },
max_validate: { max: { field: 'http.rtt.validate.us' } },
max_total: { max: { field: 'http.rtt.total.us' } },
max_write_request: { max: { field: 'http.rtt.write_request.us' } },
max_tcp_rtt: { max: { field: 'tcp.rtt.connect.us' } },
status: { terms: { field: 'monitor.status', size: 2, shard_size: 2 } },
duration: { stats: { field: 'monitor.duration.us' } },
},
@ -97,33 +86,49 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
};
const result = await this.database.search(request, params);
const buckets = get(result, 'aggregations.timeseries.buckets', []);
const buckets = dropLatestBucket(get(result, 'aggregations.timeseries.buckets', []));
return buckets.map(
({
key,
max_content,
duration: { avg, max, min },
max_write_request,
max_validate,
max_tcp_rtt,
max_response,
max_total,
status,
doc_count,
}: any) => ({
maxContent: formatChartValue(key, max_content),
maxWriteRequest: formatChartValue(key, max_write_request),
maxValidate: formatChartValue(key, max_validate),
maxTcpRtt: formatChartValue(key, max_tcp_rtt),
maxResponse: formatChartValue(key, max_response),
maxTotal: formatChartValue(key, max_total),
avgDuration: formatChartValue(key, { value: avg }),
maxDuration: formatChartValue(key, { value: max }),
minDuration: formatChartValue(key, { value: min }),
status: formatStatusBuckets(key, status.buckets, doc_count),
})
);
/**
* The code below is responsible for formatting the aggregation data we fetched above in a way
* that the chart components used by the client understands.
* There are five required values. Two are lists of points that conform to a simple (x,y) structure.
*
* The third list is for an area chart expressing a range, and it requires an (x,y,y0) structure,
* where y0 is the min value for the point and y is the max.
*
* Additionally, we supply the maximum value for duration and status, so the corresponding charts know
* what the domain size should be.
*/
const monitorChartsData: MonitorChart = {
durationArea: [],
durationLine: [],
status: [],
durationMaxValue: 0,
statusMaxCount: 0,
};
buckets.forEach(bucket => {
const x = get(bucket, 'key');
const docCount = get(bucket, 'doc_count', 0);
// update the maximum value for each point
monitorChartsData.statusMaxCount = Math.max(docCount, monitorChartsData.statusMaxCount);
monitorChartsData.durationMaxValue = Math.max(
monitorChartsData.durationMaxValue,
get(bucket, 'duration.max', 0)
);
// these points express a range that will be displayed as an area chart
monitorChartsData.durationArea.push({
x,
yMin: get(bucket, 'duration.min', null),
yMax: get(bucket, 'duration.max', null),
});
monitorChartsData.durationLine.push({ x, y: get(bucket, 'duration.avg', null) });
monitorChartsData.status.push(
formatStatusBuckets(x, get(bucket, 'status.buckets', []), docCount)
);
});
return monitorChartsData;
}
/**

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { MonitorPageTitle } from '../../../common/graphql/types';
import { MonitorChart, MonitorPageTitle } from '../../../common/graphql/types';
import { UMMonitorsAdapter } from '../adapters/monitors';
export class UMMonitorsDomain {
@ -17,7 +17,7 @@ export class UMMonitorsDomain {
monitorId: string,
dateRangeStart: string,
dateRangeEnd: string
): Promise<any> {
): Promise<MonitorChart> {
return this.adapter.getMonitorChartsData(request, monitorId, dateRangeStart, dateRangeEnd);
}

View file

@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`dropLatestBucket drops the last of a list with greater length than 1 1`] = `
Array [
Object {
"prop": "val",
},
]
`;

View file

@ -0,0 +1,25 @@
/*
* 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 { dropLatestBucket } from '../drop_latest_bucket';
describe('dropLatestBucket', () => {
it('drops the last of a list with greater length than 1', () => {
const testData = [{ prop: 'val' }, { prop: 'val' }];
const result = dropLatestBucket(testData);
expect(result).toMatchSnapshot();
});
it('returns an empty list when length === 1', () => {
const testData = [{ prop: 'val' }];
const result = dropLatestBucket(testData);
expect(result).toEqual([]);
});
it('returns an empty list when length === 0', () => {
const testData: any[] = [];
const result = dropLatestBucket(testData);
expect(result).toEqual([]);
});
});

View file

@ -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.
*/
/**
* We've had numerous requests to not display semi-full buckets (i.e. it is 13:01 and the
* bounds of our bucket are 13:00-13:05). If the first bucket isn't done filling, we'll
* start out with nothing returned, otherwise we drop the most recent bucket.
* @param buckets The bucket list
*/
export const dropLatestBucket = (buckets: any[]) =>
buckets.length > 1 ? buckets.slice(0, buckets.length - 1) : [];

View file

@ -5,6 +5,7 @@
*/
import { UMESBucket, UMESHistogramBucket } from '../adapters/database';
import { dropLatestBucket } from './drop_latest_bucket';
/**
* The charting library we're currently using requires histogram data points have an
@ -27,7 +28,7 @@ export function formatEsBucketsForHistogram<T extends UMESBucket>(
const TERMINAL_INDEX = buckets.length - 1;
const { key: terminalBucketTime } = buckets[TERMINAL_INDEX];
// drop the most recent bucket to avoid returning incomplete bucket
return buckets.slice(0, TERMINAL_INDEX).map((item, index, array) => {
return dropLatestBucket(buckets).map((item, index, array) => {
const { key } = item;
const nextItem = array[index + 1];
const bucketSize = nextItem ? Math.abs(nextItem.key - key) : Math.abs(terminalBucketTime - key);

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { dropLatestBucket } from './drop_latest_bucket';
export { formatEsBucketsForHistogram } from './format_es_buckets_for_histogram';
export { getFilteredQuery } from './get_filtered_query';
export { getFilteredQueryAndStatusFilter } from './get_filtered_query_and_status';

View file

@ -0,0 +1,45 @@
{
"monitorChartsData": {
"durationArea": [
{ "x": 1548697620000, "yMin": 106421, "yMax": 3120392 },
{ "x": 1548697920000, "yMin": 121653, "yMax": 3955186 },
{ "x": 1548698220000, "yMin": 118224, "yMax": 3705359 },
{ "x": 1548698520000, "yMin": 123345, "yMax": 6669234 },
{ "x": 1548698820000, "yMin": 117268, "yMax": 3955729 },
{ "x": 1548699120000, "yMin": 122110, "yMax": 4045216 },
{ "x": 1548699420000, "yMin": 120015, "yMax": 3682859 },
{ "x": 1548699720000, "yMin": 114751, "yMax": 3701297 },
{ "x": 1548700020000, "yMin": 111949, "yMax": 3632224 },
{ "x": 1548700320000, "yMin": 105126, "yMax": 3801401 },
{ "x": 1548700620000, "yMin": 123639, "yMax": 3925269 }
],
"durationLine": [
{ "x": 1548697620000, "y": 743928.2027027027 },
{ "x": 1548697920000, "y": 766840.0133333333 },
{ "x": 1548698220000, "y": 786970.8266666667 },
{ "x": 1548698520000, "y": 781064.7808219178 },
{ "x": 1548698820000, "y": 741563.04 },
{ "x": 1548699120000, "y": 759354.6756756756 },
{ "x": 1548699420000, "y": 737533.3866666667 },
{ "x": 1548699720000, "y": 728669.0266666666 },
{ "x": 1548700020000, "y": 719951.64 },
{ "x": 1548700320000, "y": 769181.7866666666 },
{ "x": 1548700620000, "y": 740805.2666666667 }
],
"status": [
{ "x": 1548697620000, "up": 74, "down": null, "total": 74 },
{ "x": 1548697920000, "up": 75, "down": null, "total": 75 },
{ "x": 1548698220000, "up": 75, "down": null, "total": 75 },
{ "x": 1548698520000, "up": 73, "down": null, "total": 73 },
{ "x": 1548698820000, "up": 75, "down": null, "total": 75 },
{ "x": 1548699120000, "up": 74, "down": null, "total": 74 },
{ "x": 1548699420000, "up": 75, "down": null, "total": 75 },
{ "x": 1548699720000, "up": 75, "down": null, "total": 75 },
{ "x": 1548700020000, "up": 75, "down": null, "total": 75 },
{ "x": 1548700320000, "up": 75, "down": null, "total": 75 },
{ "x": 1548700620000, "up": 75, "down": null, "total": 75 }
],
"statusMaxCount": 75,
"durationMaxValue": 6669234
}
}

View file

@ -0,0 +1,9 @@
{
"monitorChartsData": {
"durationArea": [],
"durationLine": [],
"status": [],
"statusMaxCount": 0,
"durationMaxValue": 0
}
}

View file

@ -19,6 +19,7 @@ export default function ({ getService, loadTestFile }) {
loadTestFile(require.resolve('./doc_count'));
loadTestFile(require.resolve('./error_list'));
loadTestFile(require.resolve('./filter_bar'));
loadTestFile(require.resolve('./monitor_charts'));
loadTestFile(require.resolve('./monitor_list'));
loadTestFile(require.resolve('./monitor_status_bar'));
loadTestFile(require.resolve('./ping_list'));

View file

@ -0,0 +1,54 @@
/*
* 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 expect from 'expect.js';
import { getMonitorChartsQueryString } from '../../../../../plugins/uptime/public/components/queries/monitor_charts/get_monitor_charts';
import monitorCharts from './fixtures/monitor_charts';
import monitorChartsEmptySet from './fixtures/monitor_charts_empty_set';
export default function ({ getService }) {
describe('monitorCharts query', () => {
const supertest = getService('supertest');
it('will fetch a series of data points for monitor duration and status', async () => {
const getMonitorChartsQuery = {
operationName: 'MonitorCharts',
query: getMonitorChartsQueryString,
variables: {
dateRangeStart: '2019-01-28T17:40:08.078Z',
dateRangeEnd: '2019-01-28T19:00:16.078Z',
monitorId: 'auto-http-0X131221E73F825974',
},
};
const {
body: { data },
} = await supertest
.post('/api/uptime/graphql')
.set('kbn-xsrf', 'foo')
.send({ ...getMonitorChartsQuery });
expect(data).to.eql(monitorCharts);
});
it('will fetch empty sets for a date range with no data', async () => {
const getMonitorChartsQuery = {
operationName: 'MonitorCharts',
query: getMonitorChartsQueryString,
variables: {
dateRangeStart: '2002-01-28T17:40:08.078Z',
dateRangeEnd: '2002-01-28T19:00:16.078Z',
monitorId: 'auto-http-0X131221E73F825974',
},
};
const {
body: { data },
} = await supertest
.post('/api/uptime/graphql')
.set('kbn-xsrf', 'foo')
.send({ ...getMonitorChartsQuery });
expect(data).to.eql(monitorChartsEmptySet);
});
});
}