mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Feature/issue 44550 new details panel and location map (#50518)
* update * added an embeddable maps * update map config * added options to disable zoom, hide tool tips, widgets/overlays in embeddable maps * added options to disable zoom, hide tool tips, widgets/overlays in embeddable maps * added bool option to hide header * revert panel changes * update panel * update map * added disable interactive * update uptime embeddable * update redux state and removed widget over lay hiding * refactor widget overlay prop * update layout * update rest API * remove maps code * update components * update up/down points on map * update snaps * fixed type * update request * update request to include rnage * fix tests * utilize newly added setLayers method * remove unused code * refactor code
This commit is contained in:
parent
6ca913874c
commit
d79631adaa
32 changed files with 4080 additions and 126 deletions
27
x-pack/legacy/plugins/uptime/common/runtime_types/common.ts
Normal file
27
x-pack/legacy/plugins/uptime/common/runtime_types/common.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
|
||||
export const LocationType = t.partial({
|
||||
lat: t.string,
|
||||
lon: t.string,
|
||||
});
|
||||
|
||||
export const CheckGeoType = t.partial({
|
||||
name: t.string,
|
||||
location: LocationType,
|
||||
});
|
||||
|
||||
export const SummaryType = t.partial({
|
||||
up: t.number,
|
||||
down: t.number,
|
||||
geo: CheckGeoType,
|
||||
});
|
||||
|
||||
export type Summary = t.TypeOf<typeof SummaryType>;
|
||||
export type CheckGeo = t.TypeOf<typeof CheckGeoType>;
|
||||
export type Location = t.TypeOf<typeof LocationType>;
|
|
@ -4,5 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './common';
|
||||
export * from './snapshot';
|
||||
export * from './monitor/monitor_details';
|
||||
export * from './monitor/monitor_locations';
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { CheckGeoType, SummaryType } from '../common';
|
||||
|
||||
// IO type for validation
|
||||
export const MonitorLocationType = t.partial({
|
||||
summary: SummaryType,
|
||||
geo: CheckGeoType,
|
||||
});
|
||||
|
||||
// Typescript type for type checking
|
||||
export type MonitorLocation = t.TypeOf<typeof MonitorLocationType>;
|
||||
|
||||
export const MonitorLocationsType = t.intersection([
|
||||
t.type({ monitorId: t.string }),
|
||||
t.partial({ locations: t.array(MonitorLocationType) }),
|
||||
]);
|
||||
export type MonitorLocations = t.TypeOf<typeof MonitorLocationsType>;
|
|
@ -7,6 +7,7 @@
|
|||
import chrome from 'ui/chrome';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { Plugin } from './plugin';
|
||||
import 'uiExports/embeddableFactories';
|
||||
|
||||
new Plugin(
|
||||
{ opaqueId: Symbol('uptime'), env: {} as any, config: { get: () => ({} as any) } },
|
||||
|
|
|
@ -2,71 +2,67 @@
|
|||
|
||||
exports[`MonitorStatusBar component renders duration in ms, not us 1`] = `
|
||||
<div
|
||||
class="euiPanel euiPanel--paddingMedium"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive euiFlexGroup--wrap"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive euiFlexGroup--wrap"
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
aria-label="Monitor status"
|
||||
class="euiHealth"
|
||||
style="line-height:inherit"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<svg
|
||||
class="euiIcon euiIcon--medium euiIcon--success euiIcon-isLoading"
|
||||
focusable="false"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
Up
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
aria-label="Monitor status"
|
||||
class="euiHealth"
|
||||
style="line-height:inherit"
|
||||
<a
|
||||
aria-label="Monitor URL link"
|
||||
class="euiLink euiLink--primary"
|
||||
href="https://www.example.com/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<svg
|
||||
class="euiIcon euiIcon--medium euiIcon--success euiIcon-isLoading"
|
||||
focusable="false"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
Up
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<a
|
||||
aria-label="Monitor URL link"
|
||||
class="euiLink euiLink--primary"
|
||||
href="https://www.example.com/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
https://www.example.com/
|
||||
</a>
|
||||
</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
|
||||
https://www.example.com/
|
||||
</a>
|
||||
</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
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* 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 lowPolyLayerFeatures from '../low_poly_layer.json';
|
||||
|
||||
export const mockDownPointsLayer = {
|
||||
id: 'down_points',
|
||||
label: 'Down Locations',
|
||||
sourceDescriptor: {
|
||||
type: 'GEOJSON_FILE',
|
||||
__featureCollection: {
|
||||
features: [
|
||||
{
|
||||
type: 'feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [13.399262, 52.487239],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [13.399262, 55.487239],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [14.399262, 54.487239],
|
||||
},
|
||||
},
|
||||
],
|
||||
type: 'FeatureCollection',
|
||||
},
|
||||
},
|
||||
visible: true,
|
||||
style: {
|
||||
type: 'VECTOR',
|
||||
properties: {
|
||||
fillColor: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
color: '#BC261E',
|
||||
},
|
||||
},
|
||||
lineColor: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
color: '#fff',
|
||||
},
|
||||
},
|
||||
lineWidth: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
size: 2,
|
||||
},
|
||||
},
|
||||
iconSize: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
size: 6,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'VECTOR',
|
||||
};
|
||||
|
||||
export const mockUpPointsLayer = {
|
||||
id: 'up_points',
|
||||
label: 'Up Locations',
|
||||
sourceDescriptor: {
|
||||
type: 'GEOJSON_FILE',
|
||||
__featureCollection: {
|
||||
features: [
|
||||
{
|
||||
type: 'feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [13.399262, 52.487239],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [13.399262, 55.487239],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [14.399262, 54.487239],
|
||||
},
|
||||
},
|
||||
],
|
||||
type: 'FeatureCollection',
|
||||
},
|
||||
},
|
||||
visible: true,
|
||||
style: {
|
||||
type: 'VECTOR',
|
||||
properties: {
|
||||
fillColor: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
color: '#98A2B2',
|
||||
},
|
||||
},
|
||||
lineColor: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
color: '#fff',
|
||||
},
|
||||
},
|
||||
lineWidth: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
size: 2,
|
||||
},
|
||||
},
|
||||
iconSize: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
size: 6,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'VECTOR',
|
||||
};
|
||||
|
||||
export const mockLayerList = [
|
||||
{
|
||||
id: 'low_poly_layer',
|
||||
label: 'World countries',
|
||||
minZoom: 0,
|
||||
maxZoom: 24,
|
||||
alpha: 1,
|
||||
sourceDescriptor: {
|
||||
id: 'b7486535-171b-4d3b-bb2e-33c1a0a2854c',
|
||||
type: 'GEOJSON_FILE',
|
||||
__featureCollection: lowPolyLayerFeatures,
|
||||
},
|
||||
visible: true,
|
||||
style: {
|
||||
type: 'VECTOR',
|
||||
properties: {
|
||||
fillColor: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
color: '#cad3e4',
|
||||
},
|
||||
},
|
||||
lineColor: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
color: '#fff',
|
||||
},
|
||||
},
|
||||
lineWidth: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
size: 0,
|
||||
},
|
||||
},
|
||||
iconSize: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
size: 6,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'VECTOR',
|
||||
},
|
||||
mockDownPointsLayer,
|
||||
mockUpPointsLayer,
|
||||
];
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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, { useEffect, useState } from 'react';
|
||||
import uuid from 'uuid';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { start } from '../../../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy';
|
||||
import * as i18n from './translations';
|
||||
// @ts-ignore
|
||||
import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../maps/common/constants';
|
||||
|
||||
import { MapEmbeddable } from './types';
|
||||
import { getLayerList } from './map_config';
|
||||
|
||||
export interface EmbeddedMapProps {
|
||||
upPoints: LocationPoint[];
|
||||
downPoints: LocationPoint[];
|
||||
}
|
||||
|
||||
export interface LocationPoint {
|
||||
lat: string;
|
||||
lon: string;
|
||||
}
|
||||
|
||||
const EmbeddedPanel = styled.div`
|
||||
z-index: auto;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
.embPanel__content {
|
||||
display: flex;
|
||||
flex: 1 1 100%;
|
||||
z-index: 1;
|
||||
min-height: 0; // Absolute must for Firefox to scroll contents
|
||||
}
|
||||
&&& .mapboxgl-canvas {
|
||||
animation: none !important;
|
||||
}
|
||||
`;
|
||||
|
||||
export const EmbeddedMap = ({ upPoints, downPoints }: EmbeddedMapProps) => {
|
||||
const [embeddable, setEmbeddable] = useState<MapEmbeddable>();
|
||||
|
||||
useEffect(() => {
|
||||
async function setupEmbeddable() {
|
||||
const mapState = {
|
||||
layerList: getLayerList(upPoints, downPoints),
|
||||
title: i18n.MAP_TITLE,
|
||||
};
|
||||
// @ts-ignore
|
||||
const embeddableObject = await factory.createFromState(mapState, input, undefined);
|
||||
|
||||
setEmbeddable(embeddableObject);
|
||||
}
|
||||
setupEmbeddable();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (embeddable) {
|
||||
embeddable.setLayerList(getLayerList(upPoints, downPoints));
|
||||
}
|
||||
}, [upPoints, downPoints]);
|
||||
|
||||
useEffect(() => {
|
||||
if (embeddableRoot.current && embeddable) {
|
||||
embeddable.render(embeddableRoot.current);
|
||||
}
|
||||
}, [embeddable]);
|
||||
|
||||
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 },
|
||||
viewMode: 'view',
|
||||
isLayerTOCOpen: false,
|
||||
hideFilterActions: true,
|
||||
mapCenter: { lon: 11, lat: 47, zoom: 0 },
|
||||
disableInteractive: true,
|
||||
disableTooltipControl: true,
|
||||
hideToolbarOverlay: true,
|
||||
};
|
||||
|
||||
const embeddableRoot: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
|
||||
return (
|
||||
<EmbeddedPanel>
|
||||
<div className="embPanel__content" ref={embeddableRoot} />
|
||||
</EmbeddedPanel>
|
||||
);
|
||||
};
|
||||
|
||||
EmbeddedMap.displayName = 'EmbeddedMap';
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 { getLayerList } from './map_config';
|
||||
import { mockLayerList } from './__mocks__/mock';
|
||||
import { LocationPoint } from './embedded_map';
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
return {
|
||||
v4: jest.fn(() => 'uuid.v4()'),
|
||||
};
|
||||
});
|
||||
|
||||
describe('map_config', () => {
|
||||
let upPoints: LocationPoint[];
|
||||
let downPoints: LocationPoint[];
|
||||
|
||||
beforeEach(() => {
|
||||
upPoints = [
|
||||
{ lat: '52.487239', lon: '13.399262' },
|
||||
{ lat: '55.487239', lon: '13.399262' },
|
||||
{ lat: '54.487239', lon: '14.399262' },
|
||||
];
|
||||
downPoints = [
|
||||
{ lat: '52.487239', lon: '13.399262' },
|
||||
{ lat: '55.487239', lon: '13.399262' },
|
||||
{ lat: '54.487239', lon: '14.399262' },
|
||||
];
|
||||
});
|
||||
|
||||
describe('#getLayerList', () => {
|
||||
test('it returns the low poly layer', () => {
|
||||
const layerList = getLayerList(upPoints, downPoints);
|
||||
expect(layerList).toStrictEqual(mockLayerList);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* 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 lowPolyLayerFeatures from './low_poly_layer.json';
|
||||
import { LocationPoint } from './embedded_map';
|
||||
|
||||
/**
|
||||
* Returns `Source/Destination Point-to-point` Map LayerList configuration, with a source,
|
||||
* destination, and line layer for each of the provided indexPatterns
|
||||
*
|
||||
*/
|
||||
export const getLayerList = (upPoints: LocationPoint[], downPoints: LocationPoint[]) => {
|
||||
return [getLowPolyLayer(), getDownPointsLayer(downPoints), getUpPointsLayer(upPoints)];
|
||||
};
|
||||
|
||||
export const getLowPolyLayer = () => {
|
||||
return {
|
||||
id: 'low_poly_layer',
|
||||
label: 'World countries',
|
||||
minZoom: 0,
|
||||
maxZoom: 24,
|
||||
alpha: 1,
|
||||
sourceDescriptor: {
|
||||
id: 'b7486535-171b-4d3b-bb2e-33c1a0a2854c',
|
||||
type: 'GEOJSON_FILE',
|
||||
__featureCollection: lowPolyLayerFeatures,
|
||||
},
|
||||
visible: true,
|
||||
style: {
|
||||
type: 'VECTOR',
|
||||
properties: {
|
||||
fillColor: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
color: '#cad3e4',
|
||||
},
|
||||
},
|
||||
lineColor: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
color: '#fff',
|
||||
},
|
||||
},
|
||||
lineWidth: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
size: 0,
|
||||
},
|
||||
},
|
||||
iconSize: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
size: 6,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'VECTOR',
|
||||
};
|
||||
};
|
||||
|
||||
export const getDownPointsLayer = (downPoints: LocationPoint[]) => {
|
||||
const features = downPoints?.map(point => ({
|
||||
type: 'feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [+point.lon, +point.lat],
|
||||
},
|
||||
}));
|
||||
return {
|
||||
id: 'down_points',
|
||||
label: 'Down Locations',
|
||||
sourceDescriptor: {
|
||||
type: 'GEOJSON_FILE',
|
||||
__featureCollection: {
|
||||
features,
|
||||
type: 'FeatureCollection',
|
||||
},
|
||||
},
|
||||
visible: true,
|
||||
style: {
|
||||
type: 'VECTOR',
|
||||
properties: {
|
||||
fillColor: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
color: '#BC261E',
|
||||
},
|
||||
},
|
||||
lineColor: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
color: '#fff',
|
||||
},
|
||||
},
|
||||
lineWidth: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
size: 2,
|
||||
},
|
||||
},
|
||||
iconSize: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
size: 6,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'VECTOR',
|
||||
};
|
||||
};
|
||||
|
||||
export const getUpPointsLayer = (upPoints: LocationPoint[]) => {
|
||||
const features = upPoints?.map(point => ({
|
||||
type: 'feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [+point.lon, +point.lat],
|
||||
},
|
||||
}));
|
||||
return {
|
||||
id: 'up_points',
|
||||
label: 'Up Locations',
|
||||
sourceDescriptor: {
|
||||
type: 'GEOJSON_FILE',
|
||||
__featureCollection: {
|
||||
features,
|
||||
type: 'FeatureCollection',
|
||||
},
|
||||
},
|
||||
visible: true,
|
||||
style: {
|
||||
type: 'VECTOR',
|
||||
properties: {
|
||||
fillColor: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
color: '#98A2B2',
|
||||
},
|
||||
},
|
||||
lineColor: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
color: '#fff',
|
||||
},
|
||||
},
|
||||
lineWidth: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
size: 2,
|
||||
},
|
||||
},
|
||||
iconSize: {
|
||||
type: 'STATIC',
|
||||
options: {
|
||||
size: 6,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'VECTOR',
|
||||
};
|
||||
};
|
|
@ -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 MAP_TITLE = i18n.translate(
|
||||
'xpack.uptime.components.embeddables.embeddedMap.embeddablePanelTitle',
|
||||
{
|
||||
defaultMessage: 'Monitor Observer Location Map',
|
||||
}
|
||||
);
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { Query } from 'src/plugins/data/common';
|
||||
import { TimeRange } from 'src/plugins/data/public';
|
||||
import {
|
||||
EmbeddableInput,
|
||||
EmbeddableOutput,
|
||||
IEmbeddable,
|
||||
} from '../../../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public';
|
||||
|
||||
import { esFilters } from '../../../../../../../../../src/plugins/data/public';
|
||||
|
||||
export interface MapEmbeddableInput extends EmbeddableInput {
|
||||
filters: esFilters.Filter[];
|
||||
query: Query;
|
||||
refreshConfig: {
|
||||
isPaused: boolean;
|
||||
interval: number;
|
||||
};
|
||||
timeRange?: TimeRange;
|
||||
}
|
||||
|
||||
export interface CustomProps {
|
||||
setLayerList: Function;
|
||||
}
|
||||
|
||||
export type MapEmbeddable = IEmbeddable<MapEmbeddableInput, EmbeddableOutput> & CustomProps;
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './location_map';
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EmbeddedMap, LocationPoint } from './embeddables/embedded_map';
|
||||
|
||||
const MapPanel = styled.div`
|
||||
height: 400px;
|
||||
width: 520px;
|
||||
`;
|
||||
|
||||
interface LocationMapProps {
|
||||
monitorLocations: any;
|
||||
}
|
||||
|
||||
export const LocationMap = ({ monitorLocations }: LocationMapProps) => {
|
||||
const upPoints: LocationPoint[] = [];
|
||||
const downPoints: LocationPoint[] = [];
|
||||
|
||||
if (monitorLocations?.locations) {
|
||||
monitorLocations.locations.forEach((item: any) => {
|
||||
if (item.summary.down === 0) {
|
||||
upPoints.push(item.geo.location);
|
||||
} else {
|
||||
downPoints.push(item.geo.location);
|
||||
}
|
||||
});
|
||||
}
|
||||
return (
|
||||
<MapPanel>
|
||||
<EmbeddedMap upPoints={upPoints} downPoints={downPoints} />
|
||||
</MapPanel>
|
||||
);
|
||||
};
|
|
@ -4,8 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiHealth, EuiLink, EuiPanel } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiHealth, EuiLink } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { get } from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
@ -16,6 +15,7 @@ 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[];
|
||||
|
@ -28,58 +28,33 @@ interface MonitorStatusBarProps {
|
|||
type Props = MonitorStatusBarProps & UptimeGraphQLQueryProps<MonitorStatusBarQueryResult>;
|
||||
|
||||
export const MonitorStatusBarComponent = ({ data, monitorId }: Props) => {
|
||||
if (data && data.monitorStatus && data.monitorStatus.length) {
|
||||
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 (
|
||||
<EuiPanel>
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="l" wrap>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth
|
||||
aria-label={i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.healthStatusMessageAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Monitor status',
|
||||
}
|
||||
)}
|
||||
aria-label={labels.healthStatusMessageAriaLabel}
|
||||
color={status === 'up' ? 'success' : 'danger'}
|
||||
style={{ lineHeight: 'inherit' }}
|
||||
>
|
||||
{status === 'up'
|
||||
? i18n.translate('xpack.uptime.monitorStatusBar.healthStatusMessage.upLabel', {
|
||||
defaultMessage: 'Up',
|
||||
})
|
||||
: i18n.translate('xpack.uptime.monitorStatusBar.healthStatusMessage.downLabel', {
|
||||
defaultMessage: 'Down',
|
||||
})}
|
||||
{status === 'up' ? labels.upLabel : labels.downLabel}
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
aria-label={i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.monitorUrlLinkAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Monitor URL link',
|
||||
}
|
||||
)}
|
||||
href={full}
|
||||
target="_blank"
|
||||
>
|
||||
<EuiLink aria-label={labels.monitorUrlLinkAriaLabel} href={full} target="_blank">
|
||||
{full}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexItem>
|
||||
{!!duration && (
|
||||
<EuiFlexItem
|
||||
aria-label={i18n.translate('xpack.uptime.monitorStatusBar.durationTextAriaLabel', {
|
||||
defaultMessage: 'Monitor duration in milliseconds',
|
||||
})}
|
||||
grow={false}
|
||||
>
|
||||
<EuiFlexItem aria-label={labels.durationTextAriaLabel} grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorStatusBar.healthStatus.durationInMillisecondsMessage"
|
||||
values={{ duration: convertMicrosecondsToMilliseconds(duration) }}
|
||||
|
@ -88,30 +63,15 @@ export const MonitorStatusBarComponent = ({ data, monitorId }: Props) => {
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem
|
||||
aria-label={i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.timestampFromNowTextAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Time since last check',
|
||||
}
|
||||
)}
|
||||
grow={true}
|
||||
>
|
||||
<EuiFlexItem aria-label={labels.timestampFromNowTextAriaLabel} grow={true}>
|
||||
{moment(new Date(timestamp).valueOf()).fromNow()}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<MonitorSSLCertificate tls={tls} />
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<EmptyStatusBar
|
||||
message={i18n.translate('xpack.uptime.monitorStatusBar.loadingMessage', {
|
||||
defaultMessage: 'Loading…',
|
||||
})}
|
||||
monitorId={monitorId}
|
||||
/>
|
||||
);
|
||||
return <EmptyStatusBar message={labels.loadingMessage} monitorId={monitorId} />;
|
||||
};
|
||||
|
||||
export const MonitorStatusBar = withUptimeGraphQL<
|
||||
|
|
|
@ -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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const healthStatusMessageAriaLabel = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.healthStatusMessageAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Monitor status',
|
||||
}
|
||||
);
|
||||
|
||||
export const upLabel = i18n.translate('xpack.uptime.monitorStatusBar.healthStatusMessage.upLabel', {
|
||||
defaultMessage: 'Up',
|
||||
});
|
||||
|
||||
export const downLabel = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.healthStatusMessage.downLabel',
|
||||
{
|
||||
defaultMessage: 'Down',
|
||||
}
|
||||
);
|
||||
|
||||
export const monitorUrlLinkAriaLabel = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.monitorUrlLinkAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Monitor URL link',
|
||||
}
|
||||
);
|
||||
|
||||
export const durationTextAriaLabel = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.durationTextAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Monitor duration in milliseconds',
|
||||
}
|
||||
);
|
||||
|
||||
export const timestampFromNowTextAriaLabel = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.timestampFromNowTextAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Time since last check',
|
||||
}
|
||||
);
|
||||
|
||||
export const loadingMessage = i18n.translate('xpack.uptime.monitorStatusBar.loadingMessage', {
|
||||
defaultMessage: 'Loading…',
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 { AppState } from '../../../state';
|
||||
import { getMonitorLocations } 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),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: any) => ({
|
||||
loadMonitorLocations: () => {
|
||||
const { dateStart, dateEnd, monitorId } = ownProps;
|
||||
dispatch(
|
||||
fetchMonitorLocations({
|
||||
monitorId,
|
||||
dateStart,
|
||||
dateEnd,
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const MonitorStatusDetails = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(MonitorStatusDetailsComponent);
|
||||
|
||||
export * from './monitor_status_details';
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import { LocationMap } from '../location_map';
|
||||
import { MonitorStatusBar } from '../monitor_status_bar';
|
||||
|
||||
interface MonitorStatusBarProps {
|
||||
monitorId: string;
|
||||
variables: any;
|
||||
loadMonitorLocations: any;
|
||||
monitorLocations: any;
|
||||
dateStart: any;
|
||||
dateEnd: any;
|
||||
}
|
||||
|
||||
export const MonitorStatusDetailsComponent = ({
|
||||
monitorId,
|
||||
variables,
|
||||
loadMonitorLocations,
|
||||
monitorLocations,
|
||||
dateStart,
|
||||
dateEnd,
|
||||
}: MonitorStatusBarProps) => {
|
||||
useEffect(() => {
|
||||
loadMonitorLocations(monitorId);
|
||||
}, [monitorId, dateStart, dateEnd]);
|
||||
|
||||
return (
|
||||
<EuiPanel>
|
||||
<EuiFlexGroup gutterSize="l" wrap>
|
||||
<EuiFlexItem grow={true}>
|
||||
<MonitorStatusBar monitorId={monitorId} variables={variables} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<LocationMap monitorLocations={monitorLocations} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
|
@ -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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const healthStatusMessageAriaLabel = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.healthStatusMessageAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Monitor status',
|
||||
}
|
||||
);
|
||||
|
||||
export const upLabel = i18n.translate('xpack.uptime.monitorStatusBar.healthStatusMessage.upLabel', {
|
||||
defaultMessage: 'Up',
|
||||
});
|
||||
|
||||
export const downLabel = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.healthStatusMessage.downLabel',
|
||||
{
|
||||
defaultMessage: 'Down',
|
||||
}
|
||||
);
|
||||
|
||||
export const monitorUrlLinkAriaLabel = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.monitorUrlLinkAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Monitor URL link',
|
||||
}
|
||||
);
|
||||
|
||||
export const durationTextAriaLabel = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.durationTextAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Monitor duration in milliseconds',
|
||||
}
|
||||
);
|
||||
|
||||
export const timestampFromNowTextAriaLabel = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.timestampFromNowTextAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Time since last check',
|
||||
}
|
||||
);
|
||||
|
||||
export const loadingMessage = i18n.translate('xpack.uptime.monitorStatusBar.loadingMessage', {
|
||||
defaultMessage: 'Loading…',
|
||||
});
|
|
@ -4,26 +4,19 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
// @ts-ignore No typings for EuiSpacer
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { EuiSpacer } 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,
|
||||
MonitorStatusBar,
|
||||
PingList,
|
||||
} from '../components/functional';
|
||||
import { MonitorCharts, MonitorPageTitle, PingList } from '../components/functional';
|
||||
import { UMUpdateBreadcrumbs } from '../lib/lib';
|
||||
import { UptimeSettingsContext } from '../contexts';
|
||||
import { useUrlParams } from '../hooks';
|
||||
import { stringifyUrlParams } from '../lib/helper/stringify_url_params';
|
||||
import { useTrackPageview } from '../../../infra/public';
|
||||
import { getTitle } from '../lib/helper/get_title';
|
||||
import { MonitorStatusDetails } from '../components/functional/monitor_status_details';
|
||||
|
||||
interface MonitorPageProps {
|
||||
logMonitorPageLoad: () => void;
|
||||
|
@ -92,7 +85,12 @@ export const MonitorPage = ({
|
|||
<Fragment>
|
||||
<MonitorPageTitle monitorId={monitorId} variables={{ monitorId }} />
|
||||
<EuiSpacer size="s" />
|
||||
<MonitorStatusBar monitorId={monitorId} variables={sharedVariables} />
|
||||
<MonitorStatusDetails
|
||||
monitorId={monitorId}
|
||||
variables={sharedVariables}
|
||||
dateStart={absoluteDateRangeStart}
|
||||
dateEnd={absoluteDateRangeEnd}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<MonitorCharts
|
||||
{...colors}
|
||||
|
|
|
@ -4,10 +4,17 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { MonitorLocations } from '../../../common/runtime_types';
|
||||
import { QueryParams } from './types';
|
||||
|
||||
export const FETCH_MONITOR_DETAILS = 'FETCH_MONITOR_DETAILS';
|
||||
export const FETCH_MONITOR_DETAILS_SUCCESS = 'FETCH_MONITOR_DETAILS_SUCCESS';
|
||||
export const FETCH_MONITOR_DETAILS_FAIL = 'FETCH_MONITOR_DETAILS_FAIL';
|
||||
|
||||
export const FETCH_MONITOR_LOCATIONS = 'FETCH_MONITOR_LOCATIONS';
|
||||
export const FETCH_MONITOR_LOCATIONS_SUCCESS = 'FETCH_MONITOR_LOCATIONS_SUCCESS';
|
||||
export const FETCH_MONITOR_LOCATIONS_FAIL = 'FETCH_MONITOR_LOCATIONS_FAIL';
|
||||
|
||||
export interface MonitorDetailsState {
|
||||
monitorId: string;
|
||||
error: Error;
|
||||
|
@ -28,6 +35,25 @@ interface GetMonitorDetailsFailAction {
|
|||
payload: any;
|
||||
}
|
||||
|
||||
export interface MonitorLocationsPayload extends QueryParams {
|
||||
monitorId: string;
|
||||
}
|
||||
|
||||
interface GetMonitorLocationsAction {
|
||||
type: typeof FETCH_MONITOR_LOCATIONS;
|
||||
payload: MonitorLocationsPayload;
|
||||
}
|
||||
|
||||
interface GetMonitorLocationsSuccessAction {
|
||||
type: typeof FETCH_MONITOR_LOCATIONS_SUCCESS;
|
||||
payload: MonitorLocations;
|
||||
}
|
||||
|
||||
interface GetMonitorLocationsFailAction {
|
||||
type: typeof FETCH_MONITOR_LOCATIONS_FAIL;
|
||||
payload: any;
|
||||
}
|
||||
|
||||
export function fetchMonitorDetails(monitorId: string): GetMonitorDetailsAction {
|
||||
return {
|
||||
type: FETCH_MONITOR_DETAILS,
|
||||
|
@ -51,7 +77,33 @@ export function fetchMonitorDetailsFail(error: any): GetMonitorDetailsFailAction
|
|||
};
|
||||
}
|
||||
|
||||
export function fetchMonitorLocations(payload: MonitorLocationsPayload): GetMonitorLocationsAction {
|
||||
return {
|
||||
type: FETCH_MONITOR_LOCATIONS,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchMonitorLocationsSuccess(
|
||||
monitorLocationsState: MonitorLocations
|
||||
): GetMonitorLocationsSuccessAction {
|
||||
return {
|
||||
type: FETCH_MONITOR_LOCATIONS_SUCCESS,
|
||||
payload: monitorLocationsState,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchMonitorLocationsFail(error: any): GetMonitorLocationsFailAction {
|
||||
return {
|
||||
type: FETCH_MONITOR_LOCATIONS_FAIL,
|
||||
payload: error,
|
||||
};
|
||||
}
|
||||
|
||||
export type MonitorActionTypes =
|
||||
| GetMonitorDetailsAction
|
||||
| GetMonitorDetailsSuccessAction
|
||||
| GetMonitorDetailsFailAction;
|
||||
| GetMonitorDetailsFailAction
|
||||
| GetMonitorLocationsAction
|
||||
| GetMonitorLocationsSuccessAction
|
||||
| GetMonitorLocationsFailAction;
|
||||
|
|
12
x-pack/legacy/plugins/uptime/public/state/actions/types.ts
Normal file
12
x-pack/legacy/plugins/uptime/public/state/actions/types.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface QueryParams {
|
||||
dateStart: string;
|
||||
dateEnd: string;
|
||||
filters?: string;
|
||||
statusFilter?: string;
|
||||
}
|
|
@ -6,7 +6,13 @@
|
|||
|
||||
import { ThrowReporter } from 'io-ts/lib/ThrowReporter';
|
||||
import { getApiPath } from '../../lib/helper';
|
||||
import { MonitorDetailsType, MonitorDetails } from '../../../common/runtime_types';
|
||||
import {
|
||||
MonitorDetailsType,
|
||||
MonitorDetails,
|
||||
MonitorLocations,
|
||||
MonitorLocationsType,
|
||||
} from '../../../common/runtime_types';
|
||||
import { QueryParams } from '../actions/types';
|
||||
|
||||
interface ApiRequest {
|
||||
monitorId: string;
|
||||
|
@ -27,3 +33,30 @@ export const fetchMonitorDetails = async ({
|
|||
return data;
|
||||
});
|
||||
};
|
||||
|
||||
type ApiParams = QueryParams & ApiRequest;
|
||||
|
||||
export const fetchMonitorLocations = async ({
|
||||
monitorId,
|
||||
basePath,
|
||||
dateStart,
|
||||
dateEnd,
|
||||
}: ApiParams): Promise<MonitorLocations> => {
|
||||
const url = getApiPath(`/api/uptime/monitor/locations`, basePath);
|
||||
|
||||
const params = {
|
||||
dateStart,
|
||||
dateEnd,
|
||||
monitorId,
|
||||
};
|
||||
const urlParams = new URLSearchParams(params).toString();
|
||||
const response = await fetch(`${url}?${urlParams}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
return response.json().then(data => {
|
||||
ThrowReporter.report(MonitorLocationsType.decode(data));
|
||||
return data;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -10,8 +10,11 @@ import {
|
|||
FETCH_MONITOR_DETAILS,
|
||||
FETCH_MONITOR_DETAILS_SUCCESS,
|
||||
FETCH_MONITOR_DETAILS_FAIL,
|
||||
FETCH_MONITOR_LOCATIONS,
|
||||
FETCH_MONITOR_LOCATIONS_SUCCESS,
|
||||
FETCH_MONITOR_LOCATIONS_FAIL,
|
||||
} from '../actions/monitor';
|
||||
import { fetchMonitorDetails } from '../api';
|
||||
import { fetchMonitorDetails, fetchMonitorLocations } from '../api';
|
||||
import { getBasePath } from '../selectors';
|
||||
|
||||
function* monitorDetailsEffect(action: Action<any>) {
|
||||
|
@ -25,6 +28,18 @@ function* monitorDetailsEffect(action: Action<any>) {
|
|||
}
|
||||
}
|
||||
|
||||
function* monitorLocationsEffect(action: Action<any>) {
|
||||
const payload = action.payload;
|
||||
try {
|
||||
const basePath = yield select(getBasePath);
|
||||
const response = yield call(fetchMonitorLocations, { basePath, ...payload });
|
||||
yield put({ type: FETCH_MONITOR_LOCATIONS_SUCCESS, payload: response });
|
||||
} catch (error) {
|
||||
yield put({ type: FETCH_MONITOR_LOCATIONS_FAIL, payload: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
export function* fetchMonitorDetailsEffect() {
|
||||
yield takeLatest(FETCH_MONITOR_DETAILS, monitorDetailsEffect);
|
||||
yield takeLatest(FETCH_MONITOR_LOCATIONS, monitorLocationsEffect);
|
||||
}
|
||||
|
|
|
@ -10,16 +10,24 @@ import {
|
|||
FETCH_MONITOR_DETAILS,
|
||||
FETCH_MONITOR_DETAILS_SUCCESS,
|
||||
FETCH_MONITOR_DETAILS_FAIL,
|
||||
FETCH_MONITOR_LOCATIONS,
|
||||
FETCH_MONITOR_LOCATIONS_SUCCESS,
|
||||
FETCH_MONITOR_LOCATIONS_FAIL,
|
||||
} from '../actions/monitor';
|
||||
import { MonitorLocations } from '../../../common/runtime_types';
|
||||
|
||||
type MonitorLocationsList = Map<string, MonitorLocations>;
|
||||
|
||||
export interface MonitorState {
|
||||
monitorDetailsList: MonitorDetailsState[];
|
||||
monitorLocationsList: MonitorLocationsList;
|
||||
loading: boolean;
|
||||
errors: any[];
|
||||
}
|
||||
|
||||
const initialState: MonitorState = {
|
||||
monitorDetailsList: [],
|
||||
monitorLocationsList: new Map(),
|
||||
loading: false,
|
||||
errors: [],
|
||||
};
|
||||
|
@ -42,10 +50,27 @@ export function monitorReducer(state = initialState, action: MonitorActionTypes)
|
|||
loading: false,
|
||||
};
|
||||
case FETCH_MONITOR_DETAILS_FAIL:
|
||||
const error = action.payload;
|
||||
return {
|
||||
...state,
|
||||
errors: [...state.errors, error],
|
||||
errors: [...state.errors, action.payload],
|
||||
};
|
||||
case FETCH_MONITOR_LOCATIONS:
|
||||
return {
|
||||
...state,
|
||||
loading: true,
|
||||
};
|
||||
case FETCH_MONITOR_LOCATIONS_SUCCESS:
|
||||
const monLocations = state.monitorLocationsList;
|
||||
monLocations.set(action.payload.monitorId, action.payload);
|
||||
return {
|
||||
...state,
|
||||
monitorLocationsList: monLocations,
|
||||
loading: false,
|
||||
};
|
||||
case FETCH_MONITOR_LOCATIONS_FAIL:
|
||||
return {
|
||||
...state,
|
||||
errors: [...state.errors, action.payload],
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
|
|
|
@ -11,6 +11,7 @@ describe('state selectors', () => {
|
|||
const state: AppState = {
|
||||
monitor: {
|
||||
monitorDetailsList: [],
|
||||
monitorLocationsList: new Map(),
|
||||
loading: false,
|
||||
errors: [],
|
||||
},
|
||||
|
|
|
@ -14,3 +14,7 @@ export const isIntegrationsPopupOpen = ({ ui: { integrationsPopoverOpen } }: App
|
|||
export const getMonitorDetails = (state: AppState, summary: any) => {
|
||||
return state.monitor.monitorDetailsList[summary.monitor_id];
|
||||
};
|
||||
|
||||
export const getMonitorLocations = (state: AppState, monitorId: string) => {
|
||||
return state.monitor.monitorLocationsList?.get(monitorId);
|
||||
};
|
||||
|
|
|
@ -17,4 +17,10 @@ export interface UMMonitorsAdapter {
|
|||
getFilterBar(request: any, dateRangeStart: string, dateRangeEnd: string): Promise<any>;
|
||||
getMonitorPageTitle(request: any, monitorId: string): Promise<MonitorPageTitle | null>;
|
||||
getMonitorDetails(request: any, monitorId: string): Promise<any>;
|
||||
getMonitorLocations(
|
||||
request: any,
|
||||
monitorId: string,
|
||||
dateStart: string,
|
||||
dateEnd: string
|
||||
): Promise<any>;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,12 @@ import {
|
|||
import { getHistogramIntervalFormatted } from '../../helper';
|
||||
import { DatabaseAdapter } from '../database';
|
||||
import { UMMonitorsAdapter } from './adapter_types';
|
||||
import { MonitorDetails, MonitorError } from '../../../../common/runtime_types';
|
||||
import {
|
||||
MonitorDetails,
|
||||
MonitorError,
|
||||
MonitorLocations,
|
||||
MonitorLocation,
|
||||
} from '../../../../common/runtime_types';
|
||||
|
||||
const formatStatusBuckets = (time: any, buckets: any, docCount: any) => {
|
||||
let up = null;
|
||||
|
@ -273,6 +278,11 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch data for the monitor page title.
|
||||
* @param request Kibana server request
|
||||
* @param monitorId the ID to query
|
||||
*/
|
||||
public async getMonitorDetails(request: any, monitorId: string): Promise<MonitorDetails> {
|
||||
const params = {
|
||||
index: INDEX_NAMES.HEARTBEAT,
|
||||
|
@ -320,4 +330,100 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
|
|||
timestamp: errorTimeStamp,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch data for the monitor page title.
|
||||
* @param request Kibana server request
|
||||
* @param monitorId the ID to query
|
||||
*/
|
||||
public async getMonitorLocations(
|
||||
request: any,
|
||||
monitorId: string,
|
||||
dateStart: string,
|
||||
dateEnd: string
|
||||
): Promise<MonitorLocations> {
|
||||
const params = {
|
||||
index: INDEX_NAMES.HEARTBEAT,
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
match: {
|
||||
'monitor.id': monitorId,
|
||||
},
|
||||
},
|
||||
{
|
||||
exists: {
|
||||
field: 'summary',
|
||||
},
|
||||
},
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: dateStart,
|
||||
lte: dateEnd,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
location: {
|
||||
terms: {
|
||||
field: 'observer.geo.name',
|
||||
missing: '__location_missing__',
|
||||
},
|
||||
aggs: {
|
||||
most_recent: {
|
||||
top_hits: {
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': {
|
||||
order: 'desc',
|
||||
},
|
||||
},
|
||||
_source: ['monitor', 'summary', 'observer'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await this.database.search(request, params);
|
||||
const locations = result?.aggregations?.location?.buckets ?? [];
|
||||
|
||||
const getGeo = (locGeo: any) => {
|
||||
const { name, location } = locGeo;
|
||||
const latLon = location.trim().split(',');
|
||||
return {
|
||||
name,
|
||||
location: {
|
||||
lat: latLon[0],
|
||||
lon: latLon[1],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const monLocs: MonitorLocation[] = [];
|
||||
locations.forEach((loc: any) => {
|
||||
if (loc?.key !== '__location_missing__') {
|
||||
const mostRecentLocation = loc.most_recent.hits.hits[0]._source;
|
||||
const location: MonitorLocation = {
|
||||
summary: mostRecentLocation?.summary,
|
||||
geo: getGeo(mostRecentLocation?.observer?.geo),
|
||||
};
|
||||
monLocs.push(location);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
monitorId,
|
||||
locations: monLocs,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { createGetIndexPatternRoute } from './index_pattern';
|
|||
import { createLogMonitorPageRoute, createLogOverviewPageRoute } from './telemetry';
|
||||
import { createGetSnapshotCount } from './snapshot';
|
||||
import { UMRestApiRouteCreator } from './types';
|
||||
import { createGetMonitorDetailsRoute } from './monitors';
|
||||
import { createGetMonitorDetailsRoute, createGetMonitorLocationsRoute } from './monitors';
|
||||
|
||||
export * from './types';
|
||||
export { createRouteWithAuth } from './create_route_with_auth';
|
||||
|
@ -17,6 +17,7 @@ export const restApiRoutes: UMRestApiRouteCreator[] = [
|
|||
createGetAllRoute,
|
||||
createGetIndexPatternRoute,
|
||||
createGetMonitorDetailsRoute,
|
||||
createGetMonitorLocationsRoute,
|
||||
createGetSnapshotCount,
|
||||
createLogMonitorPageRoute,
|
||||
createLogOverviewPageRoute,
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
*/
|
||||
|
||||
export { createGetMonitorDetailsRoute } from './monitors_details';
|
||||
export { createGetMonitorLocationsRoute } from './monitor_locations';
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { UMRestApiRouteCreator } from '../types';
|
||||
|
||||
export const createGetMonitorLocationsRoute: UMRestApiRouteCreator = (libs: UMServerLibs) => ({
|
||||
method: 'GET',
|
||||
path: '/api/uptime/monitor/locations',
|
||||
validate: {
|
||||
query: schema.object({
|
||||
monitorId: schema.string(),
|
||||
dateStart: schema.string(),
|
||||
dateEnd: schema.string(),
|
||||
}),
|
||||
},
|
||||
options: {
|
||||
tags: ['access:uptime'],
|
||||
},
|
||||
handler: async (_context, request, response): Promise<any> => {
|
||||
const { monitorId, dateStart, dateEnd } = request.query;
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
...(await libs.monitors.getMonitorLocations(request, monitorId, dateStart, dateEnd)),
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue