mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
39b2d71ce7
commit
775cd0b42a
70 changed files with 3195 additions and 1672 deletions
|
@ -16568,7 +16568,6 @@
|
|||
"xpack.uptime.locationMap.locations.missing.message": "重要な位置情報構成がありません。{codeBlock}フィールドを使用して、アップタイムチェック用に一意の地域を作成できます。",
|
||||
"xpack.uptime.locationMap.locations.missing.message1": "詳細については、ドキュメンテーションを参照してください。",
|
||||
"xpack.uptime.locationMap.locations.missing.title": "地理情報の欠測",
|
||||
"xpack.uptime.locationMap.locations.tags.others": "{otherLoc}その他 ...",
|
||||
"xpack.uptime.locationName.helpLinkAnnotation": "場所を追加",
|
||||
"xpack.uptime.ml.durationChart.exploreInMlApp": "ML アプリで探索",
|
||||
"xpack.uptime.ml.enableAnomalyDetectionPanel.anomalyDetectionTitle": "異常検知",
|
||||
|
@ -16652,12 +16651,7 @@
|
|||
"xpack.uptime.monitorStatusBar.locations.oneLocStatus": "{loc}場所での{status}",
|
||||
"xpack.uptime.monitorStatusBar.locations.upStatus": "{loc}場所での{status}",
|
||||
"xpack.uptime.monitorStatusBar.monitorUrlLinkAriaLabel": "監視 URL リンク",
|
||||
"xpack.uptime.monitorStatusBar.sslCertificate.overview": "証明書概要",
|
||||
"xpack.uptime.monitorStatusBar.sslCertificate.title": "証明書",
|
||||
"xpack.uptime.monitorStatusBar.sslCertificateExpired.badgeContent": "{emphasizedText}が期限切れになりました",
|
||||
"xpack.uptime.monitorStatusBar.sslCertificateExpired.label.ariaLabel": "{validityDate}に期限切れになりました",
|
||||
"xpack.uptime.monitorStatusBar.sslCertificateExpiry.badgeContent": "{emphasizedText}が期限切れになります",
|
||||
"xpack.uptime.monitorStatusBar.sslCertificateExpiry.label.ariaLabel": "{validityDate}に期限切れになります",
|
||||
"xpack.uptime.monitorStatusBar.timestampFromNowTextAriaLabel": "最終確認からの経過時間",
|
||||
"xpack.uptime.navigateToAlertingButton.content": "アラートを管理",
|
||||
"xpack.uptime.navigateToAlertingUi": "Uptime を離れてアラート管理ページに移動します",
|
||||
|
|
|
@ -16574,7 +16574,6 @@
|
|||
"xpack.uptime.locationMap.locations.missing.message": "重要的地理位置配置缺失。您可以使用 {codeBlock} 字段为您的运行时间检查创建独特的地理区域。",
|
||||
"xpack.uptime.locationMap.locations.missing.message1": "在我们的文档中获取更多的信息。",
|
||||
"xpack.uptime.locationMap.locations.missing.title": "地理信息缺失",
|
||||
"xpack.uptime.locationMap.locations.tags.others": "{otherLoc} 其他......",
|
||||
"xpack.uptime.locationName.helpLinkAnnotation": "添加位置",
|
||||
"xpack.uptime.ml.durationChart.exploreInMlApp": "在 ML 应用中浏览",
|
||||
"xpack.uptime.ml.enableAnomalyDetectionPanel.anomalyDetectionTitle": "异常检测",
|
||||
|
@ -16658,12 +16657,7 @@
|
|||
"xpack.uptime.monitorStatusBar.locations.oneLocStatus": "在 {loc} 位置{status}",
|
||||
"xpack.uptime.monitorStatusBar.locations.upStatus": "在 {loc} 位置{status}",
|
||||
"xpack.uptime.monitorStatusBar.monitorUrlLinkAriaLabel": "监测 URL 链接",
|
||||
"xpack.uptime.monitorStatusBar.sslCertificate.overview": "证书概览",
|
||||
"xpack.uptime.monitorStatusBar.sslCertificate.title": "证书",
|
||||
"xpack.uptime.monitorStatusBar.sslCertificateExpired.badgeContent": "{emphasizedText}过期",
|
||||
"xpack.uptime.monitorStatusBar.sslCertificateExpired.label.ariaLabel": "已于 {validityDate}过期",
|
||||
"xpack.uptime.monitorStatusBar.sslCertificateExpiry.badgeContent": "{emphasizedText}过期",
|
||||
"xpack.uptime.monitorStatusBar.sslCertificateExpiry.label.ariaLabel": "将于 {validityDate}过期",
|
||||
"xpack.uptime.monitorStatusBar.timestampFromNowTextAriaLabel": "自上次检查以来经过的时间",
|
||||
"xpack.uptime.navigateToAlertingButton.content": "管理告警",
|
||||
"xpack.uptime.navigateToAlertingUi": "离开 Uptime 并前往“Alerting 管理”页面",
|
||||
|
|
|
@ -6,15 +6,19 @@
|
|||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
export const LocationType = t.partial({
|
||||
export const LocationType = t.type({
|
||||
lat: t.string,
|
||||
lon: t.string,
|
||||
});
|
||||
|
||||
export const CheckGeoType = t.partial({
|
||||
name: t.string,
|
||||
location: LocationType,
|
||||
});
|
||||
export const CheckGeoType = t.intersection([
|
||||
t.type({
|
||||
name: t.string,
|
||||
}),
|
||||
t.partial({
|
||||
location: LocationType,
|
||||
}),
|
||||
]);
|
||||
|
||||
export const SummaryType = t.partial({
|
||||
up: t.number,
|
||||
|
@ -34,5 +38,6 @@ export const DateRangeType = t.type({
|
|||
|
||||
export type Summary = t.TypeOf<typeof SummaryType>;
|
||||
export type Location = t.TypeOf<typeof LocationType>;
|
||||
export type GeoPoint = t.TypeOf<typeof CheckGeoType>;
|
||||
export type StatesIndexStatus = t.TypeOf<typeof StatesIndexStatusType>;
|
||||
export type DateRange = t.TypeOf<typeof DateRangeType>;
|
||||
|
|
|
@ -7,17 +7,23 @@ import * as t from 'io-ts';
|
|||
import { CheckGeoType, SummaryType } from '../common';
|
||||
|
||||
// IO type for validation
|
||||
export const MonitorLocationType = t.partial({
|
||||
export const MonitorLocationType = t.type({
|
||||
up_history: t.number,
|
||||
down_history: t.number,
|
||||
timestamp: t.string,
|
||||
summary: SummaryType,
|
||||
geo: CheckGeoType,
|
||||
timestamp: t.string,
|
||||
});
|
||||
|
||||
// Typescript type for type checking
|
||||
export type MonitorLocation = t.TypeOf<typeof MonitorLocationType>;
|
||||
|
||||
export const MonitorLocationsType = t.intersection([
|
||||
t.type({ monitorId: t.string }),
|
||||
t.type({
|
||||
monitorId: t.string,
|
||||
up_history: t.number,
|
||||
down_history: t.number,
|
||||
}),
|
||||
t.partial({ locations: t.array(MonitorLocationType) }),
|
||||
]);
|
||||
export type MonitorLocations = t.TypeOf<typeof MonitorLocationsType>;
|
||||
|
|
|
@ -18,6 +18,10 @@ export const EXPIRES_SOON = i18n.translate('xpack.uptime.certs.expireSoon', {
|
|||
defaultMessage: 'Expires soon',
|
||||
});
|
||||
|
||||
export const EXPIRES = i18n.translate('xpack.uptime.certs.expires', {
|
||||
defaultMessage: 'Expires',
|
||||
});
|
||||
|
||||
export const SEARCH_CERTS = i18n.translate('xpack.uptime.certs.searchCerts', {
|
||||
defaultMessage: 'Search certificates',
|
||||
});
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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 URL_LABEL = i18n.translate('xpack.uptime.monitorList.table.url.name', {
|
||||
defaultMessage: 'Url',
|
||||
});
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
export * from './ml';
|
||||
export * from './ping_list';
|
||||
export * from './location_map';
|
||||
export * from './monitor_status_details';
|
||||
export * from './status_details/location_map';
|
||||
export * from './status_details';
|
||||
export * from './ping_histogram';
|
||||
export * from './monitor_charts';
|
||||
|
|
|
@ -1,282 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LocationMap component doesnt shows warning if geo is provided 1`] = `
|
||||
<EuiErrorBoundary>
|
||||
<Styled(EuiFlexGroup)
|
||||
gutterSize="none"
|
||||
justifyContent="flexEnd"
|
||||
wrap={true}
|
||||
>
|
||||
<Styled(EuiFlexItem)>
|
||||
<LocationStatusTags
|
||||
locations={
|
||||
Array [
|
||||
Object {
|
||||
"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",
|
||||
},
|
||||
Object {
|
||||
"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",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Styled(EuiFlexItem)>
|
||||
<EuiHideFor
|
||||
sizes={
|
||||
Array [
|
||||
"xs",
|
||||
]
|
||||
}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<styled.div>
|
||||
<EmbeddedMap
|
||||
downPoints={Array []}
|
||||
upPoints={
|
||||
Array [
|
||||
Object {
|
||||
"lat": "40.730610",
|
||||
"lon": " -73.935242",
|
||||
},
|
||||
Object {
|
||||
"lat": "52.487448",
|
||||
"lon": " 13.394798",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiFlexItem>
|
||||
</EuiHideFor>
|
||||
</Styled(EuiFlexGroup)>
|
||||
</EuiErrorBoundary>
|
||||
`;
|
||||
|
||||
exports[`LocationMap component renders correctly against snapshot 1`] = `
|
||||
<EuiErrorBoundary>
|
||||
<Styled(EuiFlexGroup)
|
||||
gutterSize="none"
|
||||
justifyContent="flexEnd"
|
||||
wrap={true}
|
||||
>
|
||||
<Styled(EuiFlexItem)>
|
||||
<LocationStatusTags
|
||||
locations={
|
||||
Array [
|
||||
Object {
|
||||
"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",
|
||||
},
|
||||
Object {
|
||||
"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",
|
||||
},
|
||||
Object {
|
||||
"geo": Object {
|
||||
"name": "Unnamed-location",
|
||||
},
|
||||
"summary": Object {
|
||||
"down": 0,
|
||||
"up": 4,
|
||||
},
|
||||
"timestamp": "2020-01-13T22:50:02.753Z",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Styled(EuiFlexItem)>
|
||||
<EuiHideFor
|
||||
sizes={
|
||||
Array [
|
||||
"xs",
|
||||
]
|
||||
}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<LocationMissingWarning />
|
||||
<styled.div>
|
||||
<EmbeddedMap
|
||||
downPoints={Array []}
|
||||
upPoints={
|
||||
Array [
|
||||
Object {
|
||||
"lat": "40.730610",
|
||||
"lon": " -73.935242",
|
||||
},
|
||||
Object {
|
||||
"lat": "52.487448",
|
||||
"lon": " 13.394798",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiFlexItem>
|
||||
</EuiHideFor>
|
||||
</Styled(EuiFlexGroup)>
|
||||
</EuiErrorBoundary>
|
||||
`;
|
||||
|
||||
exports[`LocationMap component renders named locations that have missing geo data 1`] = `
|
||||
<EuiErrorBoundary>
|
||||
<Styled(EuiFlexGroup)
|
||||
gutterSize="none"
|
||||
justifyContent="flexEnd"
|
||||
wrap={true}
|
||||
>
|
||||
<Styled(EuiFlexItem)>
|
||||
<LocationStatusTags
|
||||
locations={
|
||||
Array [
|
||||
Object {
|
||||
"geo": Object {
|
||||
"location": undefined,
|
||||
"name": "New York",
|
||||
},
|
||||
"summary": Object {
|
||||
"down": 0,
|
||||
"up": 4,
|
||||
},
|
||||
"timestamp": "2020-01-13T22:50:06.536Z",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Styled(EuiFlexItem)>
|
||||
<EuiHideFor
|
||||
sizes={
|
||||
Array [
|
||||
"xs",
|
||||
]
|
||||
}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<LocationMissingWarning />
|
||||
<styled.div>
|
||||
<EmbeddedMap
|
||||
downPoints={Array []}
|
||||
upPoints={Array []}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiFlexItem>
|
||||
</EuiHideFor>
|
||||
</Styled(EuiFlexGroup)>
|
||||
</EuiErrorBoundary>
|
||||
`;
|
||||
|
||||
exports[`LocationMap component shows warning if geo information is missing 1`] = `
|
||||
<EuiErrorBoundary>
|
||||
<Styled(EuiFlexGroup)
|
||||
gutterSize="none"
|
||||
justifyContent="flexEnd"
|
||||
wrap={true}
|
||||
>
|
||||
<Styled(EuiFlexItem)>
|
||||
<LocationStatusTags
|
||||
locations={
|
||||
Array [
|
||||
Object {
|
||||
"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",
|
||||
},
|
||||
Object {
|
||||
"geo": Object {
|
||||
"name": "Unnamed-location",
|
||||
},
|
||||
"summary": Object {
|
||||
"down": 0,
|
||||
"up": 4,
|
||||
},
|
||||
"timestamp": "2020-01-13T22:50:02.753Z",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Styled(EuiFlexItem)>
|
||||
<EuiHideFor
|
||||
sizes={
|
||||
Array [
|
||||
"xs",
|
||||
]
|
||||
}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<LocationMissingWarning />
|
||||
<styled.div>
|
||||
<EmbeddedMap
|
||||
downPoints={Array []}
|
||||
upPoints={
|
||||
Array [
|
||||
Object {
|
||||
"lat": "52.487448",
|
||||
"lon": " 13.394798",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiFlexItem>
|
||||
</EuiHideFor>
|
||||
</Styled(EuiFlexGroup)>
|
||||
</EuiErrorBoundary>
|
||||
`;
|
|
@ -1,682 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LocationStatusTags component renders properly against props 1`] = `
|
||||
<Fragment>
|
||||
<styled.div>
|
||||
<span>
|
||||
<styled.div
|
||||
key="0"
|
||||
>
|
||||
<EuiBadge
|
||||
color="#bd271e"
|
||||
>
|
||||
<EuiText
|
||||
size="m"
|
||||
>
|
||||
<styled.div>
|
||||
Berlin
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiBadge>
|
||||
<styled.span>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
>
|
||||
1 Mon ago
|
||||
</EuiText>
|
||||
</styled.span>
|
||||
</styled.div>
|
||||
</span>
|
||||
<span>
|
||||
<styled.div
|
||||
key="0"
|
||||
>
|
||||
<EuiBadge
|
||||
color="#d3dae6"
|
||||
>
|
||||
<EuiText
|
||||
size="m"
|
||||
>
|
||||
<styled.div>
|
||||
Berlin
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiBadge>
|
||||
<styled.span>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
>
|
||||
1 Mon ago
|
||||
</EuiText>
|
||||
</styled.span>
|
||||
</styled.div>
|
||||
<styled.div
|
||||
key="1"
|
||||
>
|
||||
<EuiBadge
|
||||
color="#d3dae6"
|
||||
>
|
||||
<EuiText
|
||||
size="m"
|
||||
>
|
||||
<styled.div>
|
||||
Islamabad
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiBadge>
|
||||
<styled.span>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
>
|
||||
1 Mon ago
|
||||
</EuiText>
|
||||
</styled.span>
|
||||
</styled.div>
|
||||
</span>
|
||||
</styled.div>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`LocationStatusTags component renders when all locations are down 1`] = `
|
||||
.c3 {
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
max-height: 229px;
|
||||
overflow: hidden;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
@media (max-width:1042px) {
|
||||
.c1 {
|
||||
display: inline-block;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<span>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
Islamabad
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="c3"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
5s ago
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
Berlin
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="c3"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
5m ago
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<span />
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`LocationStatusTags component renders when all locations are up 1`] = `
|
||||
.c3 {
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
max-height: 229px;
|
||||
overflow: hidden;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
@media (max-width:1042px) {
|
||||
.c1 {
|
||||
display: inline-block;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<span />
|
||||
<span>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#d3dae6;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
Berlin
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="c3"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
5d ago
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#d3dae6;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
Islamabad
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="c3"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
5s ago
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`LocationStatusTags component renders when there are many location 1`] = `
|
||||
Array [
|
||||
.c3 {
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
max-height: 229px;
|
||||
overflow: hidden;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
@media (max-width:1042px) {
|
||||
.c1 {
|
||||
display: inline-block;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<span>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
Islamabad
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="c3"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
5s ago
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
Berlin
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="c3"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
5m ago
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
st-paul
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="c3"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
5h ago
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
Tokyo
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="c3"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
5d ago
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
New York
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="c3"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
1 Mon ago
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
Toronto
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="c3"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
5 Mon ago
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
Sydney
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="c3"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
5 Yr ago
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
Paris
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="c3"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
5 Yr ago
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<span />
|
||||
</div>,
|
||||
.c0 {
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
@media (max-width:1042px) {
|
||||
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<div
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
<h4>
|
||||
1 Others ...
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
|
@ -1,95 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiErrorBoundary, EuiHideFor } from '@elastic/eui';
|
||||
import { LocationStatusTags } from './location_status_tags';
|
||||
import { EmbeddedMap, LocationPoint } from './embeddables/embedded_map';
|
||||
import { MonitorLocations, MonitorLocation } 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
|
||||
const MapPanel = styled.div`
|
||||
height: 240px;
|
||||
width: 520px;
|
||||
@media (min-width: 1300px) {
|
||||
margin-right: 20px;
|
||||
}
|
||||
@media (max-width: 574px) {
|
||||
height: 250px;
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const EuiFlexItemTags = styled(EuiFlexItem)`
|
||||
padding-top: 5px;
|
||||
@media (max-width: 1042px) {
|
||||
flex-basis: 80% !important;
|
||||
flex-grow: 0 !important;
|
||||
order: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const FlexGroup = styled(EuiFlexGroup)`
|
||||
@media (max-width: 850px) {
|
||||
justify-content: center;
|
||||
}
|
||||
`;
|
||||
|
||||
interface LocationMapProps {
|
||||
monitorLocations: MonitorLocations;
|
||||
}
|
||||
|
||||
export const LocationMap = ({ monitorLocations }: LocationMapProps) => {
|
||||
const upPoints: LocationPoint[] = [];
|
||||
const downPoints: LocationPoint[] = [];
|
||||
|
||||
let isGeoInfoMissing = false;
|
||||
|
||||
if (monitorLocations?.locations) {
|
||||
monitorLocations.locations.forEach((item: MonitorLocation) => {
|
||||
if (item.geo?.name === UNNAMED_LOCATION || !item.geo?.location) {
|
||||
isGeoInfoMissing = true;
|
||||
} else if (
|
||||
item.geo?.name !== UNNAMED_LOCATION &&
|
||||
!!item.geo.location.lat &&
|
||||
!!item.geo.location.lon
|
||||
) {
|
||||
// TypeScript doesn't infer that the above checks in this block's condition
|
||||
// ensure that lat and lon are defined when we try to pass the location object directly,
|
||||
// but if we destructure the values it does. Improvement to this block is welcome.
|
||||
const { lat, lon } = item.geo.location;
|
||||
if (item?.summary?.down === 0) {
|
||||
upPoints.push({ lat, lon });
|
||||
} else {
|
||||
downPoints.push({ lat, lon });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiErrorBoundary>
|
||||
<FlexGroup wrap={true} gutterSize="none" justifyContent="flexEnd">
|
||||
<EuiFlexItemTags>
|
||||
<LocationStatusTags locations={monitorLocations?.locations || []} />
|
||||
</EuiFlexItemTags>
|
||||
<EuiHideFor sizes={['xs']}>
|
||||
<EuiFlexItem grow={false}>
|
||||
{isGeoInfoMissing && <LocationMissingWarning />}
|
||||
<MapPanel>
|
||||
<EmbeddedMap upPoints={upPoints} downPoints={downPoints} />
|
||||
</MapPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiHideFor>
|
||||
</FlexGroup>
|
||||
</EuiErrorBoundary>
|
||||
);
|
||||
};
|
|
@ -1,130 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import moment from 'moment';
|
||||
import styled from 'styled-components';
|
||||
import { EuiBadge, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { UptimeThemeContext } from '../../../contexts';
|
||||
import { MonitorLocation } from '../../../../common/runtime_types';
|
||||
import { SHORT_TIMESPAN_LOCALE, SHORT_TS_LOCALE } from '../../../../common/constants';
|
||||
|
||||
const TimeStampSpan = styled.span`
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
`;
|
||||
|
||||
const TextStyle = styled.div`
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const BadgeItem = styled.div`
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
@media (max-width: 1042px) {
|
||||
display: inline-block;
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
// Set height so that it remains within panel, enough height to display 7 locations tags
|
||||
const TagContainer = styled.div`
|
||||
max-height: 229px;
|
||||
overflow: hidden;
|
||||
margin-top: auto;
|
||||
`;
|
||||
|
||||
const OtherLocationsDiv = styled.div`
|
||||
padding-left: 18px;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
locations: MonitorLocation[];
|
||||
}
|
||||
|
||||
interface StatusTag {
|
||||
label: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export const LocationStatusTags = ({ locations }: Props) => {
|
||||
const {
|
||||
colors: { gray, danger },
|
||||
} = useContext(UptimeThemeContext);
|
||||
|
||||
const upLocations: StatusTag[] = [];
|
||||
const downLocations: StatusTag[] = [];
|
||||
|
||||
locations.forEach((item: any) => {
|
||||
if (item.summary.down === 0) {
|
||||
upLocations.push({ label: item.geo.name, timestamp: new Date(item.timestamp).valueOf() });
|
||||
} else {
|
||||
downLocations.push({ label: item.geo.name, timestamp: new Date(item.timestamp).valueOf() });
|
||||
}
|
||||
});
|
||||
|
||||
// Sort lexicographically by label
|
||||
upLocations.sort((a, b) => {
|
||||
return a.label > b.label ? 1 : b.label > a.label ? -1 : 0;
|
||||
});
|
||||
|
||||
const tagLabel = (item: StatusTag, ind: number, color: string) => {
|
||||
return (
|
||||
<BadgeItem key={ind}>
|
||||
<EuiBadge color={color}>
|
||||
<EuiText size="m">
|
||||
<TextStyle>{item.label}</TextStyle>
|
||||
</EuiText>
|
||||
</EuiBadge>
|
||||
<TimeStampSpan>
|
||||
<EuiText color="subdued">{moment(item.timestamp).fromNow()}</EuiText>
|
||||
</TimeStampSpan>
|
||||
</BadgeItem>
|
||||
);
|
||||
};
|
||||
|
||||
const prevLocal: string = moment.locale() ?? 'en';
|
||||
|
||||
const renderTags = () => {
|
||||
const shortLocale = moment.locale(SHORT_TS_LOCALE) === SHORT_TS_LOCALE;
|
||||
if (!shortLocale) {
|
||||
moment.defineLocale(SHORT_TS_LOCALE, SHORT_TIMESPAN_LOCALE);
|
||||
}
|
||||
|
||||
const tags = (
|
||||
<TagContainer>
|
||||
<span>{downLocations.map((item, ind) => tagLabel(item, ind, danger))}</span>
|
||||
<span>{upLocations.map((item, ind) => tagLabel(item, ind, gray))}</span>
|
||||
</TagContainer>
|
||||
);
|
||||
|
||||
// Need to reset locale so it doesn't effect other parts of the app
|
||||
moment.locale(prevLocal);
|
||||
return tags;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderTags()}
|
||||
{locations.length > 7 && (
|
||||
<OtherLocationsDiv>
|
||||
<EuiText color="subdued">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.locationMap.locations.tags.others"
|
||||
defaultMessage="{otherLoc} Others ..."
|
||||
values={{
|
||||
otherLoc: locations.length - 7,
|
||||
}}
|
||||
/>
|
||||
</h4>
|
||||
</EuiText>
|
||||
</OtherLocationsDiv>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,55 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`MonitorStatusBar component renders duration in ms, not us 1`] = `
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--directionColumn"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<h2>
|
||||
Up in 2 Locations
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<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
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<span
|
||||
class="euiTextColor euiTextColor--subdued euiTitle euiTitle--xsmall"
|
||||
>
|
||||
<h1
|
||||
data-test-subj="monitor-page-title"
|
||||
>
|
||||
id1
|
||||
</h1>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiSpacer euiSpacer--l"
|
||||
/>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
/>
|
||||
</div>
|
||||
`;
|
|
@ -1,100 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { EuiSpacer, EuiText, EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { Tls } from '../../../../../common/runtime_types';
|
||||
import { useCertStatus } from '../../../../hooks';
|
||||
import { CERT_STATUS, CERTIFICATES_ROUTE } from '../../../../../common/constants';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* TLS information coming from monitor in ES heartbeat index
|
||||
*/
|
||||
tls: Tls | null | undefined;
|
||||
}
|
||||
|
||||
export const MonitorSSLCertificate = ({ tls }: Props) => {
|
||||
const certStatus = useCertStatus(tls?.not_after);
|
||||
|
||||
const isExpiringSoon = certStatus === CERT_STATUS.EXPIRING_SOON;
|
||||
|
||||
const isExpired = certStatus === CERT_STATUS.EXPIRED;
|
||||
|
||||
const relativeDate = moment(tls?.not_after).fromNow();
|
||||
|
||||
return certStatus ? (
|
||||
<>
|
||||
<EuiText>
|
||||
{i18n.translate('xpack.uptime.monitorStatusBar.sslCertificate.title', {
|
||||
defaultMessage: 'Certificate:',
|
||||
})}
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup wrap>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText
|
||||
className="eui-displayInline"
|
||||
grow={false}
|
||||
size="s"
|
||||
aria-label={
|
||||
isExpired
|
||||
? i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.sslCertificateExpired.label.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Expired {validityDate}',
|
||||
values: { validityDate: relativeDate },
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.sslCertificateExpiry.label.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Expires {validityDate}',
|
||||
values: { validityDate: relativeDate },
|
||||
}
|
||||
)
|
||||
}
|
||||
>
|
||||
{isExpired ? (
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorStatusBar.sslCertificateExpired.badgeContent"
|
||||
defaultMessage="Expired {emphasizedText}"
|
||||
values={{
|
||||
emphasizedText: <EuiBadge color={'danger'}>{relativeDate}</EuiBadge>,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorStatusBar.sslCertificateExpiry.badgeContent"
|
||||
defaultMessage="Expires {emphasizedText}"
|
||||
values={{
|
||||
emphasizedText: (
|
||||
<EuiBadge color={isExpiringSoon ? 'warning' : 'default'}>
|
||||
{relativeDate}
|
||||
</EuiBadge>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<Link to={CERTIFICATES_ROUTE} className="eui-displayInline">
|
||||
<EuiText style={{ whiteSpace: 'nowrap' }}>
|
||||
{i18n.translate('xpack.uptime.monitorStatusBar.sslCertificate.overview', {
|
||||
defaultMessage: 'Certificate overview',
|
||||
})}
|
||||
</EuiText>
|
||||
</Link>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
) : null;
|
||||
};
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
EuiLink,
|
||||
EuiTitle,
|
||||
EuiTextColor,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import { MonitorSSLCertificate } from './ssl_certificate';
|
||||
import * as labels from './translations';
|
||||
import { StatusByLocations } from './status_by_location';
|
||||
import { Ping } from '../../../../../common/runtime_types';
|
||||
import { MonitorLocations } from '../../../../../common/runtime_types';
|
||||
|
||||
interface MonitorStatusBarProps {
|
||||
monitorId: string;
|
||||
monitorStatus: Ping | null;
|
||||
monitorLocations: MonitorLocations;
|
||||
}
|
||||
|
||||
export const MonitorStatusBarComponent: React.FC<MonitorStatusBarProps> = ({
|
||||
monitorId,
|
||||
monitorStatus,
|
||||
monitorLocations,
|
||||
}) => {
|
||||
const full = monitorStatus?.url?.full ?? '';
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="none" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<StatusByLocations locations={monitorLocations?.locations ?? []} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<EuiLink aria-label={labels.monitorUrlLinkAriaLabel} href={full} target="_blank">
|
||||
{full}
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xs">
|
||||
<EuiTextColor color="subdued">
|
||||
<h1 data-test-subj="monitor-page-title">{monitorId}</h1>
|
||||
</EuiTextColor>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiSpacer />
|
||||
<EuiFlexItem grow={false}>
|
||||
<MonitorSSLCertificate tls={monitorStatus?.tls} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { 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,73 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`MonitorStatusBar component renders 1`] = `
|
||||
Array [
|
||||
<div>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
>
|
||||
<h2>
|
||||
Up in 2 Locations
|
||||
</h2>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="euiSpacer euiSpacer--l"
|
||||
/>,
|
||||
.c0.c0.c0 {
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
.c1.c1.c1 {
|
||||
width: 65%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
<dl
|
||||
class="euiDescriptionList euiDescriptionList--column euiDescriptionList--reverse euiDescriptionList--compressed"
|
||||
style="max-width:450px"
|
||||
>
|
||||
<dt
|
||||
class="euiDescriptionList__title c0"
|
||||
>
|
||||
Overall availability
|
||||
</dt>
|
||||
<dd
|
||||
class="euiDescriptionList__description c1"
|
||||
data-test-subj="uptimeOverallAvailability"
|
||||
>
|
||||
0.00 %
|
||||
</dd>
|
||||
<dt
|
||||
class="euiDescriptionList__title c0"
|
||||
>
|
||||
Url
|
||||
</dt>
|
||||
<dd
|
||||
class="euiDescriptionList__description c1"
|
||||
>
|
||||
<a
|
||||
aria-label="Monitor URL link"
|
||||
class="euiLink euiLink--primary"
|
||||
href=""
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
|
||||
<div
|
||||
data-euiicon-type="popout"
|
||||
/>
|
||||
</a>
|
||||
</dd>
|
||||
<dt
|
||||
class="euiDescriptionList__title c0"
|
||||
>
|
||||
Monitor ID
|
||||
</dt>
|
||||
<dd
|
||||
class="euiDescriptionList__description c1"
|
||||
data-test-subj="monitor-page-title"
|
||||
/>
|
||||
</dl>,
|
||||
]
|
||||
`;
|
|
@ -2,61 +2,91 @@
|
|||
|
||||
exports[`SSL Certificate component renders 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
.c0.c0.c0 {
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
<dt
|
||||
class="euiDescriptionList__title c0"
|
||||
>
|
||||
Certificate:
|
||||
</div>,
|
||||
TLS Certificate
|
||||
</dt>,
|
||||
<div
|
||||
class="euiSpacer euiSpacer--s"
|
||||
/>,
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive euiFlexGroup--wrap"
|
||||
.c0.c0.c0 {
|
||||
width: 65%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.c1.c1.c1 {
|
||||
margin: 0 0 0 4px;
|
||||
display: inline-block;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
<dd
|
||||
class="euiDescriptionList__description c0"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
<a
|
||||
class="eui-displayInline"
|
||||
href="/certificates"
|
||||
>
|
||||
<div
|
||||
aria-label="Expires in 2 months"
|
||||
class="euiText euiText--small eui-displayInline euiText--constrainedWidth"
|
||||
>
|
||||
Expires
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#d3dae6;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
in 2 months
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<a
|
||||
class="eui-displayInline"
|
||||
href="/certificates"
|
||||
<span
|
||||
class="euiToolTipAnchor"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium"
|
||||
style="white-space:nowrap"
|
||||
class="euiText euiText--small"
|
||||
>
|
||||
Certificate overview
|
||||
<div
|
||||
color="success"
|
||||
data-euiicon-type="lock"
|
||||
/>
|
||||
<h4
|
||||
class="c1"
|
||||
>
|
||||
Expires in 2 months
|
||||
</h4>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>,
|
||||
</span>
|
||||
</a>
|
||||
</dd>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`SSL Certificate component renders null if invalid date 1`] = `null`;
|
||||
exports[`SSL Certificate component renders null if invalid date 1`] = `
|
||||
Array [
|
||||
.c0.c0.c0 {
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
<dt
|
||||
class="euiDescriptionList__title c0"
|
||||
>
|
||||
TLS Certificate
|
||||
</dt>,
|
||||
<div
|
||||
class="euiSpacer euiSpacer--s"
|
||||
/>,
|
||||
.c0.c0.c0 {
|
||||
width: 65%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
<dd
|
||||
class="euiDescriptionList__description c0"
|
||||
>
|
||||
<a
|
||||
class="eui-displayInline"
|
||||
href="/certificates"
|
||||
>
|
||||
<span>
|
||||
--
|
||||
</span>
|
||||
</a>
|
||||
</dd>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`SSL Certificate component shallow renders 1`] = `
|
||||
<ContextProvider
|
|
@ -6,10 +6,11 @@
|
|||
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { renderWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { MonitorStatusBarComponent } from '../monitor_status_bar';
|
||||
import { MonitorStatusBar } from '../status_bar';
|
||||
import { Ping } from '../../../../../common/runtime_types';
|
||||
import * as redux from 'react-redux';
|
||||
import { renderWithRouter } from '../../../../lib';
|
||||
import { createMemoryHistory } from 'history';
|
||||
|
||||
describe('MonitorStatusBar component', () => {
|
||||
let monitorStatus: Ping;
|
||||
|
@ -49,18 +50,21 @@ describe('MonitorStatusBar component', () => {
|
|||
const spy = jest.spyOn(redux, 'useDispatch');
|
||||
spy.mockReturnValue(jest.fn());
|
||||
|
||||
const spy1 = jest.spyOn(redux, 'useSelector');
|
||||
spy1.mockReturnValue(true);
|
||||
jest.spyOn(redux, 'useSelector').mockImplementation((fn, d) => {
|
||||
if (fn.name === ' monitorStatusSelector') {
|
||||
return monitorStatus;
|
||||
} else {
|
||||
return monitorLocations;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('renders duration in ms, not us', () => {
|
||||
const component = renderWithIntl(
|
||||
<MonitorStatusBarComponent
|
||||
monitorStatus={monitorStatus}
|
||||
monitorId="id1"
|
||||
monitorLocations={monitorLocations}
|
||||
/>
|
||||
);
|
||||
it('renders', () => {
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: ['/aWQx/'],
|
||||
});
|
||||
history.location.key = 'test';
|
||||
const component = renderWithRouter(<MonitorStatusBar />, history);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { EuiBadge } from '@elastic/eui';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import { Tls } from '../../../../../common/runtime_types';
|
||||
import { MonitorSSLCertificate } from '../monitor_status_bar';
|
||||
import { MonitorSSLCertificate } from '../status_bar';
|
||||
import * as redux from 'react-redux';
|
||||
import { mountWithRouter, renderWithRouter, shallowWithRouter } from '../../../../lib';
|
||||
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../common/constants';
|
||||
|
@ -58,14 +58,12 @@ describe('SSL Certificate component', () => {
|
|||
};
|
||||
const component = mountWithRouter(<MonitorSSLCertificate tls={monitorTls} />);
|
||||
|
||||
const badgeComponent = component.find(EuiBadge);
|
||||
const lockIcon = component.find(EuiIcon);
|
||||
|
||||
expect(badgeComponent.props().color).toBe('warning');
|
||||
expect(lockIcon.props().color).toBe('warning');
|
||||
|
||||
const badgeComponentText = component.find('.euiBadge__text');
|
||||
expect(badgeComponentText.text()).toBe(moment(dateIn5Days).fromNow());
|
||||
|
||||
expect(badgeComponent.find('span.euiBadge--warning')).toBeTruthy();
|
||||
const componentText = component.find('h4');
|
||||
expect(componentText.text()).toBe('Expires soon ' + moment(dateIn5Days).fromNow());
|
||||
});
|
||||
|
||||
it('does not render the expiration date with a warning state if expiry date is greater than a month', () => {
|
||||
|
@ -75,12 +73,10 @@ describe('SSL Certificate component', () => {
|
|||
};
|
||||
const component = mountWithRouter(<MonitorSSLCertificate tls={monitorTls} />);
|
||||
|
||||
const badgeComponent = component.find(EuiBadge);
|
||||
expect(badgeComponent.props().color).toBe('default');
|
||||
const lockIcon = component.find(EuiIcon);
|
||||
expect(lockIcon.props().color).toBe('success');
|
||||
|
||||
const badgeComponentText = component.find('.euiBadge__text');
|
||||
expect(badgeComponentText.text()).toBe(moment(dateIn40Days).fromNow());
|
||||
|
||||
expect(badgeComponent.find('span.euiBadge--warning')).toHaveLength(0);
|
||||
const componentText = component.find('h4');
|
||||
expect(componentText.text()).toBe('Expires ' + moment(dateIn40Days).fromNow());
|
||||
});
|
||||
});
|
|
@ -17,10 +17,16 @@ describe('StatusByLocation component', () => {
|
|||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
timestamp: '2020-01-13T22:50:06.536Z',
|
||||
},
|
||||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
timestamp: '2020-01-13T22:50:06.536Z',
|
||||
},
|
||||
];
|
||||
const component = shallowWithIntl(<StatusByLocations locations={monitorLocations} />);
|
||||
|
@ -32,10 +38,16 @@ describe('StatusByLocation component', () => {
|
|||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
timestamp: '2020-01-13T22:50:06.536Z',
|
||||
},
|
||||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
timestamp: '2020-01-13T22:50:06.536Z',
|
||||
},
|
||||
];
|
||||
const component = renderWithIntl(<StatusByLocations locations={monitorLocations} />);
|
||||
|
@ -47,6 +59,9 @@ describe('StatusByLocation component', () => {
|
|||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
timestamp: '2020-01-13T22:50:06.536Z',
|
||||
},
|
||||
];
|
||||
const component = renderWithIntl(<StatusByLocations locations={monitorLocations} />);
|
||||
|
@ -58,6 +73,9 @@ describe('StatusByLocation component', () => {
|
|||
{
|
||||
summary: { up: 0, down: 4 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
timestamp: '2020-01-13T22:50:06.536Z',
|
||||
},
|
||||
];
|
||||
const component = renderWithIntl(<StatusByLocations locations={monitorLocations} />);
|
||||
|
@ -69,10 +87,16 @@ describe('StatusByLocation component', () => {
|
|||
{
|
||||
summary: { up: 0, down: 4 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
timestamp: '2020-01-13T22:50:06.536Z',
|
||||
},
|
||||
{
|
||||
summary: { up: 0, down: 4 },
|
||||
geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
timestamp: '2020-01-13T22:50:06.536Z',
|
||||
},
|
||||
];
|
||||
const component = renderWithIntl(<StatusByLocations locations={monitorLocations} />);
|
||||
|
@ -84,10 +108,16 @@ describe('StatusByLocation component', () => {
|
|||
{
|
||||
summary: { up: 0, down: 4 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
timestamp: '2020-01-13T22:50:06.536Z',
|
||||
},
|
||||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
timestamp: '2020-01-13T22:50:06.536Z',
|
||||
},
|
||||
];
|
||||
const component = renderWithIntl(<StatusByLocations locations={monitorLocations} />);
|
|
@ -0,0 +1,381 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AvailabilityReporting component renders correctly against snapshot 1`] = `
|
||||
Array [
|
||||
@media (max-width:1042px) {
|
||||
|
||||
}
|
||||
|
||||
<div
|
||||
class="euiSpacer euiSpacer--s"
|
||||
/>,
|
||||
.c0 {
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@media (max-width:1042px) {
|
||||
.c0 {
|
||||
display: inline-block;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
<div
|
||||
class="euiBasicTable"
|
||||
>
|
||||
<div>
|
||||
<table
|
||||
class="euiTable euiTable--compressed"
|
||||
>
|
||||
<caption
|
||||
class="euiScreenReaderOnly euiTableCaption"
|
||||
/>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
class="euiTableHeaderCell"
|
||||
data-test-subj="tableHeaderCell_label_0"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
>
|
||||
<div
|
||||
class="euiTableCellContent"
|
||||
>
|
||||
<span
|
||||
class="euiTableCellContent__text"
|
||||
>
|
||||
Location
|
||||
</span>
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
class="euiTableHeaderCell"
|
||||
data-test-subj="tableHeaderCell_availability_1"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
>
|
||||
<div
|
||||
class="euiTableCellContent euiTableCellContent--alignRight"
|
||||
>
|
||||
<span
|
||||
class="euiTableCellContent__text"
|
||||
>
|
||||
Availability
|
||||
</span>
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
class="euiTableHeaderCell"
|
||||
data-test-subj="tableHeaderCell_timestamp_2"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
>
|
||||
<div
|
||||
class="euiTableCellContent euiTableCellContent--alignRight"
|
||||
>
|
||||
<span
|
||||
class="euiTableCellContent__text"
|
||||
>
|
||||
Last check
|
||||
</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
class="euiTableRow"
|
||||
>
|
||||
<td
|
||||
class="euiTableRowCell"
|
||||
>
|
||||
<div
|
||||
class="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop"
|
||||
>
|
||||
Location
|
||||
</div>
|
||||
<div
|
||||
class="euiTableCellContent euiTableCellContent--truncateText euiTableCellContent--overflowingContent"
|
||||
>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#d3dae6;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--small"
|
||||
>
|
||||
<h4>
|
||||
au-heartbeat
|
||||
</h4>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="euiTableRowCell"
|
||||
>
|
||||
<div
|
||||
class="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop"
|
||||
>
|
||||
Availability
|
||||
</div>
|
||||
<div
|
||||
class="euiTableCellContent euiTableCellContent--alignRight euiTableCellContent--overflowingContent"
|
||||
>
|
||||
<span
|
||||
class=""
|
||||
>
|
||||
100.00 %
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="euiTableRowCell"
|
||||
>
|
||||
<div
|
||||
class="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop"
|
||||
>
|
||||
Last check
|
||||
</div>
|
||||
<div
|
||||
class="euiTableCellContent euiTableCellContent--alignRight"
|
||||
>
|
||||
<span
|
||||
class="euiTableCellContent__text"
|
||||
>
|
||||
36m ago
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
class="euiTableRow"
|
||||
>
|
||||
<td
|
||||
class="euiTableRowCell"
|
||||
>
|
||||
<div
|
||||
class="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop"
|
||||
>
|
||||
Location
|
||||
</div>
|
||||
<div
|
||||
class="euiTableCellContent euiTableCellContent--truncateText euiTableCellContent--overflowingContent"
|
||||
>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#d3dae6;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--small"
|
||||
>
|
||||
<h4>
|
||||
nyc-heartbeat
|
||||
</h4>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="euiTableRowCell"
|
||||
>
|
||||
<div
|
||||
class="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop"
|
||||
>
|
||||
Availability
|
||||
</div>
|
||||
<div
|
||||
class="euiTableCellContent euiTableCellContent--alignRight euiTableCellContent--overflowingContent"
|
||||
>
|
||||
<span
|
||||
class=""
|
||||
>
|
||||
100.00 %
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="euiTableRowCell"
|
||||
>
|
||||
<div
|
||||
class="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop"
|
||||
>
|
||||
Last check
|
||||
</div>
|
||||
<div
|
||||
class="euiTableCellContent euiTableCellContent--alignRight"
|
||||
>
|
||||
<span
|
||||
class="euiTableCellContent__text"
|
||||
>
|
||||
36m ago
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
class="euiTableRow"
|
||||
>
|
||||
<td
|
||||
class="euiTableRowCell"
|
||||
>
|
||||
<div
|
||||
class="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop"
|
||||
>
|
||||
Location
|
||||
</div>
|
||||
<div
|
||||
class="euiTableCellContent euiTableCellContent--truncateText euiTableCellContent--overflowingContent"
|
||||
>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#d3dae6;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--small"
|
||||
>
|
||||
<h4>
|
||||
spa-heartbeat
|
||||
</h4>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="euiTableRowCell"
|
||||
>
|
||||
<div
|
||||
class="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop"
|
||||
>
|
||||
Availability
|
||||
</div>
|
||||
<div
|
||||
class="euiTableCellContent euiTableCellContent--alignRight euiTableCellContent--overflowingContent"
|
||||
>
|
||||
<span
|
||||
class=""
|
||||
>
|
||||
100.00 %
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="euiTableRowCell"
|
||||
>
|
||||
<div
|
||||
class="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop"
|
||||
>
|
||||
Last check
|
||||
</div>
|
||||
<div
|
||||
class="euiTableCellContent euiTableCellContent--alignRight"
|
||||
>
|
||||
<span
|
||||
class="euiTableCellContent__text"
|
||||
>
|
||||
36m ago
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`AvailabilityReporting component shallow renders correctly against snapshot 1`] = `
|
||||
<Fragment>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiBasicTable
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"field": "label",
|
||||
"name": "Location",
|
||||
"render": [Function],
|
||||
"truncateText": true,
|
||||
},
|
||||
Object {
|
||||
"align": "right",
|
||||
"field": "availability",
|
||||
"name": "Availability",
|
||||
"render": [Function],
|
||||
},
|
||||
Object {
|
||||
"align": "right",
|
||||
"field": "timestamp",
|
||||
"name": "Last check",
|
||||
},
|
||||
]
|
||||
}
|
||||
compressed={true}
|
||||
items={
|
||||
Array [
|
||||
Object {
|
||||
"availability": 100,
|
||||
"color": "#d3dae6",
|
||||
"label": "au-heartbeat",
|
||||
"timestamp": "36m ago",
|
||||
},
|
||||
Object {
|
||||
"availability": 100,
|
||||
"color": "#d3dae6",
|
||||
"label": "nyc-heartbeat",
|
||||
"timestamp": "36m ago",
|
||||
},
|
||||
Object {
|
||||
"availability": 100,
|
||||
"color": "#d3dae6",
|
||||
"label": "spa-heartbeat",
|
||||
"timestamp": "36m ago",
|
||||
},
|
||||
]
|
||||
}
|
||||
noItemsMessage="No items found"
|
||||
onChange={[Function]}
|
||||
responsive={false}
|
||||
tableLayout="fixed"
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,56 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TagLabel component renders correctly against snapshot 1`] = `
|
||||
.c0 {
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@media (max-width:1042px) {
|
||||
.c0 {
|
||||
display: inline-block;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#fff;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--small"
|
||||
>
|
||||
<h4>
|
||||
US-East
|
||||
</h4>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TagLabel component shallow render correctly against snapshot 1`] = `
|
||||
<styled.div>
|
||||
<EuiBadge
|
||||
color="#fff"
|
||||
>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
<h4>
|
||||
US-East
|
||||
</h4>
|
||||
</EuiText>
|
||||
</EuiBadge>
|
||||
</styled.div>
|
||||
`;
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { AvailabilityReporting } from '../availability_reporting';
|
||||
import { StatusTag } from '../location_status_tags';
|
||||
|
||||
describe('AvailabilityReporting component', () => {
|
||||
let allLocations: StatusTag[];
|
||||
|
||||
beforeEach(() => {
|
||||
allLocations = [
|
||||
{
|
||||
label: 'au-heartbeat',
|
||||
timestamp: '36m ago',
|
||||
color: '#d3dae6',
|
||||
availability: 100,
|
||||
},
|
||||
{
|
||||
label: 'nyc-heartbeat',
|
||||
timestamp: '36m ago',
|
||||
color: '#d3dae6',
|
||||
availability: 100,
|
||||
},
|
||||
{ label: 'spa-heartbeat', timestamp: '36m ago', color: '#d3dae6', availability: 100 },
|
||||
];
|
||||
});
|
||||
|
||||
it('shallow renders correctly against snapshot', () => {
|
||||
const component = shallowWithIntl(<AvailabilityReporting allLocations={allLocations} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly against snapshot', () => {
|
||||
const component = renderWithIntl(<AvailabilityReporting allLocations={allLocations} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -7,7 +7,7 @@
|
|||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { MonitorLocation } from '../../../../../common/runtime_types/monitor';
|
||||
import { MonitorLocation } from '../../../../../../common/runtime_types/monitor';
|
||||
import { LocationStatusTags } from '../index';
|
||||
|
||||
describe('LocationStatusTags component', () => {
|
||||
|
@ -19,16 +19,22 @@ describe('LocationStatusTags component', () => {
|
|||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 'w').toISOString(),
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 'w').toISOString(),
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
{
|
||||
summary: { up: 0, down: 2 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 'w').toISOString(),
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
];
|
||||
const component = shallowWithIntl(<LocationStatusTags locations={monitorLocations} />);
|
||||
|
@ -41,41 +47,57 @@ describe('LocationStatusTags component', () => {
|
|||
summary: { up: 0, down: 1 },
|
||||
geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 's').toISOString(),
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
{
|
||||
summary: { up: 0, down: 1 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 'm').toISOString(),
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
{
|
||||
summary: { up: 0, down: 1 },
|
||||
geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 'h').toISOString(),
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
{
|
||||
summary: { up: 0, down: 1 },
|
||||
geo: { name: 'Tokyo', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 'd').toISOString(),
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
{
|
||||
summary: { up: 0, down: 1 },
|
||||
geo: { name: 'New York', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 'w').toISOString(),
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
{
|
||||
summary: { up: 0, down: 1 },
|
||||
geo: { name: 'Toronto', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 'M').toISOString(),
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
{
|
||||
summary: { up: 0, down: 1 },
|
||||
geo: { name: 'Sydney', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 'y').toISOString(),
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
{
|
||||
summary: { up: 0, down: 1 },
|
||||
geo: { name: 'Paris', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 'y').toISOString(),
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
];
|
||||
const component = renderWithIntl(<LocationStatusTags locations={monitorLocations} />);
|
||||
|
@ -88,11 +110,15 @@ describe('LocationStatusTags component', () => {
|
|||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 's').toISOString(),
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 'd').toISOString(),
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
];
|
||||
const component = renderWithIntl(<LocationStatusTags locations={monitorLocations} />);
|
||||
|
@ -105,11 +131,15 @@ describe('LocationStatusTags component', () => {
|
|||
summary: { up: 0, down: 2 },
|
||||
geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 's').toISOString(),
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
{
|
||||
summary: { up: 0, down: 2 },
|
||||
geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 'm').toISOString(),
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
];
|
||||
const component = renderWithIntl(<LocationStatusTags locations={monitorLocations} />);
|
|
@ -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 { TagLabel } from '../tag_label';
|
||||
|
||||
describe('TagLabel component', () => {
|
||||
it('shallow render correctly against snapshot', () => {
|
||||
const component = shallowWithIntl(<TagLabel color={'#fff'} label={'US-East'} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly against snapshot', () => {
|
||||
const component = renderWithIntl(<TagLabel color={'#fff'} label={'US-East'} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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 { EuiBasicTable, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { Pagination } from '@elastic/eui/src/components/basic_table/pagination_bar';
|
||||
import { StatusTag } from './location_status_tags';
|
||||
import { TagLabel } from './tag_label';
|
||||
import { AvailabilityLabel, LastCheckLabel, LocationLabel } from '../translations';
|
||||
|
||||
interface Props {
|
||||
allLocations: StatusTag[];
|
||||
}
|
||||
|
||||
export const formatAvailabilityValue = (val: number) => {
|
||||
const result = Math.round(val * 100) / 100;
|
||||
return result.toFixed(2);
|
||||
};
|
||||
|
||||
export const AvailabilityReporting: React.FC<Props> = ({ allLocations }) => {
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
|
||||
const cols = [
|
||||
{
|
||||
field: 'label',
|
||||
name: LocationLabel,
|
||||
truncateText: true,
|
||||
render: (val: string, item: StatusTag) => {
|
||||
return <TagLabel color={item.color} label={item.label} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'availability',
|
||||
name: AvailabilityLabel,
|
||||
align: 'right' as const,
|
||||
render: (val: number) => {
|
||||
return (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.availabilityLabelText"
|
||||
defaultMessage="{value} %"
|
||||
values={{ value: formatAvailabilityValue(val) }}
|
||||
description="A percentage value, like 23.5%"
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: LastCheckLabel,
|
||||
field: 'timestamp',
|
||||
align: 'right' as const,
|
||||
},
|
||||
];
|
||||
const pageSize = 5;
|
||||
|
||||
const pagination: Pagination = {
|
||||
pageIndex,
|
||||
pageSize,
|
||||
totalItemCount: allLocations.length,
|
||||
hidePerPageOptions: true,
|
||||
};
|
||||
|
||||
const onTableChange = ({ page }: any) => {
|
||||
setPageIndex(page.index);
|
||||
};
|
||||
|
||||
const paginationProps = allLocations.length > pageSize ? { pagination } : {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiBasicTable
|
||||
responsive={false}
|
||||
compressed={true}
|
||||
columns={cols}
|
||||
items={allLocations.slice(pageIndex * pageSize, pageIndex * pageSize + pageSize)}
|
||||
onChange={onTableChange}
|
||||
{...paginationProps}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 { AvailabilityReporting } from './availability_reporting';
|
||||
export { LocationStatusTags } from './location_status_tags';
|
||||
export { TagLabel } from './tag_label';
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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, { useContext } from 'react';
|
||||
import moment from 'moment';
|
||||
import styled from 'styled-components';
|
||||
import { UptimeThemeContext } from '../../../../contexts';
|
||||
import { MonitorLocation } from '../../../../../common/runtime_types';
|
||||
import { SHORT_TIMESPAN_LOCALE, SHORT_TS_LOCALE } from '../../../../../common/constants';
|
||||
import { AvailabilityReporting } from '../index';
|
||||
|
||||
// Set height so that it remains within panel, enough height to display 7 locations tags
|
||||
const TagContainer = styled.div`
|
||||
max-height: 246px;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
locations: MonitorLocation[];
|
||||
}
|
||||
|
||||
export interface StatusTag {
|
||||
label: string;
|
||||
timestamp: string;
|
||||
color: string;
|
||||
availability: number;
|
||||
}
|
||||
|
||||
export const LocationStatusTags = ({ locations }: Props) => {
|
||||
const {
|
||||
colors: { gray, danger },
|
||||
} = useContext(UptimeThemeContext);
|
||||
|
||||
const allLocations: StatusTag[] = [];
|
||||
const prevLocal: string = moment.locale() ?? 'en';
|
||||
|
||||
const shortLocale = moment.locale(SHORT_TS_LOCALE) === SHORT_TS_LOCALE;
|
||||
if (!shortLocale) {
|
||||
moment.defineLocale(SHORT_TS_LOCALE, SHORT_TIMESPAN_LOCALE);
|
||||
}
|
||||
|
||||
locations.forEach((item: MonitorLocation) => {
|
||||
allLocations.push({
|
||||
label: item.geo.name!,
|
||||
timestamp: moment(new Date(item.timestamp).valueOf()).fromNow(),
|
||||
color: item.summary.down === 0 ? gray : danger,
|
||||
availability: (item.up_history / (item.up_history + item.down_history)) * 100,
|
||||
});
|
||||
});
|
||||
|
||||
// Need to reset locale so it doesn't effect other parts of the app
|
||||
moment.locale(prevLocal);
|
||||
|
||||
// Sort lexicographically by label
|
||||
allLocations.sort((a, b) => {
|
||||
return a.label > b.label ? 1 : b.label > a.label ? -1 : 0;
|
||||
});
|
||||
|
||||
if (allLocations.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TagContainer>
|
||||
<AvailabilityReporting allLocations={allLocations} />
|
||||
</TagContainer>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 { EuiBadge, EuiText } from '@elastic/eui';
|
||||
|
||||
const BadgeItem = styled.div`
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
@media (max-width: 1042px) {
|
||||
display: inline-block;
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
color: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export const TagLabel: React.FC<Props> = ({ color, label }) => {
|
||||
return (
|
||||
<BadgeItem>
|
||||
<EuiBadge color={color}>
|
||||
<EuiText size="s">
|
||||
<h4>{label}</h4>
|
||||
</EuiText>
|
||||
</EuiBadge>
|
||||
</BadgeItem>
|
||||
);
|
||||
};
|
|
@ -4,9 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { MonitorStatusBarComponent } from './monitor_status_bar';
|
||||
export { MonitorStatusDetailsComponent } from './status_details';
|
||||
export { StatusByLocations } from './monitor_status_bar/status_by_location';
|
||||
export { StatusByLocations } from './status_bar/status_by_location';
|
||||
|
||||
export { MonitorStatusDetails } from './status_details_container';
|
||||
export { MonitorStatusBar } from './monitor_status_bar/status_bar_container';
|
||||
export { MonitorStatusBar } from './status_bar/status_bar';
|
||||
export { AvailabilityReporting } from './availability_reporting/availability_reporting';
|
|
@ -0,0 +1,280 @@
|
|||
// 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>
|
||||
<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,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Styled(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>
|
||||
<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": undefined,
|
||||
"name": "New York",
|
||||
},
|
||||
"summary": Object {
|
||||
"down": 0,
|
||||
"up": 4,
|
||||
},
|
||||
"timestamp": "2020-01-13T22:50:06.536Z",
|
||||
"up_history": 4,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Styled(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>
|
||||
`;
|
|
@ -6,59 +6,105 @@
|
|||
|
||||
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';
|
||||
import { LocationAvailability } from '../location_availability';
|
||||
import { MonitorLocations } from '../../../../../../common/runtime_types';
|
||||
import { LocationMissingWarning } from '../../location_map/location_missing';
|
||||
|
||||
class LocalStorageMock {
|
||||
store: Record<string, string>;
|
||||
constructor() {
|
||||
this.store = { 'xpack.uptime.detailPage.selectedView': 'list' };
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.store = {};
|
||||
}
|
||||
|
||||
getItem(key: string) {
|
||||
return this.store[key] || null;
|
||||
}
|
||||
|
||||
setItem(key: string, value: string) {
|
||||
this.store[key] = value.toString();
|
||||
}
|
||||
|
||||
removeItem(key: string) {
|
||||
delete this.store[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Note For shallow test, we need absolute time strings
|
||||
describe('LocationMap component', () => {
|
||||
describe('LocationAvailability component', () => {
|
||||
let monitorLocations: MonitorLocations;
|
||||
|
||||
beforeEach(() => {
|
||||
// @ts-ignore replacing a call to localStorage we use for monitor list size
|
||||
global.localStorage = new LocalStorageMock();
|
||||
|
||||
// @ts-ignore replacing a call to localStorage we use for monitor list size
|
||||
global.localStorage.setItem('xpack.uptime.detailPage.selectedView', 'list');
|
||||
|
||||
monitorLocations = {
|
||||
monitorId: 'wapo',
|
||||
up_history: 12,
|
||||
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,
|
||||
},
|
||||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'Unnamed-location' },
|
||||
timestamp: '2020-01-13T22:50:02.753Z',
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
it('renders correctly against snapshot', () => {
|
||||
const component = shallowWithIntl(<LocationMap monitorLocations={monitorLocations} />);
|
||||
const component = shallowWithIntl(<LocationAvailability monitorLocations={monitorLocations} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('shows warning if geo information is missing', () => {
|
||||
// @ts-ignore replacing a call to localStorage we use for monitor list size
|
||||
global.localStorage.setItem('xpack.uptime.detailPage.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(<LocationMap monitorLocations={monitorLocations} />);
|
||||
const component = shallowWithIntl(<LocationAvailability monitorLocations={monitorLocations} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
|
||||
const warningComponent = component.find(LocationMissingWarning);
|
||||
|
@ -68,20 +114,26 @@ describe('LocationMap component', () => {
|
|||
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(<LocationMap monitorLocations={monitorLocations} />);
|
||||
const component = shallowWithIntl(<LocationAvailability monitorLocations={monitorLocations} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
|
||||
const warningComponent = component.find(LocationMissingWarning);
|
||||
|
@ -91,16 +143,20 @@ describe('LocationMap component', () => {
|
|||
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(<LocationMap monitorLocations={monitorLocations} />);
|
||||
const component = shallowWithIntl(<LocationAvailability monitorLocations={monitorLocations} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -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, { useState } 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 { MonitoringFrom } from '../translations';
|
||||
import { ToggleViewBtn } from './toggle_view_btn';
|
||||
|
||||
const EuiFlexItemTags = styled(EuiFlexItem)`
|
||||
width: 350px;
|
||||
@media (max-width: 1042px) {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
interface LocationMapProps {
|
||||
monitorLocations: MonitorLocations;
|
||||
}
|
||||
|
||||
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>
|
||||
</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>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiErrorBoundary>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiButtonGroup } from '@elastic/eui';
|
||||
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' }}
|
||||
/>
|
||||
</ToggleViewButtons>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { 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 };
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
// 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>
|
||||
`;
|
|
@ -4,6 +4,7 @@ exports[`LocationMissingWarning component renders correctly against snapshot 1`]
|
|||
.c0 {
|
||||
margin-left: auto;
|
||||
margin-bottom: 3px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
<div
|
|
@ -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 React from 'react';
|
||||
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
|
||||
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();
|
||||
});
|
||||
});
|
|
@ -14,6 +14,7 @@ export const mockDownPointsLayer = {
|
|||
__featureCollection: {
|
||||
features: [
|
||||
{
|
||||
id: 'Asia',
|
||||
type: 'feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
|
@ -21,6 +22,7 @@ export const mockDownPointsLayer = {
|
|||
},
|
||||
},
|
||||
{
|
||||
id: 'APJ',
|
||||
type: 'feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
|
@ -28,6 +30,7 @@ export const mockDownPointsLayer = {
|
|||
},
|
||||
},
|
||||
{
|
||||
id: 'Canada',
|
||||
type: 'feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
|
@ -79,6 +82,7 @@ export const mockUpPointsLayer = {
|
|||
__featureCollection: {
|
||||
features: [
|
||||
{
|
||||
id: 'US-EAST',
|
||||
type: 'feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
|
@ -86,6 +90,7 @@ export const mockUpPointsLayer = {
|
|||
},
|
||||
},
|
||||
{
|
||||
id: 'US-WEST',
|
||||
type: 'feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
|
@ -93,6 +98,7 @@ export const mockUpPointsLayer = {
|
|||
},
|
||||
},
|
||||
{
|
||||
id: 'Europe',
|
||||
type: 'feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
|
@ -7,7 +7,7 @@
|
|||
import { getLayerList } from '../map_config';
|
||||
import { mockLayerList } from './__mocks__/mock';
|
||||
import { LocationPoint } from '../embedded_map';
|
||||
import { UptimeAppColors } from '../../../../../uptime_app';
|
||||
import { UptimeAppColors } from '../../../../../../uptime_app';
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
return {
|
||||
|
@ -22,14 +22,14 @@ describe('map_config', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
upPoints = [
|
||||
{ lat: '52.487239', lon: '13.399262' },
|
||||
{ lat: '55.487239', lon: '13.399262' },
|
||||
{ lat: '54.487239', lon: '14.399262' },
|
||||
{ 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 = [
|
||||
{ lat: '52.487239', lon: '13.399262' },
|
||||
{ lat: '55.487239', lon: '13.399262' },
|
||||
{ lat: '54.487239', lon: '14.399262' },
|
||||
{ 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',
|
|
@ -7,28 +7,35 @@
|
|||
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';
|
||||
} from '../../../../../../../maps/public/embeddable';
|
||||
import * as i18n from './translations';
|
||||
import { Location } from '../../../../../common/runtime_types';
|
||||
import { GeoPoint } from '../../../../../../common/runtime_types';
|
||||
import { getLayerList } from './map_config';
|
||||
import { UptimeThemeContext, UptimeStartupPluginsContext } from '../../../../contexts';
|
||||
import { UptimeThemeContext, UptimeStartupPluginsContext } from '../../../../../contexts';
|
||||
|
||||
import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../../maps/public';
|
||||
import { MapToolTipComponent } from './map_tool_tip';
|
||||
import {
|
||||
isErrorEmbeddable,
|
||||
ViewMode,
|
||||
ErrorEmbeddable,
|
||||
} from '../../../../../../../../src/plugins/embeddable/public';
|
||||
import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../maps/public';
|
||||
} from '../../../../../../../../../src/plugins/embeddable/public';
|
||||
import {
|
||||
RenderTooltipContentParams,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../../../maps/public/classes/tooltips/tooltip_property';
|
||||
|
||||
export interface EmbeddedMapProps {
|
||||
upPoints: LocationPoint[];
|
||||
downPoints: LocationPoint[];
|
||||
}
|
||||
|
||||
export type LocationPoint = Required<Location>;
|
||||
export type LocationPoint = Required<GeoPoint>;
|
||||
|
||||
const EmbeddedPanel = styled.div`
|
||||
z-index: auto;
|
||||
|
@ -58,6 +65,8 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp
|
|||
}
|
||||
const factory: any = embeddablePlugin.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE);
|
||||
|
||||
const portalNode = React.useMemo(() => createPortalNode(), []);
|
||||
|
||||
const input: MapEmbeddableInput = {
|
||||
id: uuid.v4(),
|
||||
filters: [],
|
||||
|
@ -77,12 +86,38 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp
|
|||
zoom: 0,
|
||||
},
|
||||
disableInteractive: true,
|
||||
disableTooltipControl: 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) {
|
||||
|
@ -94,11 +129,13 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp
|
|||
});
|
||||
|
||||
if (embeddableObject && !isErrorEmbeddable(embeddableObject)) {
|
||||
embeddableObject.setLayerList(getLayerList(upPoints, downPoints, colors));
|
||||
embeddableObject.setRenderTooltipContent(renderTooltipContent);
|
||||
await embeddableObject.setLayerList(getLayerList(upPoints, downPoints, colors));
|
||||
}
|
||||
|
||||
setEmbeddable(embeddableObject);
|
||||
}
|
||||
|
||||
setupEmbeddable();
|
||||
|
||||
// we want this effect to execute exactly once after the component mounts
|
||||
|
@ -126,6 +163,9 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp
|
|||
className="embPanel__content"
|
||||
ref={embeddableRoot}
|
||||
/>
|
||||
<InPortal node={portalNode}>
|
||||
<MapToolTipComponent />
|
||||
</InPortal>
|
||||
</EmbeddedPanel>
|
||||
);
|
||||
});
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import lowPolyLayerFeatures from './low_poly_layer.json';
|
||||
import { LocationPoint } from './embedded_map';
|
||||
import { UptimeAppColors } from '../../../../uptime_app';
|
||||
import { UptimeAppColors } from '../../../../../uptime_app';
|
||||
|
||||
/**
|
||||
* Returns `Source/Destination Point-to-point` Map LayerList configuration, with a source,
|
||||
|
@ -70,9 +70,10 @@ export const getLowPolyLayer = () => {
|
|||
export const getDownPointsLayer = (downPoints: LocationPoint[], dangerColor: string) => {
|
||||
const features = downPoints?.map((point) => ({
|
||||
type: 'feature',
|
||||
id: point.name,
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [+point.lon, +point.lat],
|
||||
coordinates: [+point.location.lon, +point.location.lat],
|
||||
},
|
||||
}));
|
||||
return {
|
||||
|
@ -122,9 +123,10 @@ export const getDownPointsLayer = (downPoints: LocationPoint[], dangerColor: str
|
|||
export const getUpPointsLayer = (upPoints: LocationPoint[]) => {
|
||||
const features = upPoints?.map((point) => ({
|
||||
type: 'feature',
|
||||
id: point.name,
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [+point.lon, +point.lat],
|
||||
coordinates: [+point.location.lon, +point.location.lat],
|
||||
},
|
||||
}));
|
||||
return {
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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 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 { formatAvailabilityValue } from '../../availability_reporting/availability_reporting';
|
||||
import { LastCheckLabel } from '../../translations';
|
||||
import {
|
||||
RenderTooltipContentParams,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../../../maps/public/classes/tooltips/tooltip_property';
|
||||
|
||||
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} />
|
||||
) : (
|
||||
<TagLabel label={locationName} color={danger} />
|
||||
)}
|
||||
</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);
|
|
@ -5,4 +5,4 @@
|
|||
*/
|
||||
|
||||
export * from './location_map';
|
||||
export * from './location_status_tags';
|
||||
export * from '../availability_reporting/location_status_tags';
|
|
@ -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 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>
|
||||
);
|
||||
};
|
|
@ -16,11 +16,12 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { LocationLink } from '../../common/location_link';
|
||||
import { LocationLink } from '../../../common/location_link';
|
||||
|
||||
const EuiPopoverRight = styled(EuiFlexItem)`
|
||||
margin-left: auto;
|
||||
margin-bottom: 3px;
|
||||
margin-right: 5px;
|
||||
`;
|
||||
|
||||
export const LocationMissingWarning = () => {
|
|
@ -5,5 +5,4 @@
|
|||
*/
|
||||
|
||||
export { MonitorSSLCertificate } from './ssl_certificate';
|
||||
export { MonitorStatusBarComponent } from './status_bar';
|
||||
export { MonitorStatusBar } from './status_bar_container';
|
||||
export { MonitorStatusBar } from './status_bar';
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 { Link } from 'react-router-dom';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { Tls } from '../../../../../common/runtime_types';
|
||||
import { CERTIFICATES_ROUTE } from '../../../../../common/constants';
|
||||
import { MonListDescription, MonListTitle } from './status_bar';
|
||||
import { CertStatusColumn } from '../../../overview/monitor_list/cert_status_column';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* TLS information coming from monitor in ES heartbeat index
|
||||
*/
|
||||
tls: Tls | undefined;
|
||||
}
|
||||
|
||||
export const MonitorSSLCertificate = ({ tls }: Props) => {
|
||||
return tls?.not_after ? (
|
||||
<>
|
||||
<MonListTitle>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorStatusBar.sslCertificate.title"
|
||||
defaultMessage="TLS Certificate"
|
||||
/>
|
||||
</MonListTitle>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
<MonListDescription>
|
||||
<Link to={CERTIFICATES_ROUTE} className="eui-displayInline">
|
||||
<CertStatusColumn cert={tls} boldStyle={true} />
|
||||
</Link>
|
||||
</MonListDescription>
|
||||
</>
|
||||
) : null;
|
||||
};
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiLink,
|
||||
EuiIcon,
|
||||
EuiSpacer,
|
||||
EuiDescriptionList,
|
||||
EuiDescriptionListTitle,
|
||||
EuiDescriptionListDescription,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { MonitorSSLCertificate } from './ssl_certificate';
|
||||
import * as labels from '../translations';
|
||||
import { StatusByLocations } from './status_by_location';
|
||||
import { useStatusBar } from './use_status_bar';
|
||||
import { MonitorIDLabel, OverallAvailability } from '../translations';
|
||||
import { URL_LABEL } from '../../../common/translations';
|
||||
import { MonitorLocations } from '../../../../../common/runtime_types/monitor';
|
||||
import { formatAvailabilityValue } from '../availability_reporting/availability_reporting';
|
||||
|
||||
export const MonListTitle = styled(EuiDescriptionListTitle)`
|
||||
&&& {
|
||||
width: 35%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MonListDescription = styled(EuiDescriptionListDescription)`
|
||||
&&& {
|
||||
width: 65%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MonitorStatusBar: React.FC = () => {
|
||||
const { monitorId, monitorStatus, monitorLocations = {} } = useStatusBar();
|
||||
|
||||
const { locations, up_history: ups, down_history: downs } = monitorLocations as MonitorLocations;
|
||||
|
||||
const full = monitorStatus?.url?.full ?? '';
|
||||
|
||||
const availability = (ups === 0 && downs === 0) || !ups ? 0 : (ups / (ups + downs)) * 100;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<StatusByLocations locations={locations ?? []} />
|
||||
</div>
|
||||
<EuiSpacer />
|
||||
<EuiDescriptionList
|
||||
type="column"
|
||||
compressed={true}
|
||||
textStyle="reverse"
|
||||
style={{ maxWidth: '450px' }}
|
||||
>
|
||||
<MonListTitle>{OverallAvailability}</MonListTitle>
|
||||
<MonListDescription data-test-subj="uptimeOverallAvailability">
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.availabilityLabelText"
|
||||
defaultMessage="{value} %"
|
||||
values={{ value: formatAvailabilityValue(availability) }}
|
||||
description="A percentage value, like 23.5 %"
|
||||
/>
|
||||
</MonListDescription>
|
||||
<MonListTitle>{URL_LABEL}</MonListTitle>
|
||||
<MonListDescription>
|
||||
<EuiLink aria-label={labels.monitorUrlLinkAriaLabel} href={full} target="_blank">
|
||||
{full} <EuiIcon type={'popout'} size="s" />
|
||||
</EuiLink>
|
||||
</MonListDescription>
|
||||
<MonListTitle>{MonitorIDLabel}</MonListTitle>
|
||||
<MonListDescription data-test-subj="monitor-page-title">{monitorId}</MonListDescription>
|
||||
<MonitorSSLCertificate tls={monitorStatus?.tls} />
|
||||
</EuiDescriptionList>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -4,24 +4,33 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { monitorLocationsSelector, monitorStatusSelector } from '../../../../state/selectors';
|
||||
import { MonitorStatusBarComponent } from './index';
|
||||
import { getMonitorStatusAction } from '../../../../state/actions';
|
||||
import { useGetUrlParams } from '../../../../hooks';
|
||||
import { UptimeRefreshContext } from '../../../../contexts';
|
||||
import { MonitorIdParam } from '../../../../../common/types';
|
||||
import { useGetUrlParams, useMonitorId } from '../../../../hooks';
|
||||
import { monitorLocationsSelector, monitorStatusSelector } from '../../../../state/selectors';
|
||||
import { AppState } from '../../../../state';
|
||||
import { getMonitorStatusAction } from '../../../../state/actions';
|
||||
import { Ping } from '../../../../../common/runtime_types/ping';
|
||||
import { MonitorLocations } from '../../../../../common/runtime_types/monitor';
|
||||
|
||||
export const MonitorStatusBar: React.FC<MonitorIdParam> = ({ monitorId }) => {
|
||||
interface MonitorStatusBarProps {
|
||||
monitorId: string;
|
||||
monitorStatus: Ping | null;
|
||||
monitorLocations?: MonitorLocations;
|
||||
}
|
||||
|
||||
export const useStatusBar = (): MonitorStatusBarProps => {
|
||||
const { lastRefresh } = useContext(UptimeRefreshContext);
|
||||
|
||||
const { dateRangeStart: dateStart, dateRangeEnd: dateEnd } = useGetUrlParams();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const monitorId = useMonitorId();
|
||||
|
||||
const monitorStatus = useSelector(monitorStatusSelector);
|
||||
|
||||
const monitorLocations = useSelector((state: AppState) =>
|
||||
monitorLocationsSelector(state, monitorId)
|
||||
);
|
||||
|
@ -30,11 +39,5 @@ export const MonitorStatusBar: React.FC<MonitorIdParam> = ({ monitorId }) => {
|
|||
dispatch(getMonitorStatusAction({ dateStart, dateEnd, monitorId }));
|
||||
}, [monitorId, dateStart, dateEnd, lastRefresh, dispatch]);
|
||||
|
||||
return (
|
||||
<MonitorStatusBarComponent
|
||||
monitorId={monitorId}
|
||||
monitorStatus={monitorStatus}
|
||||
monitorLocations={monitorLocations!}
|
||||
/>
|
||||
);
|
||||
return { monitorStatus, monitorLocations, monitorId };
|
||||
};
|
|
@ -7,31 +7,24 @@
|
|||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { LocationMap } from '../location_map';
|
||||
import { LocationAvailability } from './location_availability/location_availability';
|
||||
import { UptimeRefreshContext } from '../../../contexts';
|
||||
import { MonitorLocations } from '../../../../common/runtime_types';
|
||||
import { MonitorStatusBar } from './monitor_status_bar';
|
||||
import { MonitorStatusBar } from './status_bar';
|
||||
|
||||
interface MonitorStatusDetailsProps {
|
||||
monitorId: string;
|
||||
monitorLocations: MonitorLocations;
|
||||
}
|
||||
|
||||
const WrapFlexItem = styled(EuiFlexItem)`
|
||||
&&& {
|
||||
@media (max-width: 768px) {
|
||||
width: 100%;
|
||||
}
|
||||
@media (max-width: 1042px) {
|
||||
flex-basis: 520px;
|
||||
@media (max-width: 800px) {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const MonitorStatusDetailsComponent = ({
|
||||
monitorId,
|
||||
monitorLocations,
|
||||
}: MonitorStatusDetailsProps) => {
|
||||
export const MonitorStatusDetailsComponent = ({ monitorLocations }: MonitorStatusDetailsProps) => {
|
||||
const { refreshApp } = useContext(UptimeRefreshContext);
|
||||
|
||||
const [isTabActive] = useState(document.visibilityState);
|
||||
|
@ -56,12 +49,12 @@ export const MonitorStatusDetailsComponent = ({
|
|||
|
||||
return (
|
||||
<EuiPanel>
|
||||
<EuiFlexGroup gutterSize="l" wrap responsive={true}>
|
||||
<EuiFlexItem grow={true}>
|
||||
<MonitorStatusBar monitorId={monitorId} />
|
||||
<EuiFlexGroup gutterSize="l" wrap={true} responsive={true}>
|
||||
<EuiFlexItem grow={1}>
|
||||
<MonitorStatusBar />
|
||||
</EuiFlexItem>
|
||||
<WrapFlexItem grow={false}>
|
||||
<LocationMap monitorLocations={monitorLocations} />
|
||||
<WrapFlexItem grow={1}>
|
||||
<LocationAvailability monitorLocations={monitorLocations} />
|
||||
</WrapFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
|
@ -28,7 +28,5 @@ export const MonitorStatusDetails: React.FC<MonitorIdParam> = ({ monitorId }) =>
|
|||
dispatch(getMonitorLocationsAction({ dateStart, dateEnd, monitorId }));
|
||||
}, [monitorId, dateStart, dateEnd, lastRefresh, dispatch]);
|
||||
|
||||
return (
|
||||
<MonitorStatusDetailsComponent monitorId={monitorId} monitorLocations={monitorLocations!} />
|
||||
);
|
||||
return <MonitorStatusDetailsComponent monitorLocations={monitorLocations!} />;
|
||||
};
|
|
@ -48,3 +48,56 @@ export const timestampFromNowTextAriaLabel = i18n.translate(
|
|||
export const loadingMessage = i18n.translate('xpack.uptime.monitorStatusBar.loadingMessage', {
|
||||
defaultMessage: 'Loading…',
|
||||
});
|
||||
|
||||
export const MonitorIDLabel = i18n.translate('xpack.uptime.monitorStatusBar.monitor.id', {
|
||||
defaultMessage: 'Monitor ID',
|
||||
});
|
||||
|
||||
export const OverallAvailability = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.monitor.availability',
|
||||
{
|
||||
defaultMessage: 'Overall availability',
|
||||
}
|
||||
);
|
||||
|
||||
export const MonitoringFrom = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.monitor.monitoringFrom',
|
||||
{
|
||||
defaultMessage: 'Monitoring from',
|
||||
}
|
||||
);
|
||||
|
||||
export const ChangeToMapView = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.monitor.monitoringFrom.listToMap',
|
||||
{
|
||||
defaultMessage: 'Change to map view to check availability by location.',
|
||||
}
|
||||
);
|
||||
|
||||
export const ChangeToListView = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.monitor.monitoringFrom.MapToList',
|
||||
{
|
||||
defaultMessage: 'Change to list view to check availability by location.',
|
||||
}
|
||||
);
|
||||
|
||||
export const LocationLabel = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.monitor.availabilityReport.location',
|
||||
{
|
||||
defaultMessage: 'Location',
|
||||
}
|
||||
);
|
||||
|
||||
export const AvailabilityLabel = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.monitor.availabilityReport.availability',
|
||||
{
|
||||
defaultMessage: 'Availability',
|
||||
}
|
||||
);
|
||||
|
||||
export const LastCheckLabel = i18n.translate(
|
||||
'xpack.uptime.monitorStatusBar.monitor.availabilityReport.lastCheck',
|
||||
{
|
||||
defaultMessage: 'Last check',
|
||||
}
|
||||
);
|
|
@ -8,13 +8,14 @@ import React from 'react';
|
|||
import moment from 'moment';
|
||||
import styled from 'styled-components';
|
||||
import { EuiIcon, EuiText, EuiToolTip } from '@elastic/eui';
|
||||
import { Cert } from '../../../../common/runtime_types';
|
||||
import { Cert, Tls } from '../../../../common/runtime_types';
|
||||
import { useCertStatus } from '../../../hooks';
|
||||
import { EXPIRED, EXPIRES_SOON } from '../../certificates/translations';
|
||||
import { EXPIRED, EXPIRES, EXPIRES_SOON } from '../../certificates/translations';
|
||||
import { CERT_STATUS } from '../../../../common/constants';
|
||||
|
||||
interface Props {
|
||||
cert: Cert;
|
||||
cert: Cert | Tls;
|
||||
boldStyle?: boolean;
|
||||
}
|
||||
|
||||
const Span = styled.span`
|
||||
|
@ -22,7 +23,15 @@ const Span = styled.span`
|
|||
vertical-align: middle;
|
||||
`;
|
||||
|
||||
export const CertStatusColumn: React.FC<Props> = ({ cert }) => {
|
||||
const H4Text = styled.h4`
|
||||
&&& {
|
||||
margin: 0 0 0 4px;
|
||||
display: inline-block;
|
||||
color: inherit;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CertStatusColumn: React.FC<Props> = ({ cert, boldStyle = false }) => {
|
||||
const certStatus = useCertStatus(cert?.not_after);
|
||||
|
||||
const relativeDate = moment(cert?.not_after).fromNow();
|
||||
|
@ -32,9 +41,15 @@ export const CertStatusColumn: React.FC<Props> = ({ cert }) => {
|
|||
<EuiToolTip content={moment(cert?.not_after).format('L LT')}>
|
||||
<EuiText size="s">
|
||||
<EuiIcon color={color} type="lock" size="s" />
|
||||
<Span>
|
||||
{text} {relativeDate}
|
||||
</Span>
|
||||
{boldStyle ? (
|
||||
<H4Text>
|
||||
{text} {relativeDate}
|
||||
</H4Text>
|
||||
) : (
|
||||
<Span>
|
||||
{text} {relativeDate}
|
||||
</Span>
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
);
|
||||
|
@ -47,5 +62,5 @@ export const CertStatusColumn: React.FC<Props> = ({ cert }) => {
|
|||
return <CertStatus color="danger" text={EXPIRED} />;
|
||||
}
|
||||
|
||||
return certStatus ? <CertStatus color="success" text={'Expires'} /> : <span>--</span>;
|
||||
return certStatus ? <CertStatus color="success" text={EXPIRES} /> : <span>--</span>;
|
||||
};
|
||||
|
|
|
@ -30,6 +30,7 @@ import { MonitorListProps } from './monitor_list_container';
|
|||
import { MonitorList } from '../../../state/reducers/monitor_list';
|
||||
import { CertStatusColumn } from './cert_status_column';
|
||||
import { MonitorListHeader } from './monitor_list_header';
|
||||
import { URL_LABEL } from '../../common/translations';
|
||||
|
||||
interface Props extends MonitorListProps {
|
||||
pageSize: number;
|
||||
|
@ -106,7 +107,7 @@ export const MonitorListComponent: React.FC<Props> = ({
|
|||
{
|
||||
align: 'left' as const,
|
||||
field: 'state.url.full',
|
||||
name: labels.URL,
|
||||
name: URL_LABEL,
|
||||
render: (url: string, summary: MonitorSummary) => (
|
||||
<TruncatedEuiLink href={url} target="_blank" color="text">
|
||||
{url} <EuiIcon size="s" type="popout" color="subbdued" />
|
||||
|
|
|
@ -62,10 +62,6 @@ export const NO_DATA_MESSAGE = i18n.translate('xpack.uptime.monitorList.noItemMe
|
|||
description: 'This message is shown if the monitors table is rendered but has no items.',
|
||||
});
|
||||
|
||||
export const URL = i18n.translate('xpack.uptime.monitorList.table.url.name', {
|
||||
defaultMessage: 'Url',
|
||||
});
|
||||
|
||||
export const UP = i18n.translate('xpack.uptime.monitorList.statusColumn.upLabel', {
|
||||
defaultMessage: 'Up',
|
||||
});
|
||||
|
|
|
@ -2,21 +2,25 @@
|
|||
|
||||
exports[`PageHeader shallow renders extra links: page_header_with_extra_links 1`] = `
|
||||
Array [
|
||||
@media only screen and (max-width:1024px) and (min-width:868px) {
|
||||
.c0.c0.c0 .euiSuperDatePicker__flexWrapper {
|
||||
.c0 {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1024px) and (min-width:868px) {
|
||||
.c1.c1.c1 .euiSuperDatePicker__flexWrapper {
|
||||
width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:880px) {
|
||||
.c0.c0.c0 {
|
||||
.c1.c1.c1 {
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex-grow: 1;
|
||||
-ms-flex-positive: 1;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.c0.c0.c0 .euiSuperDatePicker__flexWrapper {
|
||||
.c1.c1.c1 .euiSuperDatePicker__flexWrapper {
|
||||
width: calc(100% + 8px);
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +32,7 @@ Array [
|
|||
class="euiFlexItem"
|
||||
>
|
||||
<h1
|
||||
class="euiTitle euiTitle--medium"
|
||||
class="c0 euiTitle euiTitle--medium"
|
||||
>
|
||||
TestingHeading
|
||||
</h1>
|
||||
|
@ -126,7 +130,7 @@ Array [
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero c0"
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero c1"
|
||||
style="flex-basis:485px"
|
||||
>
|
||||
<div
|
||||
|
@ -238,21 +242,25 @@ Array [
|
|||
|
||||
exports[`PageHeader shallow renders with the date picker: page_header_with_date_picker 1`] = `
|
||||
Array [
|
||||
@media only screen and (max-width:1024px) and (min-width:868px) {
|
||||
.c0.c0.c0 .euiSuperDatePicker__flexWrapper {
|
||||
.c0 {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media only screen and (max-width:1024px) and (min-width:868px) {
|
||||
.c1.c1.c1 .euiSuperDatePicker__flexWrapper {
|
||||
width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width:880px) {
|
||||
.c0.c0.c0 {
|
||||
.c1.c1.c1 {
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex-grow: 1;
|
||||
-ms-flex-positive: 1;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.c0.c0.c0 .euiSuperDatePicker__flexWrapper {
|
||||
.c1.c1.c1 .euiSuperDatePicker__flexWrapper {
|
||||
width: calc(100% + 8px);
|
||||
}
|
||||
}
|
||||
|
@ -264,7 +272,7 @@ Array [
|
|||
class="euiFlexItem"
|
||||
>
|
||||
<h1
|
||||
class="euiTitle euiTitle--medium"
|
||||
class="c0 euiTitle euiTitle--medium"
|
||||
>
|
||||
TestingHeading
|
||||
</h1>
|
||||
|
@ -273,7 +281,7 @@ Array [
|
|||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
/>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero c0"
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero c1"
|
||||
style="flex-basis:485px"
|
||||
>
|
||||
<div
|
||||
|
@ -385,14 +393,18 @@ Array [
|
|||
|
||||
exports[`PageHeader shallow renders without the date picker: page_header_no_date_picker 1`] = `
|
||||
Array [
|
||||
<div
|
||||
.c0 {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--wrap"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<h1
|
||||
class="euiTitle euiTitle--medium"
|
||||
class="c0 euiTitle euiTitle--medium"
|
||||
>
|
||||
TestingHeading
|
||||
</h1>
|
||||
|
|
|
@ -19,6 +19,7 @@ interface PageHeaderProps {
|
|||
extraLinks?: boolean;
|
||||
datePicker?: boolean;
|
||||
}
|
||||
|
||||
const SETTINGS_LINK_TEXT = i18n.translate('xpack.uptime.page_header.settingsLink', {
|
||||
defaultMessage: 'Settings',
|
||||
});
|
||||
|
@ -43,6 +44,10 @@ const StyledPicker = styled(EuiFlexItem)`
|
|||
}
|
||||
`;
|
||||
|
||||
const H1Text = styled.h1`
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
export const PageHeader = React.memo(
|
||||
({ headingText, extraLinks = false, datePicker = true }: PageHeaderProps) => {
|
||||
const DatePickerComponent = () =>
|
||||
|
@ -89,7 +94,7 @@ export const PageHeader = React.memo(
|
|||
>
|
||||
<EuiFlexItem grow={true}>
|
||||
<EuiTitle>
|
||||
<h1>{headingText}</h1>
|
||||
<H1Text>{headingText}</H1Text>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{extraLinkComponents}</EuiFlexItem>
|
||||
|
|
|
@ -32,7 +32,7 @@ export const getMonitorLocations: UMElasticsearchQueryFn<
|
|||
bool: {
|
||||
filter: [
|
||||
{
|
||||
match: {
|
||||
term: {
|
||||
'monitor.id': monitorId,
|
||||
},
|
||||
},
|
||||
|
@ -70,6 +70,18 @@ export const getMonitorLocations: UMElasticsearchQueryFn<
|
|||
_source: ['monitor', 'summary', 'observer', '@timestamp'],
|
||||
},
|
||||
},
|
||||
down_history: {
|
||||
sum: {
|
||||
field: 'summary.down',
|
||||
missing: 0,
|
||||
},
|
||||
},
|
||||
up_history: {
|
||||
sum: {
|
||||
field: 'summary.up',
|
||||
missing: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -99,10 +111,17 @@ export const getMonitorLocations: UMElasticsearchQueryFn<
|
|||
}
|
||||
};
|
||||
|
||||
let totalUps = 0;
|
||||
let totalDowns = 0;
|
||||
|
||||
const monLocs: MonitorLocation[] = [];
|
||||
locations.forEach((loc: any) => {
|
||||
const mostRecentLocation = loc.most_recent.hits.hits[0]._source;
|
||||
locations.forEach(({ most_recent: mostRecent, up_history, down_history }: any) => {
|
||||
const mostRecentLocation = mostRecent.hits.hits[0]._source;
|
||||
totalUps += up_history.value;
|
||||
totalDowns += down_history.value;
|
||||
const location: MonitorLocation = {
|
||||
up_history: up_history.value ?? 0,
|
||||
down_history: down_history.value ?? 0,
|
||||
summary: mostRecentLocation?.summary,
|
||||
geo: getGeo(mostRecentLocation?.observer?.geo),
|
||||
timestamp: mostRecentLocation['@timestamp'],
|
||||
|
@ -113,5 +132,7 @@ export const getMonitorLocations: UMElasticsearchQueryFn<
|
|||
return {
|
||||
monitorId,
|
||||
locations: monLocs,
|
||||
up_history: totalUps,
|
||||
down_history: totalDowns,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -65,7 +65,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('detail page', async () => {
|
||||
await uptimeService.navigation.goToMonitor(A11Y_TEST_MONITOR_ID);
|
||||
await uptimeService.monitor.locationMapIsRendered();
|
||||
await uptimeService.monitor.displayOverallAvailability('0.00 %');
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
||||
|
|
|
@ -11,41 +11,58 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
||||
const { uptime: uptimePage } = getPageObjects(['uptime']);
|
||||
const uptime = getService('uptime');
|
||||
const es = getService('legacyEs');
|
||||
|
||||
const monitor = () => uptime.monitor;
|
||||
const MONITOR_ID = 'location-testing-id';
|
||||
|
||||
const LessAvailMonitor = 'less-availability-monitor';
|
||||
|
||||
const addMonitorWithNoLocation = async () => {
|
||||
/**
|
||||
* This mogrify function will strip the documents of their location
|
||||
* data (but preserve their location name), which is necessary for
|
||||
* this test to work as desired.
|
||||
* @param d current document
|
||||
*/
|
||||
const mogrifyNoLocation = (d: any) => {
|
||||
if (d.observer?.geo?.location) {
|
||||
d.observer.geo.location = undefined;
|
||||
}
|
||||
return d;
|
||||
};
|
||||
await makeChecksWithStatus(es, MONITOR_ID, 5, 2, 10000, {}, 'up', mogrifyNoLocation);
|
||||
};
|
||||
|
||||
const addLessAvailMonitor = async () => {
|
||||
await makeChecksWithStatus(es, LessAvailMonitor, 5, 2, 10000, {}, 'up');
|
||||
await makeChecksWithStatus(es, LessAvailMonitor, 5, 2, 10000, {}, 'down');
|
||||
};
|
||||
|
||||
describe('Observer location', () => {
|
||||
const start = moment().subtract('15', 'm').toISOString();
|
||||
const end = moment().toISOString();
|
||||
|
||||
const MONITOR_ID = 'location-testing-id';
|
||||
before(async () => {
|
||||
await addMonitorWithNoLocation();
|
||||
await addLessAvailMonitor();
|
||||
await uptime.navigation.goToUptime();
|
||||
await uptimePage.goToRoot(true);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
/**
|
||||
* This mogrify function will strip the documents of their location
|
||||
* data (but preserve their location name), which is necessary for
|
||||
* this test to work as desired.
|
||||
* @param d current document
|
||||
*/
|
||||
const mogrifyNoLocation = (d: any) => {
|
||||
if (d.observer?.geo?.location) {
|
||||
d.observer.geo.location = undefined;
|
||||
}
|
||||
return d;
|
||||
};
|
||||
await makeChecksWithStatus(
|
||||
getService('legacyEs'),
|
||||
MONITOR_ID,
|
||||
5,
|
||||
2,
|
||||
10000,
|
||||
{},
|
||||
'up',
|
||||
mogrifyNoLocation
|
||||
);
|
||||
await uptime.navigation.goToUptime();
|
||||
await addMonitorWithNoLocation();
|
||||
await addLessAvailMonitor();
|
||||
if (!(await uptime.navigation.isOnDetailsPage()))
|
||||
await uptimePage.loadDataAndGoToMonitorPage(start, end, MONITOR_ID);
|
||||
});
|
||||
|
||||
await uptimePage.loadDataAndGoToMonitorPage(start, end, MONITOR_ID);
|
||||
it('displays the overall availability', async () => {
|
||||
await monitor().displayOverallAvailability('100.00 %');
|
||||
});
|
||||
|
||||
it('can change the view to map', async () => {
|
||||
await monitor().toggleToMapView();
|
||||
});
|
||||
|
||||
it('renders the location panel and canvas', async () => {
|
||||
|
@ -55,5 +72,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
it('renders the location missing popover when monitor has location name, but no geo data', async () => {
|
||||
await monitor().locationMissingExists();
|
||||
});
|
||||
|
||||
it('displays less monitor availability', async () => {
|
||||
await uptime.navigation.goToHomeViaBreadCrumb();
|
||||
await uptimePage.loadDataAndGoToMonitorPage(start, end, LessAvailMonitor);
|
||||
await monitor().displayOverallAvailability('50.00 %');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -58,13 +58,13 @@ export function UptimeCommonProvider({ getService }: FtrProviderContext) {
|
|||
'[data-test-subj="xpack.uptime.filterBar.filterStatusUp"]'
|
||||
);
|
||||
if (await upFilter.elementHasClass('euiFilterButton-hasActiveFilters')) {
|
||||
this.setStatusFilterUp();
|
||||
await this.setStatusFilterUp();
|
||||
}
|
||||
const downFilter = await find.byCssSelector(
|
||||
'[data-test-subj="xpack.uptime.filterBar.filterStatusDown"]'
|
||||
);
|
||||
if (await downFilter.elementHasClass('euiFilterButton-hasActiveFilters')) {
|
||||
this.setStatusFilterDown();
|
||||
await this.setStatusFilterDown();
|
||||
}
|
||||
},
|
||||
async selectFilterItem(filterType: string, option: string) {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect/expect.js';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export function UptimeMonitorProvider({ getService }: FtrProviderContext) {
|
||||
|
@ -17,6 +18,13 @@ export function UptimeMonitorProvider({ getService }: FtrProviderContext) {
|
|||
timeout: 3000,
|
||||
});
|
||||
},
|
||||
async displayOverallAvailability(availabilityVal: string) {
|
||||
return retry.tryForTime(60 * 1000, async () => {
|
||||
await testSubjects.existOrFail('uptimeOverallAvailability');
|
||||
const availability = await testSubjects.getVisibleText('uptimeOverallAvailability');
|
||||
expect(availability).to.be(availabilityVal);
|
||||
});
|
||||
},
|
||||
async locationMapIsRendered() {
|
||||
return retry.tryForTime(15000, async () => {
|
||||
await testSubjects.existOrFail('xpack.uptime.locationMap.embeddedPanel', {
|
||||
|
@ -45,5 +53,8 @@ export function UptimeMonitorProvider({ getService }: FtrProviderContext) {
|
|||
);
|
||||
});
|
||||
},
|
||||
async toggleToMapView() {
|
||||
await testSubjects.click('uptimeMonitorToggleMapBtn');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv
|
|||
},
|
||||
|
||||
goToMonitor: async (monitorId: string) => {
|
||||
// only go to monitor page if not already there
|
||||
if (!(await testSubjects.exists('uptimeMonitorPage', { timeout: 0 }))) {
|
||||
await testSubjects.click(`monitor-page-link-${monitorId}`);
|
||||
await testSubjects.existOrFail('uptimeMonitorPage', {
|
||||
|
@ -80,5 +81,13 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv
|
|||
await PageObjects.timePicker.setAbsoluteRange(dateStart, dateEnd);
|
||||
await this.goToMonitor(monitorId);
|
||||
},
|
||||
|
||||
async isOnDetailsPage() {
|
||||
return await testSubjects.exists('uptimeMonitorPage', { timeout: 0 });
|
||||
},
|
||||
|
||||
async goToHomeViaBreadCrumb() {
|
||||
await testSubjects.click('breadcrumb first');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue