[Uptime] Details page refactor for browser monitor (#84425)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
08680882ce
commit
e476baf276
|
@ -27,6 +27,8 @@ export {
|
|||
METRIC_TYPE,
|
||||
} from './hooks/use_track_metric';
|
||||
|
||||
export { useFetcher } from './hooks/use_fetcher';
|
||||
|
||||
export * from './typings';
|
||||
|
||||
export { useChartTheme } from './hooks/use_chart_theme';
|
||||
|
|
|
@ -20558,8 +20558,6 @@
|
|||
"xpack.uptime.featureRegistry.uptimeFeatureName": "アップタイム",
|
||||
"xpack.uptime.filterBar.ariaLabel": "概要ページのインプットフィルター基準",
|
||||
"xpack.uptime.filterBar.filterAllLabel": "すべて",
|
||||
"xpack.uptime.filterBar.filterDownLabel": "ダウン",
|
||||
"xpack.uptime.filterBar.filterUpLabel": "アップ",
|
||||
"xpack.uptime.filterBar.options.location.name": "場所",
|
||||
"xpack.uptime.filterBar.options.portLabel": "ポート",
|
||||
"xpack.uptime.filterBar.options.schemeLabel": "スキーム",
|
||||
|
@ -20660,8 +20658,6 @@
|
|||
"xpack.uptime.monitorList.tlsColumnLabel": "TLS証明書",
|
||||
"xpack.uptime.monitorList.viewCertificateTitle": "証明書ステータス",
|
||||
"xpack.uptime.monitorStatusBar.durationTextAriaLabel": "ミリ秒単位の監視時間",
|
||||
"xpack.uptime.monitorStatusBar.healthStatusMessage.downLabel": "ダウン",
|
||||
"xpack.uptime.monitorStatusBar.healthStatusMessage.upLabel": "アップ",
|
||||
"xpack.uptime.monitorStatusBar.healthStatusMessageAriaLabel": "監視ステータス",
|
||||
"xpack.uptime.monitorStatusBar.loadingMessage": "読み込み中…",
|
||||
"xpack.uptime.monitorStatusBar.locations.oneLocStatus": "{loc}場所での{status}",
|
||||
|
@ -20714,17 +20710,10 @@
|
|||
"xpack.uptime.pingList.expandedRow.truncated": "初めの {contentBytes} バイトを表示中。",
|
||||
"xpack.uptime.pingList.expandRow": "拡張",
|
||||
"xpack.uptime.pingList.ipAddressColumnLabel": "IP",
|
||||
"xpack.uptime.pingList.locationLabel": "場所",
|
||||
"xpack.uptime.pingList.locationNameColumnLabel": "場所",
|
||||
"xpack.uptime.pingList.recencyMessage": "最終確認 {fromNow}",
|
||||
"xpack.uptime.pingList.responseCodeColumnLabel": "応答コード",
|
||||
"xpack.uptime.pingList.statusColumnHealthDownLabel": "ダウン",
|
||||
"xpack.uptime.pingList.statusColumnHealthUpLabel": "アップ",
|
||||
"xpack.uptime.pingList.statusColumnLabel": "ステータス",
|
||||
"xpack.uptime.pingList.statusLabel": "ステータス",
|
||||
"xpack.uptime.pingList.statusOptions.allStatusOptionLabel": "すべて",
|
||||
"xpack.uptime.pingList.statusOptions.downStatusOptionLabel": "ダウン",
|
||||
"xpack.uptime.pingList.statusOptions.upStatusOptionLabel": "アップ",
|
||||
"xpack.uptime.pluginDescription": "アップタイム監視",
|
||||
"xpack.uptime.settings.blank.error": "空白にすることはできません。",
|
||||
"xpack.uptime.settings.blankNumberField.error": "数値でなければなりません。",
|
||||
|
@ -20737,17 +20726,13 @@
|
|||
"xpack.uptime.settings.saveSuccess": "設定が保存されました。",
|
||||
"xpack.uptime.settingsBreadcrumbText": "設定",
|
||||
"xpack.uptime.snapshot.donutChart.ariaLabel": "現在のステータスを表す円グラフ、{total}個中{down}個のモニターがダウンしています。",
|
||||
"xpack.uptime.snapshot.donutChart.legend.downRowLabel": "ダウン",
|
||||
"xpack.uptime.snapshot.donutChart.legend.upRowLabel": "アップ",
|
||||
"xpack.uptime.snapshot.monitor": "監視",
|
||||
"xpack.uptime.snapshot.monitors": "監視",
|
||||
"xpack.uptime.snapshot.noDataDescription": "選択した時間範囲に ping はありません。",
|
||||
"xpack.uptime.snapshot.noDataTitle": "利用可能な ping データがありません",
|
||||
"xpack.uptime.snapshot.pingsOverTimeTitle": "一定時間のピング",
|
||||
"xpack.uptime.snapshotHistogram.description": "{startTime} から {endTime} までの期間のアップタイムステータスを表示する棒グラフです。",
|
||||
"xpack.uptime.snapshotHistogram.series.downLabel": "ダウン",
|
||||
"xpack.uptime.snapshotHistogram.series.pings": "モニター接続確認",
|
||||
"xpack.uptime.snapshotHistogram.series.upLabel": "アップ",
|
||||
"xpack.uptime.snapshotHistogram.xAxisId": "ピングX軸",
|
||||
"xpack.uptime.snapshotHistogram.yAxis.title": "ピング",
|
||||
"xpack.uptime.snapshotHistogram.yAxisId": "ピングY軸",
|
||||
|
|
|
@ -20578,8 +20578,6 @@
|
|||
"xpack.uptime.featureRegistry.uptimeFeatureName": "运行时间",
|
||||
"xpack.uptime.filterBar.ariaLabel": "概览页面的输入筛选条件",
|
||||
"xpack.uptime.filterBar.filterAllLabel": "全部",
|
||||
"xpack.uptime.filterBar.filterDownLabel": "关闭",
|
||||
"xpack.uptime.filterBar.filterUpLabel": "运行",
|
||||
"xpack.uptime.filterBar.options.location.name": "位置",
|
||||
"xpack.uptime.filterBar.options.portLabel": "端口",
|
||||
"xpack.uptime.filterBar.options.schemeLabel": "方案",
|
||||
|
@ -20680,8 +20678,6 @@
|
|||
"xpack.uptime.monitorList.tlsColumnLabel": "TLS 证书",
|
||||
"xpack.uptime.monitorList.viewCertificateTitle": "证书状态",
|
||||
"xpack.uptime.monitorStatusBar.durationTextAriaLabel": "监测持续时间(毫秒)",
|
||||
"xpack.uptime.monitorStatusBar.healthStatusMessage.downLabel": "关闭",
|
||||
"xpack.uptime.monitorStatusBar.healthStatusMessage.upLabel": "运行",
|
||||
"xpack.uptime.monitorStatusBar.healthStatusMessageAriaLabel": "检测状态",
|
||||
"xpack.uptime.monitorStatusBar.loadingMessage": "正在加载……",
|
||||
"xpack.uptime.monitorStatusBar.locations.oneLocStatus": "在 {loc} 位置处于 {status}",
|
||||
|
@ -20734,17 +20730,10 @@
|
|||
"xpack.uptime.pingList.expandedRow.truncated": "显示前 {contentBytes} 字节。",
|
||||
"xpack.uptime.pingList.expandRow": "展开",
|
||||
"xpack.uptime.pingList.ipAddressColumnLabel": "IP",
|
||||
"xpack.uptime.pingList.locationLabel": "位置",
|
||||
"xpack.uptime.pingList.locationNameColumnLabel": "位置",
|
||||
"xpack.uptime.pingList.recencyMessage": "{fromNow}已检查",
|
||||
"xpack.uptime.pingList.responseCodeColumnLabel": "响应代码",
|
||||
"xpack.uptime.pingList.statusColumnHealthDownLabel": "关闭",
|
||||
"xpack.uptime.pingList.statusColumnHealthUpLabel": "运行",
|
||||
"xpack.uptime.pingList.statusColumnLabel": "状态",
|
||||
"xpack.uptime.pingList.statusLabel": "状态",
|
||||
"xpack.uptime.pingList.statusOptions.allStatusOptionLabel": "全部",
|
||||
"xpack.uptime.pingList.statusOptions.downStatusOptionLabel": "关闭",
|
||||
"xpack.uptime.pingList.statusOptions.upStatusOptionLabel": "运行",
|
||||
"xpack.uptime.pluginDescription": "运行时间监测",
|
||||
"xpack.uptime.settings.blank.error": "不能为空。",
|
||||
"xpack.uptime.settings.blankNumberField.error": "必须为数字。",
|
||||
|
@ -20757,17 +20746,13 @@
|
|||
"xpack.uptime.settings.saveSuccess": "设置已保存!",
|
||||
"xpack.uptime.settingsBreadcrumbText": "设置",
|
||||
"xpack.uptime.snapshot.donutChart.ariaLabel": "显示当前状态的饼图。{total} 个监测中有 {down} 个已关闭。",
|
||||
"xpack.uptime.snapshot.donutChart.legend.downRowLabel": "关闭",
|
||||
"xpack.uptime.snapshot.donutChart.legend.upRowLabel": "运行",
|
||||
"xpack.uptime.snapshot.monitor": "监测",
|
||||
"xpack.uptime.snapshot.monitors": "监测",
|
||||
"xpack.uptime.snapshot.noDataDescription": "选定的时间范围中没有 ping。",
|
||||
"xpack.uptime.snapshot.noDataTitle": "没有可用的 ping 数据",
|
||||
"xpack.uptime.snapshot.pingsOverTimeTitle": "时移 Ping 数",
|
||||
"xpack.uptime.snapshotHistogram.description": "显示从 {startTime} 到 {endTime} 的运行时间时移状态的条形图。",
|
||||
"xpack.uptime.snapshotHistogram.series.downLabel": "关闭",
|
||||
"xpack.uptime.snapshotHistogram.series.pings": "监测 Ping",
|
||||
"xpack.uptime.snapshotHistogram.series.upLabel": "运行",
|
||||
"xpack.uptime.snapshotHistogram.xAxisId": "Ping X 轴",
|
||||
"xpack.uptime.snapshotHistogram.yAxis.title": "Ping",
|
||||
"xpack.uptime.snapshotHistogram.yAxisId": "Ping Y 轴",
|
||||
|
|
|
@ -38,6 +38,5 @@ export const CLIENT_DEFAULTS = {
|
|||
MONITOR_LIST_SORT_DIRECTION: 'asc',
|
||||
MONITOR_LIST_SORT_FIELD: 'monitor_id',
|
||||
SEARCH: '',
|
||||
SELECTED_PING_LIST_STATUS: '',
|
||||
STATUS_FILTER: '',
|
||||
};
|
||||
|
|
|
@ -15,6 +15,16 @@ export const CERTIFICATES_ROUTE = '/certificates';
|
|||
export enum STATUS {
|
||||
UP = 'up',
|
||||
DOWN = 'down',
|
||||
COMPLETE = 'complete',
|
||||
FAILED = 'failed',
|
||||
SKIPPED = 'skipped',
|
||||
}
|
||||
|
||||
export enum MONITOR_TYPES {
|
||||
HTTP = 'http',
|
||||
TCP = 'tcp',
|
||||
ICMP = 'icmp',
|
||||
BROWSER = 'browser',
|
||||
}
|
||||
|
||||
export const ML_JOB_ID = 'high_latency_by_geo';
|
||||
|
|
|
@ -232,6 +232,7 @@ export const PingType = t.intersection([
|
|||
full: t.string,
|
||||
port: t.number,
|
||||
scheme: t.string,
|
||||
path: t.string,
|
||||
}),
|
||||
service: t.partial({
|
||||
name: t.string,
|
||||
|
@ -280,7 +281,6 @@ export const makePing = (f: {
|
|||
|
||||
export const PingsResponseType = t.type({
|
||||
total: t.number,
|
||||
locations: t.array(t.string),
|
||||
pings: t.array(PingType),
|
||||
});
|
||||
|
||||
|
@ -293,7 +293,7 @@ export const GetPingsParamsType = t.intersection([
|
|||
t.partial({
|
||||
index: t.number,
|
||||
size: t.number,
|
||||
location: t.string,
|
||||
locations: t.string,
|
||||
monitorId: t.string,
|
||||
sort: t.string,
|
||||
status: t.string,
|
||||
|
|
|
@ -6,11 +6,6 @@ exports[`LocationLink component renders a help link when location not present 1`
|
|||
target="_blank"
|
||||
>
|
||||
Add location
|
||||
|
||||
<EuiIcon
|
||||
size="s"
|
||||
type="popout"
|
||||
/>
|
||||
</EuiLink>
|
||||
`;
|
||||
|
||||
|
|
|
@ -491,7 +491,7 @@ exports[`DonutChart component renders a donut chart 1`] = `
|
|||
<span
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero c1"
|
||||
>
|
||||
Down
|
||||
Up
|
||||
</span>
|
||||
<span
|
||||
class="euiFlexItem c2"
|
||||
|
@ -532,7 +532,7 @@ exports[`DonutChart component renders a donut chart 1`] = `
|
|||
<span
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero c1"
|
||||
>
|
||||
Up
|
||||
Down
|
||||
</span>
|
||||
<span
|
||||
class="euiFlexItem c2"
|
||||
|
@ -644,7 +644,7 @@ exports[`DonutChart component renders a green check when all monitors are up 1`]
|
|||
<span
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero c2"
|
||||
>
|
||||
Down
|
||||
Up
|
||||
</span>
|
||||
<span
|
||||
class="euiFlexItem c3"
|
||||
|
@ -685,7 +685,7 @@ exports[`DonutChart component renders a green check when all monitors are up 1`]
|
|||
<span
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero c2"
|
||||
>
|
||||
Up
|
||||
Down
|
||||
</span>
|
||||
<span
|
||||
class="euiFlexItem c3"
|
||||
|
|
|
@ -6,7 +6,7 @@ exports[`DonutChartLegend applies valid props as expected 1`] = `
|
|||
color="#bd271e"
|
||||
content={23}
|
||||
data-test-subj="xpack.uptime.snapshot.donutChart.down"
|
||||
message="Down"
|
||||
message="Up"
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
|
@ -15,7 +15,7 @@ exports[`DonutChartLegend applies valid props as expected 1`] = `
|
|||
color="#d3dae6"
|
||||
content={45}
|
||||
data-test-subj="xpack.uptime.snapshot.donutChart.up"
|
||||
message="Up"
|
||||
message="Down"
|
||||
/>
|
||||
</styled.div>
|
||||
`;
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import React, { useContext } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { DonutChartLegendRow } from './donut_chart_legend_row';
|
||||
import { UptimeThemeContext } from '../../../contexts';
|
||||
import { STATUS_DOWN_LABEL, STATUS_UP_LABEL } from '../translations';
|
||||
|
||||
const LegendContainer = styled.div`
|
||||
max-width: 150px;
|
||||
|
@ -34,18 +34,14 @@ export const DonutChartLegend = ({ down, up }: Props) => {
|
|||
<DonutChartLegendRow
|
||||
color={danger}
|
||||
content={down}
|
||||
message={i18n.translate('xpack.uptime.snapshot.donutChart.legend.downRowLabel', {
|
||||
defaultMessage: 'Down',
|
||||
})}
|
||||
message={STATUS_UP_LABEL}
|
||||
data-test-subj={'xpack.uptime.snapshot.donutChart.down'}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<DonutChartLegendRow
|
||||
color={gray}
|
||||
content={up}
|
||||
message={i18n.translate('xpack.uptime.snapshot.donutChart.legend.upRowLabel', {
|
||||
defaultMessage: 'Up',
|
||||
})}
|
||||
message={STATUS_DOWN_LABEL}
|
||||
data-test-subj={'xpack.uptime.snapshot.donutChart.up'}
|
||||
/>
|
||||
</LegendContainer>
|
||||
|
|
|
@ -28,6 +28,7 @@ import { HistogramResult } from '../../../../common/runtime_types';
|
|||
import { useUrlParams } from '../../../hooks';
|
||||
import { ChartEmptyState } from './chart_empty_state';
|
||||
import { getDateRangeFromChartElement } from './utils';
|
||||
import { STATUS_DOWN_LABEL, STATUS_UP_LABEL } from '../translations';
|
||||
|
||||
export interface PingHistogramComponentProps {
|
||||
/**
|
||||
|
@ -84,14 +85,6 @@ export const PingHistogramComponent: React.FC<PingHistogramComponentProps> = ({
|
|||
} else {
|
||||
const { histogram, minInterval } = data;
|
||||
|
||||
const downSpecId = i18n.translate('xpack.uptime.snapshotHistogram.series.downLabel', {
|
||||
defaultMessage: 'Down',
|
||||
});
|
||||
|
||||
const upMonitorsId = i18n.translate('xpack.uptime.snapshotHistogram.series.upLabel', {
|
||||
defaultMessage: 'Up',
|
||||
});
|
||||
|
||||
const onBrushEnd: BrushEndListener = ({ x }) => {
|
||||
if (!x) {
|
||||
return;
|
||||
|
@ -113,8 +106,8 @@ export const PingHistogramComponent: React.FC<PingHistogramComponentProps> = ({
|
|||
|
||||
histogram.forEach(({ x, upCount, downCount }) => {
|
||||
barData.push(
|
||||
{ x, y: downCount ?? 0, type: downSpecId },
|
||||
{ x, y: upCount ?? 0, type: upMonitorsId }
|
||||
{ x, y: downCount ?? 0, type: STATUS_DOWN_LABEL },
|
||||
{ x, y: upCount ?? 0, type: STATUS_UP_LABEL }
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -168,7 +161,7 @@ export const PingHistogramComponent: React.FC<PingHistogramComponentProps> = ({
|
|||
<BarSeries
|
||||
color={[danger, gray]}
|
||||
data={barData}
|
||||
id={downSpecId}
|
||||
id={STATUS_DOWN_LABEL}
|
||||
name={i18n.translate('xpack.uptime.snapshotHistogram.series.pings', {
|
||||
defaultMessage: 'Monitor Pings',
|
||||
})}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiIcon, EuiLink, EuiText } from '@elastic/eui';
|
||||
import { EuiLink, EuiText } from '@elastic/eui';
|
||||
|
||||
interface LocationLinkProps {
|
||||
location?: string | null;
|
||||
|
@ -32,8 +32,6 @@ export const LocationLink = ({ location, textSize }: LocationLinkProps) => {
|
|||
description:
|
||||
'Text that instructs the user to navigate to our docs to add a geographic location to their data',
|
||||
})}
|
||||
|
||||
<EuiIcon size="s" type="popout" />
|
||||
</EuiLink>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,3 +9,25 @@ import { i18n } from '@kbn/i18n';
|
|||
export const URL_LABEL = i18n.translate('xpack.uptime.monitorList.table.url.name', {
|
||||
defaultMessage: 'Url',
|
||||
});
|
||||
|
||||
export const STATUS_UP_LABEL = i18n.translate('xpack.uptime.monitorList.statusColumn.upLabel', {
|
||||
defaultMessage: 'Up',
|
||||
});
|
||||
|
||||
export const STATUS_DOWN_LABEL = i18n.translate('xpack.uptime.monitorList.statusColumn.downLabel', {
|
||||
defaultMessage: 'Down',
|
||||
});
|
||||
|
||||
export const STATUS_COMPLETE_LABEL = i18n.translate(
|
||||
'xpack.uptime.monitorList.statusColumn.completeLabel',
|
||||
{
|
||||
defaultMessage: 'Complete',
|
||||
}
|
||||
);
|
||||
|
||||
export const STATUS_FAILED_LABEL = i18n.translate(
|
||||
'xpack.uptime.monitorList.statusColumn.failedLabel',
|
||||
{
|
||||
defaultMessage: 'Failed',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -2,92 +2,7 @@
|
|||
|
||||
exports[`PingList component renders sorted list without errors 1`] = `
|
||||
<EuiPanel>
|
||||
<EuiTitle
|
||||
size="s"
|
||||
>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
defaultMessage="History"
|
||||
id="xpack.uptime.pingList.checkHistoryTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiFormRow
|
||||
aria-label="Status"
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
label="Status"
|
||||
labelType="label"
|
||||
>
|
||||
<EuiSelect
|
||||
aria-label="Status"
|
||||
data-test-subj="xpack.uptime.pingList.statusSelect"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "xpack.uptime.pingList.statusOptions.all",
|
||||
"text": "All",
|
||||
"value": "",
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "xpack.uptime.pingList.statusOptions.up",
|
||||
"text": "Up",
|
||||
"value": "up",
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "xpack.uptime.pingList.statusOptions.down",
|
||||
"text": "Down",
|
||||
"value": "down",
|
||||
},
|
||||
]
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
aria-label="Location"
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
label="Location"
|
||||
labelType="label"
|
||||
>
|
||||
<EuiSelect
|
||||
aria-label="Location"
|
||||
data-test-subj="xpack.uptime.pingList.locationSelect"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "xpack.uptime.pingList.locationOptions.all",
|
||||
"text": "All",
|
||||
"value": "",
|
||||
},
|
||||
]
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<PingListHeader />
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
|
@ -112,24 +27,16 @@ exports[`PingList component renders sorted list without errors 1`] = `
|
|||
"name": "IP",
|
||||
},
|
||||
Object {
|
||||
"align": "right",
|
||||
"align": "center",
|
||||
"field": "monitor.duration.us",
|
||||
"name": "Duration",
|
||||
"render": [Function],
|
||||
},
|
||||
Object {
|
||||
"align": "right",
|
||||
"field": "error.type",
|
||||
"name": "Error type",
|
||||
"render": [Function],
|
||||
},
|
||||
Object {
|
||||
"align": "right",
|
||||
"field": "http.response.status_code",
|
||||
"name": <styled.span>
|
||||
Response code
|
||||
</styled.span>,
|
||||
"name": "Error",
|
||||
"render": [Function],
|
||||
"width": "30%",
|
||||
},
|
||||
Object {
|
||||
"align": "right",
|
||||
|
@ -181,148 +88,6 @@ exports[`PingList component renders sorted list without errors 1`] = `
|
|||
},
|
||||
"timestamp": "2019-01-28T17:47:09.075Z",
|
||||
},
|
||||
Object {
|
||||
"docId": "fejjio21",
|
||||
"monitor": Object {
|
||||
"duration": Object {
|
||||
"us": 1452,
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814D",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"status": "up",
|
||||
"type": "tcp",
|
||||
},
|
||||
"timestamp": "2019-01-28T17:47:06.077Z",
|
||||
},
|
||||
Object {
|
||||
"docId": "fewzio21",
|
||||
"error": Object {
|
||||
"message": "dial tcp 127.0.0.1:9200: connect: connection refused",
|
||||
"type": "io",
|
||||
},
|
||||
"monitor": Object {
|
||||
"duration": Object {
|
||||
"us": 1094,
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814E",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"status": "down",
|
||||
"type": "tcp",
|
||||
},
|
||||
"timestamp": "2019-01-28T17:47:07.075Z",
|
||||
},
|
||||
Object {
|
||||
"docId": "fewpi321",
|
||||
"error": Object {
|
||||
"message": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused",
|
||||
"type": "io",
|
||||
},
|
||||
"monitor": Object {
|
||||
"duration": Object {
|
||||
"us": 1597,
|
||||
},
|
||||
"id": "auto-http-0X3675F89EF061209G",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"status": "down",
|
||||
"type": "http",
|
||||
},
|
||||
"timestamp": "2019-01-28T17:47:07.074Z",
|
||||
},
|
||||
Object {
|
||||
"docId": "0ewjio21",
|
||||
"error": Object {
|
||||
"message": "dial tcp 127.0.0.1:9200: connect: connection refused",
|
||||
"type": "io",
|
||||
},
|
||||
"monitor": Object {
|
||||
"duration": Object {
|
||||
"us": 1699,
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814H",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"status": "down",
|
||||
"type": "tcp",
|
||||
},
|
||||
"timestamp": "2019-01-28T17:47:18.080Z",
|
||||
},
|
||||
Object {
|
||||
"docId": "3ewjio21",
|
||||
"error": Object {
|
||||
"message": "dial tcp 127.0.0.1:9200: connect: connection refused",
|
||||
"type": "io",
|
||||
},
|
||||
"monitor": Object {
|
||||
"duration": Object {
|
||||
"us": 5384,
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814I",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"status": "down",
|
||||
"type": "tcp",
|
||||
},
|
||||
"timestamp": "2019-01-28T17:47:19.076Z",
|
||||
},
|
||||
Object {
|
||||
"docId": "fewjip21",
|
||||
"error": Object {
|
||||
"message": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused",
|
||||
"type": "io",
|
||||
},
|
||||
"monitor": Object {
|
||||
"duration": Object {
|
||||
"us": 5397,
|
||||
},
|
||||
"id": "auto-http-0X3675F89EF061209J",
|
||||
"ip": "127.0.0.1",
|
||||
"name": "",
|
||||
"status": "down",
|
||||
"type": "http",
|
||||
},
|
||||
"timestamp": "2019-01-28T17:47:19.076Z",
|
||||
},
|
||||
Object {
|
||||
"docId": "fewjio21",
|
||||
"http": Object {
|
||||
"response": Object {
|
||||
"status_code": 200,
|
||||
},
|
||||
},
|
||||
"monitor": Object {
|
||||
"duration": Object {
|
||||
"us": 127511,
|
||||
},
|
||||
"id": "auto-tcp-0X81440A68E839814C",
|
||||
"ip": "172.217.7.4",
|
||||
"name": "",
|
||||
"status": "up",
|
||||
"type": "http",
|
||||
},
|
||||
"timestamp": "2019-01-28T17:47:19.077Z",
|
||||
},
|
||||
Object {
|
||||
"docId": "fewjik81",
|
||||
"http": Object {
|
||||
"response": Object {
|
||||
"status_code": 200,
|
||||
},
|
||||
},
|
||||
"monitor": Object {
|
||||
"duration": Object {
|
||||
"us": 287543,
|
||||
},
|
||||
"id": "auto-http-0X131221E73F825974",
|
||||
"ip": "192.30.253.112",
|
||||
"name": "",
|
||||
"status": "up",
|
||||
"type": "http",
|
||||
},
|
||||
"timestamp": "2019-01-28T17:47:19.077Z",
|
||||
},
|
||||
]
|
||||
}
|
||||
loading={false}
|
||||
|
@ -339,11 +104,11 @@ exports[`PingList component renders sorted list without errors 1`] = `
|
|||
50,
|
||||
100,
|
||||
],
|
||||
"totalItemCount": 10,
|
||||
"totalItemCount": 9231,
|
||||
}
|
||||
}
|
||||
responsive={true}
|
||||
tableLayout="fixed"
|
||||
tableLayout="auto"
|
||||
/>
|
||||
</EuiPanel>
|
||||
`;
|
||||
|
|
|
@ -6,17 +6,21 @@
|
|||
|
||||
import React from 'react';
|
||||
import { shallowWithIntl } from '@kbn/test/jest';
|
||||
import { PingListComponent, rowShouldExpand, toggleDetails } from '../ping_list';
|
||||
import { PingList } from '../ping_list';
|
||||
import { Ping, PingsResponse } from '../../../../../common/runtime_types';
|
||||
import { ExpandedRowMap } from '../../../overview/monitor_list/types';
|
||||
import { rowShouldExpand, toggleDetails } from '../columns/expand_row';
|
||||
import * as pingListHook from '../use_pings';
|
||||
import { mockReduxHooks } from '../../../../lib/helper/test_helpers';
|
||||
|
||||
mockReduxHooks();
|
||||
|
||||
describe('PingList component', () => {
|
||||
let response: PingsResponse;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeAll(() => {
|
||||
response = {
|
||||
total: 9231,
|
||||
locations: ['nyc'],
|
||||
pings: [
|
||||
{
|
||||
docId: 'fewjio21',
|
||||
|
@ -50,147 +54,19 @@ describe('PingList component', () => {
|
|||
type: 'tcp',
|
||||
},
|
||||
},
|
||||
{
|
||||
docId: 'fejjio21',
|
||||
timestamp: '2019-01-28T17:47:06.077Z',
|
||||
monitor: {
|
||||
duration: { us: 1452 },
|
||||
id: 'auto-tcp-0X81440A68E839814D',
|
||||
ip: '127.0.0.1',
|
||||
name: '',
|
||||
status: 'up',
|
||||
type: 'tcp',
|
||||
},
|
||||
},
|
||||
{
|
||||
docId: 'fewzio21',
|
||||
timestamp: '2019-01-28T17:47:07.075Z',
|
||||
error: {
|
||||
message: 'dial tcp 127.0.0.1:9200: connect: connection refused',
|
||||
type: 'io',
|
||||
},
|
||||
monitor: {
|
||||
duration: { us: 1094 },
|
||||
id: 'auto-tcp-0X81440A68E839814E',
|
||||
ip: '127.0.0.1',
|
||||
name: '',
|
||||
status: 'down',
|
||||
type: 'tcp',
|
||||
},
|
||||
},
|
||||
{
|
||||
docId: 'fewpi321',
|
||||
timestamp: '2019-01-28T17:47:07.074Z',
|
||||
error: {
|
||||
message:
|
||||
'Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused',
|
||||
type: 'io',
|
||||
},
|
||||
monitor: {
|
||||
duration: { us: 1597 },
|
||||
id: 'auto-http-0X3675F89EF061209G',
|
||||
ip: '127.0.0.1',
|
||||
name: '',
|
||||
status: 'down',
|
||||
type: 'http',
|
||||
},
|
||||
},
|
||||
{
|
||||
docId: '0ewjio21',
|
||||
timestamp: '2019-01-28T17:47:18.080Z',
|
||||
error: {
|
||||
message: 'dial tcp 127.0.0.1:9200: connect: connection refused',
|
||||
type: 'io',
|
||||
},
|
||||
monitor: {
|
||||
duration: { us: 1699 },
|
||||
id: 'auto-tcp-0X81440A68E839814H',
|
||||
ip: '127.0.0.1',
|
||||
name: '',
|
||||
status: 'down',
|
||||
type: 'tcp',
|
||||
},
|
||||
},
|
||||
{
|
||||
docId: '3ewjio21',
|
||||
timestamp: '2019-01-28T17:47:19.076Z',
|
||||
error: {
|
||||
message: 'dial tcp 127.0.0.1:9200: connect: connection refused',
|
||||
type: 'io',
|
||||
},
|
||||
monitor: {
|
||||
duration: { us: 5384 },
|
||||
id: 'auto-tcp-0X81440A68E839814I',
|
||||
ip: '127.0.0.1',
|
||||
name: '',
|
||||
status: 'down',
|
||||
type: 'tcp',
|
||||
},
|
||||
},
|
||||
{
|
||||
docId: 'fewjip21',
|
||||
timestamp: '2019-01-28T17:47:19.076Z',
|
||||
error: {
|
||||
message:
|
||||
'Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused',
|
||||
type: 'io',
|
||||
},
|
||||
monitor: {
|
||||
duration: { us: 5397 },
|
||||
id: 'auto-http-0X3675F89EF061209J',
|
||||
ip: '127.0.0.1',
|
||||
name: '',
|
||||
status: 'down',
|
||||
type: 'http',
|
||||
},
|
||||
},
|
||||
{
|
||||
docId: 'fewjio21',
|
||||
timestamp: '2019-01-28T17:47:19.077Z',
|
||||
http: { response: { status_code: 200 } },
|
||||
monitor: {
|
||||
duration: { us: 127511 },
|
||||
id: 'auto-tcp-0X81440A68E839814C',
|
||||
ip: '172.217.7.4',
|
||||
name: '',
|
||||
status: 'up',
|
||||
type: 'http',
|
||||
},
|
||||
},
|
||||
{
|
||||
docId: 'fewjik81',
|
||||
timestamp: '2019-01-28T17:47:19.077Z',
|
||||
http: { response: { status_code: 200 } },
|
||||
monitor: {
|
||||
duration: { us: 287543 },
|
||||
id: 'auto-http-0X131221E73F825974',
|
||||
ip: '192.30.253.112',
|
||||
name: '',
|
||||
status: 'up',
|
||||
type: 'http',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
jest.spyOn(pingListHook, 'usePingsList').mockReturnValue({
|
||||
...response,
|
||||
error: undefined,
|
||||
loading: false,
|
||||
failedSteps: { steps: [], checkGroup: '1-f-4d-4f' },
|
||||
});
|
||||
});
|
||||
|
||||
it('renders sorted list without errors', () => {
|
||||
const component = shallowWithIntl(
|
||||
<PingListComponent
|
||||
dateRange={{
|
||||
from: 'now-15m',
|
||||
to: 'now',
|
||||
}}
|
||||
getPings={jest.fn()}
|
||||
pruneJourneysCallback={jest.fn()}
|
||||
lastRefresh={123}
|
||||
loading={false}
|
||||
locations={[]}
|
||||
monitorId="foo"
|
||||
pings={response.pings}
|
||||
total={10}
|
||||
/>
|
||||
);
|
||||
const component = shallowWithIntl(<PingList />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Ping Timestamp component render without errors 1`] = `
|
||||
.c0 {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.c0 figure.euiImage div.stepArrowsFullScreen {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.c0 figure.euiImage-isFullScreen div.stepArrowsFullScreen {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.c0 div.stepArrows {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.c0:hover div.stepArrows {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
width: 120px;
|
||||
text-align: center;
|
||||
border: 1px solid #d3dae6;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<div
|
||||
class="euiText euiText--medium c1"
|
||||
>
|
||||
<strong>
|
||||
No image available
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
|
||||
<div
|
||||
class="stepArrowsFullScreen"
|
||||
style="position:absolute;bottom:32px;width:100%"
|
||||
/>
|
||||
<a
|
||||
class="euiLink euiLink--primary eui-textNoWrap"
|
||||
href="/step/details"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Nov 26, 2020 10:28:56 AM
|
||||
</a>
|
||||
<div
|
||||
class="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive stepArrows"
|
||||
style="position:absolute;bottom:0;left:30px"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="Next"
|
||||
class="euiButtonIcon euiButtonIcon--subdued"
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="euiButtonIcon__icon"
|
||||
data-euiicon-type="arrowLeft"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="Next"
|
||||
class="euiButtonIcon euiButtonIcon--subdued"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="euiButtonIcon__icon"
|
||||
data-euiicon-type="arrowRight"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Ping Timestamp component shallow render without errors 1`] = `
|
||||
<styled.div>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="s"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<NoImageAvailable />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
|
||||
<div
|
||||
className="stepArrowsFullScreen"
|
||||
style={
|
||||
Object {
|
||||
"bottom": 32,
|
||||
"position": "absolute",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<EuiLink
|
||||
className="eui-textNoWrap"
|
||||
href="/step/details"
|
||||
>
|
||||
Nov 26, 2020 10:28:56 AM
|
||||
</EuiLink>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
className="stepArrows"
|
||||
gutterSize="s"
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 30,
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label="Next"
|
||||
color="subdued"
|
||||
disabled={true}
|
||||
iconType="arrowLeft"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label="Next"
|
||||
color="subdued"
|
||||
disabled={false}
|
||||
iconType="arrowRight"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</styled.div>
|
||||
`;
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { shallowWithIntl, renderWithIntl } from '@kbn/test/jest';
|
||||
import { PingTimestamp } from '../ping_timestamp';
|
||||
import { mockReduxHooks } from '../../../../../lib/helper/test_helpers';
|
||||
import { Ping } from '../../../../../../common/runtime_types/ping';
|
||||
import { EuiThemeProvider } from '../../../../../../../observability/public';
|
||||
|
||||
mockReduxHooks();
|
||||
|
||||
describe('Ping Timestamp component', () => {
|
||||
let response: Ping;
|
||||
|
||||
beforeAll(() => {
|
||||
response = {
|
||||
ecs: { version: '1.6.0' },
|
||||
agent: {
|
||||
ephemeral_id: '52ce1110-464f-4d74-b94c-3c051bf12589',
|
||||
id: '3ebcd3c2-f5c3-499e-8d86-80f98e5f4c08',
|
||||
name: 'docker-desktop',
|
||||
type: 'heartbeat',
|
||||
version: '7.10.0',
|
||||
hostname: 'docker-desktop',
|
||||
},
|
||||
monitor: {
|
||||
status: 'up',
|
||||
check_group: 'f58a484f-2ffb-11eb-9b35-025000000001',
|
||||
duration: { us: 1528598 },
|
||||
id: 'basic addition and completion of single task',
|
||||
name: 'basic addition and completion of single task',
|
||||
type: 'browser',
|
||||
timespan: { lt: '2020-11-26T15:29:56.820Z', gte: '2020-11-26T15:28:56.820Z' },
|
||||
},
|
||||
url: {
|
||||
full: 'file:///opt/elastic-synthetics/examples/todos/app/index.html',
|
||||
scheme: 'file',
|
||||
domain: '',
|
||||
path: '/opt/elastic-synthetics/examples/todos/app/index.html',
|
||||
},
|
||||
synthetics: { type: 'heartbeat/summary' },
|
||||
summary: { up: 1, down: 0 },
|
||||
timestamp: '2020-11-26T15:28:56.896Z',
|
||||
docId: '0WErBXYB0mvWTKLO-yQm',
|
||||
};
|
||||
});
|
||||
|
||||
it('shallow render without errors', () => {
|
||||
const component = shallowWithIntl(
|
||||
<PingTimestamp ping={response} timestamp={response.timestamp} />
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('render without errors', () => {
|
||||
const component = renderWithIntl(
|
||||
<EuiThemeProvider darkMode={false}>
|
||||
<PingTimestamp ping={response} timestamp={response.timestamp} />
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiButtonIcon } from '@elastic/eui';
|
||||
import { Ping } from '../../../../../common/runtime_types/ping';
|
||||
import { PingListExpandedRowComponent } from '../expanded_row';
|
||||
|
||||
export const toggleDetails = (
|
||||
ping: Ping,
|
||||
expandedRows: Record<string, JSX.Element>,
|
||||
setExpandedRows: (update: Record<string, JSX.Element>) => any
|
||||
) => {
|
||||
// If already expanded, collapse
|
||||
if (expandedRows[ping.docId]) {
|
||||
delete expandedRows[ping.docId];
|
||||
setExpandedRows({ ...expandedRows });
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise expand this row
|
||||
setExpandedRows({
|
||||
...expandedRows,
|
||||
[ping.docId]: <PingListExpandedRowComponent ping={ping} />,
|
||||
});
|
||||
};
|
||||
|
||||
export function rowShouldExpand(item: Ping) {
|
||||
const errorPresent = !!item.error;
|
||||
const httpBodyPresent = item.http?.response?.body?.bytes ?? 0 > 0;
|
||||
const isBrowserMonitor = item.monitor.type === 'browser';
|
||||
return errorPresent || httpBodyPresent || isBrowserMonitor;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
item: Ping;
|
||||
expandedRows: Record<string, JSX.Element>;
|
||||
setExpandedRows: (val: Record<string, JSX.Element>) => void;
|
||||
}
|
||||
export const ExpandRowColumn = ({ item, expandedRows, setExpandedRows }: Props) => {
|
||||
return (
|
||||
<EuiButtonIcon
|
||||
data-test-subj="uptimePingListExpandBtn"
|
||||
onClick={() => toggleDetails(item, expandedRows, setExpandedRows)}
|
||||
disabled={!rowShouldExpand(item)}
|
||||
aria-label={
|
||||
expandedRows[item.docId]
|
||||
? i18n.translate('xpack.uptime.pingList.collapseRow', {
|
||||
defaultMessage: 'Collapse',
|
||||
})
|
||||
: i18n.translate('xpack.uptime.pingList.expandRow', { defaultMessage: 'Expand' })
|
||||
}
|
||||
iconType={expandedRows[item.docId] ? 'arrowUp' : 'arrowDown'}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { Ping, SyntheticsJourneyApiResponse } from '../../../../../common/runtime_types/ping';
|
||||
|
||||
interface Props {
|
||||
ping: Ping;
|
||||
failedSteps?: SyntheticsJourneyApiResponse;
|
||||
}
|
||||
|
||||
export const FailedStep = ({ ping, failedSteps }: Props) => {
|
||||
const thisFailedStep = failedSteps?.steps?.find(
|
||||
(fs) => fs.monitor.check_group === ping.monitor.check_group
|
||||
);
|
||||
|
||||
if (!thisFailedStep) {
|
||||
return <>--</>;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{thisFailedStep.synthetics?.step?.index}. {thisFailedStep.synthetics?.step?.name}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Ping } from '../../../../../common/runtime_types/ping';
|
||||
|
||||
const StyledSpan = styled.span`
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
errorType: string;
|
||||
ping: Ping;
|
||||
}
|
||||
|
||||
export const PingErrorCol = ({ errorType, ping }: Props) => {
|
||||
if (!errorType) {
|
||||
return <>--</>;
|
||||
}
|
||||
return (
|
||||
<StyledSpan>
|
||||
{errorType}:{ping.error?.message}
|
||||
</StyledSpan>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment';
|
||||
import { EuiBadge, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { Ping } from '../../../../../common/runtime_types/ping';
|
||||
import { MONITOR_TYPES, STATUS } from '../../../../../common/constants';
|
||||
import { UptimeThemeContext } from '../../../../contexts';
|
||||
import {
|
||||
STATUS_COMPLETE_LABEL,
|
||||
STATUS_DOWN_LABEL,
|
||||
STATUS_FAILED_LABEL,
|
||||
STATUS_UP_LABEL,
|
||||
} from '../../../common/translations';
|
||||
|
||||
interface Props {
|
||||
pingStatus: string;
|
||||
item: Ping;
|
||||
}
|
||||
|
||||
const getPingStatusLabel = (status: string, ping: Ping) => {
|
||||
if (ping.monitor.type === MONITOR_TYPES.BROWSER) {
|
||||
return status === 'up' ? STATUS_COMPLETE_LABEL : STATUS_FAILED_LABEL;
|
||||
}
|
||||
return status === 'up' ? STATUS_UP_LABEL : STATUS_DOWN_LABEL;
|
||||
};
|
||||
|
||||
export const PingStatusColumn = ({ pingStatus, item }: Props) => {
|
||||
const {
|
||||
colors: { dangerBehindText },
|
||||
} = useContext(UptimeThemeContext);
|
||||
|
||||
const timeStamp = moment(item.timestamp);
|
||||
|
||||
let checkedTime = '';
|
||||
|
||||
if (moment().diff(timeStamp, 'd') > 1) {
|
||||
checkedTime = timeStamp.format('ll LTS');
|
||||
} else {
|
||||
checkedTime = timeStamp.format('LTS');
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-test-subj={`xpack.uptime.pingList.ping-${item.docId}`}>
|
||||
<EuiBadge
|
||||
className="eui-textCenter"
|
||||
color={pingStatus === STATUS.UP ? 'secondary' : dangerBehindText}
|
||||
>
|
||||
{getPingStatusLabel(pingStatus, item)}
|
||||
</EuiBadge>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiText size="xs" color="subdued">
|
||||
{i18n.translate('xpack.uptime.pingList.recencyMessage', {
|
||||
values: { fromNow: checkedTime },
|
||||
defaultMessage: 'Checked {fromNow}',
|
||||
description:
|
||||
'A string used to inform our users how long ago Heartbeat pinged the selected host.',
|
||||
})}
|
||||
</EuiText>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* 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, useEffect, useState } from 'react';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiImage,
|
||||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import useIntersection from 'react-use/lib/useIntersection';
|
||||
import moment from 'moment';
|
||||
import styled from 'styled-components';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { Ping } from '../../../../../common/runtime_types/ping';
|
||||
import { getShortTimeStamp } from '../../../overview/monitor_list/columns/monitor_status_column';
|
||||
import { euiStyled, useFetcher } from '../../../../../../observability/public';
|
||||
import { getJourneyScreenshot } from '../../../../state/api/journey';
|
||||
import { UptimeSettingsContext } from '../../../../contexts';
|
||||
|
||||
const StepImage = styled(EuiImage)`
|
||||
&&& {
|
||||
display: flex;
|
||||
figcaption {
|
||||
white-space: nowrap;
|
||||
align-self: center;
|
||||
margin-left: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StepDiv = styled.div`
|
||||
figure.euiImage {
|
||||
div.stepArrowsFullScreen {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
figure.euiImage-isFullScreen {
|
||||
div.stepArrowsFullScreen {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
position: relative;
|
||||
div.stepArrows {
|
||||
display: none;
|
||||
}
|
||||
:hover {
|
||||
div.stepArrows {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
timestamp: string;
|
||||
ping: Ping;
|
||||
}
|
||||
|
||||
export const PingTimestamp = ({ timestamp, ping }: Props) => {
|
||||
const [stepNo, setStepNo] = useState(1);
|
||||
|
||||
const [stepImages, setStepImages] = useState<string[]>([]);
|
||||
|
||||
const intersectionRef = React.useRef(null);
|
||||
|
||||
const { basePath } = useContext(UptimeSettingsContext);
|
||||
|
||||
const imgPath = basePath + `/api/uptime/journey/screenshot/${ping.monitor.check_group}/${stepNo}`;
|
||||
|
||||
const intersection = useIntersection(intersectionRef, {
|
||||
root: null,
|
||||
rootMargin: '0px',
|
||||
threshold: 1,
|
||||
});
|
||||
|
||||
const { data } = useFetcher(() => {
|
||||
if (intersection && intersection.intersectionRatio === 1 && !stepImages[stepNo - 1])
|
||||
return getJourneyScreenshot(imgPath);
|
||||
}, [intersection?.intersectionRatio, stepNo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setStepImages((prevState) => [...prevState, data?.src]);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const imgSrc = stepImages[stepNo] || data?.src;
|
||||
|
||||
const ImageCaption = (
|
||||
<>
|
||||
<div
|
||||
className="stepArrowsFullScreen"
|
||||
style={{ position: 'absolute', bottom: 32, width: '100%' }}
|
||||
>
|
||||
{imgSrc && (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
disabled={stepNo === 1}
|
||||
size="m"
|
||||
onClick={() => {
|
||||
setStepNo(stepNo - 1);
|
||||
}}
|
||||
iconType="arrowLeft"
|
||||
aria-label="Next"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
Step:{stepNo} {data?.stepName}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
disabled={stepNo === data?.maxSteps}
|
||||
size="m"
|
||||
onClick={() => {
|
||||
setStepNo(stepNo + 1);
|
||||
}}
|
||||
iconType="arrowRight"
|
||||
aria-label="Next"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</div>
|
||||
<EuiLink className="eui-textNoWrap" href={'/step/details'}>
|
||||
{getShortTimeStamp(moment(timestamp))}
|
||||
</EuiLink>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<StepDiv ref={intersectionRef}>
|
||||
{imgSrc ? (
|
||||
<StepImage
|
||||
allowFullScreen={true}
|
||||
size="s"
|
||||
hasShadow
|
||||
caption={ImageCaption}
|
||||
alt="No image available"
|
||||
url={imgSrc}
|
||||
/>
|
||||
) : (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<NoImageAvailable />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem> {ImageCaption}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
<EuiFlexGroup
|
||||
className="stepArrows"
|
||||
gutterSize="s"
|
||||
alignItems="center"
|
||||
style={{ position: 'absolute', bottom: 0, left: 30 }}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
disabled={stepNo === 1}
|
||||
color="subdued"
|
||||
size="s"
|
||||
onClick={() => {
|
||||
setStepNo(stepNo - 1);
|
||||
}}
|
||||
iconType="arrowLeft"
|
||||
aria-label="Next"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
disabled={stepNo === data?.maxSteps}
|
||||
color="subdued"
|
||||
size="s"
|
||||
onClick={() => {
|
||||
setStepNo(stepNo + 1);
|
||||
}}
|
||||
iconType="arrowRight"
|
||||
aria-label="Next"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</StepDiv>
|
||||
);
|
||||
};
|
||||
|
||||
const BorderedText = euiStyled(EuiText)`
|
||||
width: 120px;
|
||||
text-align: center;
|
||||
border: 1px solid ${(props) => props.theme.eui.euiColorLightShade};
|
||||
`;
|
||||
|
||||
export const NoImageAvailable = () => {
|
||||
return (
|
||||
<BorderedText>
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.synthetics.screenshot.noImageMessage"
|
||||
defaultMessage="No image available"
|
||||
/>
|
||||
</strong>
|
||||
</BorderedText>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { EuiBadge } from '@elastic/eui';
|
||||
|
||||
const SpanWithMargin = styled.span`
|
||||
margin-right: 16px;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
statusCode: string;
|
||||
}
|
||||
export const ResponseCodeColumn = ({ statusCode }: Props) => {
|
||||
return (
|
||||
<SpanWithMargin>
|
||||
<EuiBadge>{statusCode}</EuiBadge>
|
||||
</SpanWithMargin>
|
||||
);
|
||||
};
|
|
@ -4,5 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { PingListComponent } from './ping_list';
|
||||
export { PingList } from './ping_list';
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiIcon, EuiLink, EuiText } from '@elastic/eui';
|
||||
import { EuiLink, EuiText } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
@ -24,7 +24,5 @@ export const LocationName = ({ location }: LocationNameProps) =>
|
|||
description:
|
||||
'Text that instructs the user to navigate to our docs to add a geographic location to their data',
|
||||
})}
|
||||
|
||||
<EuiIcon size="s" type="popout" />
|
||||
</EuiLink>
|
||||
);
|
||||
|
|
|
@ -4,192 +4,56 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiBasicTable,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHealth,
|
||||
EuiPanel,
|
||||
EuiSelect,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
EuiFormRow,
|
||||
EuiButtonIcon,
|
||||
} from '@elastic/eui';
|
||||
import { EuiBasicTable, EuiPanel, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import moment from 'moment';
|
||||
import React, { useCallback, useContext, useState, useEffect } from 'react';
|
||||
import React, { useCallback, useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Ping, GetPingsParams, DateRange } from '../../../../common/runtime_types';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Ping } from '../../../../common/runtime_types';
|
||||
import { convertMicrosecondsToMilliseconds as microsToMillis } from '../../../lib/helper';
|
||||
import { LocationName } from './location_name';
|
||||
import { Pagination } from '../../overview/monitor_list';
|
||||
import { PingListExpandedRowComponent } from './expanded_row';
|
||||
// import { PingListProps } from './ping_list_container';
|
||||
import { pruneJourneyState } from '../../../state/actions/journey';
|
||||
import { selectPingList } from '../../../state/selectors';
|
||||
import { UptimeRefreshContext, UptimeSettingsContext } from '../../../contexts';
|
||||
import { getPings as getPingsAction } from '../../../state/actions';
|
||||
import { PingStatusColumn } from './columns/ping_status';
|
||||
import * as I18LABELS from './translations';
|
||||
import { MONITOR_TYPES } from '../../../../common/constants';
|
||||
import { ResponseCodeColumn } from './columns/response_code';
|
||||
import { ERROR_LABEL, LOCATION_LABEL, RES_CODE_LABEL, TIMESTAMP_LABEL } from './translations';
|
||||
import { ExpandRowColumn } from './columns/expand_row';
|
||||
import { PingErrorCol } from './columns/ping_error';
|
||||
import { PingTimestamp } from './columns/ping_timestamp';
|
||||
import { FailedStep } from './columns/failed_step';
|
||||
import { usePingsList } from './use_pings';
|
||||
import { PingListHeader } from './ping_list_header';
|
||||
|
||||
export interface PingListProps {
|
||||
monitorId: string;
|
||||
}
|
||||
export const SpanWithMargin = styled.span`
|
||||
margin-right: 16px;
|
||||
`;
|
||||
|
||||
export const PingList = (props: PingListProps) => {
|
||||
const {
|
||||
error,
|
||||
loading,
|
||||
pingList: { locations, pings, total },
|
||||
} = useSelector(selectPingList);
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
|
||||
const { lastRefresh } = useContext(UptimeRefreshContext);
|
||||
|
||||
const { dateRangeStart: drs, dateRangeEnd: dre } = useContext(UptimeSettingsContext);
|
||||
export const PingList = () => {
|
||||
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const getPingsCallback = useCallback(
|
||||
(params: GetPingsParams) => dispatch(getPingsAction(params)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const pruneJourneysCallback = useCallback(
|
||||
(checkGroups: string[]) => dispatch(pruneJourneyState(checkGroups)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<PingListComponent
|
||||
dateRange={{
|
||||
from: drs,
|
||||
to: dre,
|
||||
}}
|
||||
error={error}
|
||||
getPings={getPingsCallback}
|
||||
pruneJourneysCallback={pruneJourneysCallback}
|
||||
lastRefresh={lastRefresh}
|
||||
loading={loading}
|
||||
locations={locations}
|
||||
pings={pings}
|
||||
total={total}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const AllLocationOption = {
|
||||
'data-test-subj': 'xpack.uptime.pingList.locationOptions.all',
|
||||
text: 'All',
|
||||
value: '',
|
||||
};
|
||||
|
||||
export const toggleDetails = (
|
||||
ping: Ping,
|
||||
expandedRows: Record<string, JSX.Element>,
|
||||
setExpandedRows: (update: Record<string, JSX.Element>) => any
|
||||
) => {
|
||||
// If already expanded, collapse
|
||||
if (expandedRows[ping.docId]) {
|
||||
delete expandedRows[ping.docId];
|
||||
setExpandedRows({ ...expandedRows });
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise expand this row
|
||||
setExpandedRows({
|
||||
...expandedRows,
|
||||
[ping.docId]: <PingListExpandedRowComponent ping={ping} />,
|
||||
const { error, loading, pings, total, failedSteps } = usePingsList({
|
||||
pageSize,
|
||||
pageIndex,
|
||||
});
|
||||
};
|
||||
|
||||
const SpanWithMargin = styled.span`
|
||||
margin-right: 16px;
|
||||
`;
|
||||
|
||||
interface Props extends PingListProps {
|
||||
dateRange: DateRange;
|
||||
error?: Error;
|
||||
getPings: (props: GetPingsParams) => void;
|
||||
pruneJourneysCallback: (checkGroups: string[]) => void;
|
||||
lastRefresh: number;
|
||||
loading: boolean;
|
||||
locations: string[];
|
||||
pings: Ping[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
|
||||
const statusOptions = [
|
||||
{
|
||||
'data-test-subj': 'xpack.uptime.pingList.statusOptions.all',
|
||||
text: i18n.translate('xpack.uptime.pingList.statusOptions.allStatusOptionLabel', {
|
||||
defaultMessage: 'All',
|
||||
}),
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'xpack.uptime.pingList.statusOptions.up',
|
||||
text: i18n.translate('xpack.uptime.pingList.statusOptions.upStatusOptionLabel', {
|
||||
defaultMessage: 'Up',
|
||||
}),
|
||||
value: 'up',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'xpack.uptime.pingList.statusOptions.down',
|
||||
text: i18n.translate('xpack.uptime.pingList.statusOptions.downStatusOptionLabel', {
|
||||
defaultMessage: 'Down',
|
||||
}),
|
||||
value: 'down',
|
||||
},
|
||||
];
|
||||
|
||||
export function rowShouldExpand(item: Ping) {
|
||||
const errorPresent = !!item.error;
|
||||
const httpBodyPresent = item.http?.response?.body?.bytes ?? 0 > 0;
|
||||
const isBrowserMonitor = item.monitor.type === 'browser';
|
||||
return errorPresent || httpBodyPresent || isBrowserMonitor;
|
||||
}
|
||||
|
||||
export const PingListComponent = (props: Props) => {
|
||||
const [selectedLocation, setSelectedLocation] = useState<string>('');
|
||||
const [status, setStatus] = useState<string>('');
|
||||
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const {
|
||||
dateRange: { from, to },
|
||||
error,
|
||||
getPings,
|
||||
pruneJourneysCallback,
|
||||
lastRefresh,
|
||||
loading,
|
||||
locations,
|
||||
monitorId,
|
||||
pings,
|
||||
total,
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
getPings({
|
||||
dateRange: {
|
||||
from,
|
||||
to,
|
||||
},
|
||||
location: selectedLocation,
|
||||
monitorId,
|
||||
index: pageIndex,
|
||||
size: pageSize,
|
||||
status: status !== 'all' ? status : '',
|
||||
});
|
||||
}, [from, to, getPings, monitorId, lastRefresh, selectedLocation, pageIndex, pageSize, status]);
|
||||
|
||||
const [expandedRows, setExpandedRows] = useState<Record<string, JSX.Element>>({});
|
||||
|
||||
const expandedIdsToRemove = JSON.stringify(
|
||||
Object.keys(expandedRows).filter((e) => !pings.some(({ docId }) => docId === e))
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const parsed = JSON.parse(expandedIdsToRemove);
|
||||
if (parsed.length) {
|
||||
|
@ -203,73 +67,62 @@ export const PingListComponent = (props: Props) => {
|
|||
const expandedCheckGroups = pings
|
||||
.filter((p: Ping) => Object.keys(expandedRows).some((f) => p.docId === f))
|
||||
.map(({ monitor: { check_group: cg } }) => cg);
|
||||
|
||||
const expandedCheckGroupsStr = JSON.stringify(expandedCheckGroups);
|
||||
|
||||
useEffect(() => {
|
||||
pruneJourneysCallback(JSON.parse(expandedCheckGroupsStr));
|
||||
}, [pruneJourneysCallback, expandedCheckGroupsStr]);
|
||||
|
||||
const locationOptions = !locations
|
||||
? [AllLocationOption]
|
||||
: [AllLocationOption].concat(
|
||||
locations.map((name) => ({
|
||||
text: name,
|
||||
'data-test-subj': `xpack.uptime.pingList.locationOptions.${name}`,
|
||||
value: name,
|
||||
}))
|
||||
);
|
||||
|
||||
const hasStatus = pings.reduce(
|
||||
(hasHttpStatus: boolean, currentPing) =>
|
||||
hasHttpStatus || !!currentPing.http?.response?.status_code,
|
||||
false
|
||||
);
|
||||
|
||||
const monitorType = pings?.[0]?.monitor.type;
|
||||
|
||||
const columns: any[] = [
|
||||
{
|
||||
field: 'monitor.status',
|
||||
name: i18n.translate('xpack.uptime.pingList.statusColumnLabel', {
|
||||
defaultMessage: 'Status',
|
||||
}),
|
||||
name: I18LABELS.STATUS_LABEL,
|
||||
render: (pingStatus: string, item: Ping) => (
|
||||
<div data-test-subj={`xpack.uptime.pingList.ping-${item.docId}`}>
|
||||
<EuiHealth color={pingStatus === 'up' ? 'success' : 'danger'}>
|
||||
{pingStatus === 'up'
|
||||
? i18n.translate('xpack.uptime.pingList.statusColumnHealthUpLabel', {
|
||||
defaultMessage: 'Up',
|
||||
})
|
||||
: i18n.translate('xpack.uptime.pingList.statusColumnHealthDownLabel', {
|
||||
defaultMessage: 'Down',
|
||||
})}
|
||||
</EuiHealth>
|
||||
<EuiText size="xs" color="subdued">
|
||||
{i18n.translate('xpack.uptime.pingList.recencyMessage', {
|
||||
values: { fromNow: moment(item.timestamp).fromNow() },
|
||||
defaultMessage: 'Checked {fromNow}',
|
||||
description:
|
||||
'A string used to inform our users how long ago Heartbeat pinged the selected host.',
|
||||
})}
|
||||
</EuiText>
|
||||
</div>
|
||||
<PingStatusColumn pingStatus={pingStatus} item={item} />
|
||||
),
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: 'observer.geo.name',
|
||||
name: i18n.translate('xpack.uptime.pingList.locationNameColumnLabel', {
|
||||
defaultMessage: 'Location',
|
||||
}),
|
||||
name: LOCATION_LABEL,
|
||||
render: (location: string) => <LocationName location={location} />,
|
||||
},
|
||||
...(monitorType === MONITOR_TYPES.BROWSER
|
||||
? [
|
||||
{
|
||||
align: 'left',
|
||||
field: 'timestamp',
|
||||
name: TIMESTAMP_LABEL,
|
||||
render: (timestamp: string, item: Ping) => (
|
||||
<PingTimestamp timestamp={timestamp} ping={item} />
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
// ip column not needed for browser type
|
||||
...(monitorType !== MONITOR_TYPES.BROWSER
|
||||
? [
|
||||
{
|
||||
align: 'right',
|
||||
dataType: 'number',
|
||||
field: 'monitor.ip',
|
||||
name: i18n.translate('xpack.uptime.pingList.ipAddressColumnLabel', {
|
||||
defaultMessage: 'IP',
|
||||
}),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
align: 'right',
|
||||
dataType: 'number',
|
||||
field: 'monitor.ip',
|
||||
name: i18n.translate('xpack.uptime.pingList.ipAddressColumnLabel', {
|
||||
defaultMessage: 'IP',
|
||||
}),
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
align: 'center',
|
||||
field: 'monitor.duration.us',
|
||||
name: i18n.translate('xpack.uptime.pingList.durationMsColumnLabel', {
|
||||
defaultMessage: 'Duration',
|
||||
|
@ -281,31 +134,33 @@ export const PingListComponent = (props: Props) => {
|
|||
}),
|
||||
},
|
||||
{
|
||||
align: hasStatus ? 'right' : 'center',
|
||||
field: 'error.type',
|
||||
name: i18n.translate('xpack.uptime.pingList.errorTypeColumnLabel', {
|
||||
defaultMessage: 'Error type',
|
||||
}),
|
||||
render: (errorType: string) => errorType ?? '-',
|
||||
name: ERROR_LABEL,
|
||||
width: '30%',
|
||||
render: (errorType: string, item: Ping) => <PingErrorCol ping={item} errorType={errorType} />,
|
||||
},
|
||||
...(monitorType === MONITOR_TYPES.BROWSER
|
||||
? [
|
||||
{
|
||||
field: 'monitor.status',
|
||||
align: 'left',
|
||||
name: i18n.translate('xpack.uptime.pingList.columns.failedStep', {
|
||||
defaultMessage: 'Failed step',
|
||||
}),
|
||||
render: (timestamp: string, item: Ping) => (
|
||||
<FailedStep ping={item} failedSteps={failedSteps} />
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
// Only add this column is there is any status present in list
|
||||
...(hasStatus
|
||||
? [
|
||||
{
|
||||
field: 'http.response.status_code',
|
||||
align: 'right',
|
||||
name: (
|
||||
<SpanWithMargin>
|
||||
{i18n.translate('xpack.uptime.pingList.responseCodeColumnLabel', {
|
||||
defaultMessage: 'Response code',
|
||||
})}
|
||||
</SpanWithMargin>
|
||||
),
|
||||
render: (statusCode: string) => (
|
||||
<SpanWithMargin>
|
||||
<EuiBadge>{statusCode}</EuiBadge>
|
||||
</SpanWithMargin>
|
||||
),
|
||||
name: <SpanWithMargin>{RES_CODE_LABEL}</SpanWithMargin>,
|
||||
render: (statusCode: string) => <ResponseCodeColumn statusCode={statusCode} />,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
@ -313,23 +168,13 @@ export const PingListComponent = (props: Props) => {
|
|||
align: 'right',
|
||||
width: '24px',
|
||||
isExpander: true,
|
||||
render: (item: Ping) => {
|
||||
return (
|
||||
<EuiButtonIcon
|
||||
data-test-subj="uptimePingListExpandBtn"
|
||||
onClick={() => toggleDetails(item, expandedRows, setExpandedRows)}
|
||||
disabled={!rowShouldExpand(item)}
|
||||
aria-label={
|
||||
expandedRows[item.docId]
|
||||
? i18n.translate('xpack.uptime.pingList.collapseRow', {
|
||||
defaultMessage: 'Collapse',
|
||||
})
|
||||
: i18n.translate('xpack.uptime.pingList.expandRow', { defaultMessage: 'Expand' })
|
||||
}
|
||||
iconType={expandedRows[item.docId] ? 'arrowUp' : 'arrowDown'}
|
||||
/>
|
||||
);
|
||||
},
|
||||
render: (item: Ping) => (
|
||||
<ExpandRowColumn
|
||||
item={item}
|
||||
expandedRows={expandedRows}
|
||||
setExpandedRows={setExpandedRows}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -338,63 +183,12 @@ export const PingListComponent = (props: Props) => {
|
|||
pageIndex,
|
||||
pageSize,
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
/**
|
||||
* we're not currently supporting pagination in this component
|
||||
* so the first page is the only page
|
||||
*/
|
||||
totalItemCount: total,
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPanel>
|
||||
<EuiTitle size="s">
|
||||
<h4>
|
||||
<FormattedMessage id="xpack.uptime.pingList.checkHistoryTitle" defaultMessage="History" />
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFormRow
|
||||
label="Status"
|
||||
aria-label={i18n.translate('xpack.uptime.pingList.statusLabel', {
|
||||
defaultMessage: 'Status',
|
||||
})}
|
||||
>
|
||||
<EuiSelect
|
||||
options={statusOptions}
|
||||
aria-label={i18n.translate('xpack.uptime.pingList.statusLabel', {
|
||||
defaultMessage: 'Status',
|
||||
})}
|
||||
data-test-subj="xpack.uptime.pingList.statusSelect"
|
||||
value={status}
|
||||
onChange={(selected) => {
|
||||
setStatus(selected.target.value);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
label="Location"
|
||||
aria-label={i18n.translate('xpack.uptime.pingList.locationLabel', {
|
||||
defaultMessage: 'Location',
|
||||
})}
|
||||
>
|
||||
<EuiSelect
|
||||
options={locationOptions}
|
||||
value={selectedLocation}
|
||||
aria-label={i18n.translate('xpack.uptime.pingList.locationLabel', {
|
||||
defaultMessage: 'Location',
|
||||
})}
|
||||
data-test-subj="xpack.uptime.pingList.locationSelect"
|
||||
onChange={(selected) => {
|
||||
setSelectedLocation(selected.target.value);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<PingListHeader />
|
||||
<EuiSpacer size="s" />
|
||||
<EuiBasicTable
|
||||
loading={loading}
|
||||
|
@ -410,6 +204,7 @@ export const PingListComponent = (props: Props) => {
|
|||
setPageSize(criteria.page!.size);
|
||||
setPageIndex(criteria.page!.index);
|
||||
}}
|
||||
tableLayout={'auto'}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
|
|
|
@ -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 { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
|
||||
import { StatusFilter } from '../../overview/monitor_list/status_filter';
|
||||
import { FilterGroup } from '../../overview/filter_group';
|
||||
|
||||
export const PingListHeader = () => {
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="s">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.pingList.checkHistoryTitle"
|
||||
defaultMessage="History"
|
||||
/>
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<StatusFilter />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<FilterGroup />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 STATUS_LABEL = i18n.translate('xpack.uptime.pingList.statusColumnLabel', {
|
||||
defaultMessage: 'Status',
|
||||
});
|
||||
|
||||
export const RES_CODE_LABEL = i18n.translate('xpack.uptime.pingList.responseCodeColumnLabel', {
|
||||
defaultMessage: 'Response code',
|
||||
});
|
||||
export const ERROR_TYPE_LABEL = i18n.translate('xpack.uptime.pingList.errorTypeColumnLabel', {
|
||||
defaultMessage: 'Error type',
|
||||
});
|
||||
export const ERROR_LABEL = i18n.translate('xpack.uptime.pingList.errorColumnLabel', {
|
||||
defaultMessage: 'Error',
|
||||
});
|
||||
|
||||
export const LOCATION_LABEL = i18n.translate('xpack.uptime.pingList.locationNameColumnLabel', {
|
||||
defaultMessage: 'Location',
|
||||
});
|
||||
|
||||
export const TIMESTAMP_LABEL = i18n.translate('xpack.uptime.pingList.timestampColumnLabel', {
|
||||
defaultMessage: 'Timestamp',
|
||||
});
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useCallback, useContext, useEffect } from 'react';
|
||||
import { selectPingList } from '../../../state/selectors';
|
||||
import { GetPingsParams, Ping } from '../../../../common/runtime_types/ping';
|
||||
import { getPings as getPingsAction } from '../../../state/actions';
|
||||
import { useGetUrlParams, useMonitorId } from '../../../hooks';
|
||||
import { UptimeRefreshContext, UptimeSettingsContext } from '../../../contexts';
|
||||
import { useFetcher } from '../../../../../observability/public';
|
||||
import { fetchJourneysFailedSteps } from '../../../state/api/journey';
|
||||
import { useSelectedFilters } from '../../../hooks/use_selected_filters';
|
||||
import { MONITOR_TYPES } from '../../../../common/constants';
|
||||
|
||||
interface Props {
|
||||
pageSize: number;
|
||||
pageIndex: number;
|
||||
}
|
||||
|
||||
export const usePingsList = ({ pageSize, pageIndex }: Props) => {
|
||||
const {
|
||||
error,
|
||||
loading,
|
||||
pingList: { pings, total },
|
||||
} = useSelector(selectPingList);
|
||||
|
||||
const { lastRefresh } = useContext(UptimeRefreshContext);
|
||||
|
||||
const { dateRangeStart: from, dateRangeEnd: to } = useContext(UptimeSettingsContext);
|
||||
|
||||
const { statusFilter } = useGetUrlParams();
|
||||
|
||||
const { selectedLocations } = useSelectedFilters();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const monitorId = useMonitorId();
|
||||
|
||||
const getPings = useCallback((params: GetPingsParams) => dispatch(getPingsAction(params)), [
|
||||
dispatch,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
getPings({
|
||||
monitorId,
|
||||
dateRange: {
|
||||
from,
|
||||
to,
|
||||
},
|
||||
locations: JSON.stringify(selectedLocations),
|
||||
index: pageIndex,
|
||||
size: pageSize,
|
||||
status: statusFilter !== 'all' ? statusFilter : '',
|
||||
});
|
||||
}, [
|
||||
from,
|
||||
to,
|
||||
getPings,
|
||||
monitorId,
|
||||
lastRefresh,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
statusFilter,
|
||||
selectedLocations,
|
||||
]);
|
||||
|
||||
const { data } = useFetcher(() => {
|
||||
if (pings?.length > 0 && pings.find((ping) => ping.monitor.type === MONITOR_TYPES.BROWSER))
|
||||
return fetchJourneysFailedSteps({
|
||||
checkGroups: pings.map((ping: Ping) => ping.monitor.check_group!),
|
||||
});
|
||||
}, [pings]);
|
||||
|
||||
return { error, loading, pings, total, failedSteps: data };
|
||||
};
|
|
@ -13,17 +13,17 @@ Array [
|
|||
class="euiSpacer euiSpacer--l"
|
||||
/>,
|
||||
.c0.c0.c0 {
|
||||
width: 35%;
|
||||
width: 30%;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.c1.c1.c1 {
|
||||
width: 65%;
|
||||
width: 70%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
<dl
|
||||
class="euiDescriptionList euiDescriptionList--column euiDescriptionList--reverse euiDescriptionList--compressed"
|
||||
style="max-width:450px"
|
||||
>
|
||||
<dt
|
||||
class="euiDescriptionList__title c0"
|
||||
|
@ -51,10 +51,6 @@ Array [
|
|||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
|
||||
<span
|
||||
data-euiicon-type="popout"
|
||||
/>
|
||||
<span
|
||||
aria-label="External link"
|
||||
class="euiLink__externalIcon"
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
exports[`SSL Certificate component renders 1`] = `
|
||||
Array [
|
||||
.c0.c0.c0 {
|
||||
width: 35%;
|
||||
width: 30%;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
<dt
|
||||
|
@ -15,7 +16,7 @@ Array [
|
|||
class="euiSpacer euiSpacer--s"
|
||||
/>,
|
||||
.c0.c0.c0 {
|
||||
width: 65%;
|
||||
width: 70%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
|
@ -57,7 +58,8 @@ Array [
|
|||
exports[`SSL Certificate component renders null if invalid date 1`] = `
|
||||
Array [
|
||||
.c0.c0.c0 {
|
||||
width: 35%;
|
||||
width: 30%;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
<dt
|
||||
|
@ -69,7 +71,7 @@ Array [
|
|||
class="euiSpacer euiSpacer--s"
|
||||
/>,
|
||||
.c0.c0.c0 {
|
||||
width: 65%;
|
||||
width: 70%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ Array [
|
|||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#d3dae6;color:#000"
|
||||
style="background-color:#6dccb1;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
|
@ -113,13 +113,7 @@ Array [
|
|||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<span
|
||||
class="euiTextColor euiTextColor--default"
|
||||
>
|
||||
<h4>
|
||||
au-heartbeat
|
||||
</h4>
|
||||
</span>
|
||||
au-heartbeat
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -182,7 +176,7 @@ Array [
|
|||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#d3dae6;color:#000"
|
||||
style="background-color:#ff7e62;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
|
@ -190,13 +184,7 @@ Array [
|
|||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<span
|
||||
class="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
<h4>
|
||||
nyc-heartbeat
|
||||
</h4>
|
||||
</span>
|
||||
nyc-heartbeat
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -259,7 +247,7 @@ Array [
|
|||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#d3dae6;color:#000"
|
||||
style="background-color:#ff7e62;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
|
@ -267,13 +255,7 @@ Array [
|
|||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<span
|
||||
class="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
<h4>
|
||||
spa-heartbeat
|
||||
</h4>
|
||||
</span>
|
||||
spa-heartbeat
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
|
|
@ -11,21 +11,21 @@ exports[`LocationStatusTags component renders properly against props 1`] = `
|
|||
"color": "#d3dae6",
|
||||
"label": "Berlin",
|
||||
"status": "up",
|
||||
"timestamp": "1 Mon ago",
|
||||
"timestamp": "Sept 4, 2020 9:31:38 AM",
|
||||
},
|
||||
Object {
|
||||
"availability": 100,
|
||||
"color": "#bd271e",
|
||||
"label": "Berlin",
|
||||
"status": "down",
|
||||
"timestamp": "1 Mon ago",
|
||||
"timestamp": "Sept 4, 2020 9:31:38 AM",
|
||||
},
|
||||
Object {
|
||||
"availability": 100,
|
||||
"color": "#d3dae6",
|
||||
"label": "Islamabad",
|
||||
"status": "up",
|
||||
"timestamp": "1 Mon ago",
|
||||
"timestamp": "Sept 4, 2020 9:31:38 AM",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ exports[`LocationStatusTags component renders when all locations are down 1`] =
|
|||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
style="background-color:#ff7e62;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
|
@ -150,13 +150,7 @@ exports[`LocationStatusTags component renders when all locations are down 1`] =
|
|||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<span
|
||||
class="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
<h4>
|
||||
Berlin
|
||||
</h4>
|
||||
</span>
|
||||
Berlin
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -195,7 +189,7 @@ exports[`LocationStatusTags component renders when all locations are down 1`] =
|
|||
<span
|
||||
class="euiTableCellContent__text"
|
||||
>
|
||||
5m ago
|
||||
Sept 4, 2020 9:31:38 AM
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -219,7 +213,7 @@ exports[`LocationStatusTags component renders when all locations are down 1`] =
|
|||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
style="background-color:#ff7e62;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
|
@ -227,13 +221,7 @@ exports[`LocationStatusTags component renders when all locations are down 1`] =
|
|||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<span
|
||||
class="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
<h4>
|
||||
Islamabad
|
||||
</h4>
|
||||
</span>
|
||||
Islamabad
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -272,7 +260,7 @@ exports[`LocationStatusTags component renders when all locations are down 1`] =
|
|||
<span
|
||||
class="euiTableCellContent__text"
|
||||
>
|
||||
5s ago
|
||||
Sept 4, 2020 9:31:38 AM
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -392,7 +380,7 @@ exports[`LocationStatusTags component renders when all locations are up 1`] = `
|
|||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#d3dae6;color:#000"
|
||||
style="background-color:#6dccb1;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
|
@ -400,13 +388,7 @@ exports[`LocationStatusTags component renders when all locations are up 1`] = `
|
|||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<span
|
||||
class="euiTextColor euiTextColor--default"
|
||||
>
|
||||
<h4>
|
||||
Berlin
|
||||
</h4>
|
||||
</span>
|
||||
Berlin
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -445,7 +427,7 @@ exports[`LocationStatusTags component renders when all locations are up 1`] = `
|
|||
<span
|
||||
class="euiTableCellContent__text"
|
||||
>
|
||||
5d ago
|
||||
Sept 4, 2020 9:31:38 AM
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -469,7 +451,7 @@ exports[`LocationStatusTags component renders when all locations are up 1`] = `
|
|||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#d3dae6;color:#000"
|
||||
style="background-color:#6dccb1;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
|
@ -477,13 +459,7 @@ exports[`LocationStatusTags component renders when all locations are up 1`] = `
|
|||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<span
|
||||
class="euiTextColor euiTextColor--default"
|
||||
>
|
||||
<h4>
|
||||
Islamabad
|
||||
</h4>
|
||||
</span>
|
||||
Islamabad
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -522,7 +498,7 @@ exports[`LocationStatusTags component renders when all locations are up 1`] = `
|
|||
<span
|
||||
class="euiTableCellContent__text"
|
||||
>
|
||||
5s ago
|
||||
Sept 4, 2020 9:31:38 AM
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -642,7 +618,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
|
|||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
style="background-color:#ff7e62;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
|
@ -650,13 +626,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
|
|||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<span
|
||||
class="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
<h4>
|
||||
Berlin
|
||||
</h4>
|
||||
</span>
|
||||
Berlin
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -695,7 +665,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
|
|||
<span
|
||||
class="euiTableCellContent__text"
|
||||
>
|
||||
5m ago
|
||||
Sept 4, 2020 9:31:38 AM
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -719,7 +689,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
|
|||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
style="background-color:#ff7e62;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
|
@ -727,13 +697,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
|
|||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<span
|
||||
class="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
<h4>
|
||||
Islamabad
|
||||
</h4>
|
||||
</span>
|
||||
Islamabad
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -772,7 +736,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
|
|||
<span
|
||||
class="euiTableCellContent__text"
|
||||
>
|
||||
5s ago
|
||||
Sept 4, 2020 9:31:38 AM
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -796,7 +760,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
|
|||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
style="background-color:#ff7e62;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
|
@ -804,13 +768,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
|
|||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<span
|
||||
class="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
<h4>
|
||||
New York
|
||||
</h4>
|
||||
</span>
|
||||
New York
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -849,7 +807,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
|
|||
<span
|
||||
class="euiTableCellContent__text"
|
||||
>
|
||||
1 Mon ago
|
||||
Sept 4, 2020 9:31:38 AM
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -873,7 +831,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
|
|||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
style="background-color:#ff7e62;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
|
@ -881,13 +839,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
|
|||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<span
|
||||
class="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
<h4>
|
||||
Paris
|
||||
</h4>
|
||||
</span>
|
||||
Paris
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -926,7 +878,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
|
|||
<span
|
||||
class="euiTableCellContent__text"
|
||||
>
|
||||
5 Yr ago
|
||||
Sept 4, 2020 9:31:38 AM
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -950,7 +902,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
|
|||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#bd271e;color:#fff"
|
||||
style="background-color:#ff7e62;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
|
@ -958,13 +910,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
|
|||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<span
|
||||
class="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
<h4>
|
||||
Sydney
|
||||
</h4>
|
||||
</span>
|
||||
Sydney
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -1003,7 +949,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
|
|||
<span
|
||||
class="euiTableCellContent__text"
|
||||
>
|
||||
5 Yr ago
|
||||
Sept 4, 2020 9:31:38 AM
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -18,7 +18,7 @@ exports[`TagLabel component renders correctly against snapshot 1`] = `
|
|||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color:#fff;color:#000"
|
||||
style="background-color:#ff7e62;color:#000"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
|
@ -26,13 +26,7 @@ exports[`TagLabel component renders correctly against snapshot 1`] = `
|
|||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
<span
|
||||
class="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
<h4>
|
||||
US-East
|
||||
</h4>
|
||||
</span>
|
||||
US-East
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -42,15 +36,9 @@ exports[`TagLabel component renders correctly against snapshot 1`] = `
|
|||
exports[`TagLabel component shallow render correctly against snapshot 1`] = `
|
||||
<styled.div>
|
||||
<EuiBadge
|
||||
color="#fff"
|
||||
color="secondary"
|
||||
>
|
||||
<EuiTextColor
|
||||
color="default"
|
||||
>
|
||||
<h4>
|
||||
US-East
|
||||
</h4>
|
||||
</EuiTextColor>
|
||||
US-East
|
||||
</EuiBadge>
|
||||
</styled.div>
|
||||
`;
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { renderWithIntl, shallowWithIntl } from '@kbn/test/jest';
|
||||
import { MonitorLocation } from '../../../../../../common/runtime_types/monitor';
|
||||
import { LocationStatusTags } from '../index';
|
||||
import { mockMoment } from '../../../../../lib/helper/test_helpers';
|
||||
|
||||
mockMoment();
|
||||
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => {
|
||||
return {
|
||||
|
@ -24,21 +26,21 @@ describe('LocationStatusTags component', () => {
|
|||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 'w').toISOString(),
|
||||
timestamp: 'Oct 26, 2020 7:49:20 AM',
|
||||
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(),
|
||||
timestamp: 'Oct 26, 2020 7:49:20 AM',
|
||||
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(),
|
||||
timestamp: 'Oct 26, 2020 7:49:20 AM',
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
|
@ -52,56 +54,56 @@ describe('LocationStatusTags component', () => {
|
|||
{
|
||||
summary: { up: 0, down: 1 },
|
||||
geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 's').toISOString(),
|
||||
timestamp: 'Oct 26, 2020 7:49:20 AM',
|
||||
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(),
|
||||
timestamp: 'Oct 26, 2020 7:49:20 AM',
|
||||
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(),
|
||||
timestamp: 'Oct 26, 2020 7:49:20 AM',
|
||||
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(),
|
||||
timestamp: 'Oct 26, 2020 7:49:20 AM',
|
||||
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(),
|
||||
timestamp: 'Oct 26, 2020 7:49:20 AM',
|
||||
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(),
|
||||
timestamp: 'Oct 26, 2020 7:49:20 AM',
|
||||
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(),
|
||||
timestamp: 'Oct 26, 2020 7:49:20 AM',
|
||||
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(),
|
||||
timestamp: 'Oct 26, 2020 7:49:20 AM',
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
|
@ -115,14 +117,14 @@ describe('LocationStatusTags component', () => {
|
|||
{
|
||||
summary: { up: 4, down: 0 },
|
||||
geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 's').toISOString(),
|
||||
timestamp: 'Oct 26, 2020 7:49:20 AM',
|
||||
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(),
|
||||
timestamp: 'Oct 26, 2020 7:49:20 AM',
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
|
@ -136,14 +138,14 @@ describe('LocationStatusTags component', () => {
|
|||
{
|
||||
summary: { up: 0, down: 2 },
|
||||
geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } },
|
||||
timestamp: moment().subtract('5', 's').toISOString(),
|
||||
timestamp: 'Oct 26, 2020 7:49:20 AM',
|
||||
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(),
|
||||
timestamp: 'Oct 26, 2020 7:49:20 AM',
|
||||
up_history: 4,
|
||||
down_history: 0,
|
||||
},
|
||||
|
|
|
@ -11,6 +11,7 @@ import { UptimeThemeContext } from '../../../../contexts';
|
|||
import { MonitorLocation } from '../../../../../common/runtime_types';
|
||||
import { SHORT_TIMESPAN_LOCALE, SHORT_TS_LOCALE } from '../../../../../common/constants';
|
||||
import { AvailabilityReporting } from '../index';
|
||||
import { getShortTimeStamp } from '../../../overview/monitor_list/columns/monitor_status_column';
|
||||
|
||||
// Set height so that it remains within panel, enough height to display 7 locations tags
|
||||
const TagContainer = styled.div`
|
||||
|
@ -46,7 +47,7 @@ export const LocationStatusTags = ({ locations }: Props) => {
|
|||
locations.forEach((item: MonitorLocation) => {
|
||||
allLocations.push({
|
||||
label: item.geo.name!,
|
||||
timestamp: moment(new Date(item.timestamp).valueOf()).fromNow(),
|
||||
timestamp: getShortTimeStamp(moment(new Date(item.timestamp).valueOf())),
|
||||
color: item.summary.down === 0 ? gray : danger,
|
||||
availability: (item.up_history / (item.up_history + item.down_history)) * 100,
|
||||
status: item.summary.down === 0 ? 'up' : 'down',
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiBadge, EuiTextColor } from '@elastic/eui';
|
||||
import { EuiBadge } from '@elastic/eui';
|
||||
import { StatusTag } from './location_status_tags';
|
||||
import { STATUS } from '../../../../../common/constants';
|
||||
|
||||
const BadgeItem = styled.div`
|
||||
white-space: nowrap;
|
||||
|
@ -21,11 +22,7 @@ const BadgeItem = styled.div`
|
|||
export const TagLabel: React.FC<StatusTag> = ({ color, label, status }) => {
|
||||
return (
|
||||
<BadgeItem>
|
||||
<EuiBadge color={color}>
|
||||
<EuiTextColor color={status === 'down' ? 'ghost' : 'default'}>
|
||||
<h4>{label}</h4>
|
||||
</EuiTextColor>
|
||||
</EuiBadge>
|
||||
<EuiBadge color={status === STATUS.DOWN ? 'danger' : 'secondary'}>{label}</EuiBadge>
|
||||
</BadgeItem>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,7 +8,6 @@ import React from 'react';
|
|||
import styled from 'styled-components';
|
||||
import {
|
||||
EuiLink,
|
||||
EuiIcon,
|
||||
EuiSpacer,
|
||||
EuiDescriptionList,
|
||||
EuiDescriptionListTitle,
|
||||
|
@ -27,13 +26,14 @@ import { MonitorRedirects } from './monitor_redirects';
|
|||
|
||||
export const MonListTitle = styled(EuiDescriptionListTitle)`
|
||||
&&& {
|
||||
width: 35%;
|
||||
width: 30%;
|
||||
max-width: 250px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MonListDescription = styled(EuiDescriptionListDescription)`
|
||||
&&& {
|
||||
width: 65%;
|
||||
width: 70%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
`;
|
||||
|
@ -53,12 +53,7 @@ export const MonitorStatusBar: React.FC = () => {
|
|||
<StatusByLocations locations={locations ?? []} />
|
||||
</div>
|
||||
<EuiSpacer />
|
||||
<EuiDescriptionList
|
||||
type="column"
|
||||
compressed={true}
|
||||
textStyle="reverse"
|
||||
style={{ maxWidth: '450px' }}
|
||||
>
|
||||
<EuiDescriptionList type="column" compressed={true} textStyle="reverse">
|
||||
<MonListTitle>{OverallAvailability}</MonListTitle>
|
||||
<MonListDescription data-test-subj="uptimeOverallAvailability">
|
||||
<FormattedMessage
|
||||
|
@ -70,8 +65,8 @@ export const MonitorStatusBar: React.FC = () => {
|
|||
</MonListDescription>
|
||||
<MonListTitle>{URL_LABEL}</MonListTitle>
|
||||
<MonListDescription>
|
||||
<EuiLink aria-label={labels.monitorUrlLinkAriaLabel} href={full} target="_blank">
|
||||
{full} <EuiIcon type={'popout'} size="s" />
|
||||
<EuiLink aria-label={labels.monitorUrlLinkAriaLabel} href={full} target="_blank" external>
|
||||
{full}
|
||||
</EuiLink>
|
||||
</MonListDescription>
|
||||
<MonListTitle>{MonitorIDLabel}</MonListTitle>
|
||||
|
|
|
@ -13,17 +13,6 @@ export const healthStatusMessageAriaLabel = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
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 typeLabel = i18n.translate('xpack.uptime.monitorStatusBar.type.label', {
|
||||
defaultMessage: 'Type',
|
||||
});
|
||||
|
|
|
@ -7,10 +7,13 @@
|
|||
import React, { useState } from 'react';
|
||||
import { EuiFilterGroup } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { useRouteMatch } from 'react-router-dom';
|
||||
import { FilterPopoverProps, FilterPopover } from './filter_popover';
|
||||
import { OverviewFilters } from '../../../../common/runtime_types/overview_filters';
|
||||
import { filterLabels } from './translations';
|
||||
import { useFilterUpdate } from '../../../hooks/use_filter_update';
|
||||
import { MONITOR_ROUTE } from '../../../../common/constants';
|
||||
import { useSelectedFilters } from '../../../hooks/use_selected_filters';
|
||||
|
||||
interface PresentationalComponentProps {
|
||||
loading: boolean;
|
||||
|
@ -32,15 +35,16 @@ export const FilterGroupComponent: React.FC<PresentationalComponentProps> = ({
|
|||
values: string[];
|
||||
}>({ fieldName: '', values: [] });
|
||||
|
||||
const { selectedLocations, selectedPorts, selectedSchemes, selectedTags } = useFilterUpdate(
|
||||
updatedFieldValues.fieldName,
|
||||
updatedFieldValues.values
|
||||
);
|
||||
useFilterUpdate(updatedFieldValues.fieldName, updatedFieldValues.values);
|
||||
|
||||
const { selectedLocations, selectedPorts, selectedSchemes, selectedTags } = useSelectedFilters();
|
||||
|
||||
const onFilterFieldChange = (fieldName: string, values: string[]) => {
|
||||
setUpdatedFieldValues({ fieldName, values });
|
||||
};
|
||||
|
||||
const isMonitorPage = useRouteMatch(MONITOR_ROUTE);
|
||||
|
||||
const filterPopoverProps: FilterPopoverProps[] = [
|
||||
{
|
||||
loading,
|
||||
|
@ -51,36 +55,41 @@ export const FilterGroupComponent: React.FC<PresentationalComponentProps> = ({
|
|||
selectedItems: selectedLocations,
|
||||
title: filterLabels.LOCATION,
|
||||
},
|
||||
{
|
||||
loading,
|
||||
onFilterFieldChange,
|
||||
fieldName: 'url.port',
|
||||
id: 'port',
|
||||
disabled: ports.length === 0,
|
||||
items: ports.map((p: number) => p.toString()),
|
||||
selectedItems: selectedPorts,
|
||||
title: filterLabels.PORT,
|
||||
},
|
||||
{
|
||||
loading,
|
||||
onFilterFieldChange,
|
||||
fieldName: 'monitor.type',
|
||||
id: 'scheme',
|
||||
disabled: schemes.length === 0,
|
||||
items: schemes,
|
||||
selectedItems: selectedSchemes,
|
||||
title: filterLabels.SCHEME,
|
||||
},
|
||||
{
|
||||
loading,
|
||||
onFilterFieldChange,
|
||||
fieldName: 'tags',
|
||||
id: 'tags',
|
||||
disabled: tags.length === 0,
|
||||
items: tags,
|
||||
selectedItems: selectedTags,
|
||||
title: filterLabels.TAGS,
|
||||
},
|
||||
// on monitor page we only display location filter in ping list
|
||||
...(!isMonitorPage
|
||||
? [
|
||||
{
|
||||
loading,
|
||||
onFilterFieldChange,
|
||||
fieldName: 'url.port',
|
||||
id: 'port',
|
||||
disabled: ports.length === 0,
|
||||
items: ports.map((p: number) => p.toString()),
|
||||
selectedItems: selectedPorts,
|
||||
title: filterLabels.PORT,
|
||||
},
|
||||
{
|
||||
loading,
|
||||
onFilterFieldChange,
|
||||
fieldName: 'monitor.type',
|
||||
id: 'scheme',
|
||||
disabled: schemes.length === 0,
|
||||
items: schemes,
|
||||
selectedItems: selectedSchemes,
|
||||
title: filterLabels.SCHEME,
|
||||
},
|
||||
{
|
||||
loading,
|
||||
onFilterFieldChange,
|
||||
fieldName: 'tags',
|
||||
id: 'tags',
|
||||
disabled: tags.length === 0,
|
||||
items: tags,
|
||||
selectedItems: selectedTags,
|
||||
title: filterLabels.TAGS,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
|
@ -18,9 +18,9 @@ import {
|
|||
SHORT_TS_LOCALE,
|
||||
} from '../../../../../common/constants';
|
||||
|
||||
import * as labels from '../translations';
|
||||
import { UptimeThemeContext } from '../../../../contexts';
|
||||
import { euiStyled } from '../../../../../../observability/public';
|
||||
import { STATUS_DOWN_LABEL, STATUS_UP_LABEL } from '../../../common/translations';
|
||||
|
||||
interface MonitorListStatusColumnProps {
|
||||
status: string;
|
||||
|
@ -37,9 +37,9 @@ const StatusColumnFlexG = styled(EuiFlexGroup)`
|
|||
export const getHealthMessage = (status: string): string | null => {
|
||||
switch (status) {
|
||||
case STATUS.UP:
|
||||
return labels.UP;
|
||||
return STATUS_UP_LABEL;
|
||||
case STATUS.DOWN:
|
||||
return labels.DOWN;
|
||||
return STATUS_DOWN_LABEL;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ exports[`MostRecentError component renders properly with mock data 1`] = `
|
|||
>
|
||||
<a
|
||||
data-test-subj="monitor-page-link-bad-ssl"
|
||||
href="/monitor/YmFkLXNzbA==/?selectedPingStatus=down"
|
||||
href="/monitor/YmFkLXNzbA==/?statusFilter=down"
|
||||
>
|
||||
Get https://expired.badssl.com: x509: certificate has expired or is not yet valid
|
||||
</a>
|
||||
|
|
|
@ -35,7 +35,7 @@ interface MostRecentErrorProps {
|
|||
|
||||
export const MostRecentError = ({ error, monitorId, timestamp }: MostRecentErrorProps) => {
|
||||
const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = useGetUrlParams();
|
||||
params.selectedPingStatus = 'down';
|
||||
params.statusFilter = 'down';
|
||||
const linkParameters = stringifyUrlParams(params, true);
|
||||
|
||||
const timestampStr = timestamp ? moment(new Date(timestamp).valueOf()).fromNow() : '';
|
||||
|
|
|
@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { EuiFilterGroup } from '@elastic/eui';
|
||||
import { FilterStatusButton } from './filter_status_button';
|
||||
import { useGetUrlParams } from '../../../hooks';
|
||||
import { STATUS_DOWN_LABEL, STATUS_UP_LABEL } from '../../common/translations';
|
||||
|
||||
export const StatusFilter: React.FC = () => {
|
||||
const { statusFilter } = useGetUrlParams();
|
||||
|
@ -28,18 +29,14 @@ export const StatusFilter: React.FC = () => {
|
|||
isActive={statusFilter === ''}
|
||||
/>
|
||||
<FilterStatusButton
|
||||
content={i18n.translate('xpack.uptime.filterBar.filterUpLabel', {
|
||||
defaultMessage: 'Up',
|
||||
})}
|
||||
content={STATUS_UP_LABEL}
|
||||
dataTestSubj="xpack.uptime.filterBar.filterStatusUp"
|
||||
value="up"
|
||||
withNext={true}
|
||||
isActive={statusFilter === 'up'}
|
||||
/>
|
||||
<FilterStatusButton
|
||||
content={i18n.translate('xpack.uptime.filterBar.filterDownLabel', {
|
||||
defaultMessage: 'Down',
|
||||
})}
|
||||
content={STATUS_DOWN_LABEL}
|
||||
dataTestSubj="xpack.uptime.filterBar.filterStatusDown"
|
||||
value="down"
|
||||
withNext={false}
|
||||
|
|
|
@ -62,14 +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 UP = i18n.translate('xpack.uptime.monitorList.statusColumn.upLabel', {
|
||||
defaultMessage: 'Up',
|
||||
});
|
||||
|
||||
export const DOWN = i18n.translate('xpack.uptime.monitorList.statusColumn.downLabel', {
|
||||
defaultMessage: 'Down',
|
||||
});
|
||||
|
||||
export const RESPONSE_ANOMALY_SCORE = i18n.translate(
|
||||
'xpack.uptime.monitorList.anomalyColumn.label',
|
||||
{
|
||||
|
|
|
@ -209,7 +209,7 @@ exports[`useUrlParams deletes keys that do not have truthy values 1`] = `
|
|||
}
|
||||
>
|
||||
<div>
|
||||
{"absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-12","dateRangeEnd":"now","filters":"","search":"","selectedPingStatus":"","statusFilter":"","pagination":"foo","focusConnectorField":false}
|
||||
{"pagination":"foo","absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-12","dateRangeEnd":"now","filters":"","search":"","statusFilter":"","focusConnectorField":false}
|
||||
</div>
|
||||
<button
|
||||
id="setUrlParams"
|
||||
|
@ -433,7 +433,7 @@ exports[`useUrlParams gets the expected values using the context 1`] = `
|
|||
hook={[Function]}
|
||||
>
|
||||
<div>
|
||||
{"absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-15m","dateRangeEnd":"now","filters":"","search":"","selectedPingStatus":"","statusFilter":"","focusConnectorField":false}
|
||||
{"absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-15m","dateRangeEnd":"now","filters":"","search":"","statusFilter":"","focusConnectorField":false}
|
||||
</div>
|
||||
<button
|
||||
id="setUrlParams"
|
||||
|
|
|
@ -27,6 +27,7 @@ export const makeBaseBreadcrumb = (
|
|||
// values in dateRangeStart are better for a URL.
|
||||
delete crumbParams.absoluteDateRangeStart;
|
||||
delete crumbParams.absoluteDateRangeEnd;
|
||||
delete crumbParams.statusFilter;
|
||||
const query = stringifyUrlParams(crumbParams, true);
|
||||
href += query === EMPTY_QUERY ? '' : query;
|
||||
}
|
||||
|
|
|
@ -7,24 +7,11 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useUrlParams } from './use_url_params';
|
||||
|
||||
/**
|
||||
* Handle an added or removed value to filter against for an uptime field.
|
||||
* @param fieldName the name of the field to filter against
|
||||
* @param values the list of values to use when filter a field
|
||||
*/
|
||||
interface SelectedFilters {
|
||||
selectedTags: string[];
|
||||
selectedPorts: string[];
|
||||
selectedSchemes: string[];
|
||||
selectedLocations: string[];
|
||||
selectedFilters: Map<string, string[]>;
|
||||
}
|
||||
|
||||
export const useFilterUpdate = (
|
||||
fieldName?: string,
|
||||
values?: string[],
|
||||
shouldUpdateUrl: boolean = true
|
||||
): SelectedFilters => {
|
||||
) => {
|
||||
const [getUrlParams, updateUrl] = useUrlParams();
|
||||
|
||||
const { filters: currentFilters } = getUrlParams();
|
||||
|
@ -62,12 +49,4 @@ export const useFilterUpdate = (
|
|||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fieldName, values]);
|
||||
|
||||
return {
|
||||
selectedTags: filterKueries.get('tags') || [],
|
||||
selectedPorts: filterKueries.get('url.port') || [],
|
||||
selectedSchemes: filterKueries.get('monitor.type') || [],
|
||||
selectedLocations: filterKueries.get('observer.geo.name') || [],
|
||||
selectedFilters: filterKueries,
|
||||
};
|
||||
};
|
||||
|
|
28
x-pack/plugins/uptime/public/hooks/use_selected_filters.ts
Normal file
28
x-pack/plugins/uptime/public/hooks/use_selected_filters.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { useMemo } from 'react';
|
||||
import { useGetUrlParams } from './use_url_params';
|
||||
|
||||
export const useSelectedFilters = () => {
|
||||
const { filters } = useGetUrlParams();
|
||||
|
||||
return useMemo(() => {
|
||||
let selectedFilters: Map<string, string[]>;
|
||||
try {
|
||||
selectedFilters = new Map<string, string[]>(JSON.parse(filters));
|
||||
} catch {
|
||||
selectedFilters = new Map<string, string[]>();
|
||||
}
|
||||
|
||||
return {
|
||||
selectedTags: selectedFilters.get('tags') || [],
|
||||
selectedPorts: selectedFilters.get('url.port') || [],
|
||||
selectedSchemes: selectedFilters.get('monitor.type') || [],
|
||||
selectedLocations: selectedFilters.get('observer.geo.name') || [],
|
||||
};
|
||||
}, [filters]);
|
||||
};
|
|
@ -66,7 +66,6 @@ export const mockStore = {
|
|||
loading: false,
|
||||
pingList: {
|
||||
total: 0,
|
||||
locations: [],
|
||||
pings: [],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -16,11 +16,10 @@ describe('stringifyUrlParams', () => {
|
|||
filters: 'monitor.id: bar',
|
||||
focusConnectorField: true,
|
||||
search: 'monitor.id: foo',
|
||||
selectedPingStatus: 'down',
|
||||
statusFilter: 'up',
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"?autorefreshInterval=50000&autorefreshIsPaused=false&dateRangeStart=now-15m&dateRangeEnd=now&filters=monitor.id%3A%20bar&focusConnectorField=true&search=monitor.id%3A%20foo&selectedPingStatus=down&statusFilter=up"`
|
||||
`"?autorefreshInterval=50000&autorefreshIsPaused=false&dateRangeStart=now-15m&dateRangeEnd=now&filters=monitor.id%3A%20bar&focusConnectorField=true&search=monitor.id%3A%20foo&statusFilter=up"`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -34,7 +33,6 @@ describe('stringifyUrlParams', () => {
|
|||
filters: 'monitor.id: bar',
|
||||
focusConnectorField: false,
|
||||
search: undefined,
|
||||
selectedPingStatus: undefined,
|
||||
statusFilter: '',
|
||||
pagination: undefined,
|
||||
},
|
||||
|
@ -46,6 +44,5 @@ describe('stringifyUrlParams', () => {
|
|||
|
||||
expect(result.includes('pagination')).toBeFalsy();
|
||||
expect(result.includes('search')).toBeFalsy();
|
||||
expect(result.includes('selectedPingStatus')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import moment from 'moment';
|
||||
import { Moment } from 'moment-timezone';
|
||||
import * as redux from 'react-redux';
|
||||
|
||||
export function mockMoment() {
|
||||
// avoid timezone issues
|
||||
|
@ -20,3 +21,9 @@ export function mockMoment() {
|
|||
return `15 minutes ago`;
|
||||
});
|
||||
}
|
||||
|
||||
export function mockReduxHooks(response?: any) {
|
||||
jest.spyOn(redux, 'useDispatch').mockReturnValue(jest.fn());
|
||||
|
||||
jest.spyOn(redux, 'useSelector').mockReturnValue(response);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ Object {
|
|||
"focusConnectorField": false,
|
||||
"pagination": undefined,
|
||||
"search": "",
|
||||
"selectedPingStatus": "",
|
||||
"statusFilter": "",
|
||||
}
|
||||
`;
|
||||
|
@ -29,7 +28,6 @@ Object {
|
|||
"focusConnectorField": false,
|
||||
"pagination": undefined,
|
||||
"search": "",
|
||||
"selectedPingStatus": "",
|
||||
"statusFilter": "",
|
||||
}
|
||||
`;
|
||||
|
@ -46,7 +44,6 @@ Object {
|
|||
"focusConnectorField": false,
|
||||
"pagination": undefined,
|
||||
"search": "monitor.status: down",
|
||||
"selectedPingStatus": "up",
|
||||
"statusFilter": "",
|
||||
}
|
||||
`;
|
||||
|
@ -63,7 +60,6 @@ Object {
|
|||
"focusConnectorField": false,
|
||||
"pagination": undefined,
|
||||
"search": "",
|
||||
"selectedPingStatus": "",
|
||||
"statusFilter": "",
|
||||
}
|
||||
`;
|
||||
|
@ -80,7 +76,6 @@ Object {
|
|||
"focusConnectorField": false,
|
||||
"pagination": undefined,
|
||||
"search": "",
|
||||
"selectedPingStatus": "",
|
||||
"statusFilter": "",
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -46,7 +46,6 @@ describe('getSupportedUrlParams', () => {
|
|||
DATE_RANGE_END,
|
||||
FILTERS,
|
||||
SEARCH,
|
||||
SELECTED_PING_LIST_STATUS,
|
||||
STATUS_FILTER,
|
||||
} = CLIENT_DEFAULTS;
|
||||
const result = getSupportedUrlParams({});
|
||||
|
@ -62,7 +61,6 @@ describe('getSupportedUrlParams', () => {
|
|||
focusConnectorField: false,
|
||||
pagination: undefined,
|
||||
search: SEARCH,
|
||||
selectedPingStatus: SELECTED_PING_LIST_STATUS,
|
||||
statusFilter: STATUS_FILTER,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,7 +19,6 @@ export interface UptimeUrlParams {
|
|||
pagination?: string;
|
||||
filters: string;
|
||||
search: string;
|
||||
selectedPingStatus: string;
|
||||
statusFilter: string;
|
||||
focusConnectorField?: boolean;
|
||||
}
|
||||
|
@ -32,7 +31,6 @@ const {
|
|||
DATE_RANGE_START,
|
||||
DATE_RANGE_END,
|
||||
SEARCH,
|
||||
SELECTED_PING_LIST_STATUS,
|
||||
FILTERS,
|
||||
STATUS_FILTER,
|
||||
} = CLIENT_DEFAULTS;
|
||||
|
@ -73,13 +71,13 @@ export const getSupportedUrlParams = (params: {
|
|||
dateRangeEnd,
|
||||
filters,
|
||||
search,
|
||||
selectedPingStatus,
|
||||
statusFilter,
|
||||
pagination,
|
||||
focusConnectorField,
|
||||
} = filteredParams;
|
||||
|
||||
return {
|
||||
pagination,
|
||||
absoluteDateRangeStart: parseAbsoluteDate(
|
||||
dateRangeStart || DATE_RANGE_START,
|
||||
ABSOLUTE_DATE_RANGE_START
|
||||
|
@ -95,10 +93,7 @@ export const getSupportedUrlParams = (params: {
|
|||
dateRangeEnd: dateRangeEnd || DATE_RANGE_END,
|
||||
filters: filters || FILTERS,
|
||||
search: search || SEARCH,
|
||||
selectedPingStatus:
|
||||
selectedPingStatus === undefined ? SELECTED_PING_LIST_STATUS : selectedPingStatus,
|
||||
statusFilter: statusFilter || STATUS_FILTER,
|
||||
pagination,
|
||||
focusConnectorField: !!focusConnectorField,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -82,7 +82,7 @@ export const MonitorPage: React.FC = () => {
|
|||
<EuiSpacer size="s" />
|
||||
<MonitorCharts monitorId={monitorId} />
|
||||
<EuiSpacer size="s" />
|
||||
<PingList monitorId={monitorId} />
|
||||
<PingList />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { HttpFetchError } from 'src/core/public';
|
||||
import { fetchSnapshotCount } from '../snapshot';
|
||||
import { apiService } from '../utils';
|
||||
import { HttpFetchError } from 'src/core/public';
|
||||
|
||||
describe('snapshot API', () => {
|
||||
let fetchMock: jest.SpyInstance<Partial<unknown>>;
|
||||
|
@ -15,8 +15,9 @@ describe('snapshot API', () => {
|
|||
beforeEach(() => {
|
||||
apiService.http = {
|
||||
get: jest.fn(),
|
||||
fetch: jest.fn(),
|
||||
} as any;
|
||||
fetchMock = jest.spyOn(apiService.http, 'get');
|
||||
fetchMock = jest.spyOn(apiService.http, 'fetch');
|
||||
mockResponse = { up: 3, down: 12, total: 15 };
|
||||
});
|
||||
|
||||
|
@ -31,7 +32,9 @@ describe('snapshot API', () => {
|
|||
dateRangeEnd: 'now',
|
||||
filters: 'monitor.id:"auto-http-0X21EE76EAC459873F"',
|
||||
});
|
||||
expect(fetchMock).toHaveBeenCalledWith('/api/uptime/snapshot/count', {
|
||||
expect(fetchMock).toHaveBeenCalledWith({
|
||||
asResponse: false,
|
||||
path: '/api/uptime/snapshot/count',
|
||||
query: {
|
||||
dateRangeEnd: 'now',
|
||||
dateRangeStart: 'now-15m',
|
||||
|
|
|
@ -20,3 +20,40 @@ export async function fetchJourneySteps(
|
|||
SyntheticsJourneyApiResponseType
|
||||
)) as SyntheticsJourneyApiResponse;
|
||||
}
|
||||
|
||||
export async function fetchJourneysFailedSteps({
|
||||
checkGroups,
|
||||
}: {
|
||||
checkGroups: string[];
|
||||
}): Promise<SyntheticsJourneyApiResponse> {
|
||||
return (await apiService.get(
|
||||
`/api/uptime/journeys/failed_steps`,
|
||||
{ checkGroups },
|
||||
SyntheticsJourneyApiResponseType
|
||||
)) as SyntheticsJourneyApiResponse;
|
||||
}
|
||||
|
||||
export async function getJourneyScreenshot(imgSrc: string) {
|
||||
try {
|
||||
const imgRequest = new Request(imgSrc);
|
||||
|
||||
const response = await fetch(imgRequest);
|
||||
|
||||
if (response.status !== 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const imgBlob = await response.blob();
|
||||
|
||||
const stepName = response.headers.get('caption-name');
|
||||
const maxSteps = response.headers.get('max-steps');
|
||||
|
||||
return {
|
||||
stepName,
|
||||
maxSteps: Number(maxSteps ?? 0),
|
||||
src: URL.createObjectURL(imgBlob),
|
||||
};
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,8 +59,8 @@ class ApiService {
|
|||
return ApiService.instance;
|
||||
}
|
||||
|
||||
public async get(apiUrl: string, params?: HttpFetchQuery, decodeType?: any) {
|
||||
const response = await this._http!.get(apiUrl, { query: params });
|
||||
public async get(apiUrl: string, params?: HttpFetchQuery, decodeType?: any, asResponse = false) {
|
||||
const response = await this._http!.fetch({ path: apiUrl, query: params, asResponse });
|
||||
|
||||
if (decodeType) {
|
||||
const decoded = decodeType.decode(response);
|
||||
|
|
|
@ -17,7 +17,6 @@ export interface PingListState {
|
|||
const initialState: PingListState = {
|
||||
pingList: {
|
||||
total: 0,
|
||||
locations: [],
|
||||
pings: [],
|
||||
},
|
||||
loading: false,
|
||||
|
@ -36,6 +35,7 @@ export const pingListReducer = handleActions<PingListState, PingListPayload>(
|
|||
...state,
|
||||
pingList: { ...action.payload },
|
||||
loading: false,
|
||||
error: undefined,
|
||||
}),
|
||||
|
||||
[String(getPingsFail)]: (state, action: Action<Error>) => ({
|
||||
|
|
|
@ -65,7 +65,6 @@ describe('state selectors', () => {
|
|||
loading: false,
|
||||
pingList: {
|
||||
total: 0,
|
||||
locations: [],
|
||||
pings: [],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -127,15 +127,6 @@ describe('getAll', () => {
|
|||
Array [
|
||||
Object {
|
||||
"body": Object {
|
||||
"aggs": Object {
|
||||
"locations": Object {
|
||||
"terms": Object {
|
||||
"field": "observer.geo.name",
|
||||
"missing": "N/A",
|
||||
"size": 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
|
@ -205,15 +196,6 @@ describe('getAll', () => {
|
|||
Array [
|
||||
Object {
|
||||
"body": Object {
|
||||
"aggs": Object {
|
||||
"locations": Object {
|
||||
"terms": Object {
|
||||
"field": "observer.geo.name",
|
||||
"missing": "N/A",
|
||||
"size": 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
|
@ -283,15 +265,6 @@ describe('getAll', () => {
|
|||
Array [
|
||||
Object {
|
||||
"body": Object {
|
||||
"aggs": Object {
|
||||
"locations": Object {
|
||||
"terms": Object {
|
||||
"field": "observer.geo.name",
|
||||
"missing": "N/A",
|
||||
"size": 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
|
@ -361,15 +334,6 @@ describe('getAll', () => {
|
|||
Array [
|
||||
Object {
|
||||
"body": Object {
|
||||
"aggs": Object {
|
||||
"locations": Object {
|
||||
"terms": Object {
|
||||
"field": "observer.geo.name",
|
||||
"missing": "N/A",
|
||||
"size": 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
|
@ -444,15 +408,6 @@ describe('getAll', () => {
|
|||
Array [
|
||||
Object {
|
||||
"body": Object {
|
||||
"aggs": Object {
|
||||
"locations": Object {
|
||||
"terms": Object {
|
||||
"field": "observer.geo.name",
|
||||
"missing": "N/A",
|
||||
"size": 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 { UMElasticsearchQueryFn } from '../adapters/framework';
|
||||
import { Ping } from '../../../common/runtime_types';
|
||||
|
||||
interface GetJourneyStepsParams {
|
||||
checkGroups: string[];
|
||||
}
|
||||
|
||||
export const getJourneyFailedSteps: UMElasticsearchQueryFn<GetJourneyStepsParams, Ping> = async ({
|
||||
uptimeEsClient,
|
||||
checkGroups,
|
||||
}) => {
|
||||
const params = {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
terms: {
|
||||
'synthetics.type': ['step/end'],
|
||||
},
|
||||
},
|
||||
{
|
||||
exists: {
|
||||
field: 'synthetics.error',
|
||||
},
|
||||
},
|
||||
{
|
||||
terms: {
|
||||
'monitor.check_group': checkGroups,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
sort: [{ 'synthetics.step.index': { order: 'asc' } }, { '@timestamp': { order: 'asc' } }],
|
||||
_source: {
|
||||
excludes: ['synthetics.blob'],
|
||||
},
|
||||
size: 500,
|
||||
};
|
||||
|
||||
const { body: result } = await uptimeEsClient.search({ body: params });
|
||||
|
||||
return (result.hits.hits.map((h) => {
|
||||
const source = h._source as Ping & { '@timestamp': string };
|
||||
return {
|
||||
...source,
|
||||
timestamp: source['@timestamp'],
|
||||
};
|
||||
}) as unknown) as Ping;
|
||||
};
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { UMElasticsearchQueryFn } from '../adapters/framework';
|
||||
import { ESSearchBody } from '../../../../../typings/elasticsearch';
|
||||
import { Ping } from '../../../common/runtime_types/ping';
|
||||
|
||||
interface GetJourneyScreenshotParams {
|
||||
checkGroup: string;
|
||||
|
@ -16,7 +16,9 @@ export const getJourneyScreenshot: UMElasticsearchQueryFn<
|
|||
GetJourneyScreenshotParams,
|
||||
any
|
||||
> = async ({ uptimeEsClient, checkGroup, stepIndex }) => {
|
||||
const params: ESSearchBody = {
|
||||
const params = {
|
||||
track_total_hits: true,
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -30,19 +32,38 @@ export const getJourneyScreenshot: UMElasticsearchQueryFn<
|
|||
'synthetics.type': 'step/screenshot',
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'synthetics.step.index': stepIndex,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
_source: ['synthetics.blob'],
|
||||
aggs: {
|
||||
step: {
|
||||
filter: {
|
||||
term: {
|
||||
'synthetics.step.index': stepIndex,
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
image: {
|
||||
top_hits: {
|
||||
size: 1,
|
||||
_source: ['synthetics.blob', 'synthetics.step.name'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const { body: result } = await uptimeEsClient.search({ body: params });
|
||||
if (!Array.isArray(result?.hits?.hits) || result.hits.hits.length < 1) {
|
||||
|
||||
if (result?.hits?.total.value < 1) {
|
||||
return null;
|
||||
}
|
||||
return result.hits.hits.map(({ _source }: any) => _source?.synthetics?.blob ?? null)[0];
|
||||
|
||||
const stepHit = result?.aggregations?.step.image.hits.hits[0]._source as Ping;
|
||||
|
||||
return {
|
||||
blob: stepHit.synthetics?.blob ?? null,
|
||||
stepName: stepHit?.synthetics?.step?.name ?? '',
|
||||
totalSteps: result?.hits?.total.value,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -59,7 +59,7 @@ export const getPings: UMElasticsearchQueryFn<GetPingsParams, PingsResponse> = a
|
|||
status,
|
||||
sort,
|
||||
size: sizeParam,
|
||||
location,
|
||||
locations,
|
||||
}) => {
|
||||
const size = sizeParam ?? DEFAULT_PAGE_SIZE;
|
||||
|
||||
|
@ -77,27 +77,17 @@ export const getPings: UMElasticsearchQueryFn<GetPingsParams, PingsResponse> = a
|
|||
},
|
||||
},
|
||||
sort: [{ '@timestamp': { order: (sort ?? 'desc') as 'asc' | 'desc' } }],
|
||||
aggs: {
|
||||
locations: {
|
||||
terms: {
|
||||
field: 'observer.geo.name',
|
||||
missing: 'N/A',
|
||||
size: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
...(location ? { post_filter: { term: { 'observer.geo.name': location } } } : {}),
|
||||
...((locations ?? []).length > 0
|
||||
? { post_filter: { terms: { 'observer.geo.name': locations } } }
|
||||
: {}),
|
||||
};
|
||||
|
||||
const {
|
||||
body: {
|
||||
hits: { hits, total },
|
||||
aggregations: aggs,
|
||||
},
|
||||
} = await uptimeEsClient.search({ body: searchBody });
|
||||
|
||||
const locations = aggs?.locations ?? { buckets: [{ key: 'N/A', doc_count: 0 }] };
|
||||
|
||||
const pings: Ping[] = hits.map((doc: any) => {
|
||||
const { _id, _source } = doc;
|
||||
// Calculate here the length of the content string in bytes, this is easier than in client JS, where
|
||||
|
@ -113,7 +103,6 @@ export const getPings: UMElasticsearchQueryFn<GetPingsParams, PingsResponse> = a
|
|||
|
||||
return {
|
||||
total: total.value,
|
||||
locations: locations.buckets.map((bucket) => bucket.key as string),
|
||||
pings,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -20,6 +20,7 @@ import { getSnapshotCount } from './get_snapshot_counts';
|
|||
import { getIndexStatus } from './get_index_status';
|
||||
import { getJourneySteps } from './get_journey_steps';
|
||||
import { getJourneyScreenshot } from './get_journey_screenshot';
|
||||
import { getJourneyFailedSteps } from './get_journey_failed_steps';
|
||||
|
||||
export const requests = {
|
||||
getCerts,
|
||||
|
@ -37,6 +38,7 @@ export const requests = {
|
|||
getSnapshotCount,
|
||||
getIndexStatus,
|
||||
getJourneySteps,
|
||||
getJourneyFailedSteps,
|
||||
getJourneyScreenshot,
|
||||
};
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
} from './monitors';
|
||||
import { createGetMonitorDurationRoute } from './monitors/monitors_durations';
|
||||
import { createGetIndexPatternRoute, createGetIndexStatusRoute } from './index_state';
|
||||
import { createJourneyFailedStepsRoute } from './pings/journeys';
|
||||
|
||||
export * from './types';
|
||||
export { createRouteWithAuth } from './create_route_with_auth';
|
||||
|
@ -47,4 +48,5 @@ export const restApiRoutes: UMRestApiRouteFactory[] = [
|
|||
createGetMonitorDurationRoute,
|
||||
createJourneyRoute,
|
||||
createJourneyScreenshotRoute,
|
||||
createJourneyFailedStepsRoute,
|
||||
];
|
||||
|
|
|
@ -5,12 +5,9 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { isLeft } from 'fp-ts/lib/Either';
|
||||
import { PathReporter } from 'io-ts/lib/PathReporter';
|
||||
import { UMServerLibs } from '../../lib/lib';
|
||||
import { UMRestApiRouteFactory } from '../types';
|
||||
import { API_URLS } from '../../../common/constants';
|
||||
import { GetPingsParamsType } from '../../../common/runtime_types';
|
||||
|
||||
export const createGetPingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({
|
||||
method: 'GET',
|
||||
|
@ -19,7 +16,7 @@ export const createGetPingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) =
|
|||
query: schema.object({
|
||||
from: schema.string(),
|
||||
to: schema.string(),
|
||||
location: schema.maybe(schema.string()),
|
||||
locations: schema.maybe(schema.string()),
|
||||
monitorId: schema.maybe(schema.string()),
|
||||
index: schema.maybe(schema.number()),
|
||||
size: schema.maybe(schema.number()),
|
||||
|
@ -28,17 +25,17 @@ export const createGetPingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) =
|
|||
}),
|
||||
},
|
||||
handler: async ({ uptimeEsClient }, _context, request, response): Promise<any> => {
|
||||
const { from, to, ...optional } = request.query;
|
||||
const params = GetPingsParamsType.decode({ dateRange: { from, to }, ...optional });
|
||||
if (isLeft(params)) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(new Error(PathReporter.report(params).join(';')));
|
||||
return response.badRequest({ body: { message: 'Received invalid request parameters.' } });
|
||||
}
|
||||
const { from, to, index, monitorId, status, sort, size, locations } = request.query;
|
||||
|
||||
const result = await libs.requests.getPings({
|
||||
uptimeEsClient,
|
||||
...params.right,
|
||||
dateRange: { from, to },
|
||||
index,
|
||||
monitorId,
|
||||
status,
|
||||
sort,
|
||||
size,
|
||||
locations: locations ? JSON.parse(locations) : [],
|
||||
});
|
||||
|
||||
return response.ok({
|
||||
|
|
|
@ -29,10 +29,12 @@ export const createJourneyScreenshotRoute: UMRestApiRouteFactory = (libs: UMServ
|
|||
return response.notFound();
|
||||
}
|
||||
return response.ok({
|
||||
body: Buffer.from(result, 'base64'),
|
||||
body: Buffer.from(result.blob, 'base64'),
|
||||
headers: {
|
||||
'content-type': 'image/png',
|
||||
'cache-control': 'max-age=600',
|
||||
'caption-name': result.stepName,
|
||||
'max-steps': result.totalSteps,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -31,3 +31,27 @@ export const createJourneyRoute: UMRestApiRouteFactory = (libs: UMServerLibs) =>
|
|||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const createJourneyFailedStepsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({
|
||||
method: 'GET',
|
||||
path: '/api/uptime/journeys/failed_steps',
|
||||
validate: {
|
||||
query: schema.object({
|
||||
checkGroups: schema.arrayOf(schema.string()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ uptimeEsClient }, _context, request, response) => {
|
||||
const { checkGroups } = request.query;
|
||||
const result = await libs.requests.getJourneyFailedSteps({
|
||||
uptimeEsClient,
|
||||
checkGroups,
|
||||
});
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
checkGroups,
|
||||
steps: result,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -30,10 +30,9 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
const apiResponse = await supertest.get(`/api/uptime/pings?from=${from}&to=${to}&size=10`);
|
||||
|
||||
const { total, locations, pings } = decodePingsResponseData(apiResponse.body);
|
||||
const { total, pings } = decodePingsResponseData(apiResponse.body);
|
||||
|
||||
expect(total).to.be(2000);
|
||||
expect(locations).to.eql(['mpls']);
|
||||
expect(pings).length(10);
|
||||
expect(pings.map(({ monitor: { id } }) => id)).to.eql([
|
||||
'0074-up',
|
||||
|
@ -58,10 +57,9 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
`/api/uptime/pings?from=${from}&to=${to}&size=${size}`
|
||||
);
|
||||
|
||||
const { total, locations, pings } = decodePingsResponseData(apiResponse.body);
|
||||
const { total, pings } = decodePingsResponseData(apiResponse.body);
|
||||
|
||||
expect(total).to.be(2000);
|
||||
expect(locations).to.eql(['mpls']);
|
||||
expect(pings).length(50);
|
||||
expect(pings.map(({ monitor: { id } }) => id)).to.eql([
|
||||
'0074-up',
|
||||
|
@ -127,10 +125,9 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
`/api/uptime/pings?from=${from}&to=${to}&monitorId=${monitorId}&size=${size}`
|
||||
);
|
||||
|
||||
const { total, locations, pings } = decodePingsResponseData(apiResponse.body);
|
||||
const { total, pings } = decodePingsResponseData(apiResponse.body);
|
||||
|
||||
expect(total).to.be(20);
|
||||
expect(locations).to.eql(['mpls']);
|
||||
pings.forEach(({ monitor: { id } }) => expect(id).to.eql('0001-up'));
|
||||
expect(pings.map(({ timestamp }) => timestamp)).to.eql([
|
||||
'2019-09-11T03:40:34.371Z',
|
||||
|
@ -162,10 +159,9 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
`/api/uptime/pings?from=${from}&to=${to}&monitorId=${monitorId}&size=${size}&sort=${sort}`
|
||||
);
|
||||
|
||||
const { total, locations, pings } = decodePingsResponseData(apiResponse.body);
|
||||
const { total, pings } = decodePingsResponseData(apiResponse.body);
|
||||
|
||||
expect(total).to.be(20);
|
||||
expect(locations).to.eql(['mpls']);
|
||||
expect(pings.map(({ timestamp }) => timestamp)).to.eql([
|
||||
'2019-09-11T03:31:04.380Z',
|
||||
'2019-09-11T03:31:34.366Z',
|
||||
|
|
|
@ -32,23 +32,27 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
await uptime.loadDataAndGoToMonitorPage(dateStart, dateEnd, monitorId);
|
||||
});
|
||||
|
||||
it('should select the ping list location filter', async () => {
|
||||
await uptimeService.common.selectFilterItem('location', 'mpls');
|
||||
});
|
||||
|
||||
it('should set the status filter', async () => {
|
||||
await uptimeService.common.setStatusFilterUp();
|
||||
});
|
||||
|
||||
it('displays ping data as expected', async () => {
|
||||
await uptime.checkPingListInteractions(
|
||||
[
|
||||
'XZtoHm0B0I9WX_CznN-6',
|
||||
'7ZtoHm0B0I9WX_CzJ96M',
|
||||
'pptnHm0B0I9WX_Czst5X',
|
||||
'I5tnHm0B0I9WX_CzPd46',
|
||||
'y5tmHm0B0I9WX_Czx93x',
|
||||
'XZtmHm0B0I9WX_CzUt3H',
|
||||
'-JtlHm0B0I9WX_Cz3dyX',
|
||||
'k5tlHm0B0I9WX_CzaNxm',
|
||||
'NZtkHm0B0I9WX_Cz89w9',
|
||||
'zJtkHm0B0I9WX_CzftsN',
|
||||
],
|
||||
'mpls',
|
||||
'up'
|
||||
);
|
||||
await uptime.checkPingListInteractions([
|
||||
'XZtoHm0B0I9WX_CznN-6',
|
||||
'7ZtoHm0B0I9WX_CzJ96M',
|
||||
'pptnHm0B0I9WX_Czst5X',
|
||||
'I5tnHm0B0I9WX_CzPd46',
|
||||
'y5tmHm0B0I9WX_Czx93x',
|
||||
'XZtmHm0B0I9WX_CzUt3H',
|
||||
'-JtlHm0B0I9WX_Cz3dyX',
|
||||
'k5tlHm0B0I9WX_CzaNxm',
|
||||
'NZtkHm0B0I9WX_Cz89w9',
|
||||
'zJtkHm0B0I9WX_CzftsN',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -109,17 +109,7 @@ export function UptimePageProvider({ getPageObjects, getService }: FtrProviderCo
|
|||
return commonService.clickPageSizeSelectPopoverItem(size);
|
||||
}
|
||||
|
||||
public async checkPingListInteractions(
|
||||
timestamps: string[],
|
||||
location?: string,
|
||||
status?: string
|
||||
): Promise<void> {
|
||||
if (location) {
|
||||
await monitor.setPingListLocation(location);
|
||||
}
|
||||
if (status) {
|
||||
await monitor.setPingListStatus(status);
|
||||
}
|
||||
public async checkPingListInteractions(timestamps: string[]): Promise<void> {
|
||||
return monitor.checkForPingListTimestamps(timestamps);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue