[Uptime] Remove Location map from Uptime monitor details page (#96517)

* remove LocationMap from Uptime Monitor details
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Dominique Clarke 2021-04-08 14:29:51 -04:00 committed by GitHub
parent 91e1acd98d
commit 06e01c20e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 50 additions and 4425 deletions

View file

@ -22898,7 +22898,6 @@
"xpack.uptime.certs.status.ok.label": " {okRelativeDate}",
"xpack.uptime.charts.mlAnnotation.header": "スコア:{score}",
"xpack.uptime.charts.mlAnnotation.severity": "深刻度:{severity}",
"xpack.uptime.components.embeddables.embeddedMap.embeddablePanelTitle": "オブザーバー位置情報マップを監視",
"xpack.uptime.controls.selectSeverity.criticalLabel": "致命的",
"xpack.uptime.controls.selectSeverity.majorLabel": "メジャー",
"xpack.uptime.controls.selectSeverity.minorLabel": "マイナー",
@ -22928,12 +22927,7 @@
"xpack.uptime.filterPopout.searchMessage.ariaLabel": "{title} を検索",
"xpack.uptime.filterPopover.filterItem.label": "{title} {item}でフィルタリングします。",
"xpack.uptime.integrationLink.missingDataMessage": "この統合に必要なデータが見つかりませんでした。",
"xpack.uptime.locationAvailabilityViewToggleLegend": "トグルを表示",
"xpack.uptime.locationMap.locations.missing.message": "重要な位置情報構成がありません。{codeBlock}フィールドを使用して、アップタイムチェック用に一意の地域を作成できます。",
"xpack.uptime.locationMap.locations.missing.message1": "詳細については、ドキュメンテーションを参照してください。",
"xpack.uptime.locationMap.locations.missing.title": "地理情報の欠測",
"xpack.uptime.locationName.helpLinkAnnotation": "場所を追加",
"xpack.uptime.mapToolTip.AvailabilityStat.title": "{value} %",
"xpack.uptime.ml.durationChart.exploreInMlApp": "ML アプリで探索",
"xpack.uptime.ml.enableAnomalyDetectionPanel.anomalyDetectionTitle": "異常検知",
"xpack.uptime.ml.enableAnomalyDetectionPanel.cancelLabel": "キャンセル",
@ -23585,4 +23579,4 @@
"xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。",
"xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。"
}
}
}

View file

@ -23257,7 +23257,6 @@
"xpack.uptime.certs.status.ok.label": " 对于 {okRelativeDate}",
"xpack.uptime.charts.mlAnnotation.header": "分数:{score}",
"xpack.uptime.charts.mlAnnotation.severity": "严重性:{severity}",
"xpack.uptime.components.embeddables.embeddedMap.embeddablePanelTitle": "监测观察者位置地图",
"xpack.uptime.controls.selectSeverity.criticalLabel": "紧急",
"xpack.uptime.controls.selectSeverity.majorLabel": "重大",
"xpack.uptime.controls.selectSeverity.minorLabel": "轻微",
@ -23287,12 +23286,7 @@
"xpack.uptime.filterPopout.searchMessage.ariaLabel": "搜索 {title}",
"xpack.uptime.filterPopover.filterItem.label": "按 {title} {item} 筛选。",
"xpack.uptime.integrationLink.missingDataMessage": "未找到此集成的所需数据。",
"xpack.uptime.locationAvailabilityViewToggleLegend": "视图切换",
"xpack.uptime.locationMap.locations.missing.message": "重要的地理位置配置缺失。您可以使用 {codeBlock} 字段为您的运行时间检查创建独特的地理区域。",
"xpack.uptime.locationMap.locations.missing.message1": "在我们的文档中获取更多的信息。",
"xpack.uptime.locationMap.locations.missing.title": "地理信息缺失",
"xpack.uptime.locationName.helpLinkAnnotation": "添加位置",
"xpack.uptime.mapToolTip.AvailabilityStat.title": "{value} %",
"xpack.uptime.ml.durationChart.exploreInMlApp": "在 ML 应用中浏览",
"xpack.uptime.ml.enableAnomalyDetectionPanel.anomalyDetectionTitle": "异常检测",
"xpack.uptime.ml.enableAnomalyDetectionPanel.cancelLabel": "取消",
@ -23954,4 +23948,4 @@
"xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。",
"xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。"
}
}
}

View file

