mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* [Uptime] Monitor details page left side title (#53529) * update API * update query * hide layer control and added loc tags * update test * remove unused comment * update API * remove capitalization * style fix * update types * added location status number on details page * useref instead of createRef * update interface * update import * removed redundant file * fix header for empty data * refactor for most recent check * remove redundant code * remone unused translation * update status bar * update styling * update snaps * added API tests * fix types * fixing integration tests and a typo * remove unused translations * update tests * fixed PR feedback * update feedback * update messaging * update snap Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> * fix missing file * updated duplicated label * update snap * update test Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
41120ae84a
commit
63cc1a63ef
86 changed files with 1237 additions and 991 deletions
|
@ -3,7 +3,7 @@
|
|||
## Purpose
|
||||
|
||||
The purpose of this plugin is to provide users of Heartbeat more visibility of what's happening
|
||||
in their infrasturcture. It's primarily built using React and Apollo's GraphQL tools.
|
||||
in their infrastructure. It's primarily built using React and Apollo's GraphQL tools.
|
||||
|
||||
## Layout
|
||||
|
||||
|
@ -44,7 +44,7 @@ From `~/kibana/x-pack`, run `node scripts/jest.js`.
|
|||
### Functional tests
|
||||
|
||||
In one shell, from **~/kibana/x-pack**:
|
||||
`node scripts/functional_tests-server.js`
|
||||
`node scripts/functional_tests_server.js`
|
||||
|
||||
In another shell, from **~kibana/x-pack**:
|
||||
`node ../scripts/functional_test_runner.js --grep="{TEST_NAME}"`.
|
||||
|
|
|
@ -352,25 +352,6 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "getMonitorPageTitle",
|
||||
"description": "",
|
||||
"args": [
|
||||
{
|
||||
"name": "monitorId",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": { "kind": "OBJECT", "name": "MonitorPageTitle", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "getMonitorStates",
|
||||
"description": "Fetches the current state of Uptime monitors for the given parameters.",
|
||||
|
@ -2549,45 +2530,6 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "MonitorPageTitle",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "MonitorSummaryResult",
|
||||
|
|
|
@ -32,7 +32,6 @@ export interface Query {
|
|||
|
||||
getFilterBar?: FilterBar | null;
|
||||
|
||||
getMonitorPageTitle?: MonitorPageTitle | null;
|
||||
/** Fetches the current state of Uptime monitors for the given parameters. */
|
||||
getMonitorStates?: MonitorSummaryResult | null;
|
||||
/** Fetches details about the uptime index. */
|
||||
|
@ -484,13 +483,6 @@ export interface FilterBar {
|
|||
urls?: string[] | null;
|
||||
}
|
||||
|
||||
export interface MonitorPageTitle {
|
||||
id: string;
|
||||
|
||||
url?: string | null;
|
||||
|
||||
name?: string | null;
|
||||
}
|
||||
/** The primary object returned for monitor states. */
|
||||
export interface MonitorSummaryResult {
|
||||
/** Used to go to the next page of results */
|
||||
|
@ -736,24 +728,12 @@ export interface GetMonitorChartsDataQueryArgs {
|
|||
|
||||
location?: string | null;
|
||||
}
|
||||
export interface GetLatestMonitorsQueryArgs {
|
||||
/** The lower limit of the date range. */
|
||||
dateRangeStart: string;
|
||||
/** The upper limit of the date range. */
|
||||
dateRangeEnd: string;
|
||||
/** Optional: a specific monitor ID filter. */
|
||||
monitorId?: string | null;
|
||||
/** Optional: a specific instance location filter. */
|
||||
location?: string | null;
|
||||
}
|
||||
export interface GetFilterBarQueryArgs {
|
||||
dateRangeStart: string;
|
||||
|
||||
dateRangeEnd: string;
|
||||
}
|
||||
export interface GetMonitorPageTitleQueryArgs {
|
||||
monitorId: string;
|
||||
}
|
||||
|
||||
export interface GetMonitorStatesQueryArgs {
|
||||
dateRangeStart: string;
|
||||
|
||||
|
|
|
@ -6,5 +6,4 @@
|
|||
|
||||
export * from './common';
|
||||
export * from './snapshot';
|
||||
export * from './monitor/monitor_details';
|
||||
export * from './monitor/monitor_locations';
|
||||
export * from './monitor';
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
// IO type for validation
|
|
@ -4,5 +4,5 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { MonitorSSLCertificate } from './monitor_ssl_certificate';
|
||||
export { MonitorStatusBar, MonitorStatusBarComponent } from './monitor_status_bar';
|
||||
export * from './details';
|
||||
export * from './locations';
|
|
@ -1,29 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EmptyStatusBar component renders a default message when no message provided 1`] = `
|
||||
<EuiPanel>
|
||||
<EuiFlexGroup
|
||||
gutterSize="l"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
No data found for monitor id mon_id
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
`;
|
||||
|
||||
exports[`EmptyStatusBar component renders a message when provided 1`] = `
|
||||
<EuiPanel>
|
||||
<EuiFlexGroup
|
||||
gutterSize="l"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
foobarbaz
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
`;
|
|
@ -5,22 +5,14 @@ Array [
|
|||
<div
|
||||
class="euiSpacer euiSpacer--s"
|
||||
/>,
|
||||
.c0 {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
<div
|
||||
aria-label="SSL certificate expires"
|
||||
class="euiText euiText--small euiText--constrainedWidth"
|
||||
>
|
||||
<div
|
||||
aria-label="SSL certificate expires"
|
||||
class="euiText euiText--small euiText--constrainedWidth"
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
<div
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
SSL certificate expires in 2 months
|
||||
</div>
|
||||
SSL certificate expires in 2 months
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
|
|
|
@ -2,46 +2,24 @@
|
|||
|
||||
exports[`MonitorStatusBar component renders duration in ms, not us 1`] = `
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive euiFlexGroup--wrap"
|
||||
class="euiFlexGroup euiFlexGroup--directionColumn euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
aria-label="Monitor status"
|
||||
class="euiHealth"
|
||||
style="line-height:inherit"
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="euiIcon euiIcon--medium euiIcon--success euiIcon-isLoading"
|
||||
focusable="false"
|
||||
height="16"
|
||||
role="img"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
Up
|
||||
</div>
|
||||
</div>
|
||||
<h2>
|
||||
Up in 2 Locations
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<a
|
||||
aria-label="Monitor URL link"
|
||||
|
@ -55,16 +33,23 @@ exports[`MonitorStatusBar component renders duration in ms, not us 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Monitor duration in milliseconds"
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
1234ms
|
||||
</div>
|
||||
<div
|
||||
aria-label="Time since last check"
|
||||
class="euiFlexItem"
|
||||
>
|
||||
15 minutes ago
|
||||
<span
|
||||
class="euiTextColor euiTextColor--subdued euiTitle euiTitle--xsmall"
|
||||
>
|
||||
<h1
|
||||
data-test-subj="monitor-page-title"
|
||||
>
|
||||
id1
|
||||
</h1>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiSpacer euiSpacer--l"
|
||||
/>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -227,54 +227,24 @@ exports[`UptimeDatePicker component validates props with shallow render 1`] = `
|
|||
commonlyUsedRanges={
|
||||
Array [
|
||||
Object {
|
||||
"end": "now/d",
|
||||
"end": "now",
|
||||
"label": "Today",
|
||||
"start": "now/d",
|
||||
},
|
||||
Object {
|
||||
"end": "now/w",
|
||||
"label": "This week",
|
||||
"end": "now",
|
||||
"label": "Week to date",
|
||||
"start": "now/w",
|
||||
},
|
||||
Object {
|
||||
"end": "now",
|
||||
"label": "Last 15 minutes",
|
||||
"start": "now-15m",
|
||||
"label": "Month to date",
|
||||
"start": "now/M",
|
||||
},
|
||||
Object {
|
||||
"end": "now",
|
||||
"label": "Last 30 minutes",
|
||||
"start": "now-30m",
|
||||
},
|
||||
Object {
|
||||
"end": "now",
|
||||
"label": "Last 1 hour",
|
||||
"start": "now-1h",
|
||||
},
|
||||
Object {
|
||||
"end": "now",
|
||||
"label": "Last 24 hours",
|
||||
"start": "now-24h",
|
||||
},
|
||||
Object {
|
||||
"end": "now",
|
||||
"label": "Last 7 days",
|
||||
"start": "now-7d",
|
||||
},
|
||||
Object {
|
||||
"end": "now",
|
||||
"label": "Last 30 days",
|
||||
"start": "now-30d",
|
||||
},
|
||||
Object {
|
||||
"end": "now",
|
||||
"label": "Last 90 days",
|
||||
"start": "now-90d",
|
||||
},
|
||||
Object {
|
||||
"end": "now",
|
||||
"label": "Last 2 year",
|
||||
"start": "now-1y",
|
||||
"label": "Year to date",
|
||||
"start": "now/y",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,21 +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 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();
|
||||
});
|
||||
});
|
|
@ -8,7 +8,7 @@ import React from 'react';
|
|||
import moment from 'moment';
|
||||
import { renderWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { PingTls } from '../../../../common/graphql/types';
|
||||
import { MonitorSSLCertificate } from '../monitor_status_bar';
|
||||
import { MonitorSSLCertificate } from '../monitor_status_details/monitor_status_bar';
|
||||
|
||||
describe('MonitorStatusBar component', () => {
|
||||
let monitorTls: PingTls;
|
||||
|
|
|
@ -8,34 +8,59 @@ import moment from 'moment';
|
|||
import React from 'react';
|
||||
import { renderWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { Ping } from '../../../../common/graphql/types';
|
||||
import { MonitorStatusBarComponent } from '../monitor_status_bar';
|
||||
import { MonitorStatusBarComponent } from '../monitor_status_details/monitor_status_bar';
|
||||
|
||||
describe('MonitorStatusBar component', () => {
|
||||
let monitorStatus: Ping[];
|
||||
let monitorStatus: Ping;
|
||||
let monitorLocations: any;
|
||||
let dateStart: string;
|
||||
let dateEnd: string;
|
||||
|
||||
beforeEach(() => {
|
||||
monitorStatus = [
|
||||
{
|
||||
id: 'id1',
|
||||
timestamp: moment(new Date())
|
||||
.subtract(15, 'm')
|
||||
.toString(),
|
||||
monitor: {
|
||||
duration: {
|
||||
us: 1234567,
|
||||
},
|
||||
status: 'up',
|
||||
},
|
||||
url: {
|
||||
full: 'https://www.example.com/',
|
||||
monitorStatus = {
|
||||
id: 'id1',
|
||||
timestamp: moment(new Date())
|
||||
.subtract(15, 'm')
|
||||
.toString(),
|
||||
monitor: {
|
||||
duration: {
|
||||
us: 1234567,
|
||||
},
|
||||
status: 'up',
|
||||
},
|
||||
];
|
||||
url: {
|
||||
full: 'https://www.example.com/',
|
||||
},
|
||||
};
|
||||
|
||||
monitorLocations = {
|
||||
monitorId: 'secure-avc',
|
||||
locations: [
|
||||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
},
|
||||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
dateStart = moment('01-01-2010').toString();
|
||||
dateEnd = moment('10-10-2010').toString();
|
||||
});
|
||||
|
||||
it('renders duration in ms, not us', () => {
|
||||
const component = renderWithIntl(
|
||||
<MonitorStatusBarComponent loading={false} data={{ monitorStatus }} monitorId="foo" />
|
||||
<MonitorStatusBarComponent
|
||||
monitorStatus={monitorStatus}
|
||||
monitorId="id1"
|
||||
dateStart={dateStart}
|
||||
dateEnd={dateEnd}
|
||||
monitorLocations={monitorLocations}
|
||||
loadMonitorStatus={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
@ -6,37 +6,16 @@
|
|||
|
||||
import { shallowWithIntl, renderWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import React from 'react';
|
||||
import { UptimeDatePicker, CommonlyUsedRange } from '../uptime_date_picker';
|
||||
import { UptimeDatePicker } from '../uptime_date_picker';
|
||||
|
||||
describe('UptimeDatePicker component', () => {
|
||||
let commonlyUsedRange: CommonlyUsedRange[];
|
||||
|
||||
beforeEach(() => {
|
||||
commonlyUsedRange = [
|
||||
{ from: 'now/d', to: 'now/d', display: 'Today' },
|
||||
{ from: 'now/w', to: 'now/w', display: 'This week' },
|
||||
{ from: 'now-15m', to: 'now', display: 'Last 15 minutes' },
|
||||
{ from: 'now-30m', to: 'now', display: 'Last 30 minutes' },
|
||||
{ from: 'now-1h', to: 'now', display: 'Last 1 hour' },
|
||||
{ from: 'now-24h', to: 'now', display: 'Last 24 hours' },
|
||||
{ from: 'now-7d', to: 'now', display: 'Last 7 days' },
|
||||
{ from: 'now-30d', to: 'now', display: 'Last 30 days' },
|
||||
{ from: 'now-90d', to: 'now', display: 'Last 90 days' },
|
||||
{ from: 'now-1y', to: 'now', display: 'Last 2 year' },
|
||||
];
|
||||
});
|
||||
|
||||
it('validates props with shallow render', () => {
|
||||
const component = shallowWithIntl(
|
||||
<UptimeDatePicker commonlyUsedRanges={commonlyUsedRange} refreshApp={jest.fn()} />
|
||||
);
|
||||
const component = shallowWithIntl(<UptimeDatePicker refreshApp={jest.fn()} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders properly with mock data', () => {
|
||||
const component = renderWithIntl(
|
||||
<UptimeDatePicker commonlyUsedRanges={commonlyUsedRange} refreshApp={jest.fn()} />
|
||||
);
|
||||
const component = renderWithIntl(<UptimeDatePicker refreshApp={jest.fn()} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
|
|
@ -15,12 +15,12 @@ exports[`DataMissing component renders basePath and headingMessage 1`] = `
|
|||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="{configureHeartbeatLink} to start collecting uptime data."
|
||||
id="xpack.uptime.emptyState.configureHeartbeatToGetStartedMessage"
|
||||
defaultMessage="{configureHeartbeatLink} to start logging uptime data."
|
||||
id="xpack.uptime.dataMissing.configureHeartbeatToGetStartedMessage"
|
||||
values={
|
||||
Object {
|
||||
"configureHeartbeatLink": <ForwardRef
|
||||
href="foo/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
href="/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
exports[`EmptyState component does not render empty state with appropriate base path and no docs 1`] = `
|
||||
<EmptyStateComponent
|
||||
basePath="foo"
|
||||
data={
|
||||
Object {
|
||||
"statesIndexStatus": Object {
|
||||
|
@ -118,7 +117,6 @@ exports[`EmptyState component does not render empty state with appropriate base
|
|||
loading={false}
|
||||
>
|
||||
<DataMissing
|
||||
basePath="foo"
|
||||
headingMessage="No uptime data found"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
|
@ -148,12 +146,12 @@ exports[`EmptyState component does not render empty state with appropriate base
|
|||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="{configureHeartbeatLink} to start collecting uptime data."
|
||||
id="xpack.uptime.emptyState.configureHeartbeatToGetStartedMessage"
|
||||
defaultMessage="{configureHeartbeatLink} to start logging uptime data."
|
||||
id="xpack.uptime.dataMissing.configureHeartbeatToGetStartedMessage"
|
||||
values={
|
||||
Object {
|
||||
"configureHeartbeatLink": <ForwardRef
|
||||
href="foo/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
href="/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -244,12 +242,12 @@ exports[`EmptyState component does not render empty state with appropriate base
|
|||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="{configureHeartbeatLink} to start collecting uptime data."
|
||||
id="xpack.uptime.emptyState.configureHeartbeatToGetStartedMessage"
|
||||
defaultMessage="{configureHeartbeatLink} to start logging uptime data."
|
||||
id="xpack.uptime.dataMissing.configureHeartbeatToGetStartedMessage"
|
||||
values={
|
||||
Object {
|
||||
"configureHeartbeatLink": <ForwardRef
|
||||
href="foo/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
href="/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -262,12 +260,12 @@ exports[`EmptyState component does not render empty state with appropriate base
|
|||
}
|
||||
>
|
||||
<EuiLink
|
||||
href="foo/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
href="/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
target="_blank"
|
||||
>
|
||||
<a
|
||||
className="euiLink euiLink--primary"
|
||||
href="foo/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
href="/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
|
@ -280,7 +278,7 @@ exports[`EmptyState component does not render empty state with appropriate base
|
|||
</FormattedMessage>
|
||||
</a>
|
||||
</EuiLink>
|
||||
to start collecting uptime data.
|
||||
to start logging uptime data.
|
||||
</FormattedMessage>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -301,7 +299,6 @@ exports[`EmptyState component does not render empty state with appropriate base
|
|||
|
||||
exports[`EmptyState component doesn't render child components when count is falsey 1`] = `
|
||||
<EmptyStateComponent
|
||||
basePath=""
|
||||
intl={
|
||||
Object {
|
||||
"defaultFormats": Object {},
|
||||
|
@ -470,7 +467,6 @@ exports[`EmptyState component doesn't render child components when count is fals
|
|||
|
||||
exports[`EmptyState component notifies when index does not exist 1`] = `
|
||||
<EmptyStateComponent
|
||||
basePath="foo"
|
||||
data={
|
||||
Object {
|
||||
"statesIndexStatus": Object {
|
||||
|
@ -586,7 +582,6 @@ exports[`EmptyState component notifies when index does not exist 1`] = `
|
|||
loading={false}
|
||||
>
|
||||
<DataMissing
|
||||
basePath="foo"
|
||||
headingMessage="Uptime index not found"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
|
@ -616,12 +611,12 @@ exports[`EmptyState component notifies when index does not exist 1`] = `
|
|||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="{configureHeartbeatLink} to start collecting uptime data."
|
||||
id="xpack.uptime.emptyState.configureHeartbeatToGetStartedMessage"
|
||||
defaultMessage="{configureHeartbeatLink} to start logging uptime data."
|
||||
id="xpack.uptime.dataMissing.configureHeartbeatToGetStartedMessage"
|
||||
values={
|
||||
Object {
|
||||
"configureHeartbeatLink": <ForwardRef
|
||||
href="foo/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
href="/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -712,12 +707,12 @@ exports[`EmptyState component notifies when index does not exist 1`] = `
|
|||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="{configureHeartbeatLink} to start collecting uptime data."
|
||||
id="xpack.uptime.emptyState.configureHeartbeatToGetStartedMessage"
|
||||
defaultMessage="{configureHeartbeatLink} to start logging uptime data."
|
||||
id="xpack.uptime.dataMissing.configureHeartbeatToGetStartedMessage"
|
||||
values={
|
||||
Object {
|
||||
"configureHeartbeatLink": <ForwardRef
|
||||
href="foo/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
href="/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -730,12 +725,12 @@ exports[`EmptyState component notifies when index does not exist 1`] = `
|
|||
}
|
||||
>
|
||||
<EuiLink
|
||||
href="foo/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
href="/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
target="_blank"
|
||||
>
|
||||
<a
|
||||
className="euiLink euiLink--primary"
|
||||
href="foo/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
href="/app/kibana#/home/tutorial/uptimeMonitors"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
|
@ -748,7 +743,7 @@ exports[`EmptyState component notifies when index does not exist 1`] = `
|
|||
</FormattedMessage>
|
||||
</a>
|
||||
</EuiLink>
|
||||
to start collecting uptime data.
|
||||
to start logging uptime data.
|
||||
</FormattedMessage>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -783,7 +778,6 @@ exports[`EmptyState component renders child components when count is truthy 1`]
|
|||
|
||||
exports[`EmptyState component renders error message when an error occurs 1`] = `
|
||||
<EmptyStateComponent
|
||||
basePath=""
|
||||
errors={
|
||||
Array [
|
||||
Object {
|
||||
|
@ -1043,7 +1037,6 @@ exports[`EmptyState component renders error message when an error occurs 1`] = `
|
|||
|
||||
exports[`EmptyState component renders loading state if no errors or doc count 1`] = `
|
||||
<EmptyStateComponent
|
||||
basePath=""
|
||||
intl={
|
||||
Object {
|
||||
"defaultFormats": Object {},
|
||||
|
|
|
@ -10,7 +10,7 @@ import { DataMissing } from '../data_missing';
|
|||
|
||||
describe('DataMissing component', () => {
|
||||
it('renders basePath and headingMessage', () => {
|
||||
const component = shallowWithIntl(<DataMissing basePath="foo" headingMessage="bar" />);
|
||||
const component = shallowWithIntl(<DataMissing headingMessage="bar" />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,7 +24,7 @@ describe('EmptyState component', () => {
|
|||
|
||||
it('renders child components when count is truthy', () => {
|
||||
const component = shallowWithIntl(
|
||||
<EmptyStateComponent basePath="" data={{ statesIndexStatus }} loading={false}>
|
||||
<EmptyStateComponent data={{ statesIndexStatus }} loading={false}>
|
||||
<div>Foo</div>
|
||||
<div>Bar</div>
|
||||
<div>Baz</div>
|
||||
|
@ -35,7 +35,7 @@ describe('EmptyState component', () => {
|
|||
|
||||
it(`doesn't render child components when count is falsey`, () => {
|
||||
const component = mountWithIntl(
|
||||
<EmptyStateComponent basePath="" data={undefined} loading={false}>
|
||||
<EmptyStateComponent data={undefined} loading={false}>
|
||||
<div>Shouldn't be rendered</div>
|
||||
</EmptyStateComponent>
|
||||
);
|
||||
|
@ -57,7 +57,7 @@ describe('EmptyState component', () => {
|
|||
},
|
||||
];
|
||||
const component = mountWithIntl(
|
||||
<EmptyStateComponent basePath="" data={undefined} errors={errors} loading={false}>
|
||||
<EmptyStateComponent data={undefined} errors={errors} loading={false}>
|
||||
<div>Shouldn't appear...</div>
|
||||
</EmptyStateComponent>
|
||||
);
|
||||
|
@ -66,7 +66,7 @@ describe('EmptyState component', () => {
|
|||
|
||||
it('renders loading state if no errors or doc count', () => {
|
||||
const component = mountWithIntl(
|
||||
<EmptyStateComponent basePath="" loading={true}>
|
||||
<EmptyStateComponent loading={true}>
|
||||
<div>Should appear even while loading...</div>
|
||||
</EmptyStateComponent>
|
||||
);
|
||||
|
@ -81,7 +81,7 @@ describe('EmptyState component', () => {
|
|||
indexExists: true,
|
||||
};
|
||||
const component = mountWithIntl(
|
||||
<EmptyStateComponent basePath="foo" data={{ statesIndexStatus }} loading={false}>
|
||||
<EmptyStateComponent data={{ statesIndexStatus }} loading={false}>
|
||||
<div>If this is in the snapshot the test should fail</div>
|
||||
</EmptyStateComponent>
|
||||
);
|
||||
|
@ -91,7 +91,7 @@ describe('EmptyState component', () => {
|
|||
it('notifies when index does not exist', () => {
|
||||
statesIndexStatus.indexExists = false;
|
||||
const component = mountWithIntl(
|
||||
<EmptyStateComponent basePath="foo" data={{ statesIndexStatus }} loading={false}>
|
||||
<EmptyStateComponent data={{ statesIndexStatus }} loading={false}>
|
||||
<div>This text should not render</div>
|
||||
</EmptyStateComponent>
|
||||
);
|
||||
|
|
|
@ -14,48 +14,51 @@ import {
|
|||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { UptimeSettingsContext } from '../../../contexts';
|
||||
|
||||
interface DataMissingProps {
|
||||
basePath: string;
|
||||
headingMessage: string;
|
||||
}
|
||||
|
||||
export const DataMissing = ({ basePath, headingMessage }: DataMissingProps) => (
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiPanel>
|
||||
<EuiEmptyPrompt
|
||||
iconType="uptimeApp"
|
||||
title={
|
||||
<EuiTitle size="l">
|
||||
<h3>{headingMessage}</h3>
|
||||
</EuiTitle>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.emptyState.configureHeartbeatToGetStartedMessage"
|
||||
defaultMessage="{configureHeartbeatLink} to start collecting uptime data."
|
||||
values={{
|
||||
configureHeartbeatLink: (
|
||||
<EuiLink
|
||||
target="_blank"
|
||||
href={`${basePath}/app/kibana#/home/tutorial/uptimeMonitors`}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.emptyState.configureHeartbeatLinkText"
|
||||
defaultMessage="Configure Heartbeat"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
export const DataMissing = ({ headingMessage }: DataMissingProps) => {
|
||||
const { basePath } = useContext(UptimeSettingsContext);
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiPanel>
|
||||
<EuiEmptyPrompt
|
||||
iconType="uptimeApp"
|
||||
title={
|
||||
<EuiTitle size="l">
|
||||
<h3>{headingMessage}</h3>
|
||||
</EuiTitle>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.dataMissing.configureHeartbeatToGetStartedMessage"
|
||||
defaultMessage="{configureHeartbeatLink} to start logging uptime data."
|
||||
values={{
|
||||
configureHeartbeatLink: (
|
||||
<EuiLink
|
||||
target="_blank"
|
||||
href={`${basePath}/app/kibana#/home/tutorial/uptimeMonitors`}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.emptyState.configureHeartbeatLinkText"
|
||||
defaultMessage="Configure Heartbeat"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -18,13 +18,12 @@ interface EmptyStateQueryResult {
|
|||
}
|
||||
|
||||
interface EmptyStateProps {
|
||||
basePath: string;
|
||||
children: JSX.Element[] | JSX.Element;
|
||||
}
|
||||
|
||||
type Props = UptimeGraphQLQueryProps<EmptyStateQueryResult> & EmptyStateProps;
|
||||
|
||||
export const EmptyStateComponent = ({ basePath, children, data, errors }: Props) => {
|
||||
export const EmptyStateComponent = ({ children, data, errors }: Props) => {
|
||||
if (errors) {
|
||||
return <EmptyStateError errors={errors} />;
|
||||
}
|
||||
|
@ -33,7 +32,6 @@ export const EmptyStateComponent = ({ basePath, children, data, errors }: Props)
|
|||
if (!indexExists) {
|
||||
return (
|
||||
<DataMissing
|
||||
basePath={basePath}
|
||||
headingMessage={i18n.translate('xpack.uptime.emptyState.noIndexTitle', {
|
||||
defaultMessage: 'Uptime index not found',
|
||||
})}
|
||||
|
@ -42,7 +40,6 @@ export const EmptyStateComponent = ({ basePath, children, data, errors }: Props)
|
|||
} else if (indexExists && docCount && docCount.count === 0) {
|
||||
return (
|
||||
<DataMissing
|
||||
basePath={basePath}
|
||||
headingMessage={i18n.translate('xpack.uptime.emptyState.noDataMessage', {
|
||||
defaultMessage: 'No uptime data found',
|
||||
})}
|
||||
|
|
|
@ -1,31 +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 { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
monitorId: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export const EmptyStatusBar = ({ message, monitorId }: Props) => (
|
||||
<EuiPanel>
|
||||
<EuiFlexGroup gutterSize="l">
|
||||
<EuiFlexItem grow={false}>
|
||||
{!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>
|
||||
);
|
|
@ -6,14 +6,12 @@
|
|||
|
||||
export { DonutChart } from './charts/donut_chart';
|
||||
export { EmptyState } from './empty_state';
|
||||
export { EmptyStatusBar } from './empty_status_bar';
|
||||
export { MonitorStatusBar } from './monitor_status_details';
|
||||
export { FilterGroup } from './filter_group';
|
||||
export { IntegrationLink } from './integration_link';
|
||||
export { KueryBar } from './kuery_bar';
|
||||
export { MonitorCharts } from './monitor_charts';
|
||||
export { MonitorList } from './monitor_list';
|
||||
export { MonitorPageTitle } from './monitor_page_title';
|
||||
export { MonitorStatusBar } from './monitor_status_bar';
|
||||
export { OverviewPageParsingErrorCallout } from './overview_page_parsing_error_callout';
|
||||
export { PingList } from './ping_list';
|
||||
export { Snapshot } from './snapshot';
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { getLayerList } from '../map_config';
|
||||
import { mockLayerList } from './__mocks__/mock';
|
||||
import { LocationPoint } from '../embedded_map';
|
||||
import { UptimeAppColors } from '../../../../../uptime_app';
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
return {
|
||||
|
@ -17,6 +18,7 @@ jest.mock('uuid', () => {
|
|||
describe('map_config', () => {
|
||||
let upPoints: LocationPoint[];
|
||||
let downPoints: LocationPoint[];
|
||||
let colors: Pick<UptimeAppColors, 'gray' | 'danger'>;
|
||||
|
||||
beforeEach(() => {
|
||||
upPoints = [
|
||||
|
@ -29,11 +31,15 @@ describe('map_config', () => {
|
|||
{ lat: '55.487239', lon: '13.399262' },
|
||||
{ lat: '54.487239', lon: '14.399262' },
|
||||
];
|
||||
colors = {
|
||||
danger: '#BC261E',
|
||||
gray: '#000',
|
||||
};
|
||||
});
|
||||
|
||||
describe('#getLayerList', () => {
|
||||
test('it returns the low poly layer', () => {
|
||||
const layerList = getLayerList(upPoints, downPoints, { danger: '#BC261E', gray: '#000' });
|
||||
const layerList = getLayerList(upPoints, downPoints, colors);
|
||||
expect(layerList).toStrictEqual(mockLayerList);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useContext } from 'react';
|
||||
import React, { useEffect, useState, useContext, useRef } from 'react';
|
||||
import uuid from 'uuid';
|
||||
import styled from 'styled-components';
|
||||
|
||||
|
@ -48,20 +48,31 @@ const EmbeddedPanel = styled.div`
|
|||
export const EmbeddedMap = ({ upPoints, downPoints }: EmbeddedMapProps) => {
|
||||
const { colors } = useContext(UptimeSettingsContext);
|
||||
const [embeddable, setEmbeddable] = useState<MapEmbeddable>();
|
||||
|
||||
const embeddableRoot: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
const embeddableRoot: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
|
||||
const factory = start.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE);
|
||||
|
||||
const input = {
|
||||
id: uuid.v4(),
|
||||
filters: [],
|
||||
hidePanelTitles: true,
|
||||
query: { query: '', language: 'kuery' },
|
||||
refreshConfig: { value: 0, pause: false },
|
||||
query: {
|
||||
query: '',
|
||||
language: 'kuery',
|
||||
},
|
||||
refreshConfig: {
|
||||
value: 0,
|
||||
pause: false,
|
||||
},
|
||||
viewMode: 'view',
|
||||
isLayerTOCOpen: false,
|
||||
hideFilterActions: true,
|
||||
mapCenter: { lon: 11, lat: 20, zoom: 0 },
|
||||
// Zoom Lat/Lon values are set to make sure map is in center in the panel
|
||||
// It wil also omit Greenland/Antarctica etc
|
||||
mapCenter: {
|
||||
lon: 11,
|
||||
lat: 20,
|
||||
zoom: 0,
|
||||
},
|
||||
disableInteractive: true,
|
||||
disableTooltipControl: true,
|
||||
hideToolbarOverlay: true,
|
||||
|
@ -81,16 +92,19 @@ export const EmbeddedMap = ({ upPoints, downPoints }: EmbeddedMapProps) => {
|
|||
setEmbeddable(embeddableObject);
|
||||
}
|
||||
setupEmbeddable();
|
||||
|
||||
// we want this effect to execute exactly once after the component mounts
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// update map layers based on points
|
||||
useEffect(() => {
|
||||
if (embeddable) {
|
||||
embeddable.setLayerList(getLayerList(upPoints, downPoints, colors));
|
||||
}
|
||||
}, [upPoints, downPoints, embeddable, colors]);
|
||||
|
||||
// We can only render after embeddable has already initialized
|
||||
useEffect(() => {
|
||||
if (embeddableRoot.current && embeddable) {
|
||||
embeddable.render(embeddableRoot.current);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import lowPolyLayerFeatures from './low_poly_layer.json';
|
||||
import { LocationPoint } from './embedded_map';
|
||||
import { UptimeAppColors } from '../../../../uptime_app';
|
||||
|
||||
/**
|
||||
* Returns `Source/Destination Point-to-point` Map LayerList configuration, with a source,
|
||||
|
@ -15,7 +16,7 @@ import { LocationPoint } from './embedded_map';
|
|||
export const getLayerList = (
|
||||
upPoints: LocationPoint[],
|
||||
downPoints: LocationPoint[],
|
||||
{ gray, danger }: { gray: string; danger: string }
|
||||
{ gray, danger }: Pick<UptimeAppColors, 'gray' | 'danger'>
|
||||
) => {
|
||||
return [getLowPolyLayer(), getDownPointsLayer(downPoints, danger), getUpPointsLayer(upPoints)];
|
||||
};
|
||||
|
|
|
@ -11,10 +11,12 @@ import { LocationStatusTags } from './location_status_tags';
|
|||
import { EmbeddedMap, LocationPoint } from './embeddables/embedded_map';
|
||||
import { MonitorLocations } from '../../../../common/runtime_types';
|
||||
|
||||
// These height/width values are used to make sure map is in center of panel
|
||||
// And to make sure, it doesn't take too much space
|
||||
const MapPanel = styled.div`
|
||||
height: 240px;
|
||||
width: 520px;
|
||||
margin-right: 10px;
|
||||
margin-right: 20px;
|
||||
`;
|
||||
|
||||
interface LocationMapProps {
|
||||
|
|
|
@ -1,38 +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 { EuiTextColor, EuiTitle } from '@elastic/eui';
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { MonitorPageTitle as TitleType } from '../../../common/graphql/types';
|
||||
import { UptimeGraphQLQueryProps, withUptimeGraphQL } from '../higher_order';
|
||||
import { monitorPageTitleQuery } from '../../queries';
|
||||
|
||||
interface MonitorPageTitleQueryResult {
|
||||
monitorPageTitle?: TitleType;
|
||||
}
|
||||
|
||||
interface MonitorPageTitleProps {
|
||||
monitorId: string;
|
||||
}
|
||||
|
||||
type Props = MonitorPageTitleProps & UptimeGraphQLQueryProps<MonitorPageTitleQueryResult>;
|
||||
|
||||
export const MonitorPageTitleComponent = ({ data }: Props) =>
|
||||
data && data.monitorPageTitle ? (
|
||||
<EuiTitle size="xxs">
|
||||
<EuiTextColor color="subdued">
|
||||
<h1 data-test-subj="monitor-page-title">{data.monitorPageTitle.id}</h1>
|
||||
</EuiTextColor>
|
||||
</EuiTitle>
|
||||
) : (
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
);
|
||||
|
||||
export const MonitorPageTitle = withUptimeGraphQL<
|
||||
MonitorPageTitleQueryResult,
|
||||
MonitorPageTitleProps
|
||||
>(MonitorPageTitleComponent, monitorPageTitleQuery);
|
|
@ -1,80 +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 { EuiFlexGroup, EuiFlexItem, EuiHealth, EuiLink } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { get } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { Ping } from '../../../../common/graphql/types';
|
||||
import { UptimeGraphQLQueryProps, withUptimeGraphQL } from '../../higher_order';
|
||||
import { monitorStatusBarQuery } from '../../../queries';
|
||||
import { EmptyStatusBar } from '../empty_status_bar';
|
||||
import { convertMicrosecondsToMilliseconds } from '../../../lib/helper';
|
||||
import { MonitorSSLCertificate } from './monitor_ssl_certificate';
|
||||
import * as labels from './translations';
|
||||
|
||||
interface MonitorStatusBarQueryResult {
|
||||
monitorStatus?: Ping[];
|
||||
}
|
||||
|
||||
interface MonitorStatusBarProps {
|
||||
monitorId: string;
|
||||
}
|
||||
|
||||
type Props = MonitorStatusBarProps & UptimeGraphQLQueryProps<MonitorStatusBarQueryResult>;
|
||||
|
||||
export const MonitorStatusBarComponent = ({ data, monitorId }: Props) => {
|
||||
if (data?.monitorStatus?.length) {
|
||||
const { monitor, timestamp, tls } = data.monitorStatus[0];
|
||||
const duration: number | undefined = get(monitor, 'duration.us', undefined);
|
||||
const status = get<'up' | 'down'>(monitor, 'status', 'down');
|
||||
const full = get<string>(data.monitorStatus[0], 'url.full');
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="l" wrap>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth
|
||||
aria-label={labels.healthStatusMessageAriaLabel}
|
||||
color={status === 'up' ? 'success' : 'danger'}
|
||||
style={{ lineHeight: 'inherit' }}
|
||||
>
|
||||
{status === 'up' ? labels.upLabel : labels.downLabel}
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink aria-label={labels.monitorUrlLinkAriaLabel} href={full} target="_blank">
|
||||
{full}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexItem>
|
||||
{!!duration && (
|
||||
<EuiFlexItem aria-label={labels.durationTextAriaLabel} grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorStatusBar.healthStatus.durationInMillisecondsMessage"
|
||||
values={{ duration: convertMicrosecondsToMilliseconds(duration) }}
|
||||
defaultMessage="{duration}ms"
|
||||
description="The 'ms' is an abbreviation for 'milliseconds'."
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem aria-label={labels.timestampFromNowTextAriaLabel} grow={true}>
|
||||
{moment(new Date(timestamp).valueOf()).fromNow()}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<MonitorSSLCertificate tls={tls} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return <EmptyStatusBar message={labels.loadingMessage} monitorId={monitorId} />;
|
||||
};
|
||||
|
||||
export const MonitorStatusBar = withUptimeGraphQL<
|
||||
MonitorStatusBarQueryResult,
|
||||
MonitorStatusBarProps
|
||||
>(MonitorStatusBarComponent, monitorStatusBarQuery);
|
|
@ -0,0 +1,51 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`StatusByLocation component renders all locations are down 1`] = `
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<h2>
|
||||
Down in 2 Locations
|
||||
</h2>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`StatusByLocation component renders when down in some locations 1`] = `
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<h2>
|
||||
Down in 1/2 Locations
|
||||
</h2>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`StatusByLocation component renders when only one location and it is down 1`] = `
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<h2>
|
||||
Down in 1 Location
|
||||
</h2>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`StatusByLocation component renders when only one location and it is up 1`] = `
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<h2>
|
||||
Up in 1 Location
|
||||
</h2>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`StatusByLocation component renders when up in all locations 1`] = `
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<h2>
|
||||
Up in 2 Locations
|
||||
</h2>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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 { renderWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { MonitorLocation } from '../../../../../common/runtime_types';
|
||||
import { StatusByLocations } from '../';
|
||||
|
||||
describe('StatusByLocation component', () => {
|
||||
let monitorLocations: MonitorLocation[];
|
||||
|
||||
it('renders when up in all locations', () => {
|
||||
monitorLocations = [
|
||||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
},
|
||||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
},
|
||||
];
|
||||
const component = renderWithIntl(<StatusByLocations locations={monitorLocations} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders when only one location and it is up', () => {
|
||||
monitorLocations = [
|
||||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
},
|
||||
];
|
||||
const component = renderWithIntl(<StatusByLocations locations={monitorLocations} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders when only one location and it is down', () => {
|
||||
monitorLocations = [
|
||||
{
|
||||
summary: { up: 0, down: 4 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
},
|
||||
];
|
||||
const component = renderWithIntl(<StatusByLocations locations={monitorLocations} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders all locations are down', () => {
|
||||
monitorLocations = [
|
||||
{
|
||||
summary: { up: 0, down: 4 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
},
|
||||
{
|
||||
summary: { up: 0, down: 4 },
|
||||
geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
},
|
||||
];
|
||||
const component = renderWithIntl(<StatusByLocations locations={monitorLocations} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders when down in some locations', () => {
|
||||
monitorLocations = [
|
||||
{
|
||||
summary: { up: 0, down: 4 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
},
|
||||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
},
|
||||
];
|
||||
const component = renderWithIntl(<StatusByLocations locations={monitorLocations} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -5,12 +5,12 @@
|
|||
*/
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from '../../../state';
|
||||
import { getMonitorLocations } from '../../../state/selectors';
|
||||
import { selectMonitorLocations } from '../../../state/selectors';
|
||||
import { fetchMonitorLocations } from '../../../state/actions/monitor';
|
||||
import { MonitorStatusDetailsComponent } from './monitor_status_details';
|
||||
|
||||
const mapStateToProps = (state: AppState, { monitorId }: any) => ({
|
||||
monitorLocations: getMonitorLocations(state, monitorId),
|
||||
monitorLocations: selectMonitorLocations(state, monitorId),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: any) => ({
|
||||
|
@ -32,3 +32,5 @@ export const MonitorStatusDetails = connect(
|
|||
)(MonitorStatusDetailsComponent);
|
||||
|
||||
export * from './monitor_status_details';
|
||||
export { MonitorStatusBar } from './monitor_status_bar';
|
||||
export { StatusByLocations } from './monitor_status_bar/status_by_location';
|
||||
|
|
|
@ -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 { connect } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import {
|
||||
StateProps,
|
||||
DispatchProps,
|
||||
MonitorStatusBarComponent,
|
||||
MonitorStatusBarProps,
|
||||
} from './monitor_status_bar';
|
||||
import { selectMonitorStatus, selectMonitorLocations } from '../../../../state/selectors';
|
||||
import { AppState } from '../../../../state';
|
||||
import { getMonitorStatus, getSelectedMonitor } from '../../../../state/actions';
|
||||
|
||||
const mapStateToProps = (state: AppState, ownProps: MonitorStatusBarProps) => ({
|
||||
monitorStatus: selectMonitorStatus(state),
|
||||
monitorLocations: selectMonitorLocations(state, ownProps.monitorId),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch<any>, ownProps: MonitorStatusBarProps) => ({
|
||||
loadMonitorStatus: () => {
|
||||
const { dateStart, dateEnd, monitorId } = ownProps;
|
||||
dispatch(
|
||||
getMonitorStatus({
|
||||
monitorId,
|
||||
dateStart,
|
||||
dateEnd,
|
||||
})
|
||||
);
|
||||
dispatch(
|
||||
getSelectedMonitor({
|
||||
monitorId,
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// @ts-ignore TODO: Investigate typescript issues here
|
||||
export const MonitorStatusBar = connect<StateProps, DispatchProps, MonitorStatusBarProps>(
|
||||
// @ts-ignore TODO: Investigate typescript issues here
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(MonitorStatusBarComponent);
|
||||
|
||||
export { MonitorSSLCertificate } from './monitor_ssl_certificate';
|
||||
export * from './monitor_status_bar';
|
|
@ -10,9 +10,8 @@ import moment from 'moment';
|
|||
import { EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { PingTls } from '../../../../common/graphql/types';
|
||||
import { PingTls } from '../../../../../common/graphql/types';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
|
@ -21,10 +20,6 @@ interface Props {
|
|||
tls: PingTls | null | undefined;
|
||||
}
|
||||
|
||||
const TextContainer = styled.div`
|
||||
margin-left: 20px;
|
||||
`;
|
||||
|
||||
export const MonitorSSLCertificate = ({ tls }: Props) => {
|
||||
const certificateValidity: string | undefined = get(
|
||||
tls,
|
||||
|
@ -37,27 +32,22 @@ export const MonitorSSLCertificate = ({ tls }: Props) => {
|
|||
return validExpiryDate && certificateValidity ? (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<TextContainer>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
grow={false}
|
||||
size="s"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.sslCertificateExpiry.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'SSL certificate expires',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorStatusBar.sslCertificateExpiry.content"
|
||||
defaultMessage="SSL certificate expires {certificateValidity}"
|
||||
values={{
|
||||
certificateValidity: moment(new Date(certificateValidity).valueOf()).fromNow(),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</TextContainer>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
grow={false}
|
||||
size="s"
|
||||
aria-label={i18n.translate('xpack.uptime.monitorStatusBar.sslCertificateExpiry.ariaLabel', {
|
||||
defaultMessage: 'SSL certificate expires',
|
||||
})}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorStatusBar.sslCertificateExpiry.content"
|
||||
defaultMessage="SSL certificate expires {certificateValidity}"
|
||||
values={{
|
||||
certificateValidity: moment(new Date(certificateValidity).valueOf()).fromNow(),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</>
|
||||
) : null;
|
||||
};
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiLink,
|
||||
EuiTitle,
|
||||
EuiTextColor,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import React, { useEffect } from 'react';
|
||||
import { MonitorSSLCertificate } from './monitor_ssl_certificate';
|
||||
import * as labels from './translations';
|
||||
import { StatusByLocations } from './status_by_location';
|
||||
import { Ping } from '../../../../../common/graphql/types';
|
||||
import { MonitorLocations } from '../../../../../common/runtime_types';
|
||||
|
||||
export interface StateProps {
|
||||
monitorStatus: Ping;
|
||||
monitorLocations: MonitorLocations;
|
||||
}
|
||||
|
||||
export interface DispatchProps {
|
||||
loadMonitorStatus: () => void;
|
||||
}
|
||||
|
||||
export interface MonitorStatusBarProps {
|
||||
monitorId: string;
|
||||
dateStart: string;
|
||||
dateEnd: string;
|
||||
}
|
||||
|
||||
type Props = MonitorStatusBarProps & StateProps & DispatchProps;
|
||||
|
||||
export const MonitorStatusBarComponent: React.FC<Props> = ({
|
||||
dateStart,
|
||||
dateEnd,
|
||||
monitorId,
|
||||
loadMonitorStatus,
|
||||
monitorStatus,
|
||||
monitorLocations,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
loadMonitorStatus();
|
||||
}, [dateStart, dateEnd, loadMonitorStatus]);
|
||||
|
||||
const full = monitorStatus?.url?.full ?? '';
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<StatusByLocations locations={monitorLocations?.locations ?? []} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<EuiLink aria-label={labels.monitorUrlLinkAriaLabel} href={full} target="_blank">
|
||||
{full}
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xs">
|
||||
<EuiTextColor color="subdued">
|
||||
<h1 data-test-subj="monitor-page-title">{monitorId}</h1>
|
||||
</EuiTextColor>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiSpacer />
|
||||
<EuiFlexItem grow={false}>
|
||||
<MonitorSSLCertificate tls={monitorStatus?.tls} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 { EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { MonitorLocation } from '../../../../../common/runtime_types';
|
||||
|
||||
interface StatusByLocationsProps {
|
||||
locations: MonitorLocation[];
|
||||
}
|
||||
|
||||
export const StatusByLocations = ({ locations }: StatusByLocationsProps) => {
|
||||
const upLocations: string[] = [];
|
||||
const downLocations: string[] = [];
|
||||
|
||||
if (locations)
|
||||
locations.forEach((item: any) => {
|
||||
if (item.summary.down === 0) {
|
||||
upLocations.push(item.geo.name);
|
||||
} else {
|
||||
downLocations.push(item.geo.name);
|
||||
}
|
||||
});
|
||||
|
||||
let statusMessage = '';
|
||||
let status = '';
|
||||
if (downLocations.length === 0) {
|
||||
// for Messaging like 'Up in 1 Location' or 'Up in 2 Locations'
|
||||
statusMessage = `${locations.length}`;
|
||||
status = 'Up';
|
||||
} else if (downLocations.length > 0) {
|
||||
// for Messaging like 'Down in 1/2 Locations'
|
||||
status = 'Down';
|
||||
statusMessage = `${downLocations.length}/${locations.length}`;
|
||||
if (downLocations.length === locations.length) {
|
||||
// for Messaging like 'Down in 2 Locations'
|
||||
statusMessage = `${locations.length}`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiText>
|
||||
<h2>
|
||||
{locations.length <= 1 ? (
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorStatusBar.locations.oneLocStatus"
|
||||
values={{
|
||||
status,
|
||||
loc: statusMessage,
|
||||
}}
|
||||
defaultMessage="{status} in {loc} Location"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorStatusBar.locations.upStatus"
|
||||
values={{
|
||||
status,
|
||||
loc: statusMessage,
|
||||
}}
|
||||
defaultMessage="{status} in {loc} Locations"
|
||||
/>
|
||||
)}
|
||||
</h2>
|
||||
</EuiText>
|
||||
);
|
||||
};
|
|
@ -7,7 +7,7 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import { LocationMap } from '../location_map';
|
||||
import { MonitorStatusBar } from '../monitor_status_bar';
|
||||
import { MonitorStatusBar } from './monitor_status_bar';
|
||||
|
||||
interface MonitorStatusBarProps {
|
||||
monitorId: string;
|
||||
|
@ -34,7 +34,12 @@ export const MonitorStatusDetailsComponent = ({
|
|||
<EuiPanel>
|
||||
<EuiFlexGroup gutterSize="l" wrap>
|
||||
<EuiFlexItem grow={true}>
|
||||
<MonitorStatusBar monitorId={monitorId} variables={variables} />
|
||||
<MonitorStatusBar
|
||||
monitorId={monitorId}
|
||||
variables={variables}
|
||||
dateStart={dateStart}
|
||||
dateEnd={dateEnd}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<LocationMap monitorLocations={monitorLocations} />
|
||||
|
|
|
@ -43,7 +43,7 @@ interface StoreProps {
|
|||
|
||||
/**
|
||||
* Contains functions that will dispatch actions used
|
||||
* for this component's lifecyclel
|
||||
* for this component's life cycle
|
||||
*/
|
||||
interface DispatchProps {
|
||||
loadSnapshotCount: typeof fetchSnapshotCount;
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
*/
|
||||
|
||||
import { EuiSuperDatePicker } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { useUrlParams } from '../../hooks';
|
||||
import { CLIENT_DEFAULTS } from '../../../common/constants';
|
||||
import { UptimeSettingsContext } from '../../contexts';
|
||||
|
||||
// TODO: when EUI exports types for this, this should be replaced
|
||||
interface SuperDateRangePickerRangeChangedEvent {
|
||||
|
@ -26,16 +27,14 @@ export interface CommonlyUsedRange {
|
|||
display: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
interface UptimeDatePickerProps {
|
||||
refreshApp: () => void;
|
||||
commonlyUsedRanges?: CommonlyUsedRange[];
|
||||
}
|
||||
|
||||
type UptimeDatePickerProps = Props;
|
||||
|
||||
export const UptimeDatePicker = ({ refreshApp, commonlyUsedRanges }: UptimeDatePickerProps) => {
|
||||
export const UptimeDatePicker = ({ refreshApp }: UptimeDatePickerProps) => {
|
||||
const [getUrlParams, updateUrl] = useUrlParams();
|
||||
const { autorefreshInterval, autorefreshIsPaused, dateRangeStart, dateRangeEnd } = getUrlParams();
|
||||
const { commonlyUsedRanges } = useContext(UptimeSettingsContext);
|
||||
|
||||
const euiCommonlyUsedRanges = commonlyUsedRanges
|
||||
? commonlyUsedRanges.map(
|
||||
|
|
|
@ -9,6 +9,7 @@ import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
|||
import { createContext } from 'react';
|
||||
import { UptimeAppColors } from '../uptime_app';
|
||||
import { CONTEXT_DEFAULTS } from '../../common/constants';
|
||||
import { CommonlyUsedRange } from '../components/functional/uptime_date_picker';
|
||||
|
||||
export interface UMSettingsContextValues {
|
||||
absoluteStartDate: number;
|
||||
|
@ -23,7 +24,7 @@ export interface UMSettingsContextValues {
|
|||
isInfraAvailable: boolean;
|
||||
isLogsAvailable: boolean;
|
||||
refreshApp: () => void;
|
||||
setHeadingText: (text: string) => void;
|
||||
commonlyUsedRanges?: CommonlyUsedRange[];
|
||||
}
|
||||
|
||||
const {
|
||||
|
@ -64,9 +65,6 @@ const defaultContext: UMSettingsContextValues = {
|
|||
refreshApp: () => {
|
||||
throw new Error('App refresh was not initialized, set it when you invoke the context');
|
||||
},
|
||||
setHeadingText: () => {
|
||||
throw new Error('setHeadingText was not initialized on UMSettingsContext.');
|
||||
},
|
||||
};
|
||||
|
||||
export const UptimeSettingsContext = createContext(defaultContext);
|
||||
|
|
14
x-pack/legacy/plugins/uptime/public/lib/helper/get_title.ts
Normal file
14
x-pack/legacy/plugins/uptime/public/lib/helper/get_title.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const getTitle = (name?: string) => {
|
||||
const appName = i18n.translate('xpack.uptime.title', {
|
||||
defaultMessage: 'Uptime',
|
||||
});
|
||||
return `${appName} ${name ? '| ' + name : ''} - Kibana`;
|
||||
};
|
|
@ -7,3 +7,4 @@
|
|||
export { MonitorPage } from './monitor';
|
||||
export { OverviewPage } from './overview';
|
||||
export { NotFoundPage } from './not_found';
|
||||
export { PageHeader } from './page_header';
|
||||
|
|
|
@ -5,59 +5,32 @@
|
|||
*/
|
||||
|
||||
import { EuiSpacer, EuiComboBoxOptionProps } from '@elastic/eui';
|
||||
import { ApolloQueryResult, OperationVariables, QueryOptions } from 'apollo-client';
|
||||
import gql from 'graphql-tag';
|
||||
import React, { Fragment, useContext, useEffect, useState } from 'react';
|
||||
import { getMonitorPageBreadcrumb } from '../breadcrumbs';
|
||||
import { MonitorCharts, MonitorPageTitle, PingList } from '../components/functional';
|
||||
import React, { Fragment, useContext, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { MonitorCharts, PingList } from '../components/functional';
|
||||
import { UMUpdateBreadcrumbs } from '../lib/lib';
|
||||
import { UptimeSettingsContext } from '../contexts';
|
||||
import { useUptimeTelemetry, useUrlParams, UptimePage } from '../hooks';
|
||||
import { stringifyUrlParams } from '../lib/helper/stringify_url_params';
|
||||
import { BaseLocationOptions } from '../components/functional/ping_list';
|
||||
import { useTrackPageview } from '../../../infra/public';
|
||||
import { MonitorStatusDetails } from '../components/functional/monitor_status_details';
|
||||
import { PageHeader } from './page_header';
|
||||
|
||||
interface MonitorPageProps {
|
||||
match: { params: { monitorId: string } };
|
||||
// this is the query function provided by Apollo's Client API
|
||||
query: <T, TVariables = OperationVariables>(
|
||||
options: QueryOptions<TVariables>
|
||||
) => Promise<ApolloQueryResult<T>>;
|
||||
setBreadcrumbs: UMUpdateBreadcrumbs;
|
||||
}
|
||||
|
||||
export const MonitorPage = ({ query, setBreadcrumbs, match }: MonitorPageProps) => {
|
||||
export const MonitorPage = ({ setBreadcrumbs }: MonitorPageProps) => {
|
||||
// decode 64 base string, it was decoded to make it a valid url, since monitor id can be a url
|
||||
const monitorId = atob(match.params.monitorId);
|
||||
let { monitorId } = useParams();
|
||||
monitorId = atob(monitorId || '');
|
||||
|
||||
const [pingListPageCount, setPingListPageCount] = useState<number>(10);
|
||||
const { colors, refreshApp, setHeadingText } = useContext(UptimeSettingsContext);
|
||||
const { colors, refreshApp } = useContext(UptimeSettingsContext);
|
||||
const [getUrlParams, updateUrlParams] = useUrlParams();
|
||||
const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = getUrlParams();
|
||||
const { dateRangeStart, dateRangeEnd, selectedPingStatus } = params;
|
||||
|
||||
useEffect(() => {
|
||||
query({
|
||||
query: gql`
|
||||
query MonitorPageTitle($monitorId: String!) {
|
||||
monitorPageTitle: getMonitorPageTitle(monitorId: $monitorId) {
|
||||
id
|
||||
url
|
||||
name
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { monitorId },
|
||||
}).then((result: any) => {
|
||||
const { name, url, id } = result.data.monitorPageTitle;
|
||||
const heading: string = name || url || id;
|
||||
setBreadcrumbs(getMonitorPageBreadcrumb(heading, stringifyUrlParams(params)));
|
||||
if (setHeadingText) {
|
||||
setHeadingText(heading);
|
||||
}
|
||||
});
|
||||
}, [monitorId, params, query, setBreadcrumbs, setHeadingText]);
|
||||
|
||||
const [selectedLocation, setSelectedLocation] = useState<EuiComboBoxOptionProps[]>(
|
||||
BaseLocationOptions
|
||||
);
|
||||
|
@ -78,7 +51,7 @@ export const MonitorPage = ({ query, setBreadcrumbs, match }: MonitorPageProps)
|
|||
|
||||
return (
|
||||
<Fragment>
|
||||
<MonitorPageTitle monitorId={monitorId} variables={{ monitorId }} />
|
||||
<PageHeader setBreadcrumbs={setBreadcrumbs} />
|
||||
<EuiSpacer size="s" />
|
||||
<MonitorStatusDetails
|
||||
monitorId={monitorId}
|
||||
|
|
|
@ -5,10 +5,8 @@
|
|||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { Fragment, useContext, useEffect, useState } from 'react';
|
||||
import React, { Fragment, useContext, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { getOverviewPageBreadcrumbs } from '../breadcrumbs';
|
||||
import {
|
||||
EmptyState,
|
||||
FilterGroup,
|
||||
|
@ -24,15 +22,10 @@ import { stringifyUrlParams } from '../lib/helper/stringify_url_params';
|
|||
import { useTrackPageview } from '../../../infra/public';
|
||||
import { combineFiltersAndUserSearch, stringifyKueries, toStaticIndexPattern } from '../lib/helper';
|
||||
import { AutocompleteProviderRegister, esKuery } from '../../../../../../src/plugins/data/public';
|
||||
import { PageHeader } from './page_header';
|
||||
|
||||
interface OverviewPageProps {
|
||||
basePath: string;
|
||||
autocomplete: Pick<AutocompleteProviderRegister, 'getProvider'>;
|
||||
history: any;
|
||||
location: {
|
||||
pathname: string;
|
||||
search: string;
|
||||
};
|
||||
setBreadcrumbs: UMUpdateBreadcrumbs;
|
||||
}
|
||||
|
||||
|
@ -52,8 +45,8 @@ const EuiFlexItemStyled = styled(EuiFlexItem)`
|
|||
}
|
||||
`;
|
||||
|
||||
export const OverviewPage = ({ basePath, autocomplete, setBreadcrumbs }: Props) => {
|
||||
const { colors, setHeadingText } = useContext(UptimeSettingsContext);
|
||||
export const OverviewPage = ({ autocomplete, setBreadcrumbs }: Props) => {
|
||||
const { colors } = useContext(UptimeSettingsContext);
|
||||
const [getUrlParams, updateUrl] = useUrlParams();
|
||||
const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = getUrlParams();
|
||||
const {
|
||||
|
@ -68,18 +61,6 @@ export const OverviewPage = ({ basePath, autocomplete, setBreadcrumbs }: Props)
|
|||
useUptimeTelemetry(UptimePage.Overview);
|
||||
useIndexPattern(setIndexPattern);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs(getOverviewPageBreadcrumbs());
|
||||
if (setHeadingText) {
|
||||
setHeadingText(
|
||||
i18n.translate('xpack.uptime.overviewPage.headerText', {
|
||||
defaultMessage: 'Overview',
|
||||
description: `The text that will be displayed in the app's heading when the Overview page loads.`,
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [basePath, setBreadcrumbs, setHeadingText]);
|
||||
|
||||
useTrackPageview({ app: 'uptime', path: 'overview' });
|
||||
useTrackPageview({ app: 'uptime', path: 'overview', delay: 15000 });
|
||||
|
||||
|
@ -121,7 +102,8 @@ export const OverviewPage = ({ basePath, autocomplete, setBreadcrumbs }: Props)
|
|||
|
||||
return (
|
||||
<Fragment>
|
||||
<EmptyState basePath={basePath} implementsCustomErrorState={true} variables={{}}>
|
||||
<PageHeader setBreadcrumbs={setBreadcrumbs} />
|
||||
<EmptyState implementsCustomErrorState={true} variables={{}}>
|
||||
<EuiFlexGroup gutterSize="xs" wrap responsive>
|
||||
<EuiFlexItem grow={1} style={{ flexBasis: 500 }}>
|
||||
<KueryBar autocomplete={autocomplete} />
|
||||
|
|
85
x-pack/legacy/plugins/uptime/public/pages/page_header.tsx
Normal file
85
x-pack/legacy/plugins/uptime/public/pages/page_header.tsx
Normal 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.
|
||||
*/
|
||||
|
||||
import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import React, { useEffect, useState, useContext } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useRouteMatch, useParams } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { UptimeDatePicker } from '../components/functional/uptime_date_picker';
|
||||
import { AppState } from '../state';
|
||||
import { selectSelectedMonitor } from '../state/selectors';
|
||||
import { getMonitorPageBreadcrumb, getOverviewPageBreadcrumbs } from '../breadcrumbs';
|
||||
import { stringifyUrlParams } from '../lib/helper/stringify_url_params';
|
||||
import { UptimeSettingsContext } from '../contexts';
|
||||
import { getTitle } from '../lib/helper/get_title';
|
||||
import { UMUpdateBreadcrumbs } from '../lib/lib';
|
||||
import { MONITOR_ROUTE } from '../routes';
|
||||
|
||||
interface PageHeaderProps {
|
||||
monitorStatus?: any;
|
||||
setBreadcrumbs: UMUpdateBreadcrumbs;
|
||||
}
|
||||
|
||||
export const PageHeaderComponent = ({ monitorStatus, setBreadcrumbs }: PageHeaderProps) => {
|
||||
const monitorPage = useRouteMatch({
|
||||
path: MONITOR_ROUTE,
|
||||
});
|
||||
const { refreshApp } = useContext(UptimeSettingsContext);
|
||||
|
||||
const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = useParams();
|
||||
|
||||
const headingText = i18n.translate('xpack.uptime.overviewPage.headerText', {
|
||||
defaultMessage: 'Overview',
|
||||
description: `The text that will be displayed in the app's heading when the Overview page loads.`,
|
||||
});
|
||||
|
||||
const [headerText, setHeaderText] = useState(headingText);
|
||||
|
||||
useEffect(() => {
|
||||
if (monitorPage) {
|
||||
setHeaderText(monitorStatus?.url?.full);
|
||||
if (monitorStatus?.monitor) {
|
||||
const { name, id } = monitorStatus.monitor;
|
||||
document.title = getTitle(name || id);
|
||||
}
|
||||
} else {
|
||||
document.title = getTitle();
|
||||
}
|
||||
}, [monitorStatus, monitorPage, setHeaderText]);
|
||||
|
||||
useEffect(() => {
|
||||
if (monitorPage) {
|
||||
if (headerText) {
|
||||
setBreadcrumbs(getMonitorPageBreadcrumb(headerText, stringifyUrlParams(params)));
|
||||
}
|
||||
} else {
|
||||
setBreadcrumbs(getOverviewPageBreadcrumbs());
|
||||
}
|
||||
}, [headerText, setBreadcrumbs, params, monitorPage]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h1>{headerText}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<UptimeDatePicker refreshApp={refreshApp} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: AppState) => ({
|
||||
monitorStatus: selectSelectedMonitor(state),
|
||||
});
|
||||
|
||||
export const PageHeader = connect(mapStateToProps, null)(PageHeaderComponent);
|
|
@ -7,6 +7,4 @@
|
|||
export { docCountQuery, docCountQueryString } from './doc_count_query';
|
||||
export { filterBarQuery, filterBarQueryString } from './filter_bar_query';
|
||||
export { monitorChartsQuery, monitorChartsQueryString } from './monitor_charts_query';
|
||||
export { monitorPageTitleQuery } from './monitor_page_title_query';
|
||||
export { monitorStatusBarQuery, monitorStatusBarQueryString } from './monitor_status_bar_query';
|
||||
export { pingsQuery, pingsQueryString } from './pings_query';
|
||||
|
|
|
@ -1,20 +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 gql from 'graphql-tag';
|
||||
|
||||
export const monitorPageTitleQueryString = `
|
||||
query MonitorPageTitle($monitorId: String!) {
|
||||
monitorPageTitle: getMonitorPageTitle(monitorId: $monitorId) {
|
||||
id
|
||||
url
|
||||
name
|
||||
}
|
||||
}`;
|
||||
|
||||
export const monitorPageTitleQuery = gql`
|
||||
${monitorPageTitleQueryString}
|
||||
`;
|
|
@ -1,41 +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 gql from 'graphql-tag';
|
||||
|
||||
export const monitorStatusBarQueryString = `
|
||||
query MonitorStatus($dateRangeStart: String!, $dateRangeEnd: String!, $monitorId: String, $location: String) {
|
||||
monitorStatus: getLatestMonitors(
|
||||
dateRangeStart: $dateRangeStart
|
||||
dateRangeEnd: $dateRangeEnd
|
||||
monitorId: $monitorId
|
||||
location: $location
|
||||
) {
|
||||
timestamp
|
||||
monitor {
|
||||
status
|
||||
duration {
|
||||
us
|
||||
}
|
||||
}
|
||||
observer {
|
||||
geo {
|
||||
name
|
||||
}
|
||||
}
|
||||
tls {
|
||||
certificate_not_valid_after
|
||||
}
|
||||
url {
|
||||
full
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const monitorStatusBarQuery = gql`
|
||||
${monitorStatusBarQueryString}
|
||||
`;
|
32
x-pack/legacy/plugins/uptime/public/routes.tsx
Normal file
32
x-pack/legacy/plugins/uptime/public/routes.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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, { FC } from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { MonitorPage, OverviewPage, NotFoundPage } from './pages';
|
||||
import { AutocompleteProviderRegister } from '../../../../../src/plugins/data/public';
|
||||
import { UMUpdateBreadcrumbs } from './lib/lib';
|
||||
|
||||
export const MONITOR_ROUTE = '/monitor/:monitorId/:location?';
|
||||
export const OVERVIEW_ROUTE = '/';
|
||||
|
||||
interface RouterProps {
|
||||
autocomplete: Pick<AutocompleteProviderRegister, 'getProvider'>;
|
||||
basePath: string;
|
||||
setBreadcrumbs: UMUpdateBreadcrumbs;
|
||||
}
|
||||
|
||||
export const PageRouter: FC<RouterProps> = ({ autocomplete, basePath, setBreadcrumbs }) => (
|
||||
<Switch>
|
||||
<Route path={MONITOR_ROUTE}>
|
||||
<MonitorPage setBreadcrumbs={setBreadcrumbs} />
|
||||
</Route>
|
||||
<Route path={OVERVIEW_ROUTE}>
|
||||
<OverviewPage autocomplete={autocomplete} setBreadcrumbs={setBreadcrumbs} />
|
||||
</Route>
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
);
|
|
@ -6,3 +6,4 @@
|
|||
|
||||
export * from './snapshot';
|
||||
export * from './ui';
|
||||
export * from './monitor_status';
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
import { createAction } from 'redux-actions';
|
||||
import { QueryParams } from './types';
|
||||
|
||||
export const getSelectedMonitor = createAction<{ monitorId: string }>('GET_SELECTED_MONITOR');
|
||||
export const getSelectedMonitorSuccess = createAction<QueryParams>('GET_SELECTED_MONITOR_SUCCESS');
|
||||
export const getSelectedMonitorFail = createAction<QueryParams>('GET_SELECTED_MONITOR_FAIL');
|
||||
|
||||
export const getMonitorStatus = createAction<QueryParams>('GET_MONITOR_STATUS');
|
||||
export const getMonitorStatusSuccess = createAction<QueryParams>('GET_MONITOR_STATUS_SUCCESS');
|
||||
export const getMonitorStatusFail = createAction<QueryParams>('GET_MONITOR_STATUS_FAIL');
|
|
@ -5,10 +5,12 @@
|
|||
*/
|
||||
|
||||
export interface QueryParams {
|
||||
monitorId: string;
|
||||
dateStart: string;
|
||||
dateEnd: string;
|
||||
filters?: string;
|
||||
statusFilter?: string;
|
||||
location?: string;
|
||||
}
|
||||
|
||||
export interface MonitorDetailsActionPayload {
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
|
||||
export * from './monitor';
|
||||
export * from './snapshot';
|
||||
export * from './monitor_status';
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 { getApiPath } from '../../lib/helper';
|
||||
import { QueryParams } from '../actions/types';
|
||||
import { Ping } from '../../../common/graphql/types';
|
||||
|
||||
export interface APIParams {
|
||||
basePath: string;
|
||||
monitorId: string;
|
||||
}
|
||||
|
||||
export const fetchSelectedMonitor = async ({ basePath, monitorId }: APIParams): Promise<Ping> => {
|
||||
const url = getApiPath(`/api/uptime/monitor/selected`, basePath);
|
||||
const params = {
|
||||
monitorId,
|
||||
};
|
||||
const urlParams = new URLSearchParams(params).toString();
|
||||
const response = await fetch(`${url}?${urlParams}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
const responseData = await response.json();
|
||||
return responseData;
|
||||
};
|
||||
|
||||
export const fetchMonitorStatus = async ({
|
||||
basePath,
|
||||
monitorId,
|
||||
dateStart,
|
||||
dateEnd,
|
||||
}: QueryParams & APIParams): Promise<Ping> => {
|
||||
const url = getApiPath(`/api/uptime/monitor/status`, basePath);
|
||||
const params = {
|
||||
monitorId,
|
||||
dateStart,
|
||||
dateEnd,
|
||||
};
|
||||
const urlParams = new URLSearchParams(params).toString();
|
||||
const response = await fetch(`${url}?${urlParams}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
const responseData = await response.json();
|
||||
return responseData;
|
||||
};
|
|
@ -7,8 +7,10 @@
|
|||
import { fork } from 'redux-saga/effects';
|
||||
import { fetchMonitorDetailsEffect } from './monitor';
|
||||
import { fetchSnapshotCountSaga } from './snapshot';
|
||||
import { fetchMonitorStatusEffect } from './monitor_status';
|
||||
|
||||
export function* rootEffect() {
|
||||
yield fork(fetchMonitorDetailsEffect);
|
||||
yield fork(fetchSnapshotCountSaga);
|
||||
yield fork(fetchMonitorStatusEffect);
|
||||
}
|
||||
|
|
|
@ -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 { call, put, takeLatest, select } from 'redux-saga/effects';
|
||||
import { Action } from 'redux-actions';
|
||||
import {
|
||||
getSelectedMonitor,
|
||||
getSelectedMonitorSuccess,
|
||||
getSelectedMonitorFail,
|
||||
getMonitorStatus,
|
||||
getMonitorStatusSuccess,
|
||||
getMonitorStatusFail,
|
||||
} from '../actions/monitor_status';
|
||||
import { fetchSelectedMonitor, fetchMonitorStatus } from '../api';
|
||||
import { getBasePath } from '../selectors';
|
||||
|
||||
function* selectedMonitorEffect(action: Action<any>) {
|
||||
const { monitorId } = action.payload;
|
||||
try {
|
||||
const basePath = yield select(getBasePath);
|
||||
const response = yield call(fetchSelectedMonitor, {
|
||||
monitorId,
|
||||
basePath,
|
||||
});
|
||||
yield put({ type: getSelectedMonitorSuccess, payload: response });
|
||||
} catch (error) {
|
||||
yield put({ type: getSelectedMonitorFail, payload: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
function* monitorStatusEffect(action: Action<any>) {
|
||||
const { monitorId, dateStart, dateEnd } = action.payload;
|
||||
try {
|
||||
const basePath = yield select(getBasePath);
|
||||
const response = yield call(fetchMonitorStatus, {
|
||||
monitorId,
|
||||
basePath,
|
||||
dateStart,
|
||||
dateEnd,
|
||||
});
|
||||
yield put({ type: getMonitorStatusSuccess, payload: response });
|
||||
} catch (error) {
|
||||
yield put({ type: getMonitorStatusFail, payload: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
export function* fetchMonitorStatusEffect() {
|
||||
yield takeLatest(getMonitorStatus, monitorStatusEffect);
|
||||
yield takeLatest(getSelectedMonitor, selectedMonitorEffect);
|
||||
}
|
|
@ -8,10 +8,11 @@ import { combineReducers } from 'redux';
|
|||
import { monitorReducer } from './monitor';
|
||||
import { snapshotReducer } from './snapshot';
|
||||
import { uiReducer } from './ui';
|
||||
import { monitorStatusReducer } from './monitor_status';
|
||||
|
||||
export const rootReducer = combineReducers({
|
||||
monitor: monitorReducer,
|
||||
snapshot: snapshotReducer,
|
||||
// @ts-ignore for now TODO: refactor to use redux-action
|
||||
ui: uiReducer,
|
||||
monitorStatus: monitorStatusReducer,
|
||||
});
|
||||
|
|
|
@ -19,10 +19,10 @@ import { MonitorLocations } from '../../../common/runtime_types';
|
|||
type MonitorLocationsList = Map<string, MonitorLocations>;
|
||||
|
||||
export interface MonitorState {
|
||||
monitorDetailsList: MonitorDetailsState[];
|
||||
monitorLocationsList: MonitorLocationsList;
|
||||
loading: boolean;
|
||||
errors: any[];
|
||||
monitorDetailsList: MonitorDetailsState[];
|
||||
monitorLocationsList: MonitorLocationsList;
|
||||
}
|
||||
|
||||
const initialState: MonitorState = {
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 { handleActions, Action } from 'redux-actions';
|
||||
import {
|
||||
getSelectedMonitor,
|
||||
getSelectedMonitorSuccess,
|
||||
getSelectedMonitorFail,
|
||||
getMonitorStatus,
|
||||
getMonitorStatusSuccess,
|
||||
getMonitorStatusFail,
|
||||
} from '../actions';
|
||||
import { Ping } from '../../../common/graphql/types';
|
||||
import { QueryParams } from '../actions/types';
|
||||
|
||||
export interface MonitorStatusState {
|
||||
status: Ping | null;
|
||||
monitor: Ping | null;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const initialState: MonitorStatusState = {
|
||||
status: null,
|
||||
monitor: null,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
type MonitorStatusPayload = QueryParams & Ping;
|
||||
|
||||
export const monitorStatusReducer = handleActions<MonitorStatusState, MonitorStatusPayload>(
|
||||
{
|
||||
[String(getSelectedMonitor)]: (state, action: Action<QueryParams>) => ({
|
||||
...state,
|
||||
loading: true,
|
||||
}),
|
||||
|
||||
[String(getSelectedMonitorSuccess)]: (state, action: Action<Ping>) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
monitor: { ...action.payload } as Ping,
|
||||
}),
|
||||
|
||||
[String(getSelectedMonitorFail)]: (state, action: Action<any>) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
}),
|
||||
|
||||
[String(getMonitorStatus)]: (state, action: Action<QueryParams>) => ({
|
||||
...state,
|
||||
loading: true,
|
||||
}),
|
||||
|
||||
[String(getMonitorStatusSuccess)]: (state, action: Action<Ping>) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
status: { ...action.payload } as Ping,
|
||||
}),
|
||||
|
||||
[String(getMonitorStatusFail)]: (state, action: Action<any>) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
}),
|
||||
},
|
||||
initialState
|
||||
);
|
|
@ -24,10 +24,11 @@ describe('state selectors', () => {
|
|||
errors: [],
|
||||
loading: false,
|
||||
},
|
||||
ui: {
|
||||
basePath: 'yyz',
|
||||
integrationsPopoverOpen: null,
|
||||
lastRefresh: 125,
|
||||
ui: { basePath: 'yyz', integrationsPopoverOpen: null, lastRefresh: 125 },
|
||||
monitorStatus: {
|
||||
status: null,
|
||||
monitor: null,
|
||||
loading: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -17,6 +17,14 @@ export const getMonitorDetails = (state: AppState, summary: any) => {
|
|||
return state.monitor.monitorDetailsList[summary.monitor_id];
|
||||
};
|
||||
|
||||
export const getMonitorLocations = (state: AppState, monitorId: string) => {
|
||||
export const selectMonitorLocations = (state: AppState, monitorId: string) => {
|
||||
return state.monitor.monitorLocationsList?.get(monitorId);
|
||||
};
|
||||
|
||||
export const selectSelectedMonitor = (state: AppState) => {
|
||||
return state.monitorStatus.monitor;
|
||||
};
|
||||
|
||||
export const selectMonitorStatus = (state: AppState) => {
|
||||
return state.monitorStatus.status;
|
||||
};
|
||||
|
|
|
@ -5,24 +5,24 @@
|
|||
*/
|
||||
|
||||
import DateMath from '@elastic/datemath';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import { EuiPage } from '@elastic/eui';
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ApolloProvider } from 'react-apollo';
|
||||
import { Provider as ReduxProvider } from 'react-redux';
|
||||
import { BrowserRouter as Router, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
import { BrowserRouter as Router, Route, RouteComponentProps } from 'react-router-dom';
|
||||
import { I18nStart, ChromeBreadcrumb, LegacyCoreStart } from 'src/core/public';
|
||||
import { PluginsStart } from 'ui/new_platform/new_platform';
|
||||
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { UMGraphQLClient, UMUpdateBreadcrumbs, UMUpdateBadge } from './lib/lib';
|
||||
import { MonitorPage, OverviewPage, NotFoundPage } from './pages';
|
||||
import { UptimeRefreshContext, UptimeSettingsContext, UMSettingsContextValues } from './contexts';
|
||||
import { UptimeDatePicker, CommonlyUsedRange } from './components/functional/uptime_date_picker';
|
||||
import { CommonlyUsedRange } from './components/functional/uptime_date_picker';
|
||||
import { useUrlParams } from './hooks';
|
||||
import { store } from './state';
|
||||
import { setBasePath, triggerAppRefresh } from './state/actions';
|
||||
import { PageRouter } from './routes';
|
||||
|
||||
export interface UptimeAppColors {
|
||||
danger: string;
|
||||
|
@ -93,7 +93,6 @@ const Application = (props: UptimeAppProps) => {
|
|||
}
|
||||
|
||||
const [lastRefresh, setLastRefresh] = useState<number>(Date.now());
|
||||
const [headingText, setHeadingText] = useState<string | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
renderGlobalHelpControls();
|
||||
|
@ -142,7 +141,7 @@ const Application = (props: UptimeAppProps) => {
|
|||
isInfraAvailable,
|
||||
isLogsAvailable,
|
||||
refreshApp,
|
||||
setHeadingText,
|
||||
commonlyUsedRanges,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -162,49 +161,11 @@ const Application = (props: UptimeAppProps) => {
|
|||
<UptimeSettingsContext.Provider value={initializeSettingsContextValues()}>
|
||||
<EuiPage className="app-wrapper-panel " data-test-subj="uptimeApp">
|
||||
<main>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="spaceBetween"
|
||||
gutterSize="s"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h1>{headingText}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<UptimeDatePicker
|
||||
refreshApp={refreshApp}
|
||||
commonlyUsedRanges={commonlyUsedRanges}
|
||||
{...rootRouteProps}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
<Switch>
|
||||
<Route
|
||||
path="/monitor/:monitorId/:location?"
|
||||
render={routerProps => (
|
||||
<MonitorPage
|
||||
query={client.query}
|
||||
setBreadcrumbs={setBreadcrumbs}
|
||||
{...routerProps}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/"
|
||||
render={routerProps => (
|
||||
<OverviewPage
|
||||
autocomplete={plugins.data.autocomplete}
|
||||
basePath={basePath}
|
||||
setBreadcrumbs={setBreadcrumbs}
|
||||
{...routerProps}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
<PageRouter
|
||||
autocomplete={plugins.data.autocomplete}
|
||||
basePath={basePath}
|
||||
setBreadcrumbs={setBreadcrumbs}
|
||||
/>
|
||||
</main>
|
||||
</EuiPage>
|
||||
</UptimeSettingsContext.Provider>
|
||||
|
|
|
@ -9,12 +9,8 @@ import { UMResolver } from '../../../common/graphql/resolver_types';
|
|||
import {
|
||||
FilterBar,
|
||||
GetFilterBarQueryArgs,
|
||||
GetLatestMonitorsQueryArgs,
|
||||
GetMonitorChartsDataQueryArgs,
|
||||
GetMonitorPageTitleQueryArgs,
|
||||
MonitorChart,
|
||||
MonitorPageTitle,
|
||||
Ping,
|
||||
GetSnapshotHistogramQueryArgs,
|
||||
} from '../../../common/graphql/types';
|
||||
import { UMServerLibs } from '../../lib/lib';
|
||||
|
@ -23,13 +19,6 @@ import { HistogramResult } from '../../../common/domain_types';
|
|||
|
||||
export type UMMonitorsResolver = UMResolver<any | Promise<any>, any, UMGqlRange, UMContext>;
|
||||
|
||||
export type UMLatestMonitorsResolver = UMResolver<
|
||||
Ping[] | Promise<Ping[]>,
|
||||
any,
|
||||
GetLatestMonitorsQueryArgs,
|
||||
UMContext
|
||||
>;
|
||||
|
||||
export type UMGetMonitorChartsResolver = UMResolver<
|
||||
any | Promise<any>,
|
||||
any,
|
||||
|
@ -44,13 +33,6 @@ export type UMGetFilterBarResolver = UMResolver<
|
|||
UMContext
|
||||
>;
|
||||
|
||||
export type UMGetMontiorPageTitleResolver = UMResolver<
|
||||
MonitorPageTitle | Promise<MonitorPageTitle | null> | null,
|
||||
any,
|
||||
GetMonitorPageTitleQueryArgs,
|
||||
UMContext
|
||||
>;
|
||||
|
||||
export type UMGetSnapshotHistogram = UMResolver<
|
||||
HistogramResult | Promise<HistogramResult>,
|
||||
any,
|
||||
|
@ -64,9 +46,7 @@ export const createMonitorsResolvers: CreateUMGraphQLResolvers = (
|
|||
Query: {
|
||||
getSnapshotHistogram: UMGetSnapshotHistogram;
|
||||
getMonitorChartsData: UMGetMonitorChartsResolver;
|
||||
getLatestMonitors: UMLatestMonitorsResolver;
|
||||
getFilterBar: UMGetFilterBarResolver;
|
||||
getMonitorPageTitle: UMGetMontiorPageTitleResolver;
|
||||
};
|
||||
} => ({
|
||||
Query: {
|
||||
|
@ -97,19 +77,6 @@ export const createMonitorsResolvers: CreateUMGraphQLResolvers = (
|
|||
location,
|
||||
});
|
||||
},
|
||||
async getLatestMonitors(
|
||||
_resolver,
|
||||
{ dateRangeStart, dateRangeEnd, monitorId, location },
|
||||
{ APICaller }
|
||||
): Promise<Ping[]> {
|
||||
return await libs.pings.getLatestMonitorDocs({
|
||||
callES: APICaller,
|
||||
dateRangeStart,
|
||||
dateRangeEnd,
|
||||
monitorId,
|
||||
location,
|
||||
});
|
||||
},
|
||||
async getFilterBar(
|
||||
_resolver,
|
||||
{ dateRangeStart, dateRangeEnd },
|
||||
|
@ -121,12 +88,5 @@ export const createMonitorsResolvers: CreateUMGraphQLResolvers = (
|
|||
dateRangeEnd,
|
||||
});
|
||||
},
|
||||
async getMonitorPageTitle(
|
||||
_resolver: any,
|
||||
{ monitorId },
|
||||
{ APICaller }
|
||||
): Promise<MonitorPageTitle | null> {
|
||||
return await libs.monitors.getMonitorPageTitle({ callES: APICaller, monitorId });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -114,12 +114,6 @@ export const monitorsSchema = gql`
|
|||
interval: UnsignedInteger!
|
||||
}
|
||||
|
||||
type MonitorPageTitle {
|
||||
id: String!
|
||||
url: String
|
||||
name: String
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
getMonitors(
|
||||
dateRangeStart: String!
|
||||
|
@ -156,7 +150,5 @@ export const monitorsSchema = gql`
|
|||
): [Ping!]!
|
||||
|
||||
getFilterBar(dateRangeStart: String!, dateRangeEnd: String!): FilterBar
|
||||
|
||||
getMonitorPageTitle(monitorId: String!): MonitorPageTitle
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { MonitorChart, MonitorPageTitle } from '../../../../common/graphql/types';
|
||||
import { MonitorChart } from '../../../../common/graphql/types';
|
||||
import { UMElasticsearchQueryFn } from '../framework';
|
||||
import { MonitorDetails, MonitorLocations } from '../../../../common/runtime_types';
|
||||
|
||||
|
@ -31,11 +31,6 @@ export interface GetMonitorDetailsParams {
|
|||
dateEnd: string;
|
||||
}
|
||||
|
||||
export interface GetMonitorPageTitleParams {
|
||||
/** @member monitorId the ID to query */
|
||||
monitorId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch data for the monitor page title.
|
||||
*/
|
||||
|
@ -57,7 +52,6 @@ export interface UMMonitorsAdapter {
|
|||
/**
|
||||
* Fetch data for the monitor page title.
|
||||
*/
|
||||
getMonitorPageTitle: UMElasticsearchQueryFn<{ monitorId: string }, MonitorPageTitle | null>;
|
||||
getMonitorDetails: UMElasticsearchQueryFn<GetMonitorDetailsParams, MonitorDetails>;
|
||||
getMonitorLocations: UMElasticsearchQueryFn<GetMonitorLocationsParams, MonitorLocations>;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { get } from 'lodash';
|
||||
import { INDEX_NAMES } from '../../../../common/constants';
|
||||
import { MonitorChart, Ping, LocationDurationLine } from '../../../../common/graphql/types';
|
||||
import { MonitorChart, LocationDurationLine } from '../../../../common/graphql/types';
|
||||
import { getHistogramIntervalFormatted } from '../../helper';
|
||||
import { MonitorError, MonitorLocation } from '../../../../common/runtime_types';
|
||||
import { UMMonitorsAdapter } from './adapter_types';
|
||||
|
@ -195,42 +195,6 @@ export const elasticsearchMonitorsAdapter: UMMonitorsAdapter = {
|
|||
}, {});
|
||||
},
|
||||
|
||||
getMonitorPageTitle: async ({ callES, monitorId }) => {
|
||||
const params = {
|
||||
index: INDEX_NAMES.HEARTBEAT,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: {
|
||||
term: {
|
||||
'monitor.id': monitorId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sort: [
|
||||
{
|
||||
'@timestamp': {
|
||||
order: 'desc',
|
||||
},
|
||||
},
|
||||
],
|
||||
size: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await callES('search', params);
|
||||
const pageTitle: Ping | null = get(result, 'hits.hits[0]._source', null);
|
||||
if (pageTitle === null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id: get(pageTitle, 'monitor.id', null) || monitorId,
|
||||
url: get(pageTitle, 'url.full', null),
|
||||
name: get(pageTitle, 'monitor.name', null),
|
||||
};
|
||||
},
|
||||
|
||||
getMonitorDetails: async ({ callES, monitorId, dateStart, dateEnd }) => {
|
||||
const queryFilters: any = [
|
||||
{
|
||||
|
@ -289,11 +253,6 @@ export const elasticsearchMonitorsAdapter: UMMonitorsAdapter = {
|
|||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch data for the monitor page title.
|
||||
* @param request Kibana server request
|
||||
*
|
||||
*/
|
||||
getMonitorLocations: async ({ callES, monitorId, dateStart, dateEnd }) => {
|
||||
const params = {
|
||||
index: INDEX_NAMES.HEARTBEAT,
|
||||
|
|
|
@ -411,7 +411,7 @@ describe('ElasticsearchPingsAdapter class', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getLatestMonitorDocs', () => {
|
||||
describe('getLatestMonitorStatus', () => {
|
||||
let expectedGetLatestSearchParams: any;
|
||||
beforeEach(() => {
|
||||
expectedGetLatestSearchParams = {
|
||||
|
@ -429,7 +429,7 @@ describe('ElasticsearchPingsAdapter class', () => {
|
|||
},
|
||||
},
|
||||
{
|
||||
term: { 'monitor.id': 'testmonitor' },
|
||||
term: { 'monitor.id': 'testMonitor' },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -467,7 +467,7 @@ describe('ElasticsearchPingsAdapter class', () => {
|
|||
_source: {
|
||||
'@timestamp': 123456,
|
||||
monitor: {
|
||||
id: 'testmonitor',
|
||||
id: 'testMonitor',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -483,17 +483,16 @@ describe('ElasticsearchPingsAdapter class', () => {
|
|||
|
||||
it('returns data in expected shape', async () => {
|
||||
const mockEsClient = jest.fn(async (_request: any, _params: any) => mockEsSearchResult);
|
||||
const result = await adapter.getLatestMonitorDocs({
|
||||
const result = await adapter.getLatestMonitorStatus({
|
||||
callES: mockEsClient,
|
||||
dateRangeStart: 'now-1h',
|
||||
dateRangeEnd: 'now',
|
||||
monitorId: 'testmonitor',
|
||||
dateStart: 'now-1h',
|
||||
dateEnd: 'now',
|
||||
monitorId: 'testMonitor',
|
||||
});
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].timestamp).toBe(123456);
|
||||
expect(result[0].monitor).not.toBeFalsy();
|
||||
expect(result.timestamp).toBe(123456);
|
||||
expect(result.monitor).not.toBeFalsy();
|
||||
// @ts-ignore monitor will be defined
|
||||
expect(result[0].monitor.id).toBe('testmonitor');
|
||||
expect(result.monitor.id).toBe('testMonitor');
|
||||
expect(mockEsClient).toHaveBeenCalledWith('search', expectedGetLatestSearchParams);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -33,16 +33,13 @@ export interface GetAllParams {
|
|||
|
||||
export interface GetLatestMonitorDocsParams {
|
||||
/** @member dateRangeStart timestamp bounds */
|
||||
dateRangeStart: string;
|
||||
dateStart?: string;
|
||||
|
||||
/** @member dateRangeEnd timestamp bounds */
|
||||
dateRangeEnd: string;
|
||||
dateEnd?: string;
|
||||
|
||||
/** @member monitorId optional limit to monitorId */
|
||||
monitorId?: string | null;
|
||||
|
||||
/** @member location optional location value for use in filtering*/
|
||||
location?: string | null;
|
||||
}
|
||||
|
||||
export interface GetPingHistogramParams {
|
||||
|
@ -64,7 +61,10 @@ export interface GetPingHistogramParams {
|
|||
export interface UMPingsAdapter {
|
||||
getAll: UMElasticsearchQueryFn<GetAllParams, PingResults>;
|
||||
|
||||
getLatestMonitorDocs: UMElasticsearchQueryFn<GetLatestMonitorDocsParams, Ping[]>;
|
||||
// Get the monitor meta info regardless of timestamp
|
||||
getMonitor: UMElasticsearchQueryFn<GetLatestMonitorDocsParams, Ping>;
|
||||
|
||||
getLatestMonitorStatus: UMElasticsearchQueryFn<GetLatestMonitorDocsParams, Ping>;
|
||||
|
||||
getPingHistogram: UMElasticsearchQueryFn<GetPingHistogramParams, HistogramResult>;
|
||||
|
||||
|
|
|
@ -88,7 +88,10 @@ export const elasticsearchPingsAdapter: UMPingsAdapter = {
|
|||
return results;
|
||||
},
|
||||
|
||||
getLatestMonitorDocs: async ({ callES, dateRangeStart, dateRangeEnd, monitorId, location }) => {
|
||||
// Get The monitor latest state sorted by timestamp with date range
|
||||
getLatestMonitorStatus: async ({ callES, dateStart, dateEnd, monitorId }) => {
|
||||
// TODO: Write tests for this function
|
||||
|
||||
const params = {
|
||||
index: INDEX_NAMES.HEARTBEAT,
|
||||
body: {
|
||||
|
@ -98,13 +101,12 @@ export const elasticsearchPingsAdapter: UMPingsAdapter = {
|
|||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: dateRangeStart,
|
||||
lte: dateRangeEnd,
|
||||
gte: dateStart,
|
||||
lte: dateEnd,
|
||||
},
|
||||
},
|
||||
},
|
||||
...(monitorId ? [{ term: { 'monitor.id': monitorId } }] : []),
|
||||
...(location ? [{ term: { 'observer.geo.name': location } }] : []),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -131,21 +133,45 @@ export const elasticsearchPingsAdapter: UMPingsAdapter = {
|
|||
};
|
||||
|
||||
const result = await callES('search', params);
|
||||
const buckets: any[] = get(result, 'aggregations.by_id.buckets', []);
|
||||
const ping: any = result.aggregations.by_id.buckets?.[0]?.latest.hits?.hits?.[0] ?? {};
|
||||
|
||||
return buckets.map(
|
||||
({
|
||||
latest: {
|
||||
hits: { hits },
|
||||
return {
|
||||
...ping?._source,
|
||||
timestamp: ping?._source?.['@timestamp'],
|
||||
};
|
||||
},
|
||||
|
||||
// Get the monitor meta info regardless of timestamp
|
||||
getMonitor: async ({ callES, monitorId }) => {
|
||||
const params = {
|
||||
index: INDEX_NAMES.HEARTBEAT,
|
||||
body: {
|
||||
size: 1,
|
||||
_source: ['url', 'monitor', 'observer'],
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
term: {
|
||||
'monitor.id': monitorId,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}) => {
|
||||
const timestamp = hits[0]._source[`@timestamp`];
|
||||
return {
|
||||
...hits[0]._source,
|
||||
timestamp,
|
||||
};
|
||||
}
|
||||
);
|
||||
sort: [
|
||||
{
|
||||
'@timestamp': {
|
||||
order: 'desc',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await callES('search', params);
|
||||
|
||||
return result.hits.hits[0]?._source;
|
||||
},
|
||||
|
||||
getPingHistogram: async ({
|
||||
|
|
|
@ -9,7 +9,12 @@ import { createGetIndexPatternRoute } from './index_pattern';
|
|||
import { createLogMonitorPageRoute, createLogOverviewPageRoute } from './telemetry';
|
||||
import { createGetSnapshotCount } from './snapshot';
|
||||
import { UMRestApiRouteFactory } from './types';
|
||||
import { createGetMonitorDetailsRoute, createGetMonitorLocationsRoute } from './monitors';
|
||||
import {
|
||||
createGetMonitorRoute,
|
||||
createGetMonitorDetailsRoute,
|
||||
createGetMonitorLocationsRoute,
|
||||
createGetStatusBarRoute,
|
||||
} from './monitors';
|
||||
|
||||
export * from './types';
|
||||
export { createRouteWithAuth } from './create_route_with_auth';
|
||||
|
@ -17,8 +22,10 @@ export { uptimeRouteWrapper } from './uptime_route_wrapper';
|
|||
export const restApiRoutes: UMRestApiRouteFactory[] = [
|
||||
createGetAllRoute,
|
||||
createGetIndexPatternRoute,
|
||||
createGetMonitorRoute,
|
||||
createGetMonitorDetailsRoute,
|
||||
createGetMonitorLocationsRoute,
|
||||
createGetStatusBarRoute,
|
||||
createGetSnapshotCount,
|
||||
createLogMonitorPageRoute,
|
||||
createLogOverviewPageRoute,
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
|
||||
export { createGetMonitorDetailsRoute } from './monitors_details';
|
||||
export { createGetMonitorLocationsRoute } from './monitor_locations';
|
||||
export { createGetMonitorRoute, createGetStatusBarRoute } from './status';
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { UMServerLibs } from '../../lib/lib';
|
||||
import { UMRestApiRouteFactory } from '../types';
|
||||
|
||||
export const createGetMonitorRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({
|
||||
method: 'GET',
|
||||
path: '/api/uptime/monitor/selected',
|
||||
validate: {
|
||||
query: schema.object({
|
||||
monitorId: schema.string(),
|
||||
}),
|
||||
},
|
||||
options: {
|
||||
tags: ['access:uptime'],
|
||||
},
|
||||
handler: async ({ callES }, _context, request, response): Promise<any> => {
|
||||
const { monitorId } = request.query;
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
...(await libs.pings.getMonitor({ callES, monitorId })),
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const createGetStatusBarRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({
|
||||
method: 'GET',
|
||||
path: '/api/uptime/monitor/status',
|
||||
validate: {
|
||||
query: schema.object({
|
||||
monitorId: schema.string(),
|
||||
dateStart: schema.string(),
|
||||
dateEnd: schema.string(),
|
||||
}),
|
||||
},
|
||||
options: {
|
||||
tags: ['access:uptime'],
|
||||
},
|
||||
handler: async ({ callES }, _context, request, response): Promise<any> => {
|
||||
const { monitorId, dateStart, dateEnd } = request.query;
|
||||
const result = await libs.pings.getLatestMonitorStatus({
|
||||
callES,
|
||||
monitorId,
|
||||
dateStart,
|
||||
dateEnd,
|
||||
});
|
||||
return response.ok({
|
||||
body: {
|
||||
...result,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
|
@ -11746,7 +11746,6 @@
|
|||
"xpack.uptime.emptyState.loadingMessage": "読み込み中…",
|
||||
"xpack.uptime.emptyState.noDataTitle": "利用可能なアップタイムデータがありません",
|
||||
"xpack.uptime.emptyStateError.title": "エラー",
|
||||
"xpack.uptime.emptyStatusBar.defaultMessage": "監視 ID {monitorId} のデータが見つかりません",
|
||||
"xpack.uptime.errorMessage": "エラー: {message}",
|
||||
"xpack.uptime.featureCatalogueDescription": "エンドポイントヘルスチェックとアップタイム監視を行います。",
|
||||
"xpack.uptime.featureRegistry.uptimeFeatureName": "アップタイム",
|
||||
|
@ -11786,7 +11785,6 @@
|
|||
"xpack.uptime.monitorList.statusColumn.upLabel": "アップ",
|
||||
"xpack.uptime.monitorList.statusColumnLabel": "ステータス",
|
||||
"xpack.uptime.monitorStatusBar.durationTextAriaLabel": "ミリ秒単位の監視時間",
|
||||
"xpack.uptime.monitorStatusBar.healthStatus.durationInMillisecondsMessage": "{duration}ms",
|
||||
"xpack.uptime.monitorStatusBar.healthStatusMessage.downLabel": "ダウン",
|
||||
"xpack.uptime.monitorStatusBar.healthStatusMessage.upLabel": "アップ",
|
||||
"xpack.uptime.monitorStatusBar.healthStatusMessageAriaLabel": "監視ステータス",
|
||||
|
|
|
@ -11774,7 +11774,6 @@
|
|||
"xpack.uptime.emptyState.loadingMessage": "正在加载……",
|
||||
"xpack.uptime.emptyState.noDataTitle": "没有可用的运行时间数据",
|
||||
"xpack.uptime.emptyStateError.title": "错误",
|
||||
"xpack.uptime.emptyStatusBar.defaultMessage": "未找到监测 ID {monitorId} 的数据",
|
||||
"xpack.uptime.errorMessage": "错误:{message}",
|
||||
"xpack.uptime.featureCatalogueDescription": "执行终端节点运行状况检查和运行时间监测。",
|
||||
"xpack.uptime.featureRegistry.uptimeFeatureName": "运行时间",
|
||||
|
@ -11814,7 +11813,6 @@
|
|||
"xpack.uptime.monitorList.statusColumn.upLabel": "运行",
|
||||
"xpack.uptime.monitorList.statusColumnLabel": "状态",
|
||||
"xpack.uptime.monitorStatusBar.durationTextAriaLabel": "监测持续时间(毫秒)",
|
||||
"xpack.uptime.monitorStatusBar.healthStatus.durationInMillisecondsMessage": "{duration}ms",
|
||||
"xpack.uptime.monitorStatusBar.healthStatusMessage.downLabel": "关闭",
|
||||
"xpack.uptime.monitorStatusBar.healthStatusMessage.upLabel": "运行",
|
||||
"xpack.uptime.monitorStatusBar.healthStatusMessageAriaLabel": "检测状态",
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"monitorPageTitle": {
|
||||
"id": "0002-up",
|
||||
"url": "http://localhost:5678/pattern?r=200x1",
|
||||
"name": ""
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
[
|
||||
{
|
||||
"timestamp": "2019-09-11T03:40:34.371Z",
|
||||
"monitor": {
|
||||
"status": "up",
|
||||
"duration": {
|
||||
"us": 24627
|
||||
}
|
||||
},
|
||||
"observer": {
|
||||
"geo": {
|
||||
"name": "mpls"
|
||||
}
|
||||
},
|
||||
"tls": null,
|
||||
"url": {
|
||||
"full": "http://localhost:5678/pattern?r=200x1"
|
||||
}
|
||||
}
|
||||
]
|
|
@ -10,6 +10,7 @@ import { join } from 'path';
|
|||
import { cloneDeep } from 'lodash';
|
||||
|
||||
const fixturesDir = join(__dirname, '..', 'fixtures');
|
||||
const restFixturesDir = join(__dirname, '../../rest/', 'fixtures');
|
||||
|
||||
const excludeFieldsFrom = (from: any, excluder?: (d: any) => any): any => {
|
||||
const clone = cloneDeep(from);
|
||||
|
@ -20,7 +21,11 @@ const excludeFieldsFrom = (from: any, excluder?: (d: any) => any): any => {
|
|||
};
|
||||
|
||||
export const expectFixtureEql = <T>(data: T, fixtureName: string, excluder?: (d: T) => void) => {
|
||||
const fixturePath = join(fixturesDir, `${fixtureName}.json`);
|
||||
let fixturePath = join(fixturesDir, `${fixtureName}.json`);
|
||||
if (!fs.existsSync(fixturePath)) {
|
||||
fixturePath = join(restFixturesDir, `${fixtureName}.json`);
|
||||
}
|
||||
|
||||
const dataExcluded = excludeFieldsFrom(data, excluder);
|
||||
expect(dataExcluded).not.to.be(undefined);
|
||||
if (process.env.UPDATE_UPTIME_FIXTURES) {
|
||||
|
|
|
@ -13,9 +13,7 @@ export default function({ loadTestFile }) {
|
|||
loadTestFile(require.resolve('./doc_count'));
|
||||
loadTestFile(require.resolve('./filter_bar'));
|
||||
loadTestFile(require.resolve('./monitor_charts'));
|
||||
loadTestFile(require.resolve('./monitor_page_title'));
|
||||
loadTestFile(require.resolve('./monitor_states'));
|
||||
loadTestFile(require.resolve('./monitor_status_bar'));
|
||||
loadTestFile(require.resolve('./ping_list'));
|
||||
loadTestFile(require.resolve('./snapshot_histogram'));
|
||||
});
|
||||
|
|
|
@ -1,37 +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 { monitorPageTitleQueryString } from '../../../../../legacy/plugins/uptime/public/queries/monitor_page_title_query';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { expectFixtureEql } from './helpers/expect_fixture_eql';
|
||||
|
||||
export default function({ getService }: FtrProviderContext) {
|
||||
describe('monitor_page_title', () => {
|
||||
before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat'));
|
||||
after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat'));
|
||||
|
||||
const supertest = getService('supertest');
|
||||
|
||||
it('will fetch a title for a given monitorId', async () => {
|
||||
const getMonitorTitleQuery = {
|
||||
operationName: 'MonitorPageTitle',
|
||||
query: monitorPageTitleQueryString,
|
||||
variables: {
|
||||
monitorId: '0002-up',
|
||||
},
|
||||
};
|
||||
|
||||
const {
|
||||
body: { data },
|
||||
} = await supertest
|
||||
.post('/api/uptime/graphql')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ ...getMonitorTitleQuery });
|
||||
|
||||
expectFixtureEql(data, 'monitor_page_title');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,58 +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 { monitorStatusBarQueryString } from '../../../../../legacy/plugins/uptime/public/queries';
|
||||
import { expectFixtureEql } from './helpers/expect_fixture_eql';
|
||||
|
||||
export default function({ getService }) {
|
||||
describe('monitorStatusBar query', () => {
|
||||
before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat'));
|
||||
after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat'));
|
||||
|
||||
const supertest = getService('supertest');
|
||||
|
||||
it('returns the status for all monitors with no ID filtering', async () => {
|
||||
const getMonitorStatusBarQuery = {
|
||||
operationName: 'MonitorStatus',
|
||||
query: monitorStatusBarQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2025-01-28T19:00:16.078Z',
|
||||
},
|
||||
};
|
||||
const {
|
||||
body: {
|
||||
data: { monitorStatus: responseData },
|
||||
},
|
||||
} = await supertest
|
||||
.post('/api/uptime/graphql')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ ...getMonitorStatusBarQuery });
|
||||
|
||||
expectFixtureEql(responseData, 'monitor_status_all', res =>
|
||||
res.forEach(i => delete i.millisFromNow)
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the status for only the given monitor', async () => {
|
||||
const getMonitorStatusBarQuery = {
|
||||
operationName: 'MonitorStatus',
|
||||
query: monitorStatusBarQueryString,
|
||||
variables: {
|
||||
dateRangeStart: '2019-01-28T17:40:08.078Z',
|
||||
dateRangeEnd: '2025-01-28T19:00:16.078Z',
|
||||
monitorId: '0002-up',
|
||||
},
|
||||
};
|
||||
const res = await supertest
|
||||
.post('/api/uptime/graphql')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ ...getMonitorStatusBarQuery });
|
||||
|
||||
expectFixtureEql(res.body.data.monitorStatus, 'monitor_status_by_id');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
{
|
||||
"@timestamp": "2019-09-11T03:40:34.371Z",
|
||||
"agent": {
|
||||
"ephemeral_id": "412a92a8-2142-4b1a-a7a2-1afd32e12f85",
|
||||
"hostname": "avc-x1x",
|
||||
"id": "04e1d082-65bc-4929-8d65-d0768a2621c4",
|
||||
"type": "heartbeat",
|
||||
"version": "8.0.0"
|
||||
},
|
||||
"ecs": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"event": {
|
||||
"dataset": "uptime"
|
||||
},
|
||||
"host": {
|
||||
"name": "avc-x1x"
|
||||
},
|
||||
"http": {
|
||||
"response": {
|
||||
"body": {
|
||||
"bytes": 3,
|
||||
"hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf"
|
||||
},
|
||||
"status_code": 200
|
||||
},
|
||||
"rtt": {
|
||||
"content": {
|
||||
"us": 57
|
||||
},
|
||||
"response_header": {
|
||||
"us": 262
|
||||
},
|
||||
"total": {
|
||||
"us": 20331
|
||||
},
|
||||
"validate": {
|
||||
"us": 319
|
||||
},
|
||||
"write_request": {
|
||||
"us": 82
|
||||
}
|
||||
}
|
||||
},
|
||||
"monitor": {
|
||||
"check_group": "d76f0762-d445-11e9-88e3-3e80641b9c71",
|
||||
"duration": {
|
||||
"us": 24627
|
||||
},
|
||||
"id": "0002-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": {
|
||||
"geo": {
|
||||
"location": "37.926868, -78.024902",
|
||||
"name": "mpls"
|
||||
},
|
||||
"hostname": "avc-x1x"
|
||||
},
|
||||
"resolve": {
|
||||
"ip": "127.0.0.1",
|
||||
"rtt": {
|
||||
"us": 4218
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"down": 0,
|
||||
"up": 1
|
||||
},
|
||||
"tcp": {
|
||||
"rtt": {
|
||||
"connect": {
|
||||
"us": 103
|
||||
}
|
||||
}
|
||||
},
|
||||
"timestamp": "2019-09-11T03:40:34.371Z",
|
||||
"url": {
|
||||
"domain": "localhost",
|
||||
"full": "http://localhost:5678/pattern?r=200x1",
|
||||
"path": "/pattern",
|
||||
"port": 5678,
|
||||
"query": "r=200x1",
|
||||
"scheme": "http"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"monitor": {
|
||||
"check_group": "d76f0762-d445-11e9-88e3-3e80641b9c71",
|
||||
"duration": {
|
||||
"us": 24627
|
||||
},
|
||||
"id": "0002-up",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"status": "up",
|
||||
"type": "http"
|
||||
},
|
||||
"observer": {
|
||||
"geo": {
|
||||
"location": "37.926868, -78.024902",
|
||||
"name": "mpls"
|
||||
},
|
||||
"hostname": "avc-x1x"
|
||||
},
|
||||
"url": {
|
||||
"domain": "localhost",
|
||||
"full": "http://localhost:5678/pattern?r=200x1",
|
||||
"path": "/pattern",
|
||||
"port": 5678,
|
||||
"query": "r=200x1",
|
||||
"scheme": "http"
|
||||
}
|
||||
}
|
|
@ -9,8 +9,16 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
|
|||
export default function({ getService, loadTestFile }: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
describe('uptime REST endpoints', () => {
|
||||
before('load heartbeat data', () => esArchiver.load('uptime/blank'));
|
||||
after('unload', () => esArchiver.unload('uptime/blank'));
|
||||
loadTestFile(require.resolve('./snapshot'));
|
||||
describe('with generated data', () => {
|
||||
before('load heartbeat data', () => esArchiver.load('uptime/blank'));
|
||||
after('unload', () => esArchiver.unload('uptime/blank'));
|
||||
loadTestFile(require.resolve('./snapshot'));
|
||||
});
|
||||
describe('with real-world data', () => {
|
||||
before('load heartbeat data', () => esArchiver.load('uptime/full_heartbeat'));
|
||||
after('unload', () => esArchiver.unload('uptime/full_heartbeat'));
|
||||
loadTestFile(require.resolve('./monitor_latest_status'));
|
||||
loadTestFile(require.resolve('./selected_monitor'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { expectFixtureEql } from '../graphql/helpers/expect_fixture_eql';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function({ getService }: FtrProviderContext) {
|
||||
describe('get monitor latest status API', () => {
|
||||
const dateStart = '2018-01-28T17:40:08.078Z';
|
||||
const dateEnd = '2025-01-28T19:00:16.078Z';
|
||||
const monitorId = '0002-up';
|
||||
|
||||
const supertest = getService('supertest');
|
||||
|
||||
it('returns the status for only the given monitor', async () => {
|
||||
const apiResponse = await supertest.get(
|
||||
`/api/uptime/monitor/status?monitorId=${monitorId}&dateStart=${dateStart}&dateEnd=${dateEnd}`
|
||||
);
|
||||
expectFixtureEql(apiResponse.body, 'monitor_latest_status');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { expectFixtureEql } from '../graphql/helpers/expect_fixture_eql';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function({ getService }: FtrProviderContext) {
|
||||
describe('get selected monitor by ID', () => {
|
||||
const monitorId = '0002-up';
|
||||
|
||||
const supertest = getService('supertest');
|
||||
|
||||
it('returns the monitor for give ID', async () => {
|
||||
const apiResponse = await supertest.get(
|
||||
`/api/uptime/monitor/selected?monitorId=${monitorId}`
|
||||
);
|
||||
expectFixtureEql(apiResponse.body, 'selected_monitor');
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue