[Uptime] Port unit tests to master (#29876)

* Uptime unit tests6.x (#29280)

* Add API functional tests for uptime graphQL.

* Remove obsolete code.

* Add CI group for UI functional tests.

* Delete obsolete code, rename heartbeat es archive.

* Refactor adapter methods.

* Refactor adapter methods.

* Attempt to fix ci-group tag error.

* Skip functional app tests until later PR.

* Remove unused code.

* Add unit tests for ping list and snapshot components.

* Add additional unit tests.

* Remove unused variable.

* Update tests for EmptyState component.

* Update ErrorList component tests.

* Update monitor list unit test.

* Add tests for EmptyStatusBar component.

* Write test for FilterBar component.

* Update PingList test to work with 7.x data.

* Delete obsolete snapshot.

* Add test for Snapshot component.

* Update types.

* Add snapshot histogram test.

* Write tests and improve histogram data formatting function.

* Fix bug and add test to data format function.

* Remove unused localization value.

* Resolve localization conflict.

* Clean up guaranteed truthy property reference.

* Remove expression from localization default message.
This commit is contained in:
Justin Kambic 2019-02-04 17:05:04 -05:00 committed by GitHub
parent 674a6aa2ef
commit e9d0f1b58d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 6901 additions and 1071 deletions

View file

@ -301,7 +301,11 @@
"type": {
"kind": "LIST",
"name": null,
"ofType": { "kind": "OBJECT", "name": "ErrorListItem", "ofType": null }
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "OBJECT", "name": "ErrorListItem", "ofType": null }
}
},
"isDeprecated": false,
"deprecationReason": null
@ -1553,7 +1557,11 @@
"type": {
"kind": "LIST",
"name": null,
"ofType": { "kind": "OBJECT", "name": "LatestMonitor", "ofType": null }
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "OBJECT", "name": "LatestMonitor", "ofType": null }
}
},
"isDeprecated": false,
"deprecationReason": null
@ -1713,7 +1721,11 @@
"type": {
"kind": "LIST",
"name": null,
"ofType": { "kind": "OBJECT", "name": "HistogramSeries", "ofType": null }
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "OBJECT", "name": "HistogramSeries", "ofType": null }
}
},
"isDeprecated": false,
"deprecationReason": null
@ -1744,7 +1756,11 @@
"type": {
"kind": "LIST",
"name": null,
"ofType": { "kind": "OBJECT", "name": "HistogramDataPoint", "ofType": null }
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "OBJECT", "name": "HistogramDataPoint", "ofType": null }
}
},
"isDeprecated": false,
"deprecationReason": null

View file

@ -30,7 +30,7 @@ export interface Query {
getFilterBar?: FilterBar | null;
getErrorsList?: (ErrorListItem | null)[] | null;
getErrorsList?: ErrorListItem[] | null;
getMonitorPageTitle?: MonitorPageTitle | null;
}
@ -305,7 +305,7 @@ export interface DocCount {
}
export interface LatestMonitorsResult {
monitors?: (LatestMonitor | null)[] | null;
monitors?: LatestMonitor[] | null;
}
export interface LatestMonitor {
@ -337,13 +337,13 @@ export interface Snapshot {
total?: number | null;
histogram?: (HistogramSeries | null)[] | null;
histogram?: HistogramSeries[] | null;
}
export interface HistogramSeries {
monitorId?: string | null;
data?: (HistogramDataPoint | null)[] | null;
data?: HistogramDataPoint[] | null;
}
export interface HistogramDataPoint {

View file

@ -0,0 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EmptyStatusBar component renders a default message when no message provided 1`] = `
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<EuiFlexGroup
alignItems="stretch"
component="div"
direction="row"
gutterSize="l"
justifyContent="flexStart"
responsive={true}
wrap={false}
>
<EuiFlexItem
component="div"
grow={false}
>
No data found for monitor id mon_id
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
`;
exports[`EmptyStatusBar component renders a message when provided 1`] = `
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<EuiFlexGroup
alignItems="stretch"
component="div"
direction="row"
gutterSize="l"
justifyContent="flexStart"
responsive={true}
wrap={false}
>
<EuiFlexItem
component="div"
grow={false}
>
foobarbaz
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
`;

View file

@ -0,0 +1,115 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ErrorList component renders the error list without errors 1`] = `
<Fragment>
<EuiTitle
size="xs"
textTransform="none"
>
<h5>
<FormattedMessage
defaultMessage="Error list"
id="xpack.uptime.errorList.title"
values={Object {}}
/>
</h5>
</EuiTitle>
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<EuiInMemoryTable
columns={
Array [
Object {
"field": "type",
"name": "Error type",
"sortable": true,
},
Object {
"field": "monitorId",
"name": "Monitor ID",
"render": [Function],
"sortable": true,
"width": "25%",
},
Object {
"field": "count",
"name": "Count",
"sortable": true,
},
Object {
"field": "timestamp",
"name": "Latest error",
"render": [Function],
"sortable": true,
},
Object {
"field": "statusCode",
"name": "Status code",
"sortable": true,
},
Object {
"field": "latestMessage",
"name": "Latest message",
"sortable": true,
"width": "40%",
},
]
}
executeQueryOptions={Object {}}
items={
Array [
Object {
"count": 843,
"latestMessage": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused",
"monitorId": "auto-http-0X3675F89EF0612091",
"statusCode": null,
"timestamp": "2019-01-28T18:43:15.077Z",
"type": "io",
},
Object {
"count": 748,
"latestMessage": "dial tcp 127.0.0.1:9200: connect: connection refused",
"monitorId": "auto-tcp-0X81440A68E839814C",
"statusCode": null,
"timestamp": "2019-01-28T17:59:34.075Z",
"type": "io",
},
Object {
"count": 1,
"latestMessage": "lookup www.reddit.com: no such host",
"monitorId": "auto-http-0XD9AE729FC1C1E04A",
"statusCode": null,
"timestamp": "2019-01-28T18:03:10.077Z",
"type": "io",
},
Object {
"count": 645,
"latestMessage": "received status code 301 expecting 200",
"monitorId": "auto-http-0XA8096548ECEB85B7",
"statusCode": "301",
"timestamp": "2019-01-28T18:43:07.078Z",
"type": "validate",
},
]
}
loading={false}
pagination={
Object {
"initialPageSize": 10,
"pageSizeOptions": Array [
5,
10,
20,
50,
],
}
}
responsive={true}
sorting={true}
/>
</EuiPanel>
</Fragment>
`;

View file

@ -0,0 +1,205 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FilterBar component renders the component without errors 1`] = `
<EuiFlexGroup
alignItems="stretch"
component="div"
direction="row"
gutterSize="l"
justifyContent="flexStart"
responsive={true}
wrap={false}
>
<EuiFlexItem
component="div"
grow={true}
>
<EuiSearchBar
filters={
Array [
Object {
"field": "monitor.status",
"items": Array [
Object {
"name": "Up",
"value": "up",
},
Object {
"name": "Down",
"value": "down",
},
],
"type": "field_value_toggle_group",
},
Object {
"field": "monitor.id",
"multiSelect": false,
"name": "ID",
"options": Array [
Object {
"value": "auto-tcp-0X81440A68E839814C",
"view": "auto-tcp-0X81440A68E839814C",
},
Object {
"value": "auto-http-0X3675F89EF0612091",
"view": "auto-http-0X3675F89EF0612091",
},
Object {
"value": "auto-http-0X970CBD2F2102BFA8",
"view": "auto-http-0X970CBD2F2102BFA8",
},
Object {
"value": "auto-http-0X131221E73F825974",
"view": "auto-http-0X131221E73F825974",
},
Object {
"value": "auto-http-0X9CB71300ABD5A2A8",
"view": "auto-http-0X9CB71300ABD5A2A8",
},
Object {
"value": "auto-http-0XD9AE729FC1C1E04A",
"view": "auto-http-0XD9AE729FC1C1E04A",
},
Object {
"value": "auto-http-0XDD2D4E60FD4A61C3",
"view": "auto-http-0XDD2D4E60FD4A61C3",
},
Object {
"value": "auto-http-0XA8096548ECEB85B7",
"view": "auto-http-0XA8096548ECEB85B7",
},
Object {
"value": "auto-http-0XC9CDA429418EDC2B",
"view": "auto-http-0XC9CDA429418EDC2B",
},
Object {
"value": "auto-http-0XE3B163481423197D",
"view": "auto-http-0XE3B163481423197D",
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"field": "monitor.name",
"multiSelect": false,
"name": "Name",
"options": Array [],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"field": "url.full",
"multiSelect": false,
"name": "URL",
"options": Array [
Object {
"value": "tcp://localhost:9200",
"view": "tcp://localhost:9200",
},
Object {
"value": "http://localhost:12349/",
"view": "http://localhost:12349/",
},
Object {
"value": "http://www.google.com/",
"view": "http://www.google.com/",
},
Object {
"value": "https://www.google.com/",
"view": "https://www.google.com/",
},
Object {
"value": "https://www.github.com/",
"view": "https://www.github.com/",
},
Object {
"value": "http://www.reddit.com/",
"view": "http://www.reddit.com/",
},
Object {
"value": "https://www.elastic.co",
"view": "https://www.elastic.co",
},
Object {
"value": "http://www.example.com/",
"view": "http://www.example.com/",
},
Object {
"value": "https://www.wikipedia.org/",
"view": "https://www.wikipedia.org/",
},
Object {
"value": "https://news.google.com/",
"view": "https://news.google.com/",
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"field": "url.port",
"multiSelect": false,
"name": "Port",
"options": Array [
Object {
"value": 9200,
"view": 9200,
},
Object {
"value": 12349,
"view": 12349,
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"field": "monitor.type",
"multiSelect": false,
"name": "Type",
"options": Array [
Object {
"value": "tcp",
"view": "tcp",
},
Object {
"value": "http",
"view": "http",
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
]
}
onChange={[Function]}
schema={
Object {
"fields": Object {
"monitor.host": Object {
"type": "string",
},
"monitor.id": Object {
"type": "string",
},
"monitor.ip": Object {
"type": "string",
},
"monitor.scheme": Object {
"type": "string",
},
"monitor.status": Object {
"type": "string",
},
"url.port": Object {
"type": "number",
},
},
"strict": true,
}
}
/>
</EuiFlexItem>
</EuiFlexGroup>
`;

View file

@ -0,0 +1,375 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PingList component renders sorted list without errors 1`] = `
<Fragment>
<EuiFlexGroup
alignItems="stretch"
component="div"
direction="row"
gutterSize="l"
justifyContent="flexStart"
responsive={true}
wrap={false}
>
<EuiFlexItem
component="div"
grow={false}
>
<EuiTitle
size="xs"
textTransform="none"
>
<h4>
<FormattedMessage
defaultMessage="Check History"
id="xpack.uptime.pingList.checkHistoryTitle"
values={Object {}}
/>
</h4>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={false}
>
<EuiBadge
color="primary"
iconSide="left"
>
9231
</EuiBadge>
</EuiFlexItem>
</EuiFlexGroup>
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="l"
>
<EuiFlexGroup
alignItems="stretch"
component="div"
direction="row"
gutterSize="l"
justifyContent="flexStart"
responsive={true}
wrap={false}
>
<EuiFlexItem
component="div"
grow={true}
>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Status"
>
<EuiComboBox
compressed={false}
fullWidth={false}
isClearable={false}
onChange={[MockFunction]}
options={
Array [
Object {
"label": "All",
"value": "",
},
]
}
selectedOptions={
Array [
Object {
"label": "All",
"value": "",
},
]
}
singleSelection={
Object {
"asPlainText": true,
}
}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Max Search Size"
>
<EuiFieldNumber
compressed={false}
defaultValue="200"
fullWidth={false}
isLoading={false}
max={10000}
min={0}
onBlur={[MockFunction]}
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
<EuiInMemoryTable
columns={
Array [
Object {
"field": "monitor.status",
"name": "Status",
"render": [Function],
"sortable": true,
},
Object {
"field": "timestamp",
"name": "Timestamp",
"render": [Function],
"sortable": true,
},
Object {
"field": "monitor.ip",
"name": "IP",
},
Object {
"dataType": "string",
"field": "monitor.id",
"name": "Id",
"width": "20%",
},
Object {
"field": "monitor.duration.us",
"name": "Duration ms",
"render": [Function],
"sortable": true,
},
Object {
"field": "error.type",
"name": "Error type",
},
Object {
"field": "error.message",
"name": "Error message",
"render": [Function],
},
Object {
"field": "http.response.status_code",
"name": "Response code",
},
]
}
executeQueryOptions={Object {}}
items={
Array [
Object {
"error": Object {
"message": "dial tcp 127.0.0.1:9200: connect: connection refused",
"type": "io",
},
"http": null,
"monitor": Object {
"duration": Object {
"us": 1430,
},
"id": "auto-tcp-0X81440A68E839814C",
"ip": "127.0.0.1",
"name": "",
"scheme": null,
"status": "down",
"type": "tcp",
},
"timestamp": "2019-01-28T17:47:08.078Z",
},
Object {
"error": Object {
"message": "dial tcp 127.0.0.1:9200: connect: connection refused",
"type": "io",
},
"http": null,
"monitor": Object {
"duration": Object {
"us": 1370,
},
"id": "auto-tcp-0X81440A68E839814C",
"ip": "127.0.0.1",
"name": "",
"scheme": null,
"status": "down",
"type": "tcp",
},
"timestamp": "2019-01-28T17:47:09.075Z",
},
Object {
"error": null,
"http": null,
"monitor": Object {
"duration": Object {
"us": 1452,
},
"id": "auto-tcp-0X81440A68E839814C",
"ip": "127.0.0.1",
"name": "",
"scheme": null,
"status": "up",
"type": "tcp",
},
"timestamp": "2019-01-28T17:47:06.077Z",
},
Object {
"error": Object {
"message": "dial tcp 127.0.0.1:9200: connect: connection refused",
"type": "io",
},
"http": null,
"monitor": Object {
"duration": Object {
"us": 1094,
},
"id": "auto-tcp-0X81440A68E839814C",
"ip": "127.0.0.1",
"name": "",
"scheme": null,
"status": "down",
"type": "tcp",
},
"timestamp": "2019-01-28T17:47:07.075Z",
},
Object {
"error": Object {
"message": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused",
"type": "io",
},
"http": null,
"monitor": Object {
"duration": Object {
"us": 1597,
},
"id": "auto-http-0X3675F89EF0612091",
"ip": "127.0.0.1",
"name": "",
"scheme": null,
"status": "down",
"type": "http",
},
"timestamp": "2019-01-28T17:47:07.074Z",
},
Object {
"error": Object {
"message": "dial tcp 127.0.0.1:9200: connect: connection refused",
"type": "io",
},
"http": null,
"monitor": Object {
"duration": Object {
"us": 1699,
},
"id": "auto-tcp-0X81440A68E839814C",
"ip": "127.0.0.1",
"name": "",
"scheme": null,
"status": "down",
"type": "tcp",
},
"timestamp": "2019-01-28T17:47:18.080Z",
},
Object {
"error": Object {
"message": "dial tcp 127.0.0.1:9200: connect: connection refused",
"type": "io",
},
"http": null,
"monitor": Object {
"duration": Object {
"us": 5384,
},
"id": "auto-tcp-0X81440A68E839814C",
"ip": "127.0.0.1",
"name": "",
"scheme": null,
"status": "down",
"type": "tcp",
},
"timestamp": "2019-01-28T17:47:19.076Z",
},
Object {
"error": Object {
"message": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused",
"type": "io",
},
"http": null,
"monitor": Object {
"duration": Object {
"us": 5397,
},
"id": "auto-http-0X3675F89EF0612091",
"ip": "127.0.0.1",
"name": "",
"scheme": null,
"status": "down",
"type": "http",
},
"timestamp": "2019-01-28T17:47:19.076Z",
},
Object {
"error": null,
"http": Object {
"response": Object {
"status_code": 200,
},
},
"monitor": Object {
"duration": Object {
"us": 127511,
},
"id": "auto-http-0X131221E73F825974",
"ip": "172.217.7.4",
"name": "",
"scheme": null,
"status": "up",
"type": "http",
},
"timestamp": "2019-01-28T17:47:19.077Z",
},
Object {
"error": null,
"http": Object {
"response": Object {
"status_code": 200,
},
},
"monitor": Object {
"duration": Object {
"us": 287543,
},
"id": "auto-http-0X9CB71300ABD5A2A8",
"ip": "192.30.253.112",
"name": "",
"scheme": null,
"status": "up",
"type": "http",
},
"timestamp": "2019-01-28T17:47:19.077Z",
},
]
}
loading={false}
pagination={
Object {
"initialPageSize": 10,
"pageSizeOptions": Array [
5,
10,
20,
100,
],
}
}
responsive={true}
sorting={true}
/>
</EuiPanel>
</Fragment>
`;

View file

@ -0,0 +1,147 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SnapshotHistogram component renders the component without errors 1`] = `
<FlexibleEuiSeriesChart
height={107}
stackBy="y"
width={600}
xType="time"
>
<EuiHistogramSeries
color="#FEFEFE"
data={
Array [
Object {
"x": 1548698820000,
"x0": 1548698520000,
"y": 7,
},
Object {
"x": 1548699120000,
"x0": 1548698820000,
"y": 8,
},
Object {
"x": 1548699420000,
"x0": 1548699120000,
"y": 8,
},
Object {
"x": 1548699720000,
"x0": 1548699420000,
"y": 8,
},
Object {
"x": 1548700020000,
"x0": 1548699720000,
"y": 8,
},
Object {
"x": 1548700320000,
"x0": 1548700020000,
"y": 8,
},
Object {
"x": 1548700620000,
"x0": 1548700320000,
"y": 8,
},
Object {
"x": 1548700920000,
"x0": 1548700620000,
"y": 8,
},
Object {
"x": 1548701220000,
"x0": 1548700920000,
"y": 8,
},
Object {
"x": 1548697920000,
"x0": 1548697620000,
"y": 7,
},
Object {
"x": 1548698220000,
"x0": 1548697920000,
"y": 7,
},
Object {
"x": 1548698520000,
"x0": 1548698220000,
"y": 7,
},
]
}
name="Up"
/>
<EuiHistogramSeries
color="#FF00FF"
data={
Array [
Object {
"x": 1548697920000,
"x0": 1548697620000,
"y": 3,
},
Object {
"x": 1548698220000,
"x0": 1548697920000,
"y": 3,
},
Object {
"x": 1548698520000,
"x0": 1548698220000,
"y": 3,
},
Object {
"x": 1548698820000,
"x0": 1548698520000,
"y": 3,
},
Object {
"x": 1548699120000,
"x0": 1548698820000,
"y": 2,
},
Object {
"x": 1548699420000,
"x0": 1548699120000,
"y": 2,
},
Object {
"x": 1548699720000,
"x0": 1548699420000,
"y": 2,
},
Object {
"x": 1548700020000,
"x0": 1548699720000,
"y": 2,
},
Object {
"x": 1548700320000,
"x0": 1548700020000,
"y": 2,
},
Object {
"x": 1548700620000,
"x0": 1548700320000,
"y": 2,
},
Object {
"x": 1548700920000,
"x0": 1548700620000,
"y": 2,
},
Object {
"x": 1548701220000,
"x0": 1548700920000,
"y": 2,
},
]
}
name="Down"
/>
</FlexibleEuiSeriesChart>
`;

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 React from 'react';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { EmptyStatusBar } from '../empty_status_bar';
describe('EmptyStatusBar component', () => {
it('renders a message when provided', () => {
const component = shallowWithIntl(<EmptyStatusBar message="foobarbaz" monitorId="mon_id" />);
expect(component).toMatchSnapshot();
});
it('renders a default message when no message provided', () => {
const component = shallowWithIntl(<EmptyStatusBar monitorId="mon_id" />);
expect(component).toMatchSnapshot();
});
});

View file

@ -0,0 +1,59 @@
/*
* 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 { ErrorListItem } from 'x-pack/plugins/uptime/common/graphql/types';
import { ErrorList } from '../error_list';
describe('ErrorList component', () => {
let getErrorListResponse: { errorList: ErrorListItem[] };
beforeEach(() => {
getErrorListResponse = {
errorList: [
{
latestMessage:
'Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused',
monitorId: 'auto-http-0X3675F89EF0612091',
type: 'io',
count: 843,
statusCode: null,
timestamp: '2019-01-28T18:43:15.077Z',
},
{
latestMessage: 'dial tcp 127.0.0.1:9200: connect: connection refused',
monitorId: 'auto-tcp-0X81440A68E839814C',
type: 'io',
count: 748,
statusCode: null,
timestamp: '2019-01-28T17:59:34.075Z',
},
{
latestMessage: 'lookup www.reddit.com: no such host',
monitorId: 'auto-http-0XD9AE729FC1C1E04A',
type: 'io',
count: 1,
statusCode: null,
timestamp: '2019-01-28T18:03:10.077Z',
},
{
latestMessage: 'received status code 301 expecting 200',
monitorId: 'auto-http-0XA8096548ECEB85B7',
type: 'validate',
count: 645,
statusCode: '301',
timestamp: '2019-01-28T18:43:07.078Z',
},
],
};
});
it('renders the error list without errors', () => {
const { errorList } = getErrorListResponse;
const component = shallowWithIntl(<ErrorList loading={false} errorList={errorList} />);
expect(component).toMatchSnapshot();
});
});

View file

@ -0,0 +1,38 @@
/*
* 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 { FilterBar } from '../filter_bar';
describe('FilterBar component', () => {
const data = {
filterBar: {
ports: [9200, 12349],
ids: [
{ key: 'auto-tcp-0X81440A68E839814C', url: 'tcp://localhost:9200' },
{ key: 'auto-http-0X3675F89EF0612091', url: 'http://localhost:12349/' },
{ key: 'auto-http-0X970CBD2F2102BFA8', url: 'http://www.google.com/' },
{ key: 'auto-http-0X131221E73F825974', url: 'https://www.google.com/' },
{ key: 'auto-http-0X9CB71300ABD5A2A8', url: 'https://www.github.com/' },
{ key: 'auto-http-0XD9AE729FC1C1E04A', url: 'http://www.reddit.com/' },
{ key: 'auto-http-0XDD2D4E60FD4A61C3', url: 'https://www.elastic.co' },
{ key: 'auto-http-0XA8096548ECEB85B7', url: 'http://www.example.com/' },
{ key: 'auto-http-0XC9CDA429418EDC2B', url: 'https://www.wikipedia.org/' },
{ key: 'auto-http-0XE3B163481423197D', url: 'https://news.google.com/' },
],
names: [],
schemes: ['tcp', 'http'],
},
};
it('renders the component without errors', () => {
const component = shallowWithIntl(
<FilterBar filterBar={data.filterBar} updateQuery={jest.fn()} />
);
expect(component).toMatchSnapshot();
});
});

View file

@ -0,0 +1,454 @@
/*
* 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 { LatestMonitorsResult } from '../../../../common/graphql/types';
import { MonitorList } from '../monitor_list';
describe('MonitorList component', () => {
let monitorResult: LatestMonitorsResult;
beforeEach(() => {
monitorResult = {
monitors: [
{
id: { key: 'auto-http-0X131221E73F825974', url: 'https://www.google.com/' },
ping: {
timestamp: '2019-01-28T18:43:15.077Z',
monitor: {
duration: { us: 132169 },
id: 'auto-http-0X131221E73F825974',
ip: '172.217.12.132',
name: '',
status: 'up',
},
url: { full: 'https://www.google.com/' },
},
upSeries: [
{ x: 1548697620000, y: 74 },
{ x: 1548697920000, y: 75 },
{ x: 1548698220000, y: 75 },
{ x: 1548698520000, y: 73 },
{ x: 1548698820000, y: 75 },
{ x: 1548699120000, y: 74 },
{ x: 1548699420000, y: 75 },
{ x: 1548699720000, y: 75 },
{ x: 1548700020000, y: 75 },
{ x: 1548700320000, y: 75 },
{ x: 1548700620000, y: 75 },
{ x: 1548700920000, y: 19 },
],
downSeries: [
{ x: 1548697620000, y: null },
{ x: 1548697920000, y: null },
{ x: 1548698220000, y: null },
{ x: 1548698520000, y: null },
{ x: 1548698820000, y: null },
{ x: 1548699120000, y: null },
{ x: 1548699420000, y: null },
{ x: 1548699720000, y: null },
{ x: 1548700020000, y: null },
{ x: 1548700320000, y: null },
{ x: 1548700620000, y: null },
{ x: 1548700920000, y: null },
],
},
{
id: { key: 'auto-http-0X3675F89EF0612091', url: 'http://localhost:12349/' },
ping: {
timestamp: '2019-01-28T18:43:15.077Z',
monitor: {
duration: { us: 3331 },
id: 'auto-http-0X3675F89EF0612091',
ip: '127.0.0.1',
name: '',
status: 'down',
},
url: { full: 'http://localhost:12349/' },
},
upSeries: [
{ x: 1548697620000, y: null },
{ x: 1548697920000, y: null },
{ x: 1548698220000, y: null },
{ x: 1548698520000, y: null },
{ x: 1548698820000, y: null },
{ x: 1548699120000, y: null },
{ x: 1548699420000, y: null },
{ x: 1548699720000, y: null },
{ x: 1548700020000, y: null },
{ x: 1548700320000, y: null },
{ x: 1548700620000, y: null },
{ x: 1548700920000, y: null },
],
downSeries: [
{ x: 1548697620000, y: 74 },
{ x: 1548697920000, y: 75 },
{ x: 1548698220000, y: 75 },
{ x: 1548698520000, y: 75 },
{ x: 1548698820000, y: 75 },
{ x: 1548699120000, y: 75 },
{ x: 1548699420000, y: 75 },
{ x: 1548699720000, y: 75 },
{ x: 1548700020000, y: 75 },
{ x: 1548700320000, y: 75 },
{ x: 1548700620000, y: 75 },
{ x: 1548700920000, y: 19 },
],
},
{
id: { key: 'auto-http-0X970CBD2F2102BFA8', url: 'http://www.google.com/' },
ping: {
timestamp: '2019-01-28T18:43:15.077Z',
monitor: {
duration: { us: 118727 },
id: 'auto-http-0X970CBD2F2102BFA8',
ip: '172.217.12.132',
name: '',
status: 'up',
},
url: { full: 'http://www.google.com/' },
},
upSeries: [
{ x: 1548697620000, y: 58 },
{ x: 1548697920000, y: 60 },
{ x: 1548698220000, y: 60 },
{ x: 1548698520000, y: 60 },
{ x: 1548698820000, y: 60 },
{ x: 1548699120000, y: 60 },
{ x: 1548699420000, y: 60 },
{ x: 1548699720000, y: 60 },
{ x: 1548700020000, y: 60 },
{ x: 1548700320000, y: 60 },
{ x: 1548700620000, y: 60 },
{ x: 1548700920000, y: 16 },
],
downSeries: [
{ x: 1548697620000, y: null },
{ x: 1548697920000, y: null },
{ x: 1548698220000, y: null },
{ x: 1548698520000, y: null },
{ x: 1548698820000, y: null },
{ x: 1548699120000, y: null },
{ x: 1548699420000, y: null },
{ x: 1548699720000, y: null },
{ x: 1548700020000, y: null },
{ x: 1548700320000, y: null },
{ x: 1548700620000, y: null },
{ x: 1548700920000, y: null },
],
},
{
id: { key: 'auto-http-0X9CB71300ABD5A2A8', url: 'https://www.github.com/' },
ping: {
timestamp: '2019-01-28T18:43:15.077Z',
monitor: {
duration: { us: 247244 },
id: 'auto-http-0X9CB71300ABD5A2A8',
ip: '192.30.253.112',
name: '',
status: 'up',
},
url: { full: 'https://www.github.com/' },
},
upSeries: [
{ x: 1548697620000, y: 69 },
{ x: 1548697920000, y: 70 },
{ x: 1548698220000, y: 68 },
{ x: 1548698520000, y: 69 },
{ x: 1548698820000, y: 69 },
{ x: 1548699120000, y: 69 },
{ x: 1548699420000, y: 70 },
{ x: 1548699720000, y: 70 },
{ x: 1548700020000, y: 70 },
{ x: 1548700320000, y: 69 },
{ x: 1548700620000, y: 70 },
{ x: 1548700920000, y: 18 },
],
downSeries: [
{ x: 1548697620000, y: null },
{ x: 1548697920000, y: null },
{ x: 1548698220000, y: null },
{ x: 1548698520000, y: null },
{ x: 1548698820000, y: null },
{ x: 1548699120000, y: null },
{ x: 1548699420000, y: null },
{ x: 1548699720000, y: null },
{ x: 1548700020000, y: null },
{ x: 1548700320000, y: null },
{ x: 1548700620000, y: null },
{ x: 1548700920000, y: null },
],
},
{
id: { key: 'auto-http-0XA8096548ECEB85B7', url: 'http://www.example.com/' },
ping: {
timestamp: '2019-01-28T18:43:07.078Z',
monitor: {
duration: { us: 4751074 },
id: 'auto-http-0XA8096548ECEB85B7',
ip: '198.71.248.67',
name: '',
status: 'down',
},
url: { full: 'http://www.example.com/' },
},
upSeries: [
{ x: 1548697620000, y: null },
{ x: 1548697920000, y: null },
{ x: 1548698220000, y: null },
{ x: 1548698520000, y: null },
{ x: 1548698820000, y: null },
{ x: 1548699120000, y: null },
{ x: 1548699420000, y: null },
{ x: 1548699720000, y: null },
{ x: 1548700020000, y: null },
{ x: 1548700320000, y: null },
{ x: 1548700620000, y: null },
{ x: 1548700920000, y: null },
],
downSeries: [
{ x: 1548697620000, y: 57 },
{ x: 1548697920000, y: 60 },
{ x: 1548698220000, y: 61 },
{ x: 1548698520000, y: 56 },
{ x: 1548698820000, y: 45 },
{ x: 1548699120000, y: 49 },
{ x: 1548699420000, y: 60 },
{ x: 1548699720000, y: 60 },
{ x: 1548700020000, y: 64 },
{ x: 1548700320000, y: 59 },
{ x: 1548700620000, y: 60 },
{ x: 1548700920000, y: 14 },
],
},
{
id: { key: 'auto-http-0XC9CDA429418EDC2B', url: 'https://www.wikipedia.org/' },
ping: {
timestamp: '2019-01-28T18:42:55.074Z',
monitor: {
duration: { us: 1164812 },
id: 'auto-http-0XC9CDA429418EDC2B',
ip: '208.80.154.224',
name: '',
status: 'up',
},
url: { full: 'https://www.wikipedia.org/' },
},
upSeries: [
{ x: 1548697620000, y: 5 },
{ x: 1548697920000, y: 5 },
{ x: 1548698220000, y: 5 },
{ x: 1548698520000, y: 5 },
{ x: 1548698820000, y: 5 },
{ x: 1548699120000, y: 5 },
{ x: 1548699420000, y: 5 },
{ x: 1548699720000, y: 5 },
{ x: 1548700020000, y: 5 },
{ x: 1548700320000, y: 5 },
{ x: 1548700620000, y: 5 },
{ x: 1548700920000, y: 1 },
],
downSeries: [
{ x: 1548697620000, y: null },
{ x: 1548697920000, y: null },
{ x: 1548698220000, y: null },
{ x: 1548698520000, y: null },
{ x: 1548698820000, y: null },
{ x: 1548699120000, y: null },
{ x: 1548699420000, y: null },
{ x: 1548699720000, y: null },
{ x: 1548700020000, y: null },
{ x: 1548700320000, y: null },
{ x: 1548700620000, y: null },
{ x: 1548700920000, y: null },
],
},
{
id: { key: 'auto-http-0XD9AE729FC1C1E04A', url: 'http://www.reddit.com/' },
ping: {
timestamp: '2019-01-28T18:43:13.074Z',
monitor: {
duration: { us: 299586 },
id: 'auto-http-0XD9AE729FC1C1E04A',
ip: '151.101.249.140',
name: '',
status: 'up',
},
url: { full: 'http://www.reddit.com/' },
},
upSeries: [
{ x: 1548697620000, y: 79 },
{ x: 1548697920000, y: 80 },
{ x: 1548698220000, y: 86 },
{ x: 1548698520000, y: 87 },
{ x: 1548698820000, y: 81 },
{ x: 1548699120000, y: 100 },
{ x: 1548699420000, y: 100 },
{ x: 1548699720000, y: 99 },
{ x: 1548700020000, y: 96 },
{ x: 1548700320000, y: 81 },
{ x: 1548700620000, y: 80 },
{ x: 1548700920000, y: 20 },
],
downSeries: [
{ x: 1548697620000, y: null },
{ x: 1548697920000, y: null },
{ x: 1548698220000, y: null },
{ x: 1548698520000, y: 1 },
{ x: 1548698820000, y: null },
{ x: 1548699120000, y: null },
{ x: 1548699420000, y: null },
{ x: 1548699720000, y: null },
{ x: 1548700020000, y: null },
{ x: 1548700320000, y: null },
{ x: 1548700620000, y: null },
{ x: 1548700920000, y: null },
],
},
{
id: { key: 'auto-http-0XDD2D4E60FD4A61C3', url: 'https://www.elastic.co' },
ping: {
timestamp: '2019-01-28T18:43:13.074Z',
monitor: {
duration: { us: 850870 },
id: 'auto-http-0XDD2D4E60FD4A61C3',
ip: '151.101.250.217',
name: '',
status: 'up',
},
url: { full: 'https://www.elastic.co' },
},
upSeries: [
{ x: 1548697620000, y: 79 },
{ x: 1548697920000, y: 80 },
{ x: 1548698220000, y: 86 },
{ x: 1548698520000, y: 88 },
{ x: 1548698820000, y: 81 },
{ x: 1548699120000, y: 95 },
{ x: 1548699420000, y: 94 },
{ x: 1548699720000, y: 98 },
{ x: 1548700020000, y: 93 },
{ x: 1548700320000, y: 81 },
{ x: 1548700620000, y: 80 },
{ x: 1548700920000, y: 20 },
],
downSeries: [
{ x: 1548697620000, y: null },
{ x: 1548697920000, y: null },
{ x: 1548698220000, y: null },
{ x: 1548698520000, y: null },
{ x: 1548698820000, y: null },
{ x: 1548699120000, y: null },
{ x: 1548699420000, y: null },
{ x: 1548699720000, y: null },
{ x: 1548700020000, y: null },
{ x: 1548700320000, y: null },
{ x: 1548700620000, y: null },
{ x: 1548700920000, y: null },
],
},
{
id: { key: 'auto-http-0XE3B163481423197D', url: 'https://news.google.com/' },
ping: {
timestamp: '2019-01-28T18:42:55.074Z',
monitor: {
duration: { us: 2059606 },
id: 'auto-http-0XE3B163481423197D',
ip: '216.58.219.206',
name: '',
status: 'up',
},
url: { full: 'https://news.google.com/' },
},
upSeries: [
{ x: 1548697620000, y: 5 },
{ x: 1548697920000, y: 5 },
{ x: 1548698220000, y: 5 },
{ x: 1548698520000, y: 5 },
{ x: 1548698820000, y: 5 },
{ x: 1548699120000, y: 5 },
{ x: 1548699420000, y: 5 },
{ x: 1548699720000, y: 5 },
{ x: 1548700020000, y: 5 },
{ x: 1548700320000, y: 5 },
{ x: 1548700620000, y: 5 },
{ x: 1548700920000, y: 1 },
],
downSeries: [
{ x: 1548697620000, y: null },
{ x: 1548697920000, y: null },
{ x: 1548698220000, y: null },
{ x: 1548698520000, y: null },
{ x: 1548698820000, y: null },
{ x: 1548699120000, y: null },
{ x: 1548699420000, y: null },
{ x: 1548699720000, y: null },
{ x: 1548700020000, y: null },
{ x: 1548700320000, y: null },
{ x: 1548700620000, y: null },
{ x: 1548700920000, y: null },
],
},
{
id: { key: 'auto-tcp-0X81440A68E839814C', url: 'tcp://localhost:9200' },
ping: {
timestamp: '2019-01-28T18:43:16.078Z',
monitor: {
duration: { us: 3328 },
id: 'auto-tcp-0X81440A68E839814C',
ip: '127.0.0.1',
name: '',
status: 'up',
},
url: { full: 'tcp://localhost:9200' },
},
upSeries: [
{ x: 1548697620000, y: 1 },
{ x: 1548697920000, y: null },
{ x: 1548698220000, y: 145 },
{ x: 1548698520000, y: 300 },
{ x: 1548698820000, y: 300 },
{ x: 1548699120000, y: 300 },
{ x: 1548699420000, y: 300 },
{ x: 1548699720000, y: 300 },
{ x: 1548700020000, y: 300 },
{ x: 1548700320000, y: 300 },
{ x: 1548700620000, y: 300 },
{ x: 1548700920000, y: 77 },
],
downSeries: [
{ x: 1548697620000, y: 293 },
{ x: 1548697920000, y: 300 },
{ x: 1548698220000, y: 155 },
{ x: 1548698520000, y: null },
{ x: 1548698820000, y: null },
{ x: 1548699120000, y: null },
{ x: 1548699420000, y: null },
{ x: 1548699720000, y: null },
{ x: 1548700020000, y: null },
{ x: 1548700320000, y: null },
{ x: 1548700620000, y: null },
{ x: 1548700920000, y: null },
],
},
],
};
});
it('renders a monitor list without errors', () => {
const { monitors } = monitorResult;
const component = shallowWithIntl(
<MonitorList
primaryColor="green"
dangerColor="red"
loading={false}
monitors={monitors || []}
/>
);
expect(component).toMatchSnapshot();
});
});

View file

@ -0,0 +1,203 @@
/*
* 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 { PingResults } from '../../../../common/graphql/types';
import { PingList } from '../ping_list';
describe('PingList component', () => {
let pingList: { allPings: PingResults };
beforeEach(() => {
pingList = {
allPings: {
total: 9231,
pings: [
{
timestamp: '2019-01-28T17:47:08.078Z',
http: null,
error: {
message: 'dial tcp 127.0.0.1:9200: connect: connection refused',
type: 'io',
},
monitor: {
duration: { us: 1430 },
id: 'auto-tcp-0X81440A68E839814C',
ip: '127.0.0.1',
name: '',
scheme: null,
status: 'down',
type: 'tcp',
},
},
{
timestamp: '2019-01-28T17:47:09.075Z',
http: null,
error: {
message: 'dial tcp 127.0.0.1:9200: connect: connection refused',
type: 'io',
},
monitor: {
duration: { us: 1370 },
id: 'auto-tcp-0X81440A68E839814C',
ip: '127.0.0.1',
name: '',
scheme: null,
status: 'down',
type: 'tcp',
},
},
{
timestamp: '2019-01-28T17:47:06.077Z',
http: null,
error: null,
monitor: {
duration: { us: 1452 },
id: 'auto-tcp-0X81440A68E839814C',
ip: '127.0.0.1',
name: '',
scheme: null,
status: 'up',
type: 'tcp',
},
},
{
timestamp: '2019-01-28T17:47:07.075Z',
http: null,
error: {
message: 'dial tcp 127.0.0.1:9200: connect: connection refused',
type: 'io',
},
monitor: {
duration: { us: 1094 },
id: 'auto-tcp-0X81440A68E839814C',
ip: '127.0.0.1',
name: '',
scheme: null,
status: 'down',
type: 'tcp',
},
},
{
timestamp: '2019-01-28T17:47:07.074Z',
http: null,
error: {
message:
'Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused',
type: 'io',
},
monitor: {
duration: { us: 1597 },
id: 'auto-http-0X3675F89EF0612091',
ip: '127.0.0.1',
name: '',
scheme: null,
status: 'down',
type: 'http',
},
},
{
timestamp: '2019-01-28T17:47:18.080Z',
http: null,
error: {
message: 'dial tcp 127.0.0.1:9200: connect: connection refused',
type: 'io',
},
monitor: {
duration: { us: 1699 },
id: 'auto-tcp-0X81440A68E839814C',
ip: '127.0.0.1',
name: '',
scheme: null,
status: 'down',
type: 'tcp',
},
},
{
timestamp: '2019-01-28T17:47:19.076Z',
http: null,
error: {
message: 'dial tcp 127.0.0.1:9200: connect: connection refused',
type: 'io',
},
monitor: {
duration: { us: 5384 },
id: 'auto-tcp-0X81440A68E839814C',
ip: '127.0.0.1',
name: '',
scheme: null,
status: 'down',
type: 'tcp',
},
},
{
timestamp: '2019-01-28T17:47:19.076Z',
http: null,
error: {
message:
'Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused',
type: 'io',
},
monitor: {
duration: { us: 5397 },
id: 'auto-http-0X3675F89EF0612091',
ip: '127.0.0.1',
name: '',
scheme: null,
status: 'down',
type: 'http',
},
},
{
timestamp: '2019-01-28T17:47:19.077Z',
http: { response: { status_code: 200 } },
error: null,
monitor: {
duration: { us: 127511 },
id: 'auto-http-0X131221E73F825974',
ip: '172.217.7.4',
name: '',
scheme: null,
status: 'up',
type: 'http',
},
},
{
timestamp: '2019-01-28T17:47:19.077Z',
http: { response: { status_code: 200 } },
error: null,
monitor: {
duration: { us: 287543 },
id: 'auto-http-0X9CB71300ABD5A2A8',
ip: '192.30.253.112',
name: '',
scheme: null,
status: 'up',
type: 'http',
},
},
],
},
};
});
it('renders sorted list without errors', () => {
const { allPings } = pingList;
const component = shallowWithIntl(
<PingList
loading={false}
maxSearchSize={200}
pingResults={allPings}
searchSizeOnBlur={jest.fn()}
selectedOption={{ label: 'All', value: '' }}
selectedOptionChanged={jest.fn()}
statusOptions={[{ label: 'All', value: '' }]}
/>
);
expect(component).toMatchSnapshot();
});
});

View file

@ -0,0 +1,199 @@
/*
* 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 { Snapshot } from '../snapshot';
describe('Snapshot component', () => {
const data = {
snapshot: {
up: 8,
down: 2,
total: 10,
histogram: [
{
monitorId: 'auto-tcp-0X81440A68E839814C',
data: [
{ upCount: 1, downCount: 293, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: null, downCount: 300, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 145, downCount: 155, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 300, downCount: null, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 300, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 300, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 300, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 300, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 300, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 300, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 300, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 77, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0XD9AE729FC1C1E04A',
data: [
{ upCount: 79, downCount: null, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: 80, downCount: null, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 86, downCount: null, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 87, downCount: 1, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 81, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 100, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 100, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 99, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 96, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 81, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 80, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 20, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0XDD2D4E60FD4A61C3',
data: [
{ upCount: 79, downCount: null, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: 80, downCount: null, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 86, downCount: null, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 88, downCount: null, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 81, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 95, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 94, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 98, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 93, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 81, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 80, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 20, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0X131221E73F825974',
data: [
{ upCount: 74, downCount: null, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: 75, downCount: null, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 75, downCount: null, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 73, downCount: null, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 75, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 74, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 75, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 75, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 75, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 75, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 75, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 19, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0X3675F89EF0612091',
data: [
{ upCount: null, downCount: 74, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: null, downCount: 75, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: null, downCount: 75, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: null, downCount: 75, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: null, downCount: 75, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: null, downCount: 75, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: null, downCount: 75, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: null, downCount: 75, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: null, downCount: 75, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: null, downCount: 75, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: null, downCount: 75, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: null, downCount: 19, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0X9CB71300ABD5A2A8',
data: [
{ upCount: 69, downCount: null, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: 70, downCount: null, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 68, downCount: null, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 69, downCount: null, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 69, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 69, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 70, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 70, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 70, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 69, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 70, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 18, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0X970CBD2F2102BFA8',
data: [
{ upCount: 58, downCount: null, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: 60, downCount: null, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 60, downCount: null, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 60, downCount: null, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 60, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 60, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 60, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 60, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 60, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 60, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 60, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 16, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0XA8096548ECEB85B7',
data: [
{ upCount: null, downCount: 57, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: null, downCount: 60, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: null, downCount: 61, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: null, downCount: 56, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: null, downCount: 45, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: null, downCount: 49, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: null, downCount: 60, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: null, downCount: 60, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: null, downCount: 64, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: null, downCount: 59, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: null, downCount: 60, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: null, downCount: 14, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0XC9CDA429418EDC2B',
data: [
{ upCount: 5, downCount: null, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: 5, downCount: null, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 5, downCount: null, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 5, downCount: null, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 5, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 5, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 5, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 1, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0XE3B163481423197D',
data: [
{ upCount: 5, downCount: null, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: 5, downCount: null, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 5, downCount: null, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 5, downCount: null, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 5, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 5, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 5, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 1, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
],
},
};
it('renders without errors', () => {
const { snapshot } = data;
const wrapper = shallowWithIntl(
<Snapshot dangerColor="#F050F0" primaryColor="#000000" snapshot={snapshot} />
);
expect(wrapper).toMatchSnapshot();
});
});

View file

@ -0,0 +1,193 @@
/*
* 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 { SnapshotHistogram } from '../snapshot_histogram';
describe('SnapshotHistogram component', () => {
const props = {
primaryColor: '#FEFEFE',
dangerColor: '#FF00FF',
histogram: [
{
monitorId: 'auto-tcp-0X81440A68E839814C',
data: [
{ upCount: 1, downCount: 293, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: null, downCount: 300, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 145, downCount: 155, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 300, downCount: null, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 300, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 300, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 300, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 300, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 300, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 300, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 300, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 77, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0XD9AE729FC1C1E04A',
data: [
{ upCount: 79, downCount: null, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: 80, downCount: null, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 86, downCount: null, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 87, downCount: 1, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 81, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 100, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 100, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 99, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 96, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 81, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 80, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 20, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0XDD2D4E60FD4A61C3',
data: [
{ upCount: 79, downCount: null, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: 80, downCount: null, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 86, downCount: null, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 88, downCount: null, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 81, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 95, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 94, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 98, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 93, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 81, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 80, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 20, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0X131221E73F825974',
data: [
{ upCount: 74, downCount: null, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: 75, downCount: null, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 75, downCount: null, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 73, downCount: null, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 75, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 74, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 75, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 75, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 75, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 75, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 75, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 19, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0X3675F89EF0612091',
data: [
{ upCount: null, downCount: 74, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: null, downCount: 75, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: null, downCount: 75, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: null, downCount: 75, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: null, downCount: 75, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: null, downCount: 75, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: null, downCount: 75, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: null, downCount: 75, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: null, downCount: 75, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: null, downCount: 75, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: null, downCount: 75, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: null, downCount: 19, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0X9CB71300ABD5A2A8',
data: [
{ upCount: 69, downCount: null, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: 70, downCount: null, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 68, downCount: null, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 69, downCount: null, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 69, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 69, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 70, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 70, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 70, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 69, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 70, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 18, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0X970CBD2F2102BFA8',
data: [
{ upCount: 58, downCount: null, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: 60, downCount: null, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 60, downCount: null, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 60, downCount: null, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 60, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 60, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 60, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 60, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 60, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 60, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 60, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 16, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0XA8096548ECEB85B7',
data: [
{ upCount: null, downCount: 57, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: null, downCount: 60, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: null, downCount: 61, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: null, downCount: 56, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: null, downCount: 45, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: null, downCount: 49, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: null, downCount: 60, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: null, downCount: 60, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: null, downCount: 64, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: null, downCount: 59, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: null, downCount: 60, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: null, downCount: 14, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0XC9CDA429418EDC2B',
data: [
{ upCount: 5, downCount: null, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: 5, downCount: null, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 5, downCount: null, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 5, downCount: null, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 5, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 5, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 5, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 1, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
{
monitorId: 'auto-http-0XE3B163481423197D',
data: [
{ upCount: 5, downCount: null, x: 1548697920000, x0: 1548697620000, y: 1 },
{ upCount: 5, downCount: null, x: 1548698220000, x0: 1548697920000, y: 1 },
{ upCount: 5, downCount: null, x: 1548698520000, x0: 1548698220000, y: 1 },
{ upCount: 5, downCount: null, x: 1548698820000, x0: 1548698520000, y: 1 },
{ upCount: 5, downCount: null, x: 1548699120000, x0: 1548698820000, y: 1 },
{ upCount: 5, downCount: null, x: 1548699420000, x0: 1548699120000, y: 1 },
{ upCount: 5, downCount: null, x: 1548699720000, x0: 1548699420000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700020000, x0: 1548699720000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700320000, x0: 1548700020000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700620000, x0: 1548700320000, y: 1 },
{ upCount: 5, downCount: null, x: 1548700920000, x0: 1548700620000, y: 1 },
{ upCount: 1, downCount: null, x: 1548701220000, x0: 1548700920000, y: 1 },
],
},
],
};
it('renders the component without errors', () => {
const component = shallowWithIntl(<SnapshotHistogram {...props} />);
expect(component).toMatchSnapshot();
});
});

View file

@ -0,0 +1,686 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EmptyState component doesn't render child components when count is falsey 1`] = `
<Component
intl={
Object {
"defaultFormats": Object {},
"defaultLocale": "en",
"formatDate": [Function],
"formatHTMLMessage": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatPlural": [Function],
"formatRelative": [Function],
"formatTime": [Function],
"formats": Object {
"date": Object {
"full": Object {
"day": "numeric",
"month": "long",
"weekday": "long",
"year": "numeric",
},
"long": Object {
"day": "numeric",
"month": "long",
"year": "numeric",
},
"medium": Object {
"day": "numeric",
"month": "short",
"year": "numeric",
},
"short": Object {
"day": "numeric",
"month": "numeric",
"year": "2-digit",
},
},
"number": Object {
"currency": Object {
"style": "currency",
},
"percent": Object {
"style": "percent",
},
},
"relative": Object {
"days": Object {
"units": "day",
},
"hours": Object {
"units": "hour",
},
"minutes": Object {
"units": "minute",
},
"months": Object {
"units": "month",
},
"seconds": Object {
"units": "second",
},
"years": Object {
"units": "year",
},
},
"time": Object {
"full": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"long": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"medium": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
},
"short": Object {
"hour": "numeric",
"minute": "numeric",
},
},
},
"formatters": Object {
"getDateTimeFormat": [Function],
"getMessageFormat": [Function],
"getNumberFormat": [Function],
"getPluralFormat": [Function],
"getRelativeFormat": [Function],
},
"locale": "en",
"messages": Object {},
"now": [Function],
"onError": [Function],
"textComponent": Symbol(react.fragment),
"timeZone": null,
}
}
>
<Component>
<EuiEmptyPrompt
body={
<React.Fragment>
<p>
<FormattedMessage
defaultMessage="There is no uptime data available."
id="xpack.uptime.emptyState.noDataDescription"
values={Object {}}
/>
</p>
<p>
<FormattedMessage
defaultMessage="{configureHeartbeatLink} to start logging uptime data."
id="xpack.uptime.emptyState.configureHeartbeatToGetStartedMessage"
values={
Object {
"configureHeartbeatLink": <EuiLink
color="primary"
href="https://www.elastic.co/guide/en/beats/heartbeat/current/configuring-howto-heartbeat.html"
target="_blank"
type="button"
>
<FormattedMessage
defaultMessage="Configure Heartbeat"
id="xpack.uptime.emptyState.configureHeartbeatLinkText"
values={Object {}}
/>
</EuiLink>,
}
}
/>
</p>
</React.Fragment>
}
iconColor="subdued"
title={
<EuiTitle
size="l"
textTransform="none"
>
<h3>
<FormattedMessage
defaultMessage="No Uptime Data"
id="xpack.uptime.emptyState.noDataTitle"
values={Object {}}
/>
</h3>
</EuiTitle>
}
>
<div
className="euiEmptyPrompt"
>
<EuiTextColor
color="subdued"
component="span"
>
<span
className="euiTextColor euiTextColor--subdued"
>
<EuiTitle
size="m"
textTransform="none"
>
<EuiTitle
className="euiTitle euiTitle--medium"
size="l"
textTransform="none"
>
<h3
className="euiTitle euiTitle--large euiTitle euiTitle--medium"
>
<FormattedMessage
defaultMessage="No Uptime Data"
id="xpack.uptime.emptyState.noDataTitle"
values={Object {}}
>
No Uptime Data
</FormattedMessage>
</h3>
</EuiTitle>
</EuiTitle>
<EuiSpacer
size="m"
>
<div
className="euiSpacer euiSpacer--m"
/>
</EuiSpacer>
<EuiText
grow={true}
size="m"
>
<div
className="euiText euiText--medium"
>
<p>
<FormattedMessage
defaultMessage="There is no uptime data available."
id="xpack.uptime.emptyState.noDataDescription"
values={Object {}}
>
There is no uptime data available.
</FormattedMessage>
</p>
<p>
<FormattedMessage
defaultMessage="{configureHeartbeatLink} to start logging uptime data."
id="xpack.uptime.emptyState.configureHeartbeatToGetStartedMessage"
values={
Object {
"configureHeartbeatLink": <EuiLink
color="primary"
href="https://www.elastic.co/guide/en/beats/heartbeat/current/configuring-howto-heartbeat.html"
target="_blank"
type="button"
>
<FormattedMessage
defaultMessage="Configure Heartbeat"
id="xpack.uptime.emptyState.configureHeartbeatLinkText"
values={Object {}}
/>
</EuiLink>,
}
}
>
<EuiLink
color="primary"
href="https://www.elastic.co/guide/en/beats/heartbeat/current/configuring-howto-heartbeat.html"
target="_blank"
type="button"
>
<a
className="euiLink euiLink--primary"
href="https://www.elastic.co/guide/en/beats/heartbeat/current/configuring-howto-heartbeat.html"
rel="noopener noreferrer"
target="_blank"
>
<FormattedMessage
defaultMessage="Configure Heartbeat"
id="xpack.uptime.emptyState.configureHeartbeatLinkText"
values={Object {}}
>
Configure Heartbeat
</FormattedMessage>
</a>
</EuiLink>
to start logging uptime data.
</FormattedMessage>
</p>
</div>
</EuiText>
</span>
</EuiTextColor>
</div>
</EuiEmptyPrompt>
</Component>
</Component>
`;
exports[`EmptyState component renders child components when count is truthy 1`] = `
<Fragment>
<div>
Foo
</div>
<div>
Bar
</div>
<div>
Baz
</div>
</Fragment>
`;
exports[`EmptyState component renders message while loading 1`] = `
<Component
count={1}
intl={
Object {
"defaultFormats": Object {},
"defaultLocale": "en",
"formatDate": [Function],
"formatHTMLMessage": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatPlural": [Function],
"formatRelative": [Function],
"formatTime": [Function],
"formats": Object {
"date": Object {
"full": Object {
"day": "numeric",
"month": "long",
"weekday": "long",
"year": "numeric",
},
"long": Object {
"day": "numeric",
"month": "long",
"year": "numeric",
},
"medium": Object {
"day": "numeric",
"month": "short",
"year": "numeric",
},
"short": Object {
"day": "numeric",
"month": "numeric",
"year": "2-digit",
},
},
"number": Object {
"currency": Object {
"style": "currency",
},
"percent": Object {
"style": "percent",
},
},
"relative": Object {
"days": Object {
"units": "day",
},
"hours": Object {
"units": "hour",
},
"minutes": Object {
"units": "minute",
},
"months": Object {
"units": "month",
},
"seconds": Object {
"units": "second",
},
"years": Object {
"units": "year",
},
},
"time": Object {
"full": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"long": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"medium": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
},
"short": Object {
"hour": "numeric",
"minute": "numeric",
},
},
},
"formatters": Object {
"getDateTimeFormat": [Function],
"getMessageFormat": [Function],
"getNumberFormat": [Function],
"getPluralFormat": [Function],
"getRelativeFormat": [Function],
},
"locale": "en",
"messages": Object {},
"now": [Function],
"onError": [Function],
"textComponent": Symbol(react.fragment),
"timeZone": null,
}
}
loading={true}
>
<Component>
<EuiEmptyPrompt
iconColor="subdued"
title={
<EuiFlexGroup
alignItems="stretch"
component="div"
direction="row"
gutterSize="l"
justifyContent="flexStart"
responsive={true}
wrap={false}
>
<EuiFlexItem
component="div"
grow={true}
>
<EuiTitle
size="l"
textTransform="none"
>
<h3>
Loading…
</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
>
<EuiLoadingSpinner
size="xl"
/>
</EuiFlexItem>
</EuiFlexGroup>
}
>
<div
className="euiEmptyPrompt"
>
<EuiTextColor
color="subdued"
component="span"
>
<span
className="euiTextColor euiTextColor--subdued"
>
<EuiTitle
size="m"
textTransform="none"
>
<EuiFlexGroup
alignItems="stretch"
className="euiTitle euiTitle--medium"
component="div"
direction="row"
gutterSize="l"
justifyContent="flexStart"
responsive={true}
wrap={false}
>
<div
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive euiTitle euiTitle--medium"
>
<EuiFlexItem
component="div"
grow={true}
>
<div
className="euiFlexItem"
>
<EuiTitle
size="l"
textTransform="none"
>
<h3
className="euiTitle euiTitle--large"
>
Loading…
</h3>
</EuiTitle>
</div>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
>
<div
className="euiFlexItem"
>
<EuiLoadingSpinner
size="xl"
>
<div
className="euiLoadingSpinner euiLoadingSpinner--xLarge"
/>
</EuiLoadingSpinner>
</div>
</EuiFlexItem>
</div>
</EuiFlexGroup>
</EuiTitle>
<EuiSpacer
size="m"
>
<div
className="euiSpacer euiSpacer--m"
/>
</EuiSpacer>
</span>
</EuiTextColor>
</div>
</EuiEmptyPrompt>
</Component>
</Component>
`;
exports[`EmptyState component renders the message when an error occurs 1`] = `
<Component
count={1}
error="An error occurred"
intl={
Object {
"defaultFormats": Object {},
"defaultLocale": "en",
"formatDate": [Function],
"formatHTMLMessage": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatPlural": [Function],
"formatRelative": [Function],
"formatTime": [Function],
"formats": Object {
"date": Object {
"full": Object {
"day": "numeric",
"month": "long",
"weekday": "long",
"year": "numeric",
},
"long": Object {
"day": "numeric",
"month": "long",
"year": "numeric",
},
"medium": Object {
"day": "numeric",
"month": "short",
"year": "numeric",
},
"short": Object {
"day": "numeric",
"month": "numeric",
"year": "2-digit",
},
},
"number": Object {
"currency": Object {
"style": "currency",
},
"percent": Object {
"style": "percent",
},
},
"relative": Object {
"days": Object {
"units": "day",
},
"hours": Object {
"units": "hour",
},
"minutes": Object {
"units": "minute",
},
"months": Object {
"units": "month",
},
"seconds": Object {
"units": "second",
},
"years": Object {
"units": "year",
},
},
"time": Object {
"full": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"long": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"medium": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
},
"short": Object {
"hour": "numeric",
"minute": "numeric",
},
},
},
"formatters": Object {
"getDateTimeFormat": [Function],
"getMessageFormat": [Function],
"getNumberFormat": [Function],
"getPluralFormat": [Function],
"getRelativeFormat": [Function],
},
"locale": "en",
"messages": Object {},
"now": [Function],
"onError": [Function],
"textComponent": Symbol(react.fragment),
"timeZone": null,
}
}
>
<Component
errorMessage="An error occurred"
>
<EuiEmptyPrompt
body={
<p>
An error occurred
</p>
}
iconColor="subdued"
title={
<EuiTitle
size="l"
textTransform="none"
>
<h3>
Error
</h3>
</EuiTitle>
}
>
<div
className="euiEmptyPrompt"
>
<EuiTextColor
color="subdued"
component="span"
>
<span
className="euiTextColor euiTextColor--subdued"
>
<EuiTitle
size="m"
textTransform="none"
>
<EuiTitle
className="euiTitle euiTitle--medium"
size="l"
textTransform="none"
>
<h3
className="euiTitle euiTitle--large euiTitle euiTitle--medium"
>
Error
</h3>
</EuiTitle>
</EuiTitle>
<EuiSpacer
size="m"
>
<div
className="euiSpacer euiSpacer--m"
/>
</EuiSpacer>
<EuiText
grow={true}
size="m"
>
<div
className="euiText euiText--medium"
>
<p>
An error occurred
</p>
</div>
</EuiText>
</span>
</EuiTextColor>
</div>
</EuiEmptyPrompt>
</Component>
</Component>
`;

View file

@ -0,0 +1,46 @@
/*
* 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 { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { EmptyState } from '../empty_state';
describe('EmptyState component', () => {
it('renders child components when count is truthy', () => {
const component = shallowWithIntl(
<EmptyState count={1}>
<div>Foo</div>
<div>Bar</div>
<div>Baz</div>
</EmptyState>
);
expect(component).toMatchSnapshot();
});
it(`doesn't render child components when count is falsey`, () => {
const component = mountWithIntl(
<EmptyState count={undefined}>
<div>Shouldn't be rendered</div>
</EmptyState>
);
expect(component).toMatchSnapshot();
});
it(`renders the message when an error occurs`, () => {
const component = mountWithIntl(
<EmptyState error={'An error occurred'} count={1}>
<div>Shouldn't appear...</div>
</EmptyState>
);
expect(component).toMatchSnapshot();
});
it('renders message while loading', () => {
const component = mountWithIntl(
<EmptyState loading={true} count={1}>
<div>Shouldn't appear...</div>
</EmptyState>
);
expect(component).toMatchSnapshot();
});
});

View file

@ -0,0 +1,53 @@
/*
* 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 { EuiEmptyPrompt, EuiLink, EuiTitle } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Fragment } from 'react';
export const EmptyIndex = (props: any) => (
<EuiEmptyPrompt
title={
<EuiTitle size="l">
<h3>
<FormattedMessage
id="xpack.uptime.emptyState.noDataTitle"
defaultMessage="No Uptime Data"
/>
</h3>
</EuiTitle>
}
body={
<Fragment>
<p>
<FormattedMessage
id="xpack.uptime.emptyState.noDataDescription"
defaultMessage="There is no uptime data available."
/>
</p>
<p>
<FormattedMessage
id="xpack.uptime.emptyState.configureHeartbeatToGetStartedMessage"
defaultMessage="{configureHeartbeatLink} to start logging uptime data."
values={{
configureHeartbeatLink: (
<EuiLink
target="_blank"
href="https://www.elastic.co/guide/en/beats/heartbeat/current/configuring-howto-heartbeat.html"
>
<FormattedMessage
id="xpack.uptime.emptyState.configureHeartbeatLinkText"
defaultMessage="Configure Heartbeat"
/>
</EuiLink>
),
}}
/>
</p>
</Fragment>
}
/>
);

View file

@ -0,0 +1,29 @@
/*
* 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, { Fragment } from 'react';
import { EmptyIndex } from './empty_index';
import { EmptyStateError } from './empty_state_error';
import { EmptyStateLoading } from './empty_state_loading';
interface EmptyStateProps {
children: JSX.Element[] | JSX.Element;
count: number | undefined;
error?: string;
loading?: boolean;
}
export const EmptyState = ({ children, count, error, loading }: EmptyStateProps) => {
if (error) {
return <EmptyStateError errorMessage={error} />;
}
if (loading) {
return <EmptyStateLoading />;
} else if (!count) {
return <EmptyIndex />;
}
return <Fragment>{children}</Fragment>;
};

View file

@ -0,0 +1,28 @@
/*
* 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 { EuiEmptyPrompt, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
interface EmptyStateErrorProps {
errorMessage?: string;
}
export const EmptyStateError = ({ errorMessage }: EmptyStateErrorProps) => (
<EuiEmptyPrompt
title={
<EuiTitle size="l">
<h3>
{i18n.translate('xpack.uptime.emptyStateError.title', {
defaultMessage: 'Error',
})}
</h3>
</EuiTitle>
}
body={<p>{errorMessage ? errorMessage : ''}</p>}
/>
);

View file

@ -0,0 +1,36 @@
/*
* 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 {
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingSpinner,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
export const EmptyStateLoading = (props: any) => (
<EuiEmptyPrompt
title={
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="l">
<h3>
{i18n.translate('xpack.uptime.emptyState.loadingMessage', {
defaultMessage: 'Loading…',
})}
</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem>
<EuiLoadingSpinner size="xl" />
</EuiFlexItem>
</EuiFlexGroup>
}
/>
);

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 { EmptyState } from './empty_state';

View file

@ -5,6 +5,7 @@
*/
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
interface Props {
@ -16,7 +17,14 @@ export const EmptyStatusBar = ({ message, monitorId }: Props) => (
<EuiPanel>
<EuiFlexGroup gutterSize="l">
<EuiFlexItem grow={false}>
{!message ? `No data found for monitor id ${monitorId}` : message}
{!message
? i18n.translate('xpack.uptime.emptyStatusBar.defaultMessage', {
defaultMessage: 'No data found for monitor id {monitorId}',
description:
'This is the default message we display in a status bar when there is no data available for an uptime monitor.',
values: { monitorId },
})
: message}
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>

View file

@ -0,0 +1,85 @@
/*
* 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.
*/
// @ts-ignore missing typings
import { EuiInMemoryTable, EuiPanel, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import moment from 'moment';
import React, { Fragment } from 'react';
import { Link } from 'react-router-dom';
import { ErrorListItem } from '../../../common/graphql/types';
interface ErrorListProps {
loading: boolean;
errorList: ErrorListItem[];
}
export const ErrorList = ({ loading, errorList }: ErrorListProps) => (
<Fragment>
<EuiTitle size="xs">
<h5>
<FormattedMessage id="xpack.uptime.errorList.title" defaultMessage="Error list" />
</h5>
</EuiTitle>
<EuiPanel>
<EuiInMemoryTable
loading={loading}
items={errorList}
columns={[
{
field: 'type',
name: i18n.translate('xpack.uptime.errorList.errorTypeColumnLabel', {
defaultMessage: 'Error type',
}),
sortable: true,
},
{
field: 'monitorId',
name: i18n.translate('xpack.uptime.errorList.monitorIdColumnLabel', {
defaultMessage: 'Monitor ID',
}),
render: (id: string) => <Link to={`/monitor/${id}`}>{id}</Link>,
sortable: true,
width: '25%',
},
{
field: 'count',
name: i18n.translate('xpack.uptime.errorList.CountColumnLabel', {
defaultMessage: 'Count',
}),
sortable: true,
},
{
field: 'timestamp',
name: i18n.translate('xpack.uptime.errorList.latestErrorColumnLabel', {
defaultMessage: 'Latest error',
}),
sortable: true,
render: (timestamp: string) => moment(timestamp).fromNow(),
},
{
field: 'statusCode',
name: i18n.translate('xpack.uptime.errorList.statusCodeColumnLabel', {
defaultMessage: 'Status code',
}),
sortable: true,
},
{
field: 'latestMessage',
name: i18n.translate('xpack.uptime.errorList.latestMessageColumnLabel', {
defaultMessage: 'Latest message',
}),
sortable: true,
width: '40%',
},
]}
sorting={true}
pagination={{ initialPageSize: 10, pageSizeOptions: [5, 10, 20, 50] }}
/>
</EuiPanel>
</Fragment>
);

View file

@ -0,0 +1,136 @@
/*
* 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.
*/
// @ts-ignore No typings for EuiSearchBar
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSearchBar, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { FilterBar as FilterBarType, MonitorKey } from '../../../common/graphql/types';
import { filterBarSearchSchema } from './search_schema';
interface FilterBarProps {
filterBar: FilterBarType;
updateQuery: (query: object | undefined) => void;
}
const SEARCH_THRESHOLD = 2;
export const FilterBar = ({
filterBar: { names, ports, ids, schemes },
updateQuery,
}: FilterBarProps) => {
// TODO: add a factory function + type for these filter options
const filters = [
{
type: 'field_value_toggle_group',
field: 'monitor.status',
items: [
{
value: 'up',
name: i18n.translate('xpack.uptime.filterBar.filterUpLabel', {
defaultMessage: 'Up',
}),
},
{
value: 'down',
name: i18n.translate('xpack.uptime.filterBar.filterDownLabel', {
defaultMessage: 'Down',
}),
},
],
},
// TODO: add health to this select
{
type: 'field_value_selection',
field: 'monitor.id',
name: i18n.translate('xpack.uptime.filterBar.options.idLabel', {
defaultMessage: 'ID',
}),
multiSelect: false,
options: ids
? ids.map(({ key }: MonitorKey) => ({
value: key,
view: key,
}))
: [],
searchThreshold: SEARCH_THRESHOLD,
},
{
type: 'field_value_selection',
field: 'monitor.name',
name: i18n.translate('xpack.uptime.filterBar.options.nameLabel', {
defaultMessage: 'Name',
}),
multiSelect: false,
options: names
? names.map((nameValue: string) => ({ value: nameValue, view: nameValue }))
: [],
searchThreshold: SEARCH_THRESHOLD,
},
{
type: 'field_value_selection',
field: 'url.full',
name: i18n.translate('xpack.uptime.filterBar.options.urlLabel', {
defaultMessage: 'URL',
}),
multiSelect: false,
options: ids ? ids.map(({ url }: MonitorKey) => ({ value: url, view: url })) : [],
searchThreshold: SEARCH_THRESHOLD,
},
{
type: 'field_value_selection',
field: 'url.port',
name: i18n.translate('xpack.uptime.filterBar.options.portLabel', {
defaultMessage: 'Port',
}),
multiSelect: false,
options: ports
? ports.map((portValue: any) => ({
value: portValue,
view: portValue,
}))
: [],
searchThreshold: SEARCH_THRESHOLD,
},
{
type: 'field_value_selection',
field: 'monitor.type',
name: i18n.translate('xpack.uptime.filterBar.options.typeLabel', {
defaultMessage: 'Type',
}),
multiSelect: false,
options: schemes
? schemes.map((schemeValue: string) => ({
value: schemeValue,
view: schemeValue,
}))
: [],
searchThreshold: SEARCH_THRESHOLD,
},
];
return (
<EuiFlexGroup>
<EuiFlexItem grow>
<EuiSearchBar
// TODO: update typing
onChange={({ query }: { query?: { text: string } }) => {
try {
let esQuery;
if (query && query.text) {
esQuery = EuiSearchBar.Query.toESQuery(query);
}
updateQuery(esQuery);
} catch (e) {
updateQuery(undefined);
}
}}
filters={filters}
schema={filterBarSearchSchema}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -4,9 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { EmptyState } from './empty_state';
export { EmptyStatusBar } from './empty_status_bar';
export { ErrorList } from './error_list';
export { FilterBar } from './filter_bar';
export { FilterBarLoading } from './filter_bar_loading';
export { MonitorList } from './monitor_list';
export { MonitorPageTitle } from './monitor_page_title';
export { MonitorStatusBar } from './monitor_status_bar';
export { PingList } from './ping_list';
export { Snapshot } from './snapshot';
export { SnapshotHistogram } from './snapshot_histogram';
export { SnapshotLoading } from './snapshot_loading';
export { StatusBar } from './status_bar';

View file

@ -0,0 +1,156 @@
/*
* 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 {
EuiHealth,
// @ts-ignore missing type definition
EuiHistogramSeries,
// @ts-ignore missing type definition
EuiInMemoryTable,
EuiLink,
EuiPanel,
// @ts-ignore missing type definition
EuiSeriesChart,
// @ts-ignore missing type definition
EuiSeriesChartUtils,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import moment from 'moment';
import React, { Fragment } from 'react';
import { Link } from 'react-router-dom';
import { LatestMonitor } from '../../../common/graphql/types';
import { formatSparklineCounts } from './format_sparkline_counts';
interface MonitorListProps {
primaryColor: string;
dangerColor: string;
loading: boolean;
monitors: LatestMonitor[];
}
const MONITOR_LIST_DEFAULT_PAGINATION = 10;
const monitorListPagination = {
initialPageSize: MONITOR_LIST_DEFAULT_PAGINATION,
pageSizeOptions: [5, 10, 20, 50],
};
export const MonitorList = ({ dangerColor, loading, monitors, primaryColor }: MonitorListProps) => (
<Fragment>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.uptime.monitorList.monitoringStatusTitle"
defaultMessage="Monitor status"
/>
</h5>
</EuiTitle>
<EuiPanel paddingSize="l">
<EuiInMemoryTable
columns={[
{
field: 'ping.monitor.status',
name: i18n.translate('xpack.uptime.monitorList.statusColumnLabel', {
defaultMessage: 'Status',
}),
render: (status: string) => (
<EuiHealth color={status === 'up' ? 'success' : 'danger'}>
{status === 'up'
? i18n.translate('xpack.uptime.monitorList.statusColumn.upLabel', {
defaultMessage: 'Up',
})
: i18n.translate('xpack.uptime.monitorList.statusColumn.downLabel', {
defaultMessage: 'Down',
})}
</EuiHealth>
),
sortable: true,
},
{
field: 'ping.timestamp',
name: i18n.translate('xpack.uptime.monitorList.lastUpdatedColumnLabel', {
defaultMessage: 'Last updated',
}),
render: (timestamp: string) => moment(timestamp).fromNow(),
sortable: true,
},
{
field: 'ping.monitor.id',
name: i18n.translate('xpack.uptime.monitorList.idColumnLabel', {
defaultMessage: 'ID',
}),
render: (id: string, monitor: LatestMonitor) => (
<Link to={`/monitor/${id}`}>
{monitor.ping && monitor.ping.monitor && monitor.ping.monitor.name
? monitor.ping.monitor.name
: id}
</Link>
),
},
{
field: 'ping.url.full',
name: i18n.translate('xpack.uptime.monitorList.urlColumnLabel', {
defaultMessage: 'URL',
}),
render: (url: string) => (
<EuiLink href={url} target="_blank">
{url}
</EuiLink>
),
},
{
field: 'ping.monitor.ip',
name: i18n.translate('xpack.uptime.monitorList.ipColumnLabel', {
defaultMessage: 'IP',
}),
sortable: true,
},
{
field: 'upSeries',
name: i18n.translate('xpack.uptime.monitorList.monitorHistoryColumnLabel', {
defaultMessage: 'Monitor History',
}),
// @ts-ignore TODO fix typing
render: (upSeries, monitor) => {
const { downSeries } = monitor;
return (
<EuiSeriesChart
showDefaultAxis={false}
height={70}
stackBy="y"
// TODO: style hack
style={{ marginBottom: '-20px' }}
xType={EuiSeriesChartUtils.SCALE.TIME}
>
<EuiHistogramSeries
data={formatSparklineCounts(downSeries)}
name={i18n.translate('xpack.uptime.monitorList.downLineSeries.downLabel', {
defaultMessage: 'Down',
})}
color={dangerColor}
/>
<EuiHistogramSeries
data={formatSparklineCounts(upSeries)}
name={i18n.translate('xpack.uptime.monitorList.upLineSeries.upLabel', {
defaultMessage: 'Up',
})}
color={primaryColor}
/>
</EuiSeriesChart>
);
},
},
]}
loading={loading}
items={monitors}
pagination={monitorListPagination}
sorting={true}
/>
</EuiPanel>
</Fragment>
);

View file

@ -17,7 +17,7 @@ interface Props {
timestamp?: string;
}
export const StatusBar = ({ timestamp, url, duration, status }: Props) => (
export const MonitorStatusBar = ({ timestamp, url, duration, status }: Props) => (
<EuiPanel>
<EuiFlexGroup gutterSize="l">
<EuiFlexItem grow={false}>

View file

@ -0,0 +1,205 @@
/*
* 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 {
EuiBadge,
EuiComboBox,
EuiComboBoxOptionProps,
EuiFieldNumber,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiHealth,
// @ts-ignore
EuiInMemoryTable,
EuiPanel,
EuiTitle,
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { get } from 'lodash';
import moment from 'moment';
import React, { Fragment } from 'react';
import { Ping, PingResults } from '../../../common/graphql/types';
interface PingListProps {
loading: boolean;
maxSearchSize: number;
pingResults: PingResults;
searchSizeOnBlur: (e: React.FocusEvent<HTMLInputElement>) => void;
selectedOption: EuiComboBoxOptionProps;
selectedOptionChanged: (selectedOptions: EuiComboBoxOptionProps[]) => void;
statusOptions: EuiComboBoxOptionProps[];
}
export const PingList = ({
loading,
maxSearchSize,
pingResults,
searchSizeOnBlur,
selectedOption,
selectedOptionChanged,
statusOptions,
}: PingListProps) => {
const columns = [
{
field: 'monitor.status',
name: i18n.translate('xpack.uptime.pingList.statusColumnLabel', {
defaultMessage: 'Status',
}),
render: (pingStatus: string) => (
<EuiHealth color={pingStatus === 'up' ? 'success' : 'danger'}>
{pingStatus === 'up'
? i18n.translate('xpack.uptime.pingList.statusColumnHealthUpLabel', {
defaultMessage: 'Up',
})
: i18n.translate('xpack.uptime.pingList.statusColumnHealthDownLabel', {
defaultMessage: 'Down',
})}
</EuiHealth>
),
sortable: true,
},
{
field: 'timestamp',
name: i18n.translate('xpack.uptime.pingList.timestampColumnLabel', {
defaultMessage: 'Timestamp',
}),
sortable: true,
render: (timestamp: string) => moment(timestamp).fromNow(),
},
{
field: 'monitor.ip',
name: i18n.translate('xpack.uptime.pingList.ipAddressColumnLabel', {
defaultMessage: 'IP',
}),
},
{
field: 'monitor.id',
name: i18n.translate('xpack.uptime.pingList.idColumnLabel', {
defaultMessage: 'Id',
}),
dataType: 'string',
width: '20%',
},
{
field: 'monitor.duration.us',
name: i18n.translate('xpack.uptime.pingList.durationMsColumnLabel', {
defaultMessage: 'Duration ms',
description: 'The "ms" in the default message is an abbreviation for milliseconds',
}),
render: (duration: number) => duration / 1000,
sortable: true,
},
{
field: 'error.type',
name: i18n.translate('xpack.uptime.pingList.errorTypeColumnLabel', {
defaultMessage: 'Error type',
}),
},
{
field: 'error.message',
name: i18n.translate('xpack.uptime.pingList.errorMessageColumnLabel', {
defaultMessage: 'Error message',
}),
render: (message: string) =>
message && message.length > 25 ? (
<EuiToolTip
position="top"
title={i18n.translate('xpack.uptime.pingList.columns.errorMessageTooltipTitle', {
defaultMessage: 'Error message',
})}
content={<p>{message}</p>}
>
<div>{message.slice(0, 24)}</div>
</EuiToolTip>
) : (
message
),
},
];
let pings: Ping[] = [];
let total: number = 0;
if (pingResults && pingResults.pings) {
pings = pingResults.pings;
total = pingResults.total;
const hasStatus: boolean = pings.reduce(
(hasHttpStatus: boolean, currentPing: Ping) =>
hasHttpStatus || !!get(currentPing, 'http.response.status_code'),
false
);
if (hasStatus) {
columns.push({
field: 'http.response.status_code',
name: i18n.translate('xpack.uptime.pingList.responseCodeColumnLabel', {
defaultMessage: 'Response code',
}),
});
}
}
return (
<Fragment>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h4>
<FormattedMessage
id="xpack.uptime.pingList.checkHistoryTitle"
defaultMessage="Check History"
/>
</h4>
</EuiTitle>
</EuiFlexItem>
{!!total && (
<EuiFlexItem grow={false}>
<EuiBadge color="primary">{total}</EuiBadge>
</EuiFlexItem>
)}
</EuiFlexGroup>
<EuiPanel paddingSize="l">
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
label={i18n.translate('xpack.uptime.pingList.statusLabel', {
defaultMessage: 'Status',
})}
>
<EuiComboBox
isClearable={false}
singleSelection={{ asPlainText: true }}
selectedOptions={[selectedOption]}
options={statusOptions}
onChange={selectedOptionChanged}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label={i18n.translate('xpack.uptime.pingList.maxSearchSizeLabel', {
defaultMessage: 'Max Search Size',
})}
>
<EuiFieldNumber
defaultValue={maxSearchSize.toString()}
min={0}
max={10000} // 10k is the max default size in ES, and a good max sane size for this page
onBlur={searchSizeOnBlur}
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
<EuiInMemoryTable
loading={loading}
columns={columns}
items={pings}
pagination={{ initialPageSize: 10, pageSizeOptions: [5, 10, 20, 100] }}
sorting={true}
/>
</EuiPanel>
</Fragment>
);
};

View file

@ -0,0 +1,137 @@
/*
* 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 type
EuiAreaSeries,
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
// @ts-ignore missing type
EuiHistogramSeries,
// @ts-ignore missing type
EuiPanel,
// @ts-ignore missing type
EuiSeriesChart,
// @ts-ignore missing type
EuiSeriesChartUtils,
// @ts-ignore missing type
EuiStat,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { Snapshot as SnapshotType } from '../../../common/graphql/types';
import { SnapshotHistogram } from './snapshot_histogram';
interface SnapshotProps {
dangerColor: string;
primaryColor: string;
snapshot: SnapshotType;
}
export const Snapshot = ({
dangerColor,
primaryColor,
snapshot: { up, down, total, histogram },
}: SnapshotProps) => (
<EuiFlexGroup alignItems="baseline" gutterSize="xl">
<EuiFlexItem>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.uptime.snapshot.endpointStatusTitle"
defaultMessage="Endpoint status"
/>
</h5>
</EuiTitle>
<EuiPanel>
<EuiFlexGroup justifyContent="spaceEvenly" gutterSize="xl">
<EuiFlexItem>
{/* TODO: this is a UI hack that needs to be replaced */}
<EuiPanel>
<EuiStat
description={i18n.translate('xpack.uptime.snapshot.stats.upDescription', {
defaultMessage: 'Up',
})}
textAlign="center"
title={up}
titleColor="primary"
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiPanel>
<EuiStat
description={i18n.translate('xpack.uptime.snapshot.stats.downDescription', {
defaultMessage: 'Down',
})}
textAlign="center"
title={down}
titleColor="danger"
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiPanel>
<EuiStat
description={i18n.translate('xpack.uptime.snapshot.stats.totalDescription', {
defaultMessage: 'Total',
})}
textAlign="center"
title={total}
titleColor="subdued"
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem style={{ paddingTop: '12px' }}>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.uptime.snapshot.statusOverTimeTitle"
defaultMessage="Status over time"
/>
</h5>
</EuiTitle>
{/* TODO: this is a UI hack that should be replaced */}
<EuiPanel paddingSize="s">
{histogram && (
<SnapshotHistogram
dangerColor={dangerColor}
primaryColor={primaryColor}
histogram={histogram}
/>
)}
{!histogram && (
<EuiEmptyPrompt
title={
<EuiTitle>
<h5>
<FormattedMessage
id="xpack.uptime.snapshot.noDataTitle"
defaultMessage="No histogram data available"
/>
</h5>
</EuiTitle>
}
body={
<p>
<FormattedMessage
id="xpack.uptime.snapshot.noDataDescription"
defaultMessage="Sorry, there is no data available for the histogram"
/>
</p>
}
/>
)}
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
);

View file

@ -1,92 +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 { EuiEmptyPrompt, EuiLink, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Fragment } from 'react';
import { Query } from 'react-apollo';
import { UptimeCommonProps } from '../../../uptime_app';
import { getDocCountQuery } from './get_doc_count';
interface EmptyStateProps {
children: JSX.Element[];
}
type Props = EmptyStateProps & UptimeCommonProps;
export const EmptyState = ({ autorefreshInterval, autorefreshIsPaused, children }: Props) => (
<Query
query={getDocCountQuery}
pollInterval={autorefreshIsPaused ? undefined : autorefreshInterval}
>
{({ loading, error, data }) => {
if (loading) {
return i18n.translate('xpack.uptime.emptyState.loadingMessage', {
defaultMessage: 'Loading…',
});
}
if (error) {
return i18n.translate('xpack.uptime.emptyState.errorMessage', {
values: { message: error.message },
defaultMessage: 'Error {message}',
});
}
const {
getDocCount: { count },
} = data;
return (
<Fragment>
{!count && (
<EuiEmptyPrompt
title={
<EuiTitle size="l">
<h3>
<FormattedMessage
id="xpack.uptime.emptyState.noDataTitle"
defaultMessage="No Uptime Data"
/>
</h3>
</EuiTitle>
}
body={
<Fragment>
<p>
<FormattedMessage
id="xpack.uptime.emptyState.noDataDescription"
defaultMessage="There is no uptime data available."
/>
</p>
<p>
<FormattedMessage
id="xpack.uptime.emptyState.configureHeartbeatToGetStartedMessage"
defaultMessage="{configureHeartbeatLink} to start logging uptime data."
values={{
configureHeartbeatLink: (
<EuiLink
target="_blank"
href="https://www.elastic.co/guide/en/beats/heartbeat/current/configuring-howto-heartbeat.html"
>
<FormattedMessage
id="xpack.uptime.emptyState.configureHeartbeatLinkText"
defaultMessage="Configure Heartbeat"
/>
</EuiLink>
),
}}
/>
</p>
</Fragment>
}
/>
)}
{count > 0 && children}
</Fragment>
);
}}
</Query>
);

View file

@ -0,0 +1,45 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { get } from 'lodash';
import React from 'react';
import { Query } from 'react-apollo';
import { UptimeCommonProps } from '../../../uptime_app';
import { EmptyState } from '../../functional/empty_state';
import { getDocCountQuery } from './get_doc_count';
interface EmptyStateProps {
children: JSX.Element[];
}
type Props = EmptyStateProps & UptimeCommonProps;
export const EmptyStateQuery = ({ autorefreshInterval, autorefreshIsPaused, children }: Props) => (
<Query
query={getDocCountQuery}
pollInterval={autorefreshIsPaused ? undefined : autorefreshInterval}
>
{({ loading, error, data }) => {
const count = get(data, 'getDocCount.count', 0);
return (
<EmptyState
children={children}
count={count}
error={
error
? i18n.translate('xpack.uptime.emptyState.errorMessage', {
values: { message: error.message },
defaultMessage: 'Error {message}',
})
: undefined
}
loading={loading}
/>
);
}}
</Query>
);

View file

@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { EmptyState } from './empty_state';
export { EmptyStateQuery } from './empty_state_query';

View file

@ -1,111 +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.
*/
// @ts-ignore missing typings
import { EuiInMemoryTable, EuiPanel, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import moment from 'moment';
import React, { Fragment } from 'react';
import { Query } from 'react-apollo';
import { Link } from 'react-router-dom';
import { UptimeCommonProps } from '../../../uptime_app';
import { getErrorListQuery } from './get_error_list';
interface ErrorListProps {
filters?: string;
}
type Props = ErrorListProps & UptimeCommonProps;
export const ErrorList = ({
autorefreshInterval,
autorefreshIsPaused,
dateRangeStart,
dateRangeEnd,
filters,
}: Props) => (
<Query
pollInterval={autorefreshIsPaused ? undefined : autorefreshInterval}
query={getErrorListQuery}
variables={{ dateRangeStart, dateRangeEnd, filters }}
>
{({ loading, error, data }) => {
if (error) {
return i18n.translate('xpack.uptime.errorList.errorMessage', {
values: { message: error.message },
defaultMessage: 'Error {message}',
});
}
const { errorList } = data;
return (
<Fragment>
<EuiTitle size="xs">
<h5>
<FormattedMessage id="xpack.uptime.errorList.title" defaultMessage="Error list" />
</h5>
</EuiTitle>
<EuiPanel>
<EuiInMemoryTable
loading={loading}
items={errorList}
columns={[
{
field: 'type',
name: i18n.translate('xpack.uptime.errorList.errorTypeColumnLabel', {
defaultMessage: 'Error type',
}),
sortable: true,
},
{
field: 'monitorId',
name: i18n.translate('xpack.uptime.errorList.monitorIdColumnLabel', {
defaultMessage: 'Monitor ID',
}),
render: (id: string) => <Link to={`/monitor/${id}`}>{id}</Link>,
sortable: true,
width: '25%',
},
{
field: 'count',
name: i18n.translate('xpack.uptime.errorList.CountColumnLabel', {
defaultMessage: 'Count',
}),
sortable: true,
},
{
field: 'timestamp',
name: i18n.translate('xpack.uptime.errorList.latestErrorColumnLabel', {
defaultMessage: 'Latest error',
}),
sortable: true,
render: (timestamp: string) => moment(timestamp).fromNow(),
},
{
field: 'statusCode',
name: i18n.translate('xpack.uptime.errorList.statusCodeColumnLabel', {
defaultMessage: 'Status code',
}),
sortable: true,
},
{
field: 'latestMessage',
name: i18n.translate('xpack.uptime.errorList.latestMessageColumnLabel', {
defaultMessage: 'Latest message',
}),
sortable: true,
width: '40%',
},
]}
sorting={true}
pagination={{ initialPageSize: 10, pageSizeOptions: [5, 10, 20, 50] }}
/>
</EuiPanel>
</Fragment>
);
}}
</Query>
);

View file

@ -0,0 +1,43 @@
/*
* 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 { i18n } from '@kbn/i18n';
import React from 'react';
import { Query } from 'react-apollo';
import { UptimeCommonProps } from '../../../uptime_app';
import { ErrorList } from '../../functional';
import { getErrorListQuery } from './get_error_list';
interface ErrorListProps {
filters?: string;
}
type Props = ErrorListProps & UptimeCommonProps;
export const ErrorListQuery = ({
autorefreshInterval,
autorefreshIsPaused,
dateRangeStart,
dateRangeEnd,
filters,
}: Props) => (
<Query
pollInterval={autorefreshIsPaused ? undefined : autorefreshInterval}
query={getErrorListQuery}
variables={{ dateRangeStart, dateRangeEnd, filters }}
>
{({ loading, error, data }) => {
if (error) {
return i18n.translate('xpack.uptime.errorList.errorMessage', {
values: { message: error.message },
defaultMessage: 'Error {message}',
});
}
const { errorList } = data;
return <ErrorList loading={loading} errorList={errorList} />;
}}
</Query>
);

View file

@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { ErrorList } from './error_list';
export { ErrorListQuery } from './error_list_query';

View file

@ -1,166 +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.
*/
// @ts-ignore No typings for EuiSearchBar
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSearchBar, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { Query } from 'react-apollo';
import { FilterBar as FilterBarType, MonitorKey } from '../../../../common/graphql/types';
import { UptimeCommonProps } from '../../../uptime_app';
import { FilterBarLoading } from '../../functional';
import { getFilterBarQuery } from './get_filter_bar';
import { filterBarSearchSchema } from './search_schema';
interface FilterBarProps {
updateQuery: (query: object | undefined) => void;
}
type Props = FilterBarProps & UptimeCommonProps;
const SEARCH_THRESHOLD = 8;
export const FilterBar = ({
autorefreshInterval,
autorefreshIsPaused,
dateRangeStart,
dateRangeEnd,
updateQuery,
}: Props) => (
<Query
pollInterval={autorefreshIsPaused ? undefined : autorefreshInterval}
query={getFilterBarQuery}
variables={{ dateRangeStart, dateRangeEnd }}
>
{({ loading, error, data }) => {
if (loading) {
return <FilterBarLoading />;
}
if (error) {
return i18n.translate('xpack.uptime.filterBar.errorMessage', {
values: { message: error.message },
defaultMessage: 'Error {message}',
});
}
const {
filterBar: { names, ports, ids, schemes },
}: { filterBar: FilterBarType } = data;
// TODO: add a factory function + type for these filter options
const filters = [
{
type: 'field_value_toggle_group',
field: 'monitor.status',
items: [
{
value: 'up',
name: i18n.translate('xpack.uptime.filterBar.filterUpLabel', {
defaultMessage: 'Up',
}),
},
{
value: 'down',
name: i18n.translate('xpack.uptime.filterBar.filterDownLabel', {
defaultMessage: 'Down',
}),
},
],
},
// TODO: add health to this select
{
type: 'field_value_selection',
field: 'monitor.id',
name: i18n.translate('xpack.uptime.filterBar.options.idLabel', {
defaultMessage: 'ID',
}),
multiSelect: false,
options: ids
? ids.map(({ key }: MonitorKey) => ({
value: key,
view: key,
}))
: [],
searchThreshold: SEARCH_THRESHOLD,
},
{
type: 'field_value_selection',
field: 'monitor.name',
name: i18n.translate('xpack.uptime.filterBar.options.nameLabel', {
defaultMessage: 'Name',
}),
multiSelect: false,
options: names
? names.map((nameValue: string) => ({ value: nameValue, view: nameValue }))
: [],
searchThreshold: SEARCH_THRESHOLD,
},
{
type: 'field_value_selection',
field: 'url.full',
name: i18n.translate('xpack.uptime.filterBar.options.urlLabel', {
defaultMessage: 'URL',
}),
multiSelect: false,
options: ids ? ids.map(({ url }: MonitorKey) => ({ value: url, view: url })) : [],
searchThreshold: SEARCH_THRESHOLD,
},
{
type: 'field_value_selection',
field: 'url.port',
name: i18n.translate('xpack.uptime.filterBar.options.portLabel', {
defaultMessage: 'Port',
}),
multiSelect: false,
options: ports
? ports.map((portValue: any) => ({
value: portValue,
view: portValue,
}))
: [],
searchThreshold: SEARCH_THRESHOLD,
},
{
type: 'field_value_selection',
field: 'monitor.type',
name: i18n.translate('xpack.uptime.filterBar.options.typeLabel', {
defaultMessage: 'Type',
}),
multiSelect: false,
options: schemes
? schemes.map((schemeValue: string) => ({
value: schemeValue,
view: schemeValue,
}))
: [],
searchThreshold: SEARCH_THRESHOLD,
},
];
return (
<EuiFlexGroup>
<EuiFlexItem grow>
<EuiSearchBar
// TODO: update typing
onChange={({ query }: { query?: { text: string } }) => {
try {
let esQuery;
if (query && query.text) {
esQuery = EuiSearchBar.Query.toESQuery(query);
}
updateQuery(esQuery);
} catch (e) {
updateQuery(undefined);
}
}}
filters={filters}
schema={filterBarSearchSchema}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
}}
</Query>
);

View file

@ -0,0 +1,48 @@
/*
* 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 { i18n } from '@kbn/i18n';
import React from 'react';
import { Query } from 'react-apollo';
import { UptimeCommonProps } from '../../../uptime_app';
import { FilterBar } from '../../functional';
import { getFilterBarQuery } from './get_filter_bar';
interface FilterBarProps {
updateQuery: (query: object | undefined) => void;
}
type Props = FilterBarProps & UptimeCommonProps;
export const FilterBarQuery = ({
autorefreshInterval,
autorefreshIsPaused,
dateRangeStart,
dateRangeEnd,
updateQuery,
}: Props) => (
<Query
pollInterval={autorefreshIsPaused ? undefined : autorefreshInterval}
query={getFilterBarQuery}
variables={{ dateRangeStart, dateRangeEnd }}
>
{({ loading, error, data }) => {
if (loading) {
return i18n.translate('xpack.uptime.filterBar.loadingMessage', {
defaultMessage: 'Loading…',
});
}
if (error) {
return i18n.translate('xpack.uptime.filterBar.errorMessage', {
values: { message: error.message },
defaultMessage: 'Error {message}',
});
}
const { filterBar } = data;
return <FilterBar filterBar={filterBar} updateQuery={updateQuery} />;
}}
</Query>
);

View file

@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { FilterBar } from './filter_bar';
export { FilterBarQuery } from './filter_bar_query';

View file

@ -0,0 +1,15 @@
/*
* 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 { EmptyStateQuery } from './empty_state';
export { ErrorListQuery } from './error_list';
export { FilterBarQuery } from './filter_bar';
export { MonitorChartsQuery } from './monitor_charts';
export { MonitorListQuery } from './monitor_list';
export { MonitorPageTitleQuery } from './monitor_page_title';
export { MonitorStatusBarQuery } from './monitor_status_bar';
export { PingListQuery } from './ping_list';
export { SnapshotQuery } from './snapshot';

View file

@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { MonitorCharts } from './monitor_charts';
export { MonitorChartsQuery } from './monitor_charts_query';

View file

@ -38,7 +38,7 @@ interface MonitorChartsState {
type Props = MonitorChartsProps & UptimeCommonProps;
export class MonitorCharts extends React.Component<Props, MonitorChartsState> {
export class MonitorChartsQuery extends React.Component<Props, MonitorChartsState> {
constructor(props: Props) {
super(props);
this.state = { crosshairLocation: 0 };

View file

@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { MonitorList } from './monitor_list';
export { MonitorListQuery } from './monitor_list_query';

View file

@ -1,187 +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 {
EuiHealth,
// @ts-ignore missing type definition
EuiHistogramSeries,
// @ts-ignore missing type definition
EuiInMemoryTable,
EuiLink,
EuiPanel,
// @ts-ignore missing type definition
EuiSeriesChart,
// @ts-ignore missing type definition
EuiSeriesChartUtils,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { get } from 'lodash';
import moment from 'moment';
import React, { Fragment } from 'react';
import { Query } from 'react-apollo';
import { Link } from 'react-router-dom';
import { LatestMonitor, LatestMonitorsResult } from '../../../../common/graphql/types';
import { UptimeCommonProps } from '../../../uptime_app';
import { formatSparklineCounts } from './format_sparkline_counts';
import { getMonitorListQuery } from './get_monitor_list';
interface MonitorListProps {
filters?: string;
}
type Props = MonitorListProps & UptimeCommonProps;
const MONITOR_LIST_DEFAULT_PAGINATION = 10;
const monitorListPagination = {
initialPageSize: MONITOR_LIST_DEFAULT_PAGINATION,
pageSizeOptions: [5, 10, 20, 50],
};
export const MonitorList = ({
autorefreshInterval,
autorefreshIsPaused,
colors: { danger, primary },
dateRangeStart,
dateRangeEnd,
filters,
}: Props) => (
<Query
pollInterval={autorefreshIsPaused ? undefined : autorefreshInterval}
query={getMonitorListQuery}
variables={{ dateRangeStart, dateRangeEnd, filters }}
>
{({ loading, error, data }) => {
if (error) {
return i18n.translate('xpack.uptime.monitorList.errorMessage', {
values: { message: error.message },
defaultMessage: 'Error {message}',
});
}
const monitors: LatestMonitorsResult | undefined = get(data, 'monitorStatus.monitors');
// TODO: add a better loading message than "no items found", which it displays today
return (
<Fragment>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.uptime.monitorList.monitoringStatusTitle"
defaultMessage="Monitor status"
/>
</h5>
</EuiTitle>
<EuiPanel paddingSize="l">
<EuiInMemoryTable
columns={[
{
field: 'ping.monitor.status',
name: i18n.translate('xpack.uptime.monitorList.statusColumnLabel', {
defaultMessage: 'Status',
}),
render: (status: string) => (
<EuiHealth color={status === 'up' ? 'success' : 'danger'}>
{status === 'up'
? i18n.translate('xpack.uptime.monitorList.statusColumn.upLabel', {
defaultMessage: 'Up',
})
: i18n.translate('xpack.uptime.monitorList.statusColumn.downLabel', {
defaultMessage: 'Down',
})}
</EuiHealth>
),
sortable: true,
},
{
field: 'ping.timestamp',
name: i18n.translate('xpack.uptime.monitorList.lastUpdatedColumnLabel', {
defaultMessage: 'Last updated',
}),
render: (timestamp: string) => moment(timestamp).fromNow(),
sortable: true,
},
{
field: 'ping.monitor.id',
name: i18n.translate('xpack.uptime.monitorList.idColumnLabel', {
defaultMessage: 'ID',
}),
render: (id: string, monitor: LatestMonitor) => (
<Link to={`/monitor/${id}`}>
{monitor.ping && monitor.ping.monitor && monitor.ping.monitor.name
? monitor.ping.monitor.name
: id}
</Link>
),
},
{
field: 'ping.url.full',
name: i18n.translate('xpack.uptime.monitorList.urlColumnLabel', {
defaultMessage: 'URL',
}),
render: (url: string) => (
<EuiLink href={url} target="_blank">
{url}
</EuiLink>
),
},
{
field: 'ping.monitor.ip',
name: i18n.translate('xpack.uptime.monitorList.ipColumnLabel', {
defaultMessage: 'IP',
}),
sortable: true,
},
{
field: 'upSeries',
name: i18n.translate('xpack.uptime.monitorList.monitorHistoryColumnLabel', {
defaultMessage: 'Monitor History',
}),
// @ts-ignore TODO fix typing
render: (upSeries, monitor) => {
const { downSeries } = monitor;
return (
<EuiSeriesChart
showDefaultAxis={false}
height={70}
stackBy="y"
// TODO: style hack
style={{ marginBottom: '-20px' }}
xType={EuiSeriesChartUtils.SCALE.TIME}
>
<EuiHistogramSeries
data={formatSparklineCounts(downSeries)}
name={i18n.translate(
'xpack.uptime.monitorList.downLineSeries.downLabel',
{
defaultMessage: 'Down',
}
)}
color={danger}
/>
<EuiHistogramSeries
data={formatSparklineCounts(upSeries)}
name={i18n.translate('xpack.uptime.monitorList.upLineSeries.upLabel', {
defaultMessage: 'Up',
})}
color={primary}
/>
</EuiSeriesChart>
);
},
},
]}
loading={loading}
items={monitors}
pagination={monitorListPagination}
sorting={true}
/>
</EuiPanel>
</Fragment>
);
}}
</Query>
);

View file

@ -0,0 +1,53 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { get } from 'lodash';
import React from 'react';
import { Query } from 'react-apollo';
import { LatestMonitor } from '../../../../common/graphql/types';
import { UptimeCommonProps } from '../../../uptime_app';
import { MonitorList } from '../../functional/monitor_list';
import { getMonitorListQuery } from './get_monitor_list';
interface MonitorListProps {
filters?: string;
}
type Props = MonitorListProps & UptimeCommonProps;
export const MonitorListQuery = ({
autorefreshInterval,
autorefreshIsPaused,
colors: { primary, danger },
dateRangeStart,
dateRangeEnd,
filters,
}: Props) => (
<Query
pollInterval={autorefreshIsPaused ? undefined : autorefreshInterval}
query={getMonitorListQuery}
variables={{ dateRangeStart, dateRangeEnd, filters }}
>
{({ loading, error, data }) => {
if (error) {
return i18n.translate('xpack.uptime.monitorList.errorMessage', {
values: { message: error.message },
defaultMessage: 'Error {message}',
});
}
const monitors: LatestMonitor[] | undefined = get(data, 'monitorStatus.monitors', undefined);
return (
<MonitorList
dangerColor={danger}
loading={loading}
monitors={monitors || []}
primaryColor={primary}
/>
);
}}
</Query>
);

View file

@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { MonitorStatusBar } from './monitor_status_bar';
export { MonitorStatusBarQuery } from './monitor_status_bar_query';

View file

@ -11,8 +11,7 @@ import React from 'react';
import { Query } from 'react-apollo';
import { Ping } from 'x-pack/plugins/uptime/common/graphql/types';
import { UptimeCommonProps } from '../../../uptime_app';
import { StatusBar } from '../../functional';
import { EmptyStatusBar } from '../../functional/empty_status_bar';
import { EmptyStatusBar, MonitorStatusBar } from '../../functional';
import { formatDuration } from './format_duration';
import { getMonitorStatusBarQuery } from './get_monitor_status_bar';
@ -28,7 +27,7 @@ interface MonitorStatusBarQueryParams {
type Props = MonitorStatusBarProps & UptimeCommonProps;
export const MonitorStatusBar = ({
export const MonitorStatusBarQuery = ({
dateRangeStart,
dateRangeEnd,
monitorId,
@ -42,7 +41,14 @@ export const MonitorStatusBar = ({
>
{({ loading, error, data }: MonitorStatusBarQueryParams) => {
if (loading) {
return <EmptyStatusBar message="Fetching data" monitorId={monitorId} />;
return (
<EmptyStatusBar
message={i18n.translate('xpack.uptime.monitorStatusBar.loadingMessage', {
defaultMessage: 'Loading…',
})}
monitorId={monitorId}
/>
);
}
if (error) {
return i18n.translate('xpack.uptime.monitorStatusBar.errorMessage', {
@ -61,7 +67,7 @@ export const MonitorStatusBar = ({
const full = get(url, 'full', undefined);
return (
<StatusBar
<MonitorStatusBar
duration={formatDuration(duration)}
status={status}
timestamp={timestamp}

View file

@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { Pings } from './ping_list';
export { PingListQuery } from './ping_list_query';

View file

@ -1,281 +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 {
EuiBadge,
EuiComboBox,
EuiComboBoxOptionProps,
EuiFieldNumber,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiHealth,
// @ts-ignore
EuiInMemoryTable,
EuiPanel,
EuiTitle,
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { get } from 'lodash';
import moment from 'moment';
import React, { Fragment } from 'react';
import { Query } from 'react-apollo';
import { UMPingSortDirectionArg } from '../../../../common/domain_types';
import { Ping } from '../../../../common/graphql/types';
import { UptimeCommonProps } from '../../../uptime_app';
import { getPingsQuery } from './get_pings';
interface PingListProps {
monitorId?: string;
sort?: UMPingSortDirectionArg;
size?: number;
}
type Props = PingListProps & UptimeCommonProps;
interface PingListState {
statusOptions: EuiComboBoxOptionProps[];
selectedOption: EuiComboBoxOptionProps;
maxSearchSize: number;
}
export class Pings extends React.Component<Props, PingListState> {
constructor(props: Props) {
super(props);
const statusOptions = [
{
label: i18n.translate('xpack.uptime.pingList.statusOptions.allStatusOptionLabel', {
defaultMessage: 'All',
}),
value: '',
},
{
label: i18n.translate('xpack.uptime.pingList.statusOptions.upStatusOptionLabel', {
defaultMessage: 'Up',
}),
value: 'up',
},
{
label: i18n.translate('xpack.uptime.pingList.statusOptions.downStatusOptionLabel', {
defaultMessage: 'Down',
}),
value: 'down',
},
];
this.state = {
statusOptions,
selectedOption: statusOptions[2],
maxSearchSize: 200,
};
}
public render() {
const {
monitorId,
dateRangeStart,
dateRangeEnd,
autorefreshIsPaused,
autorefreshInterval,
sort,
size,
} = this.props;
const { statusOptions, selectedOption } = this.state;
return (
<Query
pollInterval={autorefreshIsPaused ? undefined : autorefreshInterval}
variables={{
monitorId,
dateRangeStart,
dateRangeEnd,
status:
selectedOption.value === 'up' || selectedOption.value === 'down'
? selectedOption.value
: '',
// TODO: get rid of the magic number
size: this.state.maxSearchSize || size || 200,
sort: sort || 'desc',
}}
query={getPingsQuery}
>
{({ loading, error, data }) => {
if (error) {
return i18n.translate('xpack.uptime.pingList.errorMessage', {
values: { message: error.message },
defaultMessage: 'Error {message}',
});
}
const total = get(data, 'allPings.total');
const pings = get(data, 'allPings.pings', []);
const columns = [
{
field: 'monitor.status',
name: i18n.translate('xpack.uptime.pingList.statusColumnLabel', {
defaultMessage: 'Status',
}),
render: (pingStatus: string) => (
<EuiHealth color={pingStatus === 'up' ? 'success' : 'danger'}>
{pingStatus === 'up'
? i18n.translate('xpack.uptime.pingList.statusColumnHealthUpLabel', {
defaultMessage: 'Up',
})
: i18n.translate('xpack.uptime.pingList.statusColumnHealthDownLabel', {
defaultMessage: 'Down',
})}
</EuiHealth>
),
sortable: true,
},
{
field: 'timestamp',
name: i18n.translate('xpack.uptime.pingList.timestampColumnLabel', {
defaultMessage: 'Timestamp',
}),
sortable: true,
render: (timestamp: string) => moment(timestamp).fromNow(),
},
{
field: 'monitor.ip',
name: i18n.translate('xpack.uptime.pingList.ipAddressColumnLabel', {
defaultMessage: 'IP',
}),
},
{
field: 'monitor.id',
name: i18n.translate('xpack.uptime.pingList.idColumnLabel', {
defaultMessage: 'Id',
}),
dataType: 'string',
width: '20%',
},
{
field: 'monitor.duration.us',
name: i18n.translate('xpack.uptime.pingList.durationMsColumnLabel', {
defaultMessage: 'Duration ms',
description: 'The "ms" in the default message is an abbreviation for milliseconds',
}),
render: (duration: number) => duration / 1000,
sortable: true,
},
{
field: 'error.type',
name: i18n.translate('xpack.uptime.pingList.errorTypeColumnLabel', {
defaultMessage: 'Error type',
}),
},
{
field: 'error.message',
name: i18n.translate('xpack.uptime.pingList.errorMessageColumnLabel', {
defaultMessage: 'Error message',
}),
render: (message: string) =>
message && message.length > 25 ? (
<EuiToolTip
position="top"
title={i18n.translate(
'xpack.uptime.pingList.columns.errorMessageTooltipTitle',
{
defaultMessage: 'Error message',
}
)}
content={<p>{message}</p>}
>
<div>{message.slice(0, 24)}</div>
</EuiToolTip>
) : (
message
),
},
];
const hasStatus = pings.reduce(
(hasHttpStatus: boolean, currentPing: Ping) =>
hasHttpStatus || get(currentPing, 'http.response.status_code'),
false
);
if (hasStatus) {
columns.push({
field: 'http.response.status_code',
name: i18n.translate('xpack.uptime.pingList.responseCodeColumnLabel', {
defaultMessage: 'Response code',
}),
});
}
return (
<Fragment>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h4>
<FormattedMessage
id="xpack.uptime.pingList.checkHistoryTitle"
defaultMessage="Check History"
/>
</h4>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiBadge color="primary">{total}</EuiBadge>
</EuiFlexItem>
</EuiFlexGroup>
<EuiPanel paddingSize="l">
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
label={i18n.translate('xpack.uptime.pingList.statusLabel', {
defaultMessage: 'Status',
})}
>
<EuiComboBox
isClearable={false}
singleSelection={{ asPlainText: true }}
selectedOptions={[this.state.selectedOption]}
options={statusOptions}
onChange={selectedOptions => {
if (selectedOptions[0]) {
this.setState({ selectedOption: selectedOptions[0] });
}
}}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label={i18n.translate('xpack.uptime.pingList.maxSearchSizeLabel', {
defaultMessage: 'Max Search Size',
})}
>
<EuiFieldNumber
defaultValue={this.state.maxSearchSize.toString()}
min={0}
max={10000} // 10k is the max default size in ES, and a good max sane size for this page
onBlur={e => {
const sanitizedValue = parseInt(e.target.value, 10);
if (!isNaN(sanitizedValue)) {
this.setState({
maxSearchSize: sanitizedValue >= 10000 ? 10000 : sanitizedValue,
});
}
}}
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
<EuiInMemoryTable
loading={loading}
columns={columns}
items={pings}
pagination={{ initialPageSize: 10, pageSizeOptions: [5, 10, 20, 100] }}
sorting={true}
/>
</EuiPanel>
</Fragment>
);
}}
</Query>
);
}
}

View file

@ -0,0 +1,126 @@
/*
* 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 { EuiComboBoxOptionProps } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { Query } from 'react-apollo';
import { UMPingSortDirectionArg } from '../../../../common/domain_types';
import { UptimeCommonProps } from '../../../uptime_app';
import { PingList } from '../../functional';
import { getPingsQuery } from './get_pings';
interface PingListProps {
monitorId?: string;
sort?: UMPingSortDirectionArg;
size?: number;
}
type Props = PingListProps & UptimeCommonProps;
interface PingListState {
statusOptions: EuiComboBoxOptionProps[];
selectedOption: EuiComboBoxOptionProps;
maxSearchSize: number;
}
export class PingListQuery extends React.Component<Props, PingListState> {
constructor(props: Props) {
super(props);
const statusOptions = [
{
label: i18n.translate('xpack.uptime.pingList.statusOptions.allStatusOptionLabel', {
defaultMessage: 'All',
}),
value: '',
},
{
label: i18n.translate('xpack.uptime.pingList.statusOptions.upStatusOptionLabel', {
defaultMessage: 'Up',
}),
value: 'up',
},
{
label: i18n.translate('xpack.uptime.pingList.statusOptions.downStatusOptionLabel', {
defaultMessage: 'Down',
}),
value: 'down',
},
];
this.state = {
statusOptions,
selectedOption: statusOptions[2],
maxSearchSize: 200,
};
}
public render() {
const {
monitorId,
dateRangeStart,
dateRangeEnd,
autorefreshIsPaused,
autorefreshInterval,
sort,
size,
} = this.props;
const { selectedOption } = this.state;
return (
<Query
pollInterval={autorefreshIsPaused ? undefined : autorefreshInterval}
variables={{
monitorId,
dateRangeStart,
dateRangeEnd,
status:
selectedOption.value === 'up' || selectedOption.value === 'down'
? selectedOption.value
: '',
// TODO: get rid of the magic number
size: this.state.maxSearchSize || size || 200,
sort: sort || 'desc',
}}
query={getPingsQuery}
>
{({ loading, error, data }) => {
if (error) {
return i18n.translate('xpack.uptime.pingList.errorMessage', {
values: { message: error.message },
defaultMessage: 'Error {message}',
});
}
const { allPings } = data;
return (
<PingList
loading={loading}
maxSearchSize={this.state.maxSearchSize}
pingResults={allPings}
searchSizeOnBlur={this.onSearchSizeBlur}
selectedOption={this.state.selectedOption}
selectedOptionChanged={this.onSelectedOptionChange}
statusOptions={this.state.statusOptions}
/>
);
}}
</Query>
);
}
private onSearchSizeBlur = (e: React.FocusEvent<HTMLInputElement>) => {
const sanitizedValue = parseInt(e.target.value, 10);
if (!isNaN(sanitizedValue)) {
this.setState({
maxSearchSize: sanitizedValue >= 10000 ? 10000 : sanitizedValue,
});
}
};
private onSelectedOptionChange = (selectedOptions: EuiComboBoxOptionProps[]) => {
if (selectedOptions[0]) {
this.setState({ selectedOption: selectedOptions[0] });
}
};
}

View file

@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { Snapshot } from './snapshot';
export { SnapshotQuery } from './snapshot_query';

View file

@ -1,165 +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 {
// @ts-ignore missing type
EuiAreaSeries,
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
// @ts-ignore missing type
EuiHistogramSeries,
// @ts-ignore missing type
EuiPanel,
// @ts-ignore missing type
EuiSeriesChart,
// @ts-ignore missing type
EuiSeriesChartUtils,
// @ts-ignore missing type
EuiStat,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { Query } from 'react-apollo';
import { UptimeCommonProps } from '../../../uptime_app';
import { SnapshotHistogram, SnapshotLoading } from '../../functional';
import { getSnapshotQuery } from './get_snapshot';
interface SnapshotProps {
filters?: string;
}
type Props = SnapshotProps & UptimeCommonProps;
export const Snapshot = ({
autorefreshIsPaused,
autorefreshInterval,
colors: { danger, primary },
dateRangeStart,
dateRangeEnd,
filters,
}: Props) => (
<Query
pollInterval={autorefreshIsPaused ? undefined : autorefreshInterval}
query={getSnapshotQuery}
variables={{ dateRangeStart, dateRangeEnd, filters }}
>
{({ loading, error, data }) => {
if (loading) {
return <SnapshotLoading />;
}
if (error) {
return i18n.translate('xpack.uptime.snapshot.errorMessage', {
values: { message: error.message },
defaultMessage: 'Error {message}',
});
}
const {
snapshot: { up, down, total, histogram },
} = data;
return (
<EuiFlexGroup alignItems="baseline" gutterSize="xl">
<EuiFlexItem>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.uptime.snapshot.endpointStatusTitle"
defaultMessage="Endpoint status"
/>
</h5>
</EuiTitle>
<EuiPanel>
<EuiFlexGroup justifyContent="spaceEvenly" gutterSize="xl">
<EuiFlexItem>
{/* TODO: this is a UI hack that needs to be replaced */}
<EuiPanel>
<EuiStat
description={i18n.translate('xpack.uptime.snapshot.stats.upDescription', {
defaultMessage: 'Up',
})}
textAlign="center"
title={up}
titleColor="primary"
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiPanel>
<EuiStat
description={i18n.translate('xpack.uptime.snapshot.stats.downDescription', {
defaultMessage: 'Down',
})}
textAlign="center"
title={down}
titleColor="danger"
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiPanel>
<EuiStat
description={i18n.translate('xpack.uptime.snapshot.stats.totalDescription', {
defaultMessage: 'Total',
})}
textAlign="center"
title={total}
titleColor="subdued"
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem style={{ paddingTop: '12px' }}>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.uptime.snapshot.statusOverTimeTitle"
defaultMessage="Status over time"
/>
</h5>
</EuiTitle>
{/* TODO: this is a UI hack that should be replaced */}
<EuiPanel paddingSize="s">
{histogram && (
<SnapshotHistogram
dangerColor={danger}
primaryColor={primary}
histogram={histogram}
/>
)}
{!histogram && (
<EuiEmptyPrompt
title={
<EuiTitle>
<h5>
<FormattedMessage
id="xpack.uptime.snapshot.noDataTitle"
defaultMessage="No histogram data available"
/>
</h5>
</EuiTitle>
}
body={
<p>
<FormattedMessage
id="xpack.uptime.snapshot.noDataDescription"
defaultMessage="Sorry, there is no data available for the histogram"
/>
</p>
}
/>
)}
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
);
}}
</Query>
);

View file

@ -0,0 +1,50 @@
/*
* 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 { i18n } from '@kbn/i18n';
import React from 'react';
import { Query } from 'react-apollo';
import { UptimeCommonProps } from '../../../uptime_app';
import { Snapshot } from '../../functional';
import { getSnapshotQuery } from './get_snapshot';
interface SnapshotProps {
filters?: string;
}
type Props = SnapshotProps & UptimeCommonProps;
export const SnapshotQuery = ({
autorefreshIsPaused,
autorefreshInterval,
colors: { primary, danger },
dateRangeStart,
dateRangeEnd,
filters,
}: Props) => (
<Query
pollInterval={autorefreshIsPaused ? undefined : autorefreshInterval}
query={getSnapshotQuery}
variables={{ dateRangeStart, dateRangeEnd, filters }}
>
{({ loading, error, data }) => {
if (loading) {
return i18n.translate('xpack.uptime.snapshot.loadingMessage', {
defaultMessage: 'Loading…',
});
}
if (error) {
return i18n.translate('xpack.uptime.snapshot.errorMessage', {
values: { message: error.message },
defaultMessage: 'Error {message}',
});
}
const { snapshot } = data;
return <Snapshot dangerColor={danger} primaryColor={primary} snapshot={snapshot} />;
}}
</Query>
);

View file

@ -0,0 +1,137 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`formatHistogramData adds to existing down count 1`] = `
Object {
"downSeriesData": Array [
Object {
"x": 10,
"x0": 11,
"y": 2,
},
Object {
"x": 11,
"x0": 12,
"y": 2,
},
Object {
"x": 12,
"x0": 13,
"y": 1,
},
],
"upSeriesData": Array [
Object {
"x": 10,
"x0": 11,
"y": 1,
},
Object {
"x": 11,
"x0": 12,
"y": 1,
},
Object {
"x": 12,
"x0": 13,
"y": 2,
},
],
}
`;
exports[`formatHistogramData adds to existing up count 1`] = `
Object {
"downSeriesData": Array [
Object {
"x": 10,
"x0": 11,
"y": 1,
},
Object {
"x": 11,
"x0": 12,
"y": 1,
},
],
"upSeriesData": Array [
Object {
"x": 10,
"x0": 11,
"y": 1,
},
Object {
"x": 11,
"x0": 12,
"y": 1,
},
Object {
"x": 12,
"x0": 13,
"y": 2,
},
],
}
`;
exports[`formatHistogramData doesn't add an entry to either count when none exists 1`] = `
Object {
"downSeriesData": Array [
Object {
"x": 10,
"x0": 11,
"y": 2,
},
Object {
"x": 11,
"x0": 12,
"y": 1,
},
Object {
"x": 12,
"x0": 13,
"y": 1,
},
],
"upSeriesData": Array [
Object {
"x": 10,
"x0": 11,
"y": 1,
},
Object {
"x": 11,
"x0": 12,
"y": 1,
},
Object {
"x": 12,
"x0": 13,
"y": 2,
},
],
}
`;
exports[`formatHistogramData filters out null data sets 1`] = `
Object {
"downSeriesData": Array [
Object {
"x": 10,
"x0": 11,
"y": 1,
},
Object {
"x": 11,
"x0": 12,
"y": 1,
},
],
"upSeriesData": Array [
Object {
"x": 12,
"x0": 13,
"y": 1,
},
],
}
`;

View file

@ -0,0 +1,279 @@
/*
* 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 { HistogramSeries } from 'x-pack/plugins/uptime/common/graphql/types';
import { formatHistogramData } from '../format_histogram_data';
describe('formatHistogramData', () => {
it('returns an empty arrays when no data is provided', () => {
const result = formatHistogramData([]);
expect(result).toEqual({ downSeriesData: [], upSeriesData: [] });
});
it('filters out null data sets', () => {
const seriesList: HistogramSeries[] = [
{
monitorId: 'monitor1',
data: null,
},
{
monitorId: 'monitor2',
data: [
{
upCount: null,
downCount: 2,
x: 10,
x0: 11,
y: 4,
},
{
upCount: null,
downCount: 2,
x: 11,
x0: 12,
y: 4,
},
{
upCount: 4,
downCount: null,
x: 12,
x0: 13,
y: 4,
},
],
},
];
const result = formatHistogramData(seriesList);
expect(result).toMatchSnapshot();
});
it('adds to existing up count', () => {
const seriesList: HistogramSeries[] = [
{
monitorId: 'monitor1',
data: [
{
upCount: 3,
downCount: null,
x: 10,
x0: 11,
y: 4,
},
{
upCount: 3,
downCount: null,
x: 11,
x0: 12,
y: 4,
},
{
upCount: 3,
downCount: null,
x: 12,
x0: 13,
y: 4,
},
],
},
{
monitorId: 'monitor2',
data: [
{
upCount: null,
downCount: 2,
x: 10,
x0: 11,
y: 4,
},
{
upCount: null,
downCount: 2,
x: 11,
x0: 12,
y: 4,
},
{
upCount: 4,
downCount: null,
x: 12,
x0: 13,
y: 4,
},
],
},
];
const result = formatHistogramData(seriesList);
expect(result).toMatchSnapshot();
});
it('adds to existing down count', () => {
const seriesList: HistogramSeries[] = [
{
monitorId: 'monitor1',
data: [
{
upCount: 3,
downCount: null,
x: 10,
x0: 11,
y: 4,
},
{
upCount: 3,
downCount: null,
x: 11,
x0: 12,
y: 4,
},
{
upCount: 3,
downCount: null,
x: 12,
x0: 13,
y: 4,
},
],
},
{
monitorId: 'monitor2',
data: [
{
upCount: null,
downCount: 2,
x: 10,
x0: 11,
y: 4,
},
{
upCount: null,
downCount: 2,
x: 11,
x0: 12,
y: 4,
},
{
upCount: 4,
downCount: null,
x: 12,
x0: 13,
y: 4,
},
],
},
{
monitorId: 'monitor3',
data: [
{
upCount: null,
downCount: 24,
x: 10,
x0: 11,
y: 4,
},
{
upCount: null,
downCount: 23,
x: 11,
x0: 12,
y: 4,
},
{
upCount: null,
downCount: 35,
x: 12,
x0: 13,
y: 4,
},
],
},
];
const result = formatHistogramData(seriesList);
expect(result).toMatchSnapshot();
});
it(`doesn't add an entry to either count when none exists`, () => {
const seriesList: HistogramSeries[] = [
{
monitorId: 'monitor1',
data: [
{
upCount: 3,
downCount: null,
x: 10,
x0: 11,
y: 4,
},
{
upCount: 3,
downCount: null,
x: 11,
x0: 12,
y: 4,
},
{
upCount: 3,
downCount: null,
x: 12,
x0: 13,
y: 4,
},
],
},
{
monitorId: 'monitor2',
data: [
{
upCount: null,
downCount: 2,
x: 10,
x0: 11,
y: 4,
},
{
upCount: null,
downCount: 2,
x: 11,
x0: 12,
y: 4,
},
{
upCount: 4,
downCount: null,
x: 12,
x0: 13,
y: 4,
},
],
},
{
monitorId: 'monitor3',
data: [
{
upCount: null,
downCount: 24,
x: 10,
x0: 11,
y: 4,
},
{
upCount: null,
downCount: null,
x: 11,
x0: 12,
y: 4,
},
{
upCount: null,
downCount: 35,
x: 12,
x0: 13,
y: 4,
},
],
},
];
const result = formatHistogramData(seriesList);
expect(result).toMatchSnapshot();
});
});

View file

@ -6,36 +6,48 @@
import { HistogramDataPoint, HistogramSeries } from '../../../../common/graphql/types';
export const formatHistogramData = (histogram: HistogramSeries[]) => {
const histogramSeriesData: { upSeriesData: any[]; downSeriesData: any[] } = {
upSeriesData: [],
downSeriesData: [],
};
// TODO: there's a lot of nesting here, refactor this function
histogram.forEach(({ data }) => {
if (data) {
data.forEach(dataPoint => {
if (dataPoint) {
const { x, x0, downCount } = dataPoint;
interface FormattedHistogramData {
upSeriesData: HistogramDataPoint[];
downSeriesData: HistogramDataPoint[];
}
/**
* This function reduces a series of monitors' histograms into a singular
* series, which is then displayed as a unified snapshot of the performance
* of all the monitors over time.
* @param histograms The series data for the provided monitors
*/
export const formatHistogramData = (histograms: HistogramSeries[]): FormattedHistogramData => {
return histograms
.map(({ data }) => data)
.filter(series => series !== null && series !== undefined)
.reduce(
(accumulatedData: FormattedHistogramData, data) => {
// `data` will not be null/undefined because those elements are filtered
data!.forEach(dataPoint => {
const { x, x0, downCount, upCount } = dataPoint;
const findPointInSeries = (hdp: HistogramDataPoint) => hdp.x === x && hdp.x0 === x0;
const upEntry = histogramSeriesData.upSeriesData.find(findPointInSeries);
const downEntry = histogramSeriesData.downSeriesData.find(findPointInSeries);
const upEntry = accumulatedData.upSeriesData.find(findPointInSeries);
const downEntry = accumulatedData.downSeriesData.find(findPointInSeries);
if (downCount) {
if (downEntry) {
downEntry.y += 1;
} else {
histogramSeriesData.downSeriesData.push({ x, x0, y: 1 });
accumulatedData.downSeriesData.push({ x, x0, y: 1 });
}
} else {
} else if (upCount) {
if (upEntry) {
upEntry.y += 1;
} else {
histogramSeriesData.upSeriesData.push({ x, x0, y: 1 });
accumulatedData.upSeriesData.push({ x, x0, y: 1 });
}
}
}
});
}
});
return histogramSeriesData;
});
return accumulatedData;
},
{
upSeriesData: [],
downSeriesData: [],
}
);
};

View file

@ -12,10 +12,12 @@ import {
} from '@elastic/eui';
import React, { Fragment } from 'react';
import { getMonitorPageBreadcrumb } from '../breadcrumbs';
import { MonitorCharts } from '../components/queries/monitor_charts';
import { MonitorPageTitleQuery } from '../components/queries/monitor_page_title';
import { MonitorStatusBar } from '../components/queries/monitor_status_bar';
import { Pings } from '../components/queries/ping_list';
import {
MonitorChartsQuery,
MonitorPageTitleQuery,
MonitorStatusBarQuery,
PingListQuery,
} from '../components/queries';
import { UMUpdateBreadcrumbs } from '../lib/lib';
import { UptimeCommonProps } from '../uptime_app';
@ -45,11 +47,11 @@ export class MonitorPage extends React.Component<Props> {
<Fragment>
<MonitorPageTitleQuery monitorId={id} {...this.props} />
<EuiSpacer />
<MonitorStatusBar monitorId={id} {...this.props} />
<MonitorStatusBarQuery monitorId={id} {...this.props} />
<EuiSpacer />
<MonitorCharts monitorId={id} {...this.props} />
<MonitorChartsQuery monitorId={id} {...this.props} />
<EuiSpacer />
<Pings monitorId={id} {...this.props} />
<PingListQuery monitorId={id} {...this.props} />
</Fragment>
);
}

View file

@ -7,11 +7,13 @@
import { EuiSpacer } from '@elastic/eui';
import React, { Fragment } from 'react';
import { getOverviewPageBreadcrumbs } from '../breadcrumbs';
import { EmptyState } from '../components/queries/empty_state';
import { ErrorList } from '../components/queries/error_list';
import { FilterBar } from '../components/queries/filter_bar';
import { MonitorList } from '../components/queries/monitor_list';
import { Snapshot } from '../components/queries/snapshot';
import {
EmptyStateQuery,
ErrorListQuery,
FilterBarQuery,
MonitorListQuery,
SnapshotQuery,
} from '../components/queries';
import { UMUpdateBreadcrumbs } from '../lib/lib';
import { UptimeCommonProps } from '../uptime_app';
@ -40,19 +42,19 @@ export class OverviewPage extends React.Component<Props, OverviewPageState> {
public render() {
return (
<Fragment>
<EmptyState {...this.props}>
<FilterBar
<EmptyStateQuery {...this.props}>
<FilterBarQuery
{...this.props}
updateQuery={(query: object | undefined) => {
this.setState({ currentFilterQuery: query ? JSON.stringify(query) : query });
}}
/>
<Snapshot filters={this.state.currentFilterQuery} {...this.props} />
<SnapshotQuery filters={this.state.currentFilterQuery} {...this.props} />
<EuiSpacer size="xl" />
<MonitorList filters={this.state.currentFilterQuery} {...this.props} />
<MonitorListQuery filters={this.state.currentFilterQuery} {...this.props} />
<EuiSpacer />
<ErrorList filters={this.state.currentFilterQuery} {...this.props} />
</EmptyState>
<ErrorListQuery filters={this.state.currentFilterQuery} {...this.props} />
</EmptyStateQuery>
</Fragment>
);
}

View file

@ -25,14 +25,14 @@ export const monitorsSchema = gql`
type HistogramSeries {
monitorId: String
data: [HistogramDataPoint]
data: [HistogramDataPoint!]
}
type Snapshot {
up: Int
down: Int
total: Int
histogram: [HistogramSeries]
histogram: [HistogramSeries!]
}
type DataPoint {
@ -78,7 +78,7 @@ export const monitorsSchema = gql`
}
type LatestMonitorsResult {
monitors: [LatestMonitor]
monitors: [LatestMonitor!]
}
type ErrorListItem {
@ -115,7 +115,7 @@ export const monitorsSchema = gql`
getFilterBar(dateRangeStart: String!, dateRangeEnd: String!): FilterBar
getErrorsList(dateRangeStart: String!, dateRangeEnd: String!, filters: String): [ErrorListItem]
getErrorsList(dateRangeStart: String!, dateRangeEnd: String!, filters: String): [ErrorListItem!]
getMonitorPageTitle(monitorId: String!): MonitorPageTitle
}

View file

@ -32,7 +32,6 @@ export default function ({ getService }) {
.post('/api/uptime/graphql')
.set('kbn-xsrf', 'foo')
.send({ ...getMonitorStatusBarQuery });
expect({ monitorStatus: responseData.map(status => omit(status, 'millisFromNow')) }).to.eql(
monitorStatus
);