@ -1,8 +1,16 @@
{
"configPath": ["xpack", "uptime"],
"configPath": [
"xpack",
"uptime"
],
"id": "uptime",
"kibanaVersion": "kibana",
"optionalPlugins": ["data", "home", "observability", "ml"],
"optionalPlugins": [
"data",
"home",
"observability",
"ml"
],
"requiredPlugins": [
"alerting",
"embeddable",
@ -14,5 +22,12 @@
"server": true,
"ui": true,
"version": "8.0.0",
"requiredBundles": ["observability", "kibanaReact", "kibanaUtils", "home", "data", "ml", "maps"]
}
"requiredBundles": [
"observability",
"kibanaReact",
"kibanaUtils",
"home",
"data",
"ml"
]
}

View file

@ -7,7 +7,6 @@
export * from './ml';
export * from './ping_list';
export * from './status_details/location_map';
export * from './status_details';
export * from './ping_histogram';
export * from './monitor_charts';

View file

@ -1,234 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LocationAvailability component doesnt shows warning if geo is provided 1`] = `
<EuiErrorBoundary>
<EuiFlexGroup
gutterSize="none"
responsive={false}
style={
Object {
"flexGrow": 0,
}
}
>
<EuiFlexItem />
<EuiFlexItem
grow={false}
>
<ToggleViewBtn
onChange={[Function]}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup
gutterSize="none"
justifyContent="flexEnd"
wrap={true}
>
<EuiFlexItem
grow={false}
>
<LocationMap
downPoints={Array []}
upPoints={
Array [
Object {
"location": Object {
"lat": "40.730610",
"lon": " -73.935242",
},
"name": "New York",
},
Object {
"location": Object {
"lat": "52.487448",
"lon": " 13.394798",
},
"name": "Tokyo",
},
]
}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiErrorBoundary>
`;
exports[`LocationAvailability component renders correctly against snapshot 1`] = `
<EuiErrorBoundary>
<EuiFlexGroup
gutterSize="none"
responsive={false}
style={
Object {
"flexGrow": 0,
}
}
>
<EuiFlexItem>
<EuiTitle
size="s"
>
<h3>
Monitoring from
</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<ToggleViewBtn
onChange={[Function]}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup
gutterSize="none"
justifyContent="flexEnd"
wrap={true}
>
<Styled(EuiFlexItem)
grow={true}
>
<LocationStatusTags
locations={
Array [
Object {
"down_history": 0,
"geo": Object {
"location": Object {
"lat": "40.730610",
"lon": " -73.935242",
},
"name": "New York",
},
"summary": Object {
"down": 0,
"up": 4,
},
"timestamp": "2020-01-13T22:50:06.536Z",
"up_history": 4,
},
Object {
"down_history": 0,
"geo": Object {
"location": Object {
"lat": "52.487448",
"lon": " 13.394798",
},
"name": "Tokyo",
},
"summary": Object {
"down": 0,
"up": 4,
},
"timestamp": "2020-01-13T22:50:04.354Z",
"up_history": 4,
},
Object {
"down_history": 0,
"geo": Object {
"name": "Unnamed-location",
},
"summary": Object {
"down": 0,
"up": 4,
},
"timestamp": "2020-01-13T22:50:02.753Z",
"up_history": 4,
},
]
}
/>
</Styled(EuiFlexItem)>
</EuiFlexGroup>
</EuiErrorBoundary>
`;
exports[`LocationAvailability component renders named locations that have missing geo data 1`] = `
<EuiErrorBoundary>
<EuiFlexGroup
gutterSize="none"
responsive={false}
style={
Object {
"flexGrow": 0,
}
}
>
<EuiFlexItem>
<LocationMissingWarning />
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<ToggleViewBtn
onChange={[Function]}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup
gutterSize="none"
justifyContent="flexEnd"
wrap={true}
>
<EuiFlexItem
grow={false}
>
<LocationMap
downPoints={Array []}
upPoints={Array []}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiErrorBoundary>
`;
exports[`LocationAvailability component shows warning if geo information is missing 1`] = `
<EuiErrorBoundary>
<EuiFlexGroup
gutterSize="none"
responsive={false}
style={
Object {
"flexGrow": 0,
}
}
>
<EuiFlexItem>
<LocationMissingWarning />
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<ToggleViewBtn
onChange={[Function]}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup
gutterSize="none"
justifyContent="flexEnd"
wrap={true}
>
<EuiFlexItem
grow={false}
>
<LocationMap
downPoints={Array []}
upPoints={
Array [
Object {
"location": Object {
"lat": "52.487448",
"lon": " 13.394798",
},
"name": "Tokyo",
},
]
}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiErrorBoundary>
`;

View file

@ -6,28 +6,16 @@
*/
import React from 'react';
import { shallowWithIntl } from '@kbn/test/jest';
import { screen } from '@testing-library/react';
import { render } from '../../../../lib/helper/rtl_helpers';
import { LocationAvailability } from './location_availability';
import { MonitorLocations } from '../../../../../common/runtime_types';
import { LocationMissingWarning } from '../location_map/location_missing';
// Note For shallow test, we need absolute time strings
describe('LocationAvailability component', () => {
let monitorLocations: MonitorLocations;
let localStorageMock: any;
let selectedView = 'list';
beforeEach(() => {
localStorageMock = {
getItem: jest.fn().mockImplementation(() => selectedView),
setItem: jest.fn(),
};
Object.defineProperty(window, 'localStorage', {
value: localStorageMock,
});
monitorLocations = {
monitorId: 'wapo',
up_history: 12,
@ -41,104 +29,34 @@ describe('LocationAvailability component', () => {
down_history: 0,
},
{
summary: { up: 4, down: 0 },
summary: { up: 2, down: 2 },
geo: { name: 'Tokyo', location: { lat: '52.487448', lon: ' 13.394798' } },
timestamp: '2020-01-13T22:50:04.354Z',
up_history: 4,
down_history: 0,
up_history: 2,
down_history: 2,
},
{
summary: { up: 4, down: 0 },
summary: { up: 0, down: 4 },
geo: { name: 'Unnamed-location' },
timestamp: '2020-01-13T22:50:02.753Z',
up_history: 4,
down_history: 0,
up_history: 0,
down_history: 4,
},
],
};
});
it('renders correctly against snapshot', () => {
const component = shallowWithIntl(<LocationAvailability monitorLocations={monitorLocations} />);
expect(component).toMatchSnapshot();
});
it('shows warning if geo information is missing', () => {
selectedView = 'map';
monitorLocations = {
monitorId: 'wapo',
up_history: 8,
down_history: 0,
locations: [
{
summary: { up: 4, down: 0 },
geo: { name: 'Tokyo', location: { lat: '52.487448', lon: ' 13.394798' } },
timestamp: '2020-01-13T22:50:04.354Z',
up_history: 4,
down_history: 0,
},
{
summary: { up: 4, down: 0 },
geo: { name: 'Unnamed-location' },
timestamp: '2020-01-13T22:50:02.753Z',
up_history: 4,
down_history: 0,
},
],
};
const component = shallowWithIntl(<LocationAvailability monitorLocations={monitorLocations} />);
expect(component).toMatchSnapshot();
const warningComponent = component.find(LocationMissingWarning);
expect(warningComponent).toHaveLength(1);
});
it('doesnt shows warning if geo is provided', () => {
monitorLocations = {
monitorId: 'wapo',
up_history: 8,
down_history: 0,
locations: [
{
summary: { up: 4, down: 0 },
geo: { name: 'New York', location: { lat: '40.730610', lon: ' -73.935242' } },
timestamp: '2020-01-13T22:50:06.536Z',
up_history: 4,
down_history: 0,
},
{
summary: { up: 4, down: 0 },
geo: { name: 'Tokyo', location: { lat: '52.487448', lon: ' 13.394798' } },
timestamp: '2020-01-13T22:50:04.354Z',
up_history: 4,
down_history: 0,
},
],
};
const component = shallowWithIntl(<LocationAvailability monitorLocations={monitorLocations} />);
expect(component).toMatchSnapshot();
const warningComponent = component.find(LocationMissingWarning);
expect(warningComponent).toHaveLength(0);
});
it('renders named locations that have missing geo data', () => {
monitorLocations = {
monitorId: 'wapo',
up_history: 4,
down_history: 0,
locations: [
{
summary: { up: 4, down: 0 },
geo: { name: 'New York', location: undefined },
timestamp: '2020-01-13T22:50:06.536Z',
up_history: 4,
down_history: 0,
},
],
};
const component = shallowWithIntl(<LocationAvailability monitorLocations={monitorLocations} />);
expect(component).toMatchSnapshot();
it('renders correctly', () => {
render(<LocationAvailability monitorLocations={monitorLocations} />);
expect(screen.getByRole('heading', { name: 'Monitoring from', level: 3 }));
expect(screen.getByText('New York')).toBeInTheDocument();
expect(screen.getByText('Tokyo')).toBeInTheDocument();
expect(screen.getByText('Unnamed-location')).toBeInTheDocument();
expect(screen.getByText('100.00 %')).toBeInTheDocument();
expect(screen.getByText('50.00 %')).toBeInTheDocument();
expect(screen.getByText('0.00 %')).toBeInTheDocument();
expect(screen.getByText('Jan 13, 2020 5:50:06 PM')).toBeInTheDocument();
expect(screen.getByText('Jan 13, 2020 5:50:04 PM')).toBeInTheDocument();
expect(screen.getByText('Jan 13, 2020 5:50:02 PM')).toBeInTheDocument();
});
});

View file

@ -5,18 +5,12 @@
* 2.0.
*/
import React, { useState } from 'react';
import React from 'react';
import styled from 'styled-components';
import { EuiFlexGroup, EuiFlexItem, EuiErrorBoundary, EuiTitle } from '@elastic/eui';
import { LocationStatusTags } from '../availability_reporting';
import { LocationPoint } from '../location_map/embeddables/embedded_map';
import { MonitorLocations, MonitorLocation } from '../../../../../common/runtime_types';
import { UNNAMED_LOCATION } from '../../../../../common/constants';
import { LocationMissingWarning } from '../location_map/location_missing';
import { useSelectedView } from './use_selected_view';
import { LocationMap } from '../location_map';
import { MonitorLocations } from '../../../../../common/runtime_types';
import { MonitoringFrom } from '../translations';
import { ToggleViewBtn } from './toggle_view_btn';
const EuiFlexItemTags = styled(EuiFlexItem)`
width: 350px;
@ -30,61 +24,20 @@ interface LocationMapProps {
}
export const LocationAvailability = ({ monitorLocations }: LocationMapProps) => {
const upPoints: LocationPoint[] = [];
const downPoints: LocationPoint[] = [];
let isAnyGeoInfoMissing = false;
if (monitorLocations?.locations) {
monitorLocations.locations.forEach(({ geo, summary }: MonitorLocation) => {
if (geo?.name === UNNAMED_LOCATION || !geo?.location) {
isAnyGeoInfoMissing = true;
} else if (!!geo.location.lat && !!geo.location.lon) {
if (summary?.down === 0) {
upPoints.push(geo as LocationPoint);
} else {
downPoints.push(geo as LocationPoint);
}
}
});
}
const { selectedView: initialView } = useSelectedView();
const [selectedView, setSelectedView] = useState(initialView);
return (
<EuiErrorBoundary>
<EuiFlexGroup responsive={false} gutterSize={'none'} style={{ flexGrow: 0 }}>
{selectedView === 'list' && (
<EuiFlexItem>
<EuiTitle size="s">
<h3>{MonitoringFrom}</h3>
</EuiTitle>
</EuiFlexItem>
)}
{selectedView === 'map' && (
<EuiFlexItem>{isAnyGeoInfoMissing && <LocationMissingWarning />}</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<ToggleViewBtn
onChange={(val) => {
setSelectedView(val);
}}
/>
<EuiFlexItem>
<EuiTitle size="s">
<h3>{MonitoringFrom}</h3>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup wrap={true} gutterSize="none" justifyContent="flexEnd">
{selectedView === 'list' && (
<EuiFlexItemTags grow={true}>
<LocationStatusTags locations={monitorLocations?.locations || []} />
</EuiFlexItemTags>
)}
{selectedView === 'map' && (
<EuiFlexItem grow={false}>
<LocationMap upPoints={upPoints} downPoints={downPoints} />
</EuiFlexItem>
)}
<EuiFlexItemTags grow={true}>
<LocationStatusTags locations={monitorLocations?.locations || []} />
</EuiFlexItemTags>
</EuiFlexGroup>
</EuiErrorBoundary>
);

View file

@ -1,66 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import * as React from 'react';
import styled from 'styled-components';
import { EuiButtonGroup } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useSelectedView } from './use_selected_view';
import { ChangeToListView, ChangeToMapView } from '../translations';
const ToggleViewButtons = styled.span`
margin-left: auto;
`;
interface Props {
onChange: (val: string) => void;
}
export const ToggleViewBtn = ({ onChange }: Props) => {
const toggleButtons = [
{
id: `listBtn`,
label: ChangeToMapView,
name: 'listView',
iconType: 'list',
'data-test-subj': 'uptimeMonitorToggleListBtn',
'aria-label': ChangeToMapView,
},
{
id: `mapBtn`,
label: ChangeToListView,
name: 'mapView',
iconType: 'mapMarker',
'data-test-subj': 'uptimeMonitorToggleMapBtn',
'aria-label': ChangeToListView,
},
];
const { selectedView, setSelectedView } = useSelectedView();
const onChangeView = (optionId: string) => {
const currView = optionId === 'listBtn' ? 'list' : 'map';
setSelectedView(currView);
onChange(currView);
};
return (
<ToggleViewButtons>
<EuiButtonGroup
options={toggleButtons}
idToSelectedMap={{ listBtn: selectedView === 'list', mapBtn: selectedView === 'map' }}
onChange={(id) => onChangeView(id)}
type="multi"
isIconOnly
style={{ marginLeft: 'auto' }}
legend={i18n.translate('xpack.uptime.locationAvailabilityViewToggleLegend', {
defaultMessage: 'View toggle',
})}
/>
</ToggleViewButtons>
);
};

View file

@ -1,27 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useEffect, useState } from 'react';
const localKey = 'xpack.uptime.detailPage.selectedView';
interface Props {
selectedView: string;
setSelectedView: (val: string) => void;
}
export const useSelectedView = (): Props => {
const getSelectedView = localStorage.getItem(localKey) ?? 'list';
const [selectedView, setSelectedView] = useState(getSelectedView);
useEffect(() => {
localStorage.setItem(localKey, selectedView);
}, [selectedView]);
return { selectedView, setSelectedView };
};

View file

@ -1,27 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LocationMap component renders correctly against snapshot 1`] = `
<styled.div>
<EmbeddedMap
downPoints={Array []}
upPoints={
Array [
Object {
"location": Object {
"lat": "40.730610",
"lon": " -73.935242",
},
"name": "New York",
},
Object {
"location": Object {
"lat": "52.487448",
"lon": " 13.394798",
},
"name": "Tokyo",
},
]
}
/>
</styled.div>
`;

View file

@ -1,123 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LocationMissingWarning component renders correctly against snapshot 1`] = `
.c0 {
margin-left: auto;
margin-bottom: 3px;
margin-right: 5px;
}
<div
class="euiFlexGroup euiFlexGroup--directionRow"
data-test-subj="xpack.uptime.locationMap.locationMissing"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero c0"
>
<div
class="euiPopover euiPopover--anchorDownCenter"
id="popover"
>
<div
class="euiPopover__anchor"
>
<button
class="euiButton euiButton--warning euiButton--small"
type="button"
>
<span
class="euiButtonContent euiButton__content"
>
<span
class="euiButtonContent__icon"
data-euiicon-type="alert"
/>
<span
class="euiButton__text"
>
Geo Information Missing
</span>
</span>
</button>
</div>
</div>
</div>
</div>
`;
exports[`LocationMissingWarning component shallow render correctly against snapshot 1`] = `
<EuiFlexGroup
data-test-subj="xpack.uptime.locationMap.locationMissing"
gutterSize="none"
responsive={false}
>
<Styled(EuiFlexItem)
grow={false}
>
<EuiPopover
anchorPosition="downCenter"
button={
<EuiButton
color="warning"
iconType="alert"
onClick={[Function]}
size="s"
>
<FormattedMessage
defaultMessage="Geo Information Missing"
id="xpack.uptime.locationMap.locations.missing.title"
values={Object {}}
/>
</EuiButton>
}
closePopover={[Function]}
display="inlineBlock"
hasArrow={true}
id="popover"
isOpen={false}
ownFocus={true}
panelPaddingSize="m"
>
<EuiText
style={
Object {
"width": "350px",
}
}
>
<FormattedMessage
defaultMessage="Important geo location configuration is missing. You can use the {codeBlock} field to create distinctive geographic regions for your uptime checks."
id="xpack.uptime.locationMap.locations.missing.message"
values={
Object {
"codeBlock": <EuiCode>
observer.geo.??
</EuiCode>,
}
}
/>
</EuiText>
<EuiSpacer
size="xs"
/>
<EuiText
style={
Object {
"width": "350px",
}
}
>
<FormattedMessage
defaultMessage="Get more information in our documentation."
id="xpack.uptime.locationMap.locations.missing.message1"
values={Object {}}
/>
<EuiSpacer
size="xs"
/>
<LocationLink />
</EuiText>
</EuiPopover>
</Styled(EuiFlexItem)>
</EuiFlexGroup>
`;

