diff --git a/x-pack/legacy/plugins/uptime/common/constants/constants.ts b/x-pack/legacy/plugins/uptime/common/constants/constants.ts new file mode 100644 index 0000000000000..0c35bc9734486 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/common/constants/constants.ts @@ -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 const UNNAMED_LOCATION = 'Unnamed-location'; diff --git a/x-pack/legacy/plugins/uptime/common/constants/index.ts b/x-pack/legacy/plugins/uptime/common/constants/index.ts index e3c4352f0a484..0a95960825f02 100644 --- a/x-pack/legacy/plugins/uptime/common/constants/index.ts +++ b/x-pack/legacy/plugins/uptime/common/constants/index.ts @@ -11,3 +11,4 @@ export { INDEX_NAMES } from './index_names'; export * from './capabilities'; export { PLUGIN } from './plugin'; export { QUERY, STATES } from './query'; +export * from './constants'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/__snapshots__/location_map.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/__snapshots__/location_map.test.tsx.snap new file mode 100644 index 0000000000000..a03a89be35ec2 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/__snapshots__/location_map.test.tsx.snap @@ -0,0 +1,203 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LocationMap component doesnt shows warning if geo is provided 1`] = ` + + + + + + + + + + + + +`; + +exports[`LocationMap component renders correctly against snapshot 1`] = ` + + + + + + + + + + + + + +`; + +exports[`LocationMap component shows warning if geo information is missing 1`] = ` + + + + + + + + + + + + + +`; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/__snapshots__/location_missing.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/__snapshots__/location_missing.test.tsx.snap new file mode 100644 index 0000000000000..fb949c7f3b4c2 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/__snapshots__/location_missing.test.tsx.snap @@ -0,0 +1,123 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LocationMissingWarning component renders correctly against snapshot 1`] = ` +
+
+
+
+ +
+
+
+
+`; + +exports[`LocationMissingWarning component shallow render correctly against snapshot 1`] = ` + + + + Geo Information Missing + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + id="popover" + isOpen={false} + ownFocus={false} + panelPaddingSize="m" + > + + + observer.geo.?? + , + } + } + /> + + + + + + + + + + +`; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/__snapshots__/location_status_tags.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/__snapshots__/location_status_tags.test.tsx.snap index 37bb820adac94..437150083f76f 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/__snapshots__/location_status_tags.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/__snapshots__/location_status_tags.test.tsx.snap @@ -1,5 +1,82 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`LocationStatusTags component renders properly against props 1`] = ` + + + + + + + + Berlin + + + + + + 1 Mon ago + + + + + + + + + + Berlin + + + + + + 1 Mon ago + + + + + + + + Islamabad + + + + + + 1 Mon ago + + + + + + +`; + exports[`LocationStatusTags component renders when all locations are down 1`] = ` .c3 { display: inline-block; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/location_map.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/location_map.test.tsx new file mode 100644 index 0000000000000..46cb0c3d63a0e --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/location_map.test.tsx @@ -0,0 +1,90 @@ +/* + * 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 { LocationMap } from '../location_map'; +import { MonitorLocations } from '../../../../../common/runtime_types'; +import { LocationMissingWarning } from '../location_missing'; + +// Note For shallow test, we need absolute time strings +describe('LocationMap component', () => { + let monitorLocations: MonitorLocations; + + beforeEach(() => { + monitorLocations = { + monitorId: 'wapo', + locations: [ + { + summary: { up: 4, down: 0 }, + geo: { name: 'New York', location: { lat: '40.730610', lon: ' -73.935242' } }, + timestamp: '2020-01-13T22:50:06.536Z', + }, + { + summary: { up: 4, down: 0 }, + geo: { name: 'Tokyo', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-13T22:50:04.354Z', + }, + { + summary: { up: 4, down: 0 }, + geo: { name: 'Unnamed-location' }, + timestamp: '2020-01-13T22:50:02.753Z', + }, + ], + }; + }); + + it('renders correctly against snapshot', () => { + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('shows warning if geo information is missing', () => { + monitorLocations = { + monitorId: 'wapo', + locations: [ + { + summary: { up: 4, down: 0 }, + geo: { name: 'Tokyo', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-13T22:50:04.354Z', + }, + { + summary: { up: 4, down: 0 }, + geo: { name: 'Unnamed-location' }, + timestamp: '2020-01-13T22:50:02.753Z', + }, + ], + }; + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + + const warningComponent = component.find(LocationMissingWarning); + expect(warningComponent).toHaveLength(1); + }); + + it('doesnt shows warning if geo is provided', () => { + monitorLocations = { + monitorId: 'wapo', + locations: [ + { + summary: { up: 4, down: 0 }, + geo: { name: 'New York', location: { lat: '40.730610', lon: ' -73.935242' } }, + timestamp: '2020-01-13T22:50:06.536Z', + }, + { + summary: { up: 4, down: 0 }, + geo: { name: 'Tokyo', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-13T22:50:04.354Z', + }, + ], + }; + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + + const warningComponent = component.find(LocationMissingWarning); + expect(warningComponent).toHaveLength(0); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/location_missing.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/location_missing.test.tsx new file mode 100644 index 0000000000000..66e3b5952a3dc --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/location_missing.test.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { LocationMissingWarning } from '../location_missing'; + +describe('LocationMissingWarning component', () => { + it('shallow render correctly against snapshot', () => { + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('renders correctly against snapshot', () => { + const component = renderWithIntl(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/location_status_tags.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/location_status_tags.test.tsx index dcab589b794aa..2359938dbbc35 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/location_status_tags.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/location_status_tags.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import moment from 'moment'; -import { renderWithIntl } from 'test_utils/enzyme_helpers'; +import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { MonitorLocation } from '../../../../../common/runtime_types/monitor'; import { LocationStatusTags } from '../'; @@ -14,6 +14,34 @@ import { LocationStatusTags } from '../'; describe('LocationStatusTags component', () => { let monitorLocations: MonitorLocation[]; + it('renders properly against props', () => { + monitorLocations = [ + { + summary: { up: 4, down: 0 }, + geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: moment() + .subtract('5', 'w') + .toISOString(), + }, + { + summary: { up: 4, down: 0 }, + geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: moment() + .subtract('5', 'w') + .toISOString(), + }, + { + summary: { up: 0, down: 2 }, + geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: moment() + .subtract('5', 'w') + .toISOString(), + }, + ]; + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + }); + it('renders when there are many location', () => { monitorLocations = [ { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx index 9a9bf3fe71dc1..d35e1281260e2 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx @@ -6,10 +6,12 @@ import React from 'react'; import styled from 'styled-components'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiErrorBoundary } from '@elastic/eui'; import { LocationStatusTags } from './location_status_tags'; import { EmbeddedMap, LocationPoint } from './embeddables/embedded_map'; import { MonitorLocations } from '../../../../common/runtime_types'; +import { UNNAMED_LOCATION } from '../../../../common/constants'; +import { LocationMissingWarning } from './location_missing'; // 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 @@ -27,25 +29,34 @@ export const LocationMap = ({ monitorLocations }: LocationMapProps) => { const upPoints: LocationPoint[] = []; const downPoints: LocationPoint[] = []; + let isGeoInfoMissing = false; + if (monitorLocations?.locations) { monitorLocations.locations.forEach((item: any) => { - if (item.summary.down === 0) { - upPoints.push(item.geo.location); - } else { - downPoints.push(item.geo.location); + if (item.geo?.name !== UNNAMED_LOCATION) { + if (item.summary.down === 0) { + upPoints.push(item.geo.location); + } else { + downPoints.push(item.geo.location); + } + } else if (item.geo?.name === UNNAMED_LOCATION) { + isGeoInfoMissing = true; } }); } return ( - - - - - - - - - - + + + + + + + {isGeoInfoMissing && } + + + + + + ); }; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_missing.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_missing.tsx new file mode 100644 index 0000000000000..3a3319208ea3c --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_missing.tsx @@ -0,0 +1,64 @@ +/* + * 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, { useState } from 'react'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiPopover, + EuiSpacer, + EuiText, + EuiCode, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { LocationLink } from '../monitor_list/monitor_list_drawer'; + +export const LocationMissingWarning = () => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const togglePopover = () => { + setIsPopoverOpen(!isPopoverOpen); + }; + + const button = ( + + Geo Information Missing + + ); + + return ( + + + + + observer.geo.?? }} + /> + + + + + + + + + + + ); +}; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx index 7a632dab5f2cc..b8735f682adef 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx @@ -25,6 +25,7 @@ const BadgeItem = styled.div` margin-bottom: 5px; `; +// Set height so that it remains within panel, enough height to display 7 locations tags const TagContainer = styled.div` padding: 10px; max-height: 229px; @@ -65,7 +66,7 @@ export const LocationStatusTags = ({ locations }: Props) => { return a.label > b.label ? 1 : b.label > a.label ? -1 : 0; }); - moment.locale('en', { + moment.updateLocale('en', { relativeTime: { future: 'in %s', past: '%s ago', diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/__snapshots__/monitor_status_list.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/__snapshots__/monitor_status_list.test.tsx.snap index 6934ecdcf639a..27ce47ff28b77 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/__snapshots__/monitor_status_list.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/__snapshots__/monitor_status_list.test.tsx.snap @@ -14,6 +14,25 @@ exports[`MonitorStatusList component renders checks 1`] = ` locationNames={Set {}} status="up" /> + + + , + } + } + /> + + `; @@ -31,5 +50,24 @@ exports[`MonitorStatusList component renders null in place of child status with locationNames={Set {}} status="up" /> + + + , + } + } + /> + + `; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_status_list.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_status_list.tsx index 66e9fce155ed2..04c5dc7d71371 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_status_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_status_list.tsx @@ -11,6 +11,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { Check } from '../../../../../common/graphql/types'; import { LocationLink } from './location_link'; import { MonitorStatusRow } from './monitor_status_row'; +import { UNNAMED_LOCATION } from '../../../../../common/constants'; interface MonitorStatusListProps { /** @@ -21,7 +22,6 @@ interface MonitorStatusListProps { export const UP = 'up'; export const DOWN = 'down'; -export const UNNAMED_LOCATION = 'unnamed-location'; export const MonitorStatusList = ({ checks }: MonitorStatusListProps) => { const upChecks: Set = new Set(); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_status_row.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_status_row.tsx index 66fe7fe4a5279..23f11b88517fc 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_status_row.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_status_row.tsx @@ -8,7 +8,8 @@ import React, { useContext } from 'react'; import { EuiHealth, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { UptimeSettingsContext } from '../../../../contexts'; -import { UNNAMED_LOCATION, UP } from './monitor_status_list'; +import { UP } from './monitor_status_list'; +import { UNNAMED_LOCATION } from '../../../../../common/constants'; interface MonitorStatusRowProps { /** diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts index b237fd8771f58..c86e0db9ae04a 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { INDEX_NAMES } from '../../../../common/constants'; +import { INDEX_NAMES, UNNAMED_LOCATION } from '../../../../common/constants'; import { MonitorChart, LocationDurationLine } from '../../../../common/graphql/types'; import { getHistogramIntervalFormatted } from '../../helper'; import { MonitorError, MonitorLocation } from '../../../../common/runtime_types'; @@ -347,28 +347,32 @@ export const elasticsearchMonitorsAdapter: UMMonitorsAdapter = { 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], - }, - }; + if (locGeo) { + const { name, location } = locGeo; + const latLon = location.trim().split(','); + return { + name, + location: { + lat: latLon[0], + lon: latLon[1], + }, + }; + } else { + return { + name: UNNAMED_LOCATION, + }; + } }; 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), - timestamp: mostRecentLocation['@timestamp'], - }; - monLocs.push(location); - } + const mostRecentLocation = loc.most_recent.hits.hits[0]._source; + const location: MonitorLocation = { + summary: mostRecentLocation?.summary, + geo: getGeo(mostRecentLocation?.observer?.geo), + timestamp: mostRecentLocation['@timestamp'], + }; + monLocs.push(location); }); return {