View file

@ -1,192 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import lowPolyLayerFeatures from '../low_poly_layer.json';
export const mockDownPointsLayer = {
id: 'down_points',
label: 'Down Locations',
sourceDescriptor: {
type: 'GEOJSON_FILE',
__featureCollection: {
features: [
{
id: 'Asia',
type: 'feature',
geometry: {
type: 'Point',
coordinates: [13.399262, 52.487239],
},
},
{
id: 'APJ',
type: 'feature',
geometry: {
type: 'Point',
coordinates: [13.399262, 55.487239],
},
},
{
id: 'Canada',
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: [
{
id: 'US-EAST',
type: 'feature',
geometry: {
type: 'Point',
coordinates: [13.399262, 52.487239],
},
},
{
id: 'US-WEST',
type: 'feature',
geometry: {
type: 'Point',
coordinates: [13.399262, 55.487239],
},
},
{
id: 'Europe',
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,
];

View file

@ -1,174 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useEffect, useState, useContext, useRef } from 'react';
import uuid from 'uuid';
import styled from 'styled-components';
import { createPortalNode, InPortal, OutPortal } from 'react-reverse-portal';
import {
MapEmbeddable,
MapEmbeddableInput,
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
} from '../../../../../../../maps/public/embeddable';
import * as i18n from './translations';
import { GeoPoint } from '../../../../../../common/runtime_types';
import { getLayerList } from './map_config';
import { UptimeThemeContext, UptimeStartupPluginsContext } from '../../../../../contexts';
import {
isErrorEmbeddable,
ViewMode,
ErrorEmbeddable,
} from '../../../../../../../../../src/plugins/embeddable/public';
import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../../maps/public';
import { MapToolTipComponent } from './map_tool_tip';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import type { RenderTooltipContentParams } from '../../../../../../../maps/public/classes/tooltips/tooltip_property';
export interface EmbeddedMapProps {
upPoints: LocationPoint[];
downPoints: LocationPoint[];
}
export type LocationPoint = Required<GeoPoint>;
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 = React.memo(({ upPoints, downPoints }: EmbeddedMapProps) => {
const { colors } = useContext(UptimeThemeContext);
const [embeddable, setEmbeddable] = useState<MapEmbeddable | ErrorEmbeddable | undefined>();
const embeddableRoot: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
const { embeddable: embeddablePlugin } = useContext(UptimeStartupPluginsContext);
if (!embeddablePlugin) {
throw new Error('Embeddable start plugin not found');
}
const factory: any = embeddablePlugin.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE);
const portalNode = React.useMemo(() => createPortalNode(), []);
const input: MapEmbeddableInput = {
id: uuid.v4(),
attributes: { title: '' },
filters: [],
hidePanelTitles: true,
refreshConfig: {
value: 0,
pause: false,
},
viewMode: ViewMode.VIEW,
isLayerTOCOpen: false,
hideFilterActions: true,
// 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,
},
mapSettings: {
disableInteractive: true,
hideToolbarOverlay: true,
hideLayerControl: true,
hideViewControl: true,
},
};
const renderTooltipContent = ({
addFilters,
closeTooltip,
features,
isLocked,
getLayerName,
loadFeatureProperties,
loadFeatureGeometry,
}: RenderTooltipContentParams) => {
const props = {
addFilters,
closeTooltip,
isLocked,
getLayerName,
loadFeatureProperties,
loadFeatureGeometry,
};
const relevantFeatures = features.filter(
(item: any) => item.layerId === 'up_points' || item.layerId === 'down_points'
);
if (relevantFeatures.length > 0) {
return <OutPortal {...props} node={portalNode} features={relevantFeatures} />;
}
closeTooltip();
return null;
};
useEffect(() => {
async function setupEmbeddable() {
if (!factory) {
throw new Error('Map embeddable not found.');
}
const embeddableObject: any = await factory.create({
...input,
title: i18n.MAP_TITLE,
});
if (embeddableObject && !isErrorEmbeddable(embeddableObject)) {
embeddableObject.setRenderTooltipContent(renderTooltipContent);
embeddableObject.setLayerList(getLayerList(upPoints, downPoints, colors));
}
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 && !isErrorEmbeddable(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);
}
}, [embeddable, embeddableRoot]);
return (
<EmbeddedPanel>
<div
data-test-subj="xpack.uptime.locationMap.embeddedPanel"
className="embPanel__content"
ref={embeddableRoot}
/>
<InPortal node={portalNode}>
<MapToolTipComponent />
</InPortal>
</EmbeddedPanel>
);
});
EmbeddedMap.displayName = 'EmbeddedMap';

View file

@ -1,47 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { getLayerList } from './map_config';
import { mockLayerList } from './__mocks__/poly_layer_mock';
import { LocationPoint } from './embedded_map';
import { UptimeAppColors } from '../../../../../apps/uptime_app';
jest.mock('uuid', () => {
return {
v4: jest.fn(() => 'uuid.v4()'),
};
});
describe('map_config', () => {
let upPoints: LocationPoint[];
let downPoints: LocationPoint[];
let colors: Pick<UptimeAppColors, 'gray' | 'danger'>;
beforeEach(() => {
upPoints = [
{ name: 'US-EAST', location: { lat: '52.487239', lon: '13.399262' } },
{ location: { lat: '55.487239', lon: '13.399262' }, name: 'US-WEST' },
{ location: { lat: '54.487239', lon: '14.399262' }, name: 'Europe' },
];
downPoints = [
{ location: { lat: '52.487239', lon: '13.399262' }, name: 'Asia' },
{ location: { lat: '55.487239', lon: '13.399262' }, name: 'APJ' },
{ location: { lat: '54.487239', lon: '14.399262' }, name: 'Canada' },
];
colors = {
danger: '#BC261E',
gray: '#000',
};
});
describe('#getLayerList', () => {
test('it returns the low poly layer', () => {
const layerList = getLayerList(upPoints, downPoints, colors);
expect(layerList).toStrictEqual(mockLayerList);
});
});
});

View file

@ -1,175 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import lowPolyLayerFeatures from './low_poly_layer.json';
import { LocationPoint } from './embedded_map';
import { UptimeAppColors } from '../../../../../apps/uptime_app';
/**
* 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[],
{ danger }: Pick<UptimeAppColors, 'danger'>
) => {
return [getLowPolyLayer(), getDownPointsLayer(downPoints, danger), 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[], dangerColor: string) => {
const features = downPoints?.map((point) => ({
type: 'feature',
id: point.name,
geometry: {
type: 'Point',
coordinates: [+point.location.lon, +point.location.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: dangerColor,
},
},
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',
id: point.name,
geometry: {
type: 'Point',
coordinates: [+point.location.lon, +point.location.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',
};
};

View file

@ -1,91 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import moment from 'moment';
import { i18n } from '@kbn/i18n';
import React, { useContext } from 'react';
import { useSelector } from 'react-redux';
import {
EuiDescriptionList,
EuiDescriptionListDescription,
EuiDescriptionListTitle,
EuiOutsideClickDetector,
EuiPopoverTitle,
} from '@elastic/eui';
import { TagLabel } from '../../availability_reporting';
import { UptimeThemeContext } from '../../../../../contexts';
import { AppState } from '../../../../../state';
import { monitorLocationsSelector } from '../../../../../state/selectors';
import { useMonitorId } from '../../../../../hooks';
import { MonitorLocation } from '../../../../../../common/runtime_types/monitor';
import type { RenderTooltipContentParams } from '../../../../../../../maps/public';
import { formatAvailabilityValue } from '../../availability_reporting/availability_reporting';
import { LastCheckLabel } from '../../translations';
type MapToolTipProps = Partial<RenderTooltipContentParams>;
export const MapToolTipComponent = ({ closeTooltip, features = [] }: MapToolTipProps) => {
const { id: featureId, layerId } = features[0] ?? {};
const locationName = featureId?.toString();
const {
colors: { gray, danger },
} = useContext(UptimeThemeContext);
const monitorId = useMonitorId();
const monitorLocations = useSelector((state: AppState) =>
monitorLocationsSelector(state, monitorId)
);
if (!locationName || !monitorLocations?.locations) {
return null;
}
const {
timestamp,
up_history: ups,
down_history: downs,
}: MonitorLocation = monitorLocations.locations!.find(
({ geo }: MonitorLocation) => geo.name === locationName
)!;
const availability = (ups / (ups + downs)) * 100;
return (
<EuiOutsideClickDetector
onOutsideClick={() => {
if (closeTooltip != null) {
closeTooltip();
}
}}
>
<>
<EuiPopoverTitle>
{layerId === 'up_points' ? (
<TagLabel label={locationName} color={gray} status="up" />
) : (
<TagLabel label={locationName} color={danger} status="down" />
)}
</EuiPopoverTitle>
<EuiDescriptionList type="column" textStyle="reverse" compressed={true}>
<EuiDescriptionListTitle>Availability</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
{i18n.translate('xpack.uptime.mapToolTip.AvailabilityStat.title', {
defaultMessage: '{value} %',
values: { value: formatAvailabilityValue(availability) },
description: 'A percentage value like 23.5%',
})}
</EuiDescriptionListDescription>
<EuiDescriptionListTitle>{LastCheckLabel}</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
{moment(timestamp).fromNow()}
</EuiDescriptionListDescription>
</EuiDescriptionList>
</>
</EuiOutsideClickDetector>
);
};
export const MapToolTip = React.memo(MapToolTipComponent);

View file

@ -1,15 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
export const MAP_TITLE = i18n.translate(
'xpack.uptime.components.embeddables.embeddedMap.embeddablePanelTitle',
{
defaultMessage: 'Monitor Observer Location Map',
}
);

View file

@ -1,9 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './location_map';
export * from '../availability_reporting/location_status_tags';

View file

@ -1,34 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { shallowWithIntl } from '@kbn/test/jest';
import { LocationMap } from './location_map';
import { LocationPoint } from './embeddables/embedded_map';
// Note For shallow test, we need absolute time strings
describe('LocationMap component', () => {
let upPoints: LocationPoint[];
beforeEach(() => {
upPoints = [
{
name: 'New York',
location: { lat: '40.730610', lon: ' -73.935242' },
},
{
name: 'Tokyo',
location: { lat: '52.487448', lon: ' 13.394798' },
},
];
});
it('renders correctly against snapshot', () => {
const component = shallowWithIntl(<LocationMap upPoints={upPoints} downPoints={[]} />);
expect(component).toMatchSnapshot();
});
});

View file

@ -1,35 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import styled from 'styled-components';
import { EmbeddedMap, LocationPoint } from './embeddables/embedded_map';
// 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: 65px;
@media (max-width: 574px) {
height: 250px;
width: 100%;
}
`;
interface Props {
upPoints: LocationPoint[];
downPoints: LocationPoint[];
}
export const LocationMap = ({ upPoints, downPoints }: Props) => {
return (
<MapPanel>
<EmbeddedMap upPoints={upPoints} downPoints={downPoints} />
</MapPanel>
);
};

View file

@ -1,22 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { renderWithIntl, shallowWithIntl } from '@kbn/test/jest';
import { LocationMissingWarning } from './location_missing';
describe('LocationMissingWarning component', () => {
it('shallow render correctly against snapshot', () => {
const component = shallowWithIntl(<LocationMissingWarning />);
expect(component).toMatchSnapshot();
});
it('renders correctly against snapshot', () => {
const component = renderWithIntl(<LocationMissingWarning />);
expect(component).toMatchSnapshot();
});
});

View file

@ -1,79 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useState } from 'react';
import {
EuiButton,
EuiFlexGroup,
EuiFlexItem,
EuiPopover,
EuiSpacer,
EuiText,
EuiCode,
} from '@elastic/eui';
import styled from 'styled-components';
import { FormattedMessage } from '@kbn/i18n/react';
import { LocationLink } from '../../../common/location_link';
const EuiPopoverRight = styled(EuiFlexItem)`
margin-left: auto;
margin-bottom: 3px;
margin-right: 5px;
`;
export const LocationMissingWarning = () => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const togglePopover = () => {
setIsPopoverOpen(!isPopoverOpen);
};
const button = (
<EuiButton iconType="alert" size="s" color="warning" onClick={togglePopover}>
<FormattedMessage
id="xpack.uptime.locationMap.locations.missing.title"
defaultMessage="Geo Information Missing"
/>
</EuiButton>
);
return (
<EuiFlexGroup
data-test-subj="xpack.uptime.locationMap.locationMissing"
gutterSize="none"
responsive={false}
>
<EuiPopoverRight grow={false}>
<EuiPopover
id="popover"
button={button}
isOpen={isPopoverOpen}
closePopover={togglePopover}
>
<EuiText style={{ width: '350px' }}>
<FormattedMessage
id="xpack.uptime.locationMap.locations.missing.message"
defaultMessage="Important geo location configuration is missing.
You can use the {codeBlock} field to create distinctive geographic regions for
your uptime checks."
values={{ codeBlock: <EuiCode>observer.geo.??</EuiCode> }}
/>
</EuiText>
<EuiSpacer size="xs" />
<EuiText style={{ width: '350px' }}>
<FormattedMessage
id="xpack.uptime.locationMap.locations.missing.message1"
defaultMessage="Get more information in our documentation."
/>
<EuiSpacer size="xs" />
<LocationLink />
</EuiText>
</EuiPopover>
</EuiPopoverRight>
</EuiFlexGroup>
);
};