From 7cf0e49c897512a827de870c3504888b29824499 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Fri, 14 Aug 2020 09:45:02 +0200 Subject: [PATCH] [Uptime] Singular alert (#74659) Co-authored-by: Elastic Machine --- .../translations/translations/ja-JP.json | 10 - .../translations/translations/zh-CN.json | 10 - .../uptime/common/runtime_types/ping/ping.ts | 6 + x-pack/plugins/uptime/common/translations.ts | 17 + .../__snapshots__/monitor_list.test.tsx.snap | 20 + .../monitor_list_drawer.test.tsx.snap | 5 + .../__tests__/monitor_status.test.ts | 4 +- .../public/lib/alert_types/monitor_status.tsx | 2 +- .../public/lib/alert_types/translations.ts | 13 - .../lib/alerts/__tests__/status_check.test.ts | 819 ++++++++++-------- .../server/lib/alerts/duration_anomaly.ts | 2 +- .../uptime/server/lib/alerts/status_check.ts | 609 +++++-------- .../uptime/server/lib/alerts/translations.ts | 77 ++ .../plugins/uptime/server/lib/alerts/types.ts | 2 +- .../server/lib/alerts/uptime_alert_wrapper.ts | 34 + .../uptime/server/lib/compose/kibana.ts | 14 +- .../get_monitor_availability.test.ts | 192 ++-- .../__tests__/get_monitor_status.test.ts | 148 ++-- .../lib/requests/get_monitor_availability.ts | 15 +- .../server/lib/requests/get_monitor_status.ts | 46 +- .../uptime/server/lib/requests/index.ts | 49 +- .../server/lib/requests/uptime_requests.ts | 57 -- 22 files changed, 1128 insertions(+), 1023 deletions(-) create mode 100644 x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts delete mode 100644 x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5f52bfe981440..8c92e7359b2f7 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -18868,10 +18868,6 @@ "xpack.uptime.alerts.anomaly.criteriaExpression.description": "監視するとき", "xpack.uptime.alerts.anomaly.scoreExpression.ariaLabel": "異常アラートしきい値の条件を表示する式。", "xpack.uptime.alerts.anomaly.scoreExpression.description": "異常と重要度があります", - "xpack.uptime.alerts.availability.emptyMessage": "可用性しきい値({threshold} %)未満のモニターはありません", - "xpack.uptime.alerts.availability.monitorSummary": "{nameOrId}({url}): {availabilityRatio}%", - "xpack.uptime.alerts.availability.multiItemTitle": "可用性しきい値({threshold} %)未満の上位{monitorCount}個のモニター:\n", - "xpack.uptime.alerts.availability.singleItemTitle": "可用性しきい値({threshold} %)未満のモニター:\n", "xpack.uptime.alerts.durationAnomaly": "アップタイム期間異常", "xpack.uptime.alerts.durationAnomaly.actionVariables.state.anomalyStartTimestamp": "異常の開始のISO8601タイムスタンプ", "xpack.uptime.alerts.durationAnomaly.actionVariables.state.expectedResponseTime": "想定応答時間", @@ -18884,11 +18880,6 @@ "xpack.uptime.alerts.durationAnomaly.actionVariables.state.slowestAnomalyResponse": "単位(ミリ秒、秒)が関連付けられた異常バケット中の最も遅い応答時間。", "xpack.uptime.alerts.durationAnomaly.clientName": "アップタイム期間異常", "xpack.uptime.alerts.durationAnomaly.defaultActionMessage": "{anomalyStartTimestamp}に、{monitor}、url {monitorUrl}で異常({severity}レベル)応答時間が検出されました。異常重要度スコアは{severityScore}です。\n位置情報{observerLocation}から高い応答時間{slowestAnomalyResponse}が検出されました。想定された応答時間は{expectedResponseTime}です。", - "xpack.uptime.alerts.message.emptyTitle": "停止状況監視 ID を受信していません。", - "xpack.uptime.alerts.message.fullListOverflow": "... とその他 {overflowCount} {pluralizedMonitor}", - "xpack.uptime.alerts.message.multipleTitle": "停止状況監視: ", - "xpack.uptime.alerts.message.overflowBody": "... とその他 {overflowCount} 監視", - "xpack.uptime.alerts.message.singularTitle": "停止状況監視: ", "xpack.uptime.alerts.monitorStatus": "稼働状況の監視ステータス", "xpack.uptime.alerts.monitorStatus.actionVariables.context.downMonitorsWithGeo.description": "アラートによって「ダウン」と検知された一部またはすべてのモニターを示す、生成された概要。", "xpack.uptime.alerts.monitorStatus.actionVariables.context.message.description": "現在ダウンしているモニターを要約する生成されたメッセージ。", @@ -18915,7 +18906,6 @@ "xpack.uptime.alerts.monitorStatus.availability.unit.headline": "時間範囲単位を選択します", "xpack.uptime.alerts.monitorStatus.availability.unit.selectable": "この選択を使用して、このアラートの可用性範囲単位を設定", "xpack.uptime.alerts.monitorStatus.clientName": "稼働状況の監視ステータス", - "xpack.uptime.alerts.monitorStatus.defaultActionMessage": "{contextMessage}\n前回トリガー日時:{lastTriggered}\n", "xpack.uptime.alerts.monitorStatus.filterBar.ariaLabel": "監視状態アラートのフィルター基準を許可するインプット", "xpack.uptime.alerts.monitorStatus.filters.anyLocation": "任意の場所", "xpack.uptime.alerts.monitorStatus.filters.anyPort": "任意のポート", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index a97de80243ded..5ab70ff7a9d04 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -18876,10 +18876,6 @@ "xpack.uptime.alerts.anomaly.criteriaExpression.description": "当监测", "xpack.uptime.alerts.anomaly.scoreExpression.ariaLabel": "显示异常告警阈值的条件的表达式。", "xpack.uptime.alerts.anomaly.scoreExpression.description": "具有异常,严重性为", - "xpack.uptime.alerts.availability.emptyMessage": "没有监测低于可用性阈值 ({threshold} %)", - "xpack.uptime.alerts.availability.monitorSummary": "{nameOrId}({url}):{availabilityRatio}%", - "xpack.uptime.alerts.availability.multiItemTitle": "低于可用性阈值 ({threshold} %) 的排名前 {monitorCount} 监测:\n", - "xpack.uptime.alerts.availability.singleItemTitle": "低于可用性阈值 ({threshold} %) 的监测:\n", "xpack.uptime.alerts.durationAnomaly": "Uptime 持续时间异常", "xpack.uptime.alerts.durationAnomaly.actionVariables.state.anomalyStartTimestamp": "异常开始的 ISO8601 时间戳。", "xpack.uptime.alerts.durationAnomaly.actionVariables.state.expectedResponseTime": "预期响应时间", @@ -18892,11 +18888,6 @@ "xpack.uptime.alerts.durationAnomaly.actionVariables.state.slowestAnomalyResponse": "在附加单位(ms、s)的异常存储桶期间最慢的响应时间。", "xpack.uptime.alerts.durationAnomaly.clientName": "Uptime 持续时间异常", "xpack.uptime.alerts.durationAnomaly.defaultActionMessage": "{anomalyStartTimestamp} 在 url {monitorUrl} 的 {monitor} 上检测到异常({severity} 级别)响应时间。异常严重性分数为 {severityScore}。\n从位置 {observerLocation} 检测到高达 {slowestAnomalyResponse} 的响应时间。预期响应时间为 {expectedResponseTime}。", - "xpack.uptime.alerts.message.emptyTitle": "未接收到已关闭监测 ID", - "xpack.uptime.alerts.message.fullListOverflow": "...以及 {overflowCount} 个其他{pluralizedMonitor}", - "xpack.uptime.alerts.message.multipleTitle": "已关闭监测: ", - "xpack.uptime.alerts.message.overflowBody": "... 以及 {overflowCount} 个其他监测", - "xpack.uptime.alerts.message.singularTitle": "已关闭监测: ", "xpack.uptime.alerts.monitorStatus": "运行时间监测状态", "xpack.uptime.alerts.monitorStatus.actionVariables.context.downMonitorsWithGeo.description": "生成的摘要,显示告警已检测为“关闭”的部分或所有监测", "xpack.uptime.alerts.monitorStatus.actionVariables.context.message.description": "生成的消息,汇总当前关闭的监测", @@ -18923,7 +18914,6 @@ "xpack.uptime.alerts.monitorStatus.availability.unit.headline": "选择时间范围单位", "xpack.uptime.alerts.monitorStatus.availability.unit.selectable": "使用此选择来设置此告警的可用性范围单位", "xpack.uptime.alerts.monitorStatus.clientName": "运行时间监测状态", - "xpack.uptime.alerts.monitorStatus.defaultActionMessage": "{contextMessage}\n上次触发时间:{lastTriggered}\n", "xpack.uptime.alerts.monitorStatus.filterBar.ariaLabel": "允许对监测状态告警使用筛选条件的输入", "xpack.uptime.alerts.monitorStatus.filters.anyLocation": "任意位置", "xpack.uptime.alerts.monitorStatus.filters.anyPort": "任意端口", diff --git a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts index 0a4d6310927c4..f954f8ba30849 100644 --- a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts +++ b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts @@ -214,6 +214,9 @@ export const makePing = (f: { ip?: string; status?: string; duration?: number; + location?: string; + name?: string; + url?: string; }): Ping => { return { docId: f.docId || 'myDocId', @@ -224,7 +227,10 @@ export const makePing = (f: { ip: f.ip || '127.0.0.1', status: f.status || 'up', duration: { us: f.duration || 100000 }, + name: f.name, }, + ...(f.location ? { observer: { geo: { name: f.location } } } : {}), + ...(f.url ? { url: { full: f.url } } : {}), }; }; diff --git a/x-pack/plugins/uptime/common/translations.ts b/x-pack/plugins/uptime/common/translations.ts index 81f46df86f02e..a4a20a1445f57 100644 --- a/x-pack/plugins/uptime/common/translations.ts +++ b/x-pack/plugins/uptime/common/translations.ts @@ -16,3 +16,20 @@ export const VALUE_MUST_BE_GREATER_THAN_ZERO = i18n.translate( export const VALUE_MUST_BE_AN_INTEGER = i18n.translate('xpack.uptime.settings.invalid.nanError', { defaultMessage: 'Value must be an integer.', }); + +export const MonitorStatusTranslations = { + defaultActionMessage: i18n.translate('xpack.uptime.alerts.monitorStatus.defaultActionMessage', { + defaultMessage: + 'Monitor {monitorName} with url {monitorUrl} is {statusMessage} from {observerLocation}. The latest error message is {latestErrorMessage}', + values: { + monitorName: '{{state.monitorName}}', + monitorUrl: '{{{state.monitorUrl}}}', + statusMessage: '{{state.statusMessage}}', + latestErrorMessage: '{{{state.latestErrorMessage}}}', + observerLocation: '{{state.observerLocation}}', + }, + }), + name: i18n.translate('xpack.uptime.alerts.monitorStatus.clientName', { + defaultMessage: 'Uptime monitor status', + }), +}; diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap index 42ac821c10c7a..2ae8454b99893 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap @@ -213,6 +213,7 @@ exports[`MonitorList component MonitorListPagination component renders the pagin }, "id": "foo", "ip": "127.0.0.1", + "name": undefined, "status": "up", "type": "icmp", }, @@ -226,6 +227,7 @@ exports[`MonitorList component MonitorListPagination component renders the pagin }, "id": "foo", "ip": "127.0.0.2", + "name": undefined, "status": "up", "type": "icmp", }, @@ -239,6 +241,7 @@ exports[`MonitorList component MonitorListPagination component renders the pagin }, "id": "foo", "ip": "127.0.0.3", + "name": undefined, "status": "down", "type": "icmp", }, @@ -266,6 +269,7 @@ exports[`MonitorList component MonitorListPagination component renders the pagin }, "id": "bar", "ip": "127.0.0.1", + "name": undefined, "status": "down", "type": "icmp", }, @@ -279,6 +283,7 @@ exports[`MonitorList component MonitorListPagination component renders the pagin }, "id": "bar", "ip": "127.0.0.1", + "name": undefined, "status": "down", "type": "icmp", }, @@ -516,6 +521,7 @@ exports[`MonitorList component renders error list 1`] = ` }, "id": "foo", "ip": "127.0.0.1", + "name": undefined, "status": "up", "type": "icmp", }, @@ -529,6 +535,7 @@ exports[`MonitorList component renders error list 1`] = ` }, "id": "foo", "ip": "127.0.0.2", + "name": undefined, "status": "up", "type": "icmp", }, @@ -542,6 +549,7 @@ exports[`MonitorList component renders error list 1`] = ` }, "id": "foo", "ip": "127.0.0.3", + "name": undefined, "status": "down", "type": "icmp", }, @@ -569,6 +577,7 @@ exports[`MonitorList component renders error list 1`] = ` }, "id": "bar", "ip": "127.0.0.1", + "name": undefined, "status": "down", "type": "icmp", }, @@ -582,6 +591,7 @@ exports[`MonitorList component renders error list 1`] = ` }, "id": "bar", "ip": "127.0.0.1", + "name": undefined, "status": "down", "type": "icmp", }, @@ -714,6 +724,7 @@ exports[`MonitorList component renders loading state 1`] = ` }, "id": "foo", "ip": "127.0.0.1", + "name": undefined, "status": "up", "type": "icmp", }, @@ -727,6 +738,7 @@ exports[`MonitorList component renders loading state 1`] = ` }, "id": "foo", "ip": "127.0.0.2", + "name": undefined, "status": "up", "type": "icmp", }, @@ -740,6 +752,7 @@ exports[`MonitorList component renders loading state 1`] = ` }, "id": "foo", "ip": "127.0.0.3", + "name": undefined, "status": "down", "type": "icmp", }, @@ -767,6 +780,7 @@ exports[`MonitorList component renders loading state 1`] = ` }, "id": "bar", "ip": "127.0.0.1", + "name": undefined, "status": "down", "type": "icmp", }, @@ -780,6 +794,7 @@ exports[`MonitorList component renders loading state 1`] = ` }, "id": "bar", "ip": "127.0.0.1", + "name": undefined, "status": "down", "type": "icmp", }, @@ -1611,6 +1626,7 @@ exports[`MonitorList component shallow renders the monitor list 1`] = ` }, "id": "foo", "ip": "127.0.0.1", + "name": undefined, "status": "up", "type": "icmp", }, @@ -1624,6 +1640,7 @@ exports[`MonitorList component shallow renders the monitor list 1`] = ` }, "id": "foo", "ip": "127.0.0.2", + "name": undefined, "status": "up", "type": "icmp", }, @@ -1637,6 +1654,7 @@ exports[`MonitorList component shallow renders the monitor list 1`] = ` }, "id": "foo", "ip": "127.0.0.3", + "name": undefined, "status": "down", "type": "icmp", }, @@ -1664,6 +1682,7 @@ exports[`MonitorList component shallow renders the monitor list 1`] = ` }, "id": "bar", "ip": "127.0.0.1", + "name": undefined, "status": "down", "type": "icmp", }, @@ -1677,6 +1696,7 @@ exports[`MonitorList component shallow renders the monitor list 1`] = ` }, "id": "bar", "ip": "127.0.0.1", + "name": undefined, "status": "down", "type": "icmp", }, diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/__tests__/__snapshots__/monitor_list_drawer.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/__tests__/__snapshots__/monitor_list_drawer.test.tsx.snap index 329fb8bade106..42c885dfaf515 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/__tests__/__snapshots__/monitor_list_drawer.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/__tests__/__snapshots__/monitor_list_drawer.test.tsx.snap @@ -113,6 +113,7 @@ exports[`MonitorListDrawer component renders a MonitorListDrawer when there are }, "id": "foo", "ip": "127.0.0.1", + "name": undefined, "status": "up", "type": "icmp", }, @@ -126,6 +127,7 @@ exports[`MonitorListDrawer component renders a MonitorListDrawer when there are }, "id": "foo", "ip": "127.0.0.1", + "name": undefined, "status": "down", "type": "icmp", }, @@ -139,6 +141,7 @@ exports[`MonitorListDrawer component renders a MonitorListDrawer when there are }, "id": "foo", "ip": "127.0.0.2", + "name": undefined, "status": "up", "type": "icmp", }, @@ -152,6 +155,7 @@ exports[`MonitorListDrawer component renders a MonitorListDrawer when there are }, "id": "foo", "ip": "127.0.0.3", + "name": undefined, "status": "down", "type": "icmp", }, @@ -284,6 +288,7 @@ exports[`MonitorListDrawer component renders a MonitorListDrawer when there is o }, "id": "foo", "ip": "127.0.0.1", + "name": undefined, "status": "up", "type": "icmp", }, diff --git a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts index e999768d4e55d..6af817c82ad95 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts +++ b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts @@ -202,9 +202,7 @@ describe('monitor status alert type', () => { ).toMatchInlineSnapshot(` Object { "alertParamsExpression": [Function], - "defaultActionMessage": "{{context.message}} - Last triggered at: {{state.lastTriggeredAt}} - ", + "defaultActionMessage": "Monitor {{state.monitorName}} with url {{{state.monitorUrl}}} is {{state.statusMessage}} from {{state.observerLocation}}. The latest error message is {{{state.latestErrorMessage}}}", "iconClass": "uptimeApp", "id": "xpack.uptime.alerts.monitorStatus", "name": { "certExpirationThreshold": 30, "heartbeatIndices": "heartbeat-8*", }, + "filters": undefined, "locations": Array [], "numTimes": 5, - "shouldCheckStatus": true, "timerange": Object { "from": "now-15m", "to": "now", @@ -114,19 +114,28 @@ describe('status check alert', () => { it('triggers when monitors are down and provides expected state', async () => { toISOStringSpy.mockImplementation(() => 'foo date string'); - const mockGetter = jest.fn(); + const mockGetter: jest.Mock = jest.fn(); + mockGetter.mockReturnValue([ { - monitor_id: 'first', + monitorId: 'first', location: 'harrisburg', count: 234, status: 'down', + monitorInfo: makePing({ + id: 'first', + location: 'harrisburg', + }), }, { - monitor_id: 'first', + monitorId: 'first', location: 'fairbanks', count: 234, status: 'down', + monitorInfo: makePing({ + id: 'first', + location: 'fairbanks', + }), }, ]); const { server, libs, plugins } = bootstrapDependencies({ getMonitorStatus: mockGetter }); @@ -136,7 +145,7 @@ describe('status check alert', () => { // @ts-ignore the executor can return `void`, but ours never does const state: Record = await alert.executor(options); expect(mockGetter).toHaveBeenCalledTimes(1); - expect(alertServices.alertInstanceFactory).toHaveBeenCalledTimes(1); + expect(alertServices.alertInstanceFactory).toHaveBeenCalledTimes(2); expect(mockGetter.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -146,9 +155,9 @@ describe('status check alert', () => { "certExpirationThreshold": 30, "heartbeatIndices": "heartbeat-8*", }, + "filters": undefined, "locations": Array [], "numTimes": 5, - "shouldCheckStatus": true, "timerange": Object { "from": "now-15m", "to": "now", @@ -157,7 +166,7 @@ describe('status check alert', () => { ] `); const [{ value: alertInstanceMock }] = alertServices.alertInstanceFactory.mock.results; - expect(alertInstanceMock.replaceState).toHaveBeenCalledTimes(1); + expect(alertInstanceMock.replaceState).toHaveBeenCalledTimes(2); expect(alertInstanceMock.replaceState.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -168,30 +177,23 @@ describe('status check alert', () => { "lastCheckedAt": "foo date string", "lastResolvedAt": undefined, "lastTriggeredAt": "foo date string", - "monitors": Array [ - Object { - "count": 234, - "location": "fairbanks", - "monitor_id": "first", - "status": "down", - }, - Object { - "count": 234, - "location": "harrisburg", - "monitor_id": "first", - "status": "down", - }, - ], + "latestErrorMessage": undefined, + "monitorId": "first", + "monitorName": "first", + "monitorType": "myType", + "monitorUrl": undefined, + "observerHostname": undefined, + "observerLocation": "harrisburg", + "statusMessage": "down", }, ] `); - expect(alertInstanceMock.scheduleActions).toHaveBeenCalledTimes(1); + expect(alertInstanceMock.scheduleActions).toHaveBeenCalledTimes(2); expect(alertInstanceMock.scheduleActions.mock.calls[0]).toMatchInlineSnapshot(` Array [ "xpack.uptime.alerts.actionGroups.monitorStatus", Object { - "downMonitorsWithGeo": "first from fairbanks; first from harrisburg; ", - "message": "Down monitor: first", + "message": "Monitor first with url is down from harrisburg. The latest error message is ", }, ] `); @@ -199,19 +201,29 @@ describe('status check alert', () => { it('supports 7.7 alert format', async () => { toISOStringSpy.mockImplementation(() => '7.7 date'); - const mockGetter = jest.fn(); + const mockGetter: jest.Mock = jest.fn(); + mockGetter.mockReturnValue([ { - monitor_id: 'first', + monitorId: 'first', location: 'harrisburg', count: 234, status: 'down', + monitorInfo: makePing({ + id: 'first', + location: 'harrisburg', + }), }, { - monitor_id: 'first', + monitorId: 'first', location: 'fairbanks', count: 234, status: 'down', + + monitorInfo: makePing({ + id: 'first', + location: 'fairbanks', + }), }, ]); const { server, libs, plugins } = bootstrapDependencies({ @@ -227,8 +239,9 @@ describe('status check alert', () => { }); const alertServices: AlertServicesMock = options.services; const state = await alert.executor(options); + const [{ value: alertInstanceMock }] = alertServices.alertInstanceFactory.mock.results; - expect(alertInstanceMock.replaceState).toHaveBeenCalledTimes(1); + expect(alertInstanceMock.replaceState).toHaveBeenCalledTimes(2); expect(alertInstanceMock.replaceState.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -239,20 +252,14 @@ describe('status check alert', () => { "lastCheckedAt": "7.7 date", "lastResolvedAt": undefined, "lastTriggeredAt": "7.7 date", - "monitors": Array [ - Object { - "count": 234, - "location": "fairbanks", - "monitor_id": "first", - "status": "down", - }, - Object { - "count": 234, - "location": "harrisburg", - "monitor_id": "first", - "status": "down", - }, - ], + "latestErrorMessage": undefined, + "monitorId": "first", + "monitorName": "first", + "monitorType": "myType", + "monitorUrl": undefined, + "observerHostname": undefined, + "observerLocation": "harrisburg", + "statusMessage": "down", }, ] `); @@ -272,19 +279,28 @@ describe('status check alert', () => { it('supports 7.8 alert format', async () => { expect.assertions(5); toISOStringSpy.mockImplementation(() => 'foo date string'); - const mockGetter = jest.fn(); + const mockGetter: jest.Mock = jest.fn(); mockGetter.mockReturnValue([ { - monitor_id: 'first', + monitorId: 'first', location: 'harrisburg', count: 234, status: 'down', + monitorInfo: makePing({ + id: 'first', + location: 'harrisburg', + }), }, { - monitor_id: 'first', + monitorId: 'first', location: 'fairbanks', count: 234, status: 'down', + + monitorInfo: makePing({ + id: 'first', + location: 'fairbanks', + }), }, ]); const { server, libs, plugins } = bootstrapDependencies({ @@ -317,7 +333,166 @@ describe('status check alert', () => { "certExpirationThreshold": 30, "heartbeatIndices": "heartbeat-8*", }, - "filters": "{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"url.port\\":12349}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"url.port\\":5601}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"url.port\\":443}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"observer.geo.name\\":\\"harrisburg\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"monitor.type\\":\\"http\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"tags\\":\\"unsecured\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"tags\\":\\"containers\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"tags\\":\\"org:google\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}}]}}]}}]}},{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\":\\"monitor.ip\\"}}],\\"minimum_should_match\\":1}}]}}", + "filters": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "url.port": 12349, + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "url.port": 5601, + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "url.port": 443, + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "harrisburg", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "monitor.type": "http", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "tags": "unsecured", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "tags": "containers", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "tags": "org:google", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "monitor.ip", + }, + }, + ], + }, + }, + ], + }, + }, "locations": Array [], "numTimes": 3, "timerange": Object { @@ -327,7 +502,7 @@ describe('status check alert', () => { }, ] `); - expect(alertInstanceMock.replaceState).toHaveBeenCalledTimes(1); + expect(alertInstanceMock.replaceState).toHaveBeenCalledTimes(2); expect(alertInstanceMock.replaceState.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -338,20 +513,14 @@ describe('status check alert', () => { "lastCheckedAt": "foo date string", "lastResolvedAt": undefined, "lastTriggeredAt": "foo date string", - "monitors": Array [ - Object { - "count": 234, - "location": "fairbanks", - "monitor_id": "first", - "status": "down", - }, - Object { - "count": 234, - "location": "harrisburg", - "monitor_id": "first", - "status": "down", - }, - ], + "latestErrorMessage": undefined, + "monitorId": "first", + "monitorName": "first", + "monitorType": "myType", + "monitorUrl": undefined, + "observerHostname": undefined, + "observerLocation": "harrisburg", + "statusMessage": "down", }, ] `); @@ -390,6 +559,7 @@ describe('status check alert', () => { search: 'url.full: *', }); await alert.executor(options); + expect(mockGetter).toHaveBeenCalledTimes(1); expect(mockGetter.mock.calls[0]).toMatchInlineSnapshot(` Array [ @@ -400,7 +570,36 @@ describe('status check alert', () => { "certExpirationThreshold": 30, "heartbeatIndices": "heartbeat-8*", }, - "filters": "{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"monitor.type\\":\\"http\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\":\\"url.full\\"}}],\\"minimum_should_match\\":1}}]}}", + "filters": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "monitor.type": "http", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "url.full", + }, + }, + ], + }, + }, + ], + }, + }, "locations": Array [], "numTimes": 20, "timerange": Object { @@ -415,57 +614,60 @@ describe('status check alert', () => { it('supports availability checks', async () => { expect.assertions(8); toISOStringSpy.mockImplementation(() => 'availability test'); - const mockGetter = jest.fn(); - mockGetter.mockReturnValue([ - { - monitor_id: 'first', - location: 'harrisburg', - count: 234, - status: 'down', - }, - { - monitor_id: 'first', - location: 'fairbanks', - count: 234, - status: 'down', - }, - ]); - const mockAvailability = jest.fn(); + const mockGetter: jest.Mock = jest.fn(); + mockGetter.mockReturnValue([]); + const mockAvailability: jest.Mock = jest.fn(); mockAvailability.mockReturnValue([ { monitorId: 'foo', location: 'harrisburg', - name: 'Foo', - url: 'https://foo.com', up: 2341, down: 17, availabilityRatio: 0.992790500424088, + monitorInfo: makePing({ + id: 'foo', + location: 'harrisburg', + name: 'Foo', + url: 'https://foo.com', + }), }, { monitorId: 'foo', location: 'fairbanks', - name: 'Foo', - url: 'https://foo.com', up: 2343, down: 47, availabilityRatio: 0.980334728033473, + monitorInfo: makePing({ + id: 'foo', + location: 'fairbanks', + name: 'Foo', + url: 'https://foo.com', + }), }, { monitorId: 'unreliable', location: 'fairbanks', - name: 'Unreliable', - url: 'https://unreliable.co', up: 2134, down: 213, availabilityRatio: 0.909245845760545, + monitorInfo: makePing({ + id: 'unreliable', + location: 'fairbanks', + name: 'Unreliable', + url: 'https://unreliable.co', + }), }, { monitorId: 'no-name', location: 'fairbanks', - url: 'https://no-name.co', up: 2134, down: 213, availabilityRatio: 0.909245845760545, + monitorInfo: makePing({ + id: 'no-name', + location: 'fairbanks', + url: 'https://no-name.co', + }), }, ]); const { server, libs, plugins } = bootstrapDependencies({ @@ -487,11 +689,12 @@ describe('status check alert', () => { tags: ['unsecured', 'containers', 'org:google'], }, shouldCheckAvailability: true, + shouldCheckStatus: false, }); const alertServices: AlertServicesMock = options.services; const state = await alert.executor(options); const [{ value: alertInstanceMock }] = alertServices.alertInstanceFactory.mock.results; - expect(alertInstanceMock.replaceState).toHaveBeenCalledTimes(1); + expect(alertInstanceMock.replaceState).toHaveBeenCalledTimes(4); expect(alertInstanceMock.replaceState.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -502,22 +705,42 @@ describe('status check alert', () => { "lastCheckedAt": "availability test", "lastResolvedAt": undefined, "lastTriggeredAt": "availability test", - "monitors": Array [], + "latestErrorMessage": undefined, + "monitorId": "foo", + "monitorName": "Foo", + "monitorType": "myType", + "monitorUrl": "https://foo.com", + "observerHostname": undefined, + "observerLocation": "harrisburg", + "statusMessage": "below threshold with 99.28% availability expected is 99.34%", }, ] `); - expect(alertInstanceMock.scheduleActions).toHaveBeenCalledTimes(1); + expect(alertInstanceMock.scheduleActions).toHaveBeenCalledTimes(4); expect(alertInstanceMock.scheduleActions.mock.calls).toMatchInlineSnapshot(` Array [ Array [ "xpack.uptime.alerts.actionGroups.monitorStatus", Object { - "downMonitorsWithGeo": "", - "message": "Top 3 Monitors Below Availability Threshold (99.34 %): - Unreliable(https://unreliable.co): 90.925% - no-name(https://no-name.co): 90.925% - Foo(https://foo.com): 98.033% - ", + "message": "Monitor Foo with url https://foo.com is below threshold with 99.28% availability expected is 99.34% from harrisburg. The latest error message is ", + }, + ], + Array [ + "xpack.uptime.alerts.actionGroups.monitorStatus", + Object { + "message": "Monitor Foo with url https://foo.com is below threshold with 98.03% availability expected is 99.34% from fairbanks. The latest error message is ", + }, + ], + Array [ + "xpack.uptime.alerts.actionGroups.monitorStatus", + Object { + "message": "Monitor Unreliable with url https://unreliable.co is below threshold with 90.92% availability expected is 99.34% from fairbanks. The latest error message is ", + }, + ], + Array [ + "xpack.uptime.alerts.actionGroups.monitorStatus", + Object { + "message": "Monitor no-name with url https://no-name.co is below threshold with 90.92% availability expected is 99.34% from fairbanks. The latest error message is ", }, ], ] @@ -573,7 +796,9 @@ describe('status check alert', () => { }, search: 'ur.port: *', shouldCheckAvailability: true, + shouldCheckStatus: false, }); + await alert.executor(options); expect(mockAvailability).toHaveBeenCalledTimes(1); expect(mockAvailability.mock.calls[0]).toMatchInlineSnapshot(` @@ -613,8 +838,11 @@ describe('status check alert', () => { threshold: '90', }, shouldCheckAvailability: true, + shouldCheckStatus: false, }); + await alert.executor(options); + expect(mockAvailability).toHaveBeenCalledTimes(1); expect(mockAvailability.mock.calls[0]).toMatchInlineSnapshot(` Array [ @@ -635,116 +863,6 @@ describe('status check alert', () => { }); }); - describe('fullListByIdAndLocation', () => { - it('renders a list of all monitors', () => { - const statuses: GetMonitorStatusResult[] = [ - { - location: 'harrisburg', - monitor_id: 'first', - status: 'down', - count: 34, - }, - { - location: 'fairbanks', - monitor_id: 'second', - status: 'down', - count: 23, - }, - { - location: 'fairbanks', - monitor_id: 'first', - status: 'down', - count: 23, - }, - { - location: 'harrisburg', - monitor_id: 'second', - status: 'down', - count: 34, - }, - ]; - expect(fullListByIdAndLocation(statuses)).toMatchInlineSnapshot( - `"first from fairbanks; first from harrisburg; second from fairbanks; second from harrisburg; "` - ); - }); - - it('renders a list of monitors when greater than limit', () => { - const statuses: GetMonitorStatusResult[] = [ - { - location: 'fairbanks', - monitor_id: 'second', - status: 'down', - count: 23, - }, - { - location: 'fairbanks', - monitor_id: 'first', - status: 'down', - count: 23, - }, - { - location: 'harrisburg', - monitor_id: 'first', - status: 'down', - count: 34, - }, - { - location: 'harrisburg', - monitor_id: 'second', - status: 'down', - count: 34, - }, - ]; - expect(fullListByIdAndLocation(statuses.slice(0, 2), 1)).toMatchInlineSnapshot( - `"first from fairbanks; ...and 1 other monitor/location"` - ); - }); - - it('renders expected list of monitors when limit difference > 1', () => { - const statuses: GetMonitorStatusResult[] = [ - { - location: 'fairbanks', - monitor_id: 'second', - status: 'down', - count: 23, - }, - { - location: 'harrisburg', - monitor_id: 'first', - status: 'down', - count: 34, - }, - { - location: 'harrisburg', - monitor_id: 'second', - status: 'down', - count: 34, - }, - { - location: 'harrisburg', - monitor_id: 'third', - status: 'down', - count: 34, - }, - { - location: 'fairbanks', - monitor_id: 'third', - status: 'down', - count: 23, - }, - { - location: 'fairbanks', - monitor_id: 'first', - status: 'down', - count: 23, - }, - ]; - expect(fullListByIdAndLocation(statuses, 4)).toMatchInlineSnapshot( - `"first from fairbanks; first from harrisburg; second from fairbanks; second from harrisburg; ...and 2 other monitors/locations"` - ); - }); - }); - describe('alert factory', () => { let alert: AlertType; @@ -820,16 +938,26 @@ describe('status check alert', () => { mockGetIndexPattern.mockReturnValue(undefined); it('returns `undefined` for no filters or search', async () => { - expect(await generateFilterDSL(mockGetIndexPattern)).toBeUndefined(); + expect( + await generateFilterDSL( + mockGetIndexPattern, + { 'monitor.type': [], 'observer.geo.name': [], tags: [], 'url.port': [] }, + '' + ) + ).toBeUndefined(); }); it('creates a filter string for filters only', async () => { - const res = await generateFilterDSL(mockGetIndexPattern, { - 'monitor.type': [], - 'observer.geo.name': ['us-east', 'us-west'], - tags: [], - 'url.port': [], - }); + const res = await generateFilterDSL( + mockGetIndexPattern, + { + 'monitor.type': [], + 'observer.geo.name': ['us-east', 'us-west'], + tags: [], + 'url.port': [], + }, + '' + ); expect(res).toMatchInlineSnapshot(` Object { "bool": Object { @@ -866,8 +994,13 @@ describe('status check alert', () => { }); it('creates a filter string for search only', async () => { - expect(await generateFilterDSL(mockGetIndexPattern, undefined, 'monitor.id: "kibana-dev"')) - .toMatchInlineSnapshot(` + expect( + await generateFilterDSL( + mockGetIndexPattern, + { 'monitor.type': [], 'observer.geo.name': [], tags: [], 'url.port': [] }, + 'monitor.id: "kibana-dev"' + ) + ).toMatchInlineSnapshot(` Object { "bool": Object { "minimum_should_match": 1, @@ -987,217 +1120,159 @@ describe('status check alert', () => { }); describe('uniqueMonitorIds', () => { - let items: GetMonitorStatusResult[]; + let downItems: GetMonitorStatusResult[]; + let availItems: GetMonitorAvailabilityResult[]; beforeEach(() => { - items = [ + downItems = [ { - monitor_id: 'first', + monitorId: 'first', location: 'harrisburg', count: 234, status: 'down', + monitorInfo: makePing({}), }, { - monitor_id: 'first', + monitorId: 'first', location: 'fairbanks', count: 312, status: 'down', + monitorInfo: makePing({}), }, { - monitor_id: 'second', + monitorId: 'second', location: 'harrisburg', count: 325, status: 'down', + monitorInfo: makePing({}), }, { - monitor_id: 'second', + monitorId: 'second', location: 'fairbanks', count: 331, status: 'down', + monitorInfo: makePing({}), }, + ]; + + availItems = [ { - monitor_id: 'third', - location: 'harrisburg', - count: 331, - status: 'down', - }, - { - monitor_id: 'third', - location: 'fairbanks', - count: 342, - status: 'down', - }, - { - monitor_id: 'fourth', + monitorId: 'first', location: 'harrisburg', - count: 355, - status: 'down', + monitorInfo: makePing({}), + up: 2134, + down: 213, + availabilityRatio: 0.909245845760545, }, { - monitor_id: 'fourth', + monitorId: 'first', location: 'fairbanks', - count: 342, - status: 'down', + monitorInfo: makePing({}), + up: 2134, + down: 213, + availabilityRatio: 0.909245845760545, }, { - monitor_id: 'fifth', + monitorId: 'second', location: 'harrisburg', - count: 342, - status: 'down', + monitorInfo: makePing({}), + up: 2134, + down: 213, + availabilityRatio: 0.909245845760545, }, { - monitor_id: 'fifth', + monitorId: 'second', location: 'fairbanks', - count: 342, - status: 'down', + monitorInfo: makePing({}), + up: 2134, + down: 213, + availabilityRatio: 0.909245845760545, }, ]; }); it('creates a set of unique IDs from a list of composite unique objects', () => { - expect(uniqueMonitorIds(items)).toEqual( - new Set(['first', 'second', 'third', 'fourth', 'fifth']) - ); - }); - }); - - describe('contextMessage', () => { - let ids: string[]; - beforeEach(() => { - ids = ['first', 'second', 'third', 'fourth', 'fifth']; - }); - - it('creates a message with appropriate number of monitors', () => { - expect(contextMessage(ids, 3, [], '0', false, true)).toMatchInlineSnapshot( - `"Down monitors: first, second, third... and 2 other monitors"` - ); - }); - - it('throws an error if `max` is less than 2', () => { - expect(() => contextMessage(ids, 1, [], '0', false, true)).toThrowErrorMatchingInlineSnapshot( - '"Maximum value must be greater than 2, received 1."' - ); - }); - - it('returns only the ids if length < max', () => { - expect(contextMessage(ids.slice(0, 2), 3, [], '0', false, true)).toMatchInlineSnapshot( - `"Down monitors: first, second"` - ); - }); - - it('returns a default message when no monitors are provided', () => { - expect(contextMessage([], 3, [], '0', false, true)).toMatchInlineSnapshot( - `"No down monitor IDs received"` + expect(getUniqueIdsByLoc(downItems, availItems)).toEqual( + new Set([ + 'firstharrisburg', + 'firstfairbanks', + 'secondharrisburg', + 'secondfairbanks', + ]) ); }); }); - describe('availabilityMessage', () => { - it('creates message for singular item', () => { + describe('statusMessage', () => { + it('creates message for down item', () => { expect( - availabilityMessage( - [ - { - monitorId: 'test-node-service', - location: 'fairbanks', - name: 'Test Node Service', - url: 'http://localhost:12349', - up: 821.0, - down: 2450.0, - availabilityRatio: 0.25099357994497096, - }, - ], - '59' + getStatusMessage( + makePing({ + id: 'test-node-service', + location: 'fairbanks', + name: 'Test Node Service', + url: 'http://localhost:12349', + }) ) - ).toMatchInlineSnapshot(` - "Monitor Below Availability Threshold (59 %): - Test Node Service(http://localhost:12349): 25.099% - " - `); + ).toMatchInlineSnapshot(`"down"`); }); - it('creates message for multiple items', () => { + it('creates message for availability item', () => { expect( - availabilityMessage( - [ - { - monitorId: 'test-node-service', - location: 'fairbanks', + getStatusMessage( + undefined, + { + monitorId: 'test-node-service', + location: 'harrisburg', + up: 3389.0, + down: 2450.0, + availabilityRatio: 0.5804076040417879, + monitorInfo: makePing({ name: 'Test Node Service', url: 'http://localhost:12349', - up: 821.0, - down: 2450.0, - availabilityRatio: 0.25099357994497096, - }, - { - monitorId: 'test-node-service', + id: 'test-node-service', location: 'harrisburg', - name: 'Test Node Service', - url: 'http://localhost:12349', - up: 3389.0, - down: 2450.0, - availabilityRatio: 0.5804076040417879, - }, - ], - '59' + }), + }, + { + threshold: '90', + range: 5, + rangeUnit: 'm', + } ) - ).toMatchInlineSnapshot(` - "Top 2 Monitors Below Availability Threshold (59 %): - Test Node Service(http://localhost:12349): 25.099% - Test Node Service(http://localhost:12349): 58.041% - " - `); + ).toMatchInlineSnapshot(`"below threshold with 58.04% availability expected is 90%"`); }); - it('caps message for multiple items', () => { + it('creates message for down and availability item', () => { expect( - availabilityMessage( - [ - { - monitorId: 'test-node-service', - location: 'fairbanks', + getStatusMessage( + makePing({ + id: 'test-node-service', + location: 'fairbanks', + name: 'Test Node Service', + url: 'http://localhost:12349', + }), + { + monitorId: 'test-node-service', + location: 'harrisburg', + up: 3389.0, + down: 2450.0, + availabilityRatio: 0.5804076040417879, + monitorInfo: makePing({ name: 'Test Node Service', url: 'http://localhost:12349', - up: 821.0, - down: 2450.0, - availabilityRatio: 0.250993579944971, - }, - { - monitorId: 'test-node-service', + id: 'test-node-service', location: 'harrisburg', - name: 'Test Node Service', - url: 'http://localhost:12349', - up: 3389.0, - down: 2450.0, - availabilityRatio: 0.58040760404178, - }, - { - monitorId: 'test-node-service', - location: 'berlin', - name: 'Test Node Service', - url: 'http://localhost:12349', - up: 3645.0, - down: 2982.0, - availabilityRatio: 0.550022634676324, - }, - { - monitorId: 'test-node-service', - location: 'st paul', - name: 'Test Node Service', - url: 'http://localhost:12349', - up: 3601.0, - down: 2681.0, - availabilityRatio: 0.573225087551735, - }, - ], - '59' + }), + }, + { + threshold: '90', + range: 5, + rangeUnit: 'm', + } ) - ).toMatchInlineSnapshot(` - "Top 3 Monitors Below Availability Threshold (59 %): - Test Node Service(http://localhost:12349): 25.099% - Test Node Service(http://localhost:12349): 55.002% - Test Node Service(http://localhost:12349): 57.323% - " - `); + ).toMatchInlineSnapshot( + `"down and also below threshold with 58.04% availability expected is 90%"` + ); }); }); }); diff --git a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts index a71913d0eea9a..9ed453d286285 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts @@ -12,12 +12,12 @@ import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants/alerts'; import { commonStateTranslations, durationAnomalyTranslations } from './translations'; import { AnomaliesTableRecord } from '../../../../ml/common/types/anomalies'; import { getSeverityType } from '../../../../ml/common/util/anomaly_utils'; -import { getLatestMonitor } from '../requests'; import { savedObjectsAdapter } from '../saved_objects'; import { UptimeCorePlugins } from '../adapters/framework'; import { UptimeAlertTypeFactory } from './types'; import { Ping } from '../../../common/runtime_types/ping'; import { getMLJobId } from '../../../common/lib'; +import { getLatestMonitor } from '../requests/get_latest_monitor'; const { DURATION_ANOMALY } = ACTION_GROUP_DEFINITIONS; diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index 8ca2e857a52c9..134472ba0693f 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -5,225 +5,46 @@ */ import { schema } from '@kbn/config-schema'; -import { isRight } from 'fp-ts/lib/Either'; -import { ThrowReporter } from 'io-ts/lib/ThrowReporter'; import { i18n } from '@kbn/i18n'; -import { AlertExecutorOptions } from '../../../../alerts/server'; +import { ILegacyScopedClusterClient } from 'kibana/server'; +import Mustache from 'mustache'; import { UptimeAlertTypeFactory } from './types'; -import { GetMonitorStatusResult } from '../requests'; import { esKuery, IIndexPattern } from '../../../../../../src/plugins/data/server'; import { JsonObject } from '../../../../../../src/plugins/kibana_utils/common'; import { - StatusCheckParamsType, - StatusCheckParams, StatusCheckFilters, - AtomicStatusCheckParamsType, - MonitorAvailabilityType, DynamicSettings, + Ping, + GetMonitorAvailabilityParams, } from '../../../common/runtime_types'; import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants/alerts'; -import { savedObjectsAdapter } from '../saved_objects'; import { updateState } from './common'; -import { commonStateTranslations } from './translations'; +import { commonMonitorStateI18, commonStateTranslations, DOWN_LABEL } from './translations'; import { stringifyKueries, combineFiltersAndUserSearch } from '../../../common/lib'; import { GetMonitorAvailabilityResult } from '../requests/get_monitor_availability'; import { UMServerLibs } from '../lib'; +import { GetMonitorStatusResult } from '../requests/get_monitor_status'; +import { UNNAMED_LOCATION } from '../../../common/constants'; +import { uptimeAlertWrapper } from './uptime_alert_wrapper'; +import { MonitorStatusTranslations } from '../../../common/translations'; const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS; -/** - * Reduce a composite-key array of status results to a set of unique IDs. - * @param items to reduce - */ -export const uniqueMonitorIds = (items: GetMonitorStatusResult[]): Set => - // eslint-disable-next-line @typescript-eslint/naming-convention - items.reduce((acc, { monitor_id }) => { - acc.add(monitor_id); - return acc; - }, new Set()); - -const sortAvailabilityResultByRatioAsc = ( - a: GetMonitorAvailabilityResult, - b: GetMonitorAvailabilityResult -): number => (a.availabilityRatio ?? 100) - (b.availabilityRatio ?? 100); - -/** - * Map an availability result object to a descriptive string. - */ -const mapAvailabilityResultToString = ({ - availabilityRatio, - name, - monitorId, - url, -}: GetMonitorAvailabilityResult) => - i18n.translate('xpack.uptime.alerts.availability.monitorSummary', { - defaultMessage: '{nameOrId}({url}): {availabilityRatio}%', - values: { - nameOrId: name || monitorId, - url, - availabilityRatio: ((availabilityRatio ?? 1.0) * 100).toPrecision(5), - }, - }); - -const reduceAvailabilityStringsToMessage = (threshold: string) => ( - prev: string, - cur: string, - _ind: number, - array: string[] -) => { - let prefix: string = ''; - if (prev !== '') { - prefix = prev; - } else if (array.length > 1) { - prefix = i18n.translate('xpack.uptime.alerts.availability.multiItemTitle', { - defaultMessage: `Top {monitorCount} Monitors Below Availability Threshold ({threshold} %):\n`, - values: { - monitorCount: Math.min(array.length, MESSAGE_AVAILABILITY_MAX), - threshold, - }, - }); - } else { - prefix = i18n.translate('xpack.uptime.alerts.availability.singleItemTitle', { - defaultMessage: `Monitor Below Availability Threshold ({threshold} %):\n`, - values: { threshold }, - }); - } - return prefix + `${cur}\n`; -}; - -const MESSAGE_AVAILABILITY_MAX = 3; - -/** - * Creates a summary message from a list of availability check result objects. - * @param availabilityResult the list of results - * @param threshold the threshold used by the check - */ -export const availabilityMessage = ( - availabilityResult: GetMonitorAvailabilityResult[], - threshold: string, - max: number = MESSAGE_AVAILABILITY_MAX -): string => { - return availabilityResult.length > 0 - ? // if there are results, map each item to a descriptive string, and reduce the list - availabilityResult - .sort(sortAvailabilityResultByRatioAsc) - .slice(0, max) - .map(mapAvailabilityResultToString) - .reduce(reduceAvailabilityStringsToMessage(threshold), '') - : // if there are no results, return an empty list default string - i18n.translate('xpack.uptime.alerts.availability.emptyMessage', { - defaultMessage: `No monitors were below Availability Threshold ({threshold} %)`, - values: { - threshold, - }, - }); -}; - -/** - * Generates a message to include in contexts of alerts. - * @param monitors the list of monitors to include in the message - * @param max the maximum number of items the summary should contain - */ -export const contextMessage = ( - monitorIds: string[], - max: number, - availabilityResult: GetMonitorAvailabilityResult[], - availabilityThreshold: string, - availabilityWasChecked: boolean, - statusWasChecked: boolean -): string => { - const MIN = 2; - if (max < MIN) throw new Error(`Maximum value must be greater than ${MIN}, received ${max}.`); - - // generate the message - let message = ''; - if (statusWasChecked) { - if (monitorIds.length === 1) { - message = i18n.translate('xpack.uptime.alerts.message.singularTitle', { - defaultMessage: 'Down monitor: ', - }); - } else if (monitorIds.length) { - message = i18n.translate('xpack.uptime.alerts.message.multipleTitle', { - defaultMessage: 'Down monitors: ', - }); - } else { - message = i18n.translate('xpack.uptime.alerts.message.emptyTitle', { - defaultMessage: 'No down monitor IDs received', - }); - } - - for (let i = 0; i < monitorIds.length; i++) { - const id = monitorIds[i]; - if (i === max) { - message = - message + - i18n.translate('xpack.uptime.alerts.message.overflowBody', { - defaultMessage: `... and {overflowCount} other monitors`, - values: { - overflowCount: monitorIds.length - i, - }, - }); - break; - } else if (i === 0) { - message = message + id; - } else { - message = message + `, ${id}`; - } - } - } - - if (availabilityWasChecked) { - const availabilityMsg = availabilityMessage(availabilityResult, availabilityThreshold); - return message ? message + '\n' + availabilityMsg : availabilityMsg; - } +const uniqueDownMonitorIds = (items: GetMonitorStatusResult[]): Set => + items.reduce((acc, { monitorId, location }) => acc.add(monitorId + location), new Set()); - return message; -}; +const uniqueAvailMonitorIds = (items: GetMonitorAvailabilityResult[]): Set => + items.reduce((acc, { monitorId, location }) => acc.add(monitorId + location), new Set()); -/** - * Creates an exhaustive list of all the down monitors. - * @param list all the monitors that are down - * @param sizeLimit the max monitors, we shouldn't allow an arbitrarily long string - */ -export const fullListByIdAndLocation = ( - list: GetMonitorStatusResult[], - sizeLimit: number = 1000 +export const getUniqueIdsByLoc = ( + downMonitorsByLocation: GetMonitorStatusResult[], + availabilityResults: GetMonitorAvailabilityResult[] ) => { - return ( - list - // sort by id, then location - .sort((a, b) => { - if (a.monitor_id > b.monitor_id) { - return 1; - } else if (a.monitor_id < b.monitor_id) { - return -1; - } else if (a.location > b.location) { - return 1; - } - return -1; - }) - .slice(0, sizeLimit) - .reduce( - (cur, { monitor_id: id, location }) => - cur + `${id} from ${location ?? 'Unnamed location'}; `, - '' - ) + - (sizeLimit < list.length - ? i18n.translate('xpack.uptime.alerts.message.fullListOverflow', { - defaultMessage: '...and {overflowCount} other {pluralizedMonitor}', - values: { - pluralizedMonitor: - list.length - sizeLimit === 1 ? 'monitor/location' : 'monitors/locations', - overflowCount: list.length - sizeLimit, - }, - }) - : '') - ); -}; + const uniqueDownsIdsByLoc = uniqueDownMonitorIds(downMonitorsByLocation); + const uniqueAvailIdsByLoc = uniqueAvailMonitorIds(availabilityResults); -// Right now the maximum number of monitors shown in the message is hardcoded here. -// we might want to make this a parameter in the future -const DEFAULT_MAX_MESSAGE_ROWS = 3; + return new Set([...uniqueDownsIdsByLoc, ...uniqueAvailIdsByLoc]); +}; export const hasFilters = (filters?: StatusCheckFilters) => { if (!filters) return false; @@ -237,25 +58,18 @@ export const hasFilters = (filters?: StatusCheckFilters) => { export const generateFilterDSL = async ( getIndexPattern: () => Promise, - filters?: StatusCheckFilters, - search?: string + filters: StatusCheckFilters, + search: string ): Promise => { const filtersExist = hasFilters(filters); if (!filtersExist && !search) return undefined; - let filterString: string | undefined; + let filterString = ''; if (filtersExist) { filterString = stringifyKueries(new Map(Object.entries(filters ?? {}))); } - let combinedString: string | undefined; - if (filterString && search) { - combinedString = combineFiltersAndUserSearch(filterString, search); - } else if (filterString) { - combinedString = filterString; - } else if (search) { - combinedString = search; - } + const combinedString = combineFiltersAndUserSearch(filterString, search); return esKuery.toElasticsearchQuery( esKuery.fromKueryExpression(combinedString ?? ''), @@ -266,183 +80,232 @@ export const generateFilterDSL = async ( const formatFilterString = async ( libs: UMServerLibs, dynamicSettings: DynamicSettings, - options: AlertExecutorOptions, - filters?: StatusCheckFilters, - search?: string + callES: ILegacyScopedClusterClient['callAsCurrentUser'], + filters: StatusCheckFilters, + search: string ) => - JSON.stringify( - await generateFilterDSL( - () => - libs.requests.getIndexPattern({ - callES: options.services.callCluster, - dynamicSettings, - }), - filters, - search - ) + await generateFilterDSL( + () => + libs.requests.getIndexPattern({ + callES, + dynamicSettings, + }), + filters, + search ); -export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ - id: 'xpack.uptime.alerts.monitorStatus', - name: i18n.translate('xpack.uptime.alerts.monitorStatus', { - defaultMessage: 'Uptime monitor status', - }), - validate: { - params: schema.object({ - availability: schema.maybe( - schema.object({ - range: schema.number(), - rangeUnit: schema.string(), - threshold: schema.string(), - }) - ), - filters: schema.maybe( - schema.oneOf([ - // deprecated - schema.object({ - 'monitor.type': schema.maybe(schema.arrayOf(schema.string())), - 'observer.geo.name': schema.maybe(schema.arrayOf(schema.string())), - tags: schema.maybe(schema.arrayOf(schema.string())), - 'url.port': schema.maybe(schema.arrayOf(schema.string())), - }), - schema.string(), - ]) - ), - // deprecated - locations: schema.maybe(schema.arrayOf(schema.string())), - numTimes: schema.number(), - search: schema.maybe(schema.string()), - shouldCheckStatus: schema.maybe(schema.boolean()), - shouldCheckAvailability: schema.maybe(schema.boolean()), - timerangeCount: schema.maybe(schema.number()), - timerangeUnit: schema.maybe(schema.string()), - // deprecated - timerange: schema.maybe( - schema.object({ - from: schema.string(), - to: schema.string(), - }) - ), - version: schema.maybe(schema.number()), - }), - }, - defaultActionGroupId: MONITOR_STATUS.id, - actionGroups: [ - { - id: MONITOR_STATUS.id, - name: MONITOR_STATUS.name, - }, - ], - actionVariables: { - context: [ +export const getMonitorSummary = (monitorInfo: Ping) => { + return { + monitorUrl: monitorInfo.url?.full, + monitorId: monitorInfo.monitor?.id, + monitorName: monitorInfo.monitor?.name ?? monitorInfo.monitor?.id, + monitorType: monitorInfo.monitor?.type, + latestErrorMessage: monitorInfo.error?.message, + observerLocation: monitorInfo.observer?.geo?.name ?? UNNAMED_LOCATION, + observerHostname: monitorInfo.agent?.name, + }; +}; + +const generateMessageForOlderVersions = (fields: Record) => { + const messageTemplate = MonitorStatusTranslations.defaultActionMessage; + + // Monitor {{state.monitorName}} with url {{{state.monitorUrl}}} is {{state.statusMessage}} from + // {{state.observerLocation}}. The latest error message is {{{state.latestErrorMessage}}} + + return Mustache.render(messageTemplate, { state: { ...fields } }); +}; + +export const getStatusMessage = ( + downMonInfo?: Ping, + availMonInfo?: GetMonitorAvailabilityResult, + availability?: GetMonitorAvailabilityParams +) => { + let statusMessage = ''; + if (downMonInfo) { + statusMessage = DOWN_LABEL; + } + let availabilityMessage = ''; + + if (availMonInfo) { + availabilityMessage = i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.availabilityMessage', { - name: 'message', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.context.message.description', - { - defaultMessage: 'A generated message summarizing the currently down monitors', - } - ), - }, + defaultMessage: + 'below threshold with {availabilityRatio}% availability expected is {expectedAvailability}%', + values: { + availabilityRatio: (availMonInfo.availabilityRatio! * 100).toFixed(2), + expectedAvailability: availability?.threshold, + }, + } + ); + } + if (availMonInfo && downMonInfo) { + return i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.downAndAvailabilityMessage', { - name: 'downMonitorsWithGeo', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.context.downMonitorsWithGeo.description', - { - defaultMessage: - 'A generated summary that shows some or all of the monitors detected as "down" by the alert', - } + defaultMessage: '{statusMessage} and also {availabilityMessage}', + values: { + statusMessage, + availabilityMessage, + }, + } + ); + } + return statusMessage + availabilityMessage; +}; + +export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) => + uptimeAlertWrapper({ + id: 'xpack.uptime.alerts.monitorStatus', + name: i18n.translate('xpack.uptime.alerts.monitorStatus', { + defaultMessage: 'Uptime monitor status', + }), + validate: { + params: schema.object({ + availability: schema.maybe( + schema.object({ + range: schema.number(), + rangeUnit: schema.string(), + threshold: schema.string(), + }) + ), + filters: schema.maybe( + schema.oneOf([ + // deprecated + schema.object({ + 'monitor.type': schema.maybe(schema.arrayOf(schema.string())), + 'observer.geo.name': schema.maybe(schema.arrayOf(schema.string())), + tags: schema.maybe(schema.arrayOf(schema.string())), + 'url.port': schema.maybe(schema.arrayOf(schema.string())), + }), + schema.string(), + ]) + ), + // deprecated + locations: schema.maybe(schema.arrayOf(schema.string())), + numTimes: schema.number(), + search: schema.maybe(schema.string()), + shouldCheckStatus: schema.boolean(), + shouldCheckAvailability: schema.boolean(), + timerangeCount: schema.maybe(schema.number()), + timerangeUnit: schema.maybe(schema.string()), + // deprecated + timerange: schema.maybe( + schema.object({ + from: schema.string(), + to: schema.string(), + }) ), + version: schema.maybe(schema.number()), + }), + }, + defaultActionGroupId: MONITOR_STATUS.id, + actionGroups: [ + { + id: MONITOR_STATUS.id, + name: MONITOR_STATUS.name, }, ], - state: [...commonStateTranslations], - }, - producer: 'uptime', - async executor(options: AlertExecutorOptions) { - const { params: rawParams } = options; - const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( - options.services.savedObjectsClient - ); - const atomicDecoded = AtomicStatusCheckParamsType.decode(rawParams); - const availabilityDecoded = MonitorAvailabilityType.decode(rawParams); - const decoded = StatusCheckParamsType.decode(rawParams); - let filterString: string = ''; - let params: StatusCheckParams; - if (isRight(atomicDecoded)) { - const { filters, search, numTimes, timerangeCount, timerangeUnit } = atomicDecoded.right; - const timerange = { from: `now-${String(timerangeCount) + timerangeUnit}`, to: 'now' }; - filterString = await formatFilterString(libs, dynamicSettings, options, filters, search); - params = { - timerange, + actionVariables: { + context: [ + { + name: 'message', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.context.message.description', + { + defaultMessage: 'A generated message summarizing the currently down monitors', + } + ), + }, + { + name: 'downMonitorsWithGeo', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.context.downMonitorsWithGeo.description', + { + defaultMessage: + 'A generated summary that shows some or all of the monitors detected as "down" by the alert', + } + ), + }, + ], + state: [...commonMonitorStateI18, ...commonStateTranslations], + }, + async executor( + { params: rawParams, state, services: { alertInstanceFactory } }, + callES, + dynamicSettings + ) { + const { + filters, + search, numTimes, - locations: [], - filters: filterString, - }; - } else if (isRight(decoded)) { - params = decoded.right; - } else if (!isRight(availabilityDecoded)) { - ThrowReporter.report(decoded); - return { - error: 'Alert param types do not conform to required shape.', + timerangeCount, + timerangeUnit, + availability, + shouldCheckAvailability, + shouldCheckStatus, + timerange: oldVersionTimeRange, + } = rawParams; + + const timerange = oldVersionTimeRange || { + from: `now-${String(timerangeCount) + timerangeUnit}`, + to: 'now', }; - } - let availabilityResults: GetMonitorAvailabilityResult[] = []; - if ( - isRight(availabilityDecoded) && - availabilityDecoded.right.shouldCheckAvailability === true - ) { - const { filters, search } = availabilityDecoded.right; - if (filterString === '' && (filters || search)) { - filterString = await formatFilterString(libs, dynamicSettings, options, filters, search); + const filterString = await formatFilterString(libs, dynamicSettings, callES, filters, search); + + let availabilityResults: GetMonitorAvailabilityResult[] = []; + if (shouldCheckAvailability) { + availabilityResults = await libs.requests.getMonitorAvailability({ + callES, + dynamicSettings, + ...availability, + filters: JSON.stringify(filterString) || undefined, + }); } - availabilityResults = await libs.requests.getMonitorAvailability({ - callES: options.services.callCluster, - dynamicSettings, - ...availabilityDecoded.right.availability, - filters: filterString || undefined, - }); - } + let downMonitorsByLocation: GetMonitorStatusResult[] = []; - /* This is called `monitorsByLocation` but it's really - * monitors by location by status. The query we run to generate this - * filters on the status field, so effectively there should be one and only one - * status represented in the result set. */ - let monitorsByLocation: GetMonitorStatusResult[] = []; - - // old alert versions are missing this field so it must default to true - const verifiedParams = StatusCheckParamsType.decode(params!); - if (isRight(verifiedParams) && (verifiedParams.right?.shouldCheckStatus ?? true)) { - monitorsByLocation = await libs.requests.getMonitorStatus({ - callES: options.services.callCluster, - dynamicSettings, - ...verifiedParams.right, - }); - } + // if oldVersionTimeRange present means it's 7.7 format and + // after that shouldCheckStatus should be explicitly false + if (!(!oldVersionTimeRange && shouldCheckStatus === false)) { + downMonitorsByLocation = await libs.requests.getMonitorStatus({ + callES, + dynamicSettings, + timerange, + numTimes, + locations: [], + filters: filterString, + }); + } - // if no monitors are down for our query, we don't need to trigger an alert - if (monitorsByLocation.length || availabilityResults.length) { - const uniqueIds = uniqueMonitorIds(monitorsByLocation); - const alertInstance = options.services.alertInstanceFactory(MONITOR_STATUS.id); - alertInstance.replaceState({ - ...options.state, - monitors: monitorsByLocation, - ...updateState(options.state, true), - }); - alertInstance.scheduleActions(MONITOR_STATUS.id, { - message: contextMessage( - Array.from(uniqueIds.keys()), - DEFAULT_MAX_MESSAGE_ROWS, - availabilityResults, - isRight(availabilityDecoded) ? availabilityDecoded.right.availability.threshold : '100', - isRight(availabilityDecoded) && availabilityDecoded.right.shouldCheckAvailability, - rawParams?.shouldCheckStatus ?? false - ), - downMonitorsWithGeo: fullListByIdAndLocation(monitorsByLocation), + const mergedIdsByLoc = getUniqueIdsByLoc(downMonitorsByLocation, availabilityResults); + + mergedIdsByLoc.forEach((monIdByLoc) => { + const alertInstance = alertInstanceFactory(MONITOR_STATUS.id + monIdByLoc); + + const availMonInfo = availabilityResults.find( + ({ monitorId, location }) => monitorId + location === monIdByLoc + ); + + const downMonInfo = downMonitorsByLocation.find( + ({ monitorId, location }) => monitorId + location === monIdByLoc + )?.monitorInfo; + + const monitorSummary = getMonitorSummary(downMonInfo || availMonInfo?.monitorInfo!); + const statusMessage = getStatusMessage(downMonInfo!, availMonInfo!, availability); + + alertInstance.replaceState({ + ...updateState(state, true), + ...monitorSummary, + statusMessage, + }); + + alertInstance.scheduleActions(MONITOR_STATUS.id, { + message: generateMessageForOlderVersions({ ...monitorSummary, statusMessage }), + }); }); - } - return updateState(options.state, monitorsByLocation.length > 0); - }, -}); + return updateState(state, downMonitorsByLocation.length > 0); + }, + }); diff --git a/x-pack/plugins/uptime/server/lib/alerts/translations.ts b/x-pack/plugins/uptime/server/lib/alerts/translations.ts index 50eedcd4fa69e..8e5c0e76ad589 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/translations.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/translations.ts @@ -6,6 +6,79 @@ import { i18n } from '@kbn/i18n'; +export const commonMonitorStateI18 = [ + { + name: 'monitorName', + description: i18n.translate('xpack.uptime.alerts.monitorStatus.actionVariables.state.monitor', { + defaultMessage: 'A human friendly rendering of name or ID, preferring name (e.g. My Monitor)', + }), + }, + { + name: 'monitorId', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.monitorId', + { + defaultMessage: 'ID of the monitor.', + } + ), + }, + { + name: 'monitorUrl', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.monitorUrl', + { + defaultMessage: 'URL of the monitor.', + } + ), + }, + { + name: 'monitorType', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.monitorType', + { + defaultMessage: 'Type (e.g. HTTP/TCP) of the monitor.', + } + ), + }, + { + name: 'statusMessage', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.statusMessage', + { + defaultMessage: + 'Status message e.g down or is below availability threshold in case of availability check or both.', + } + ), + }, + { + name: 'latestErrorMessage', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.lastErrorMessage', + { + defaultMessage: 'Monitor latest error message', + } + ), + }, + { + name: 'observerLocation', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.observerLocation', + { + defaultMessage: 'Observer location from which heartbeat check is performed.', + } + ), + }, + { + name: 'observerHostname', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.observerHostname', + { + defaultMessage: 'Observer hostname from which heartbeat check is performed.', + } + ), + }, +]; + export const commonStateTranslations = [ { name: 'firstCheckedAt', @@ -238,3 +311,7 @@ export const durationAnomalyTranslations = { }, ], }; + +export const DOWN_LABEL = i18n.translate('xpack.uptime.alerts.monitorStatus.actionVariables.down', { + defaultMessage: 'down', +}); diff --git a/x-pack/plugins/uptime/server/lib/alerts/types.ts b/x-pack/plugins/uptime/server/lib/alerts/types.ts index 172930bc3dd3b..0a80b36046860 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/types.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/types.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertType } from '../../../../alerts/server'; import { UptimeCorePlugins, UptimeCoreSetup } from '../adapters'; import { UMServerLibs } from '../lib'; +import { AlertType } from '../../../../alerts/server'; export type UptimeAlertTypeFactory = ( server: UptimeCoreSetup, diff --git a/x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts b/x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts new file mode 100644 index 0000000000000..5963b371f844f --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts @@ -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 { ILegacyScopedClusterClient } from 'kibana/server'; +import { AlertExecutorOptions, AlertType, State } from '../../../../alerts/server'; +import { savedObjectsAdapter } from '../saved_objects'; +import { DynamicSettings } from '../../../common/runtime_types'; + +export interface UptimeAlertType extends Omit { + executor: ( + options: AlertExecutorOptions, + callES: ILegacyScopedClusterClient['callAsCurrentUser'], + dynamicSettings: DynamicSettings + ) => Promise; +} + +export const uptimeAlertWrapper = (uptimeAlert: UptimeAlertType) => ({ + ...uptimeAlert, + producer: 'uptime', + executor: async (options: AlertExecutorOptions) => { + const { + services: { callCluster: callES }, + } = options; + + const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( + options.services.savedObjectsClient + ); + + return uptimeAlert.executor(options, callES, dynamicSettings); + }, +}); diff --git a/x-pack/plugins/uptime/server/lib/compose/kibana.ts b/x-pack/plugins/uptime/server/lib/compose/kibana.ts index edda5cb283323..783a77b9e5377 100644 --- a/x-pack/plugins/uptime/server/lib/compose/kibana.ts +++ b/x-pack/plugins/uptime/server/lib/compose/kibana.ts @@ -5,23 +5,17 @@ */ import { UMKibanaBackendFrameworkAdapter } from '../adapters/framework'; -import * as requests from '../requests'; +import { requests } from '../requests'; import { licenseCheck } from '../domains'; -import { UMDomainLibs, UMServerLibs } from '../lib'; +import { UMServerLibs } from '../lib'; import { UptimeCoreSetup } from '../adapters/framework'; export function compose(server: UptimeCoreSetup): UMServerLibs { const framework = new UMKibanaBackendFrameworkAdapter(server); - const domainLibs: UMDomainLibs = { - requests: { - ...requests, - }, - license: licenseCheck, - }; - return { framework, - ...domainLibs, + requests, + license: licenseCheck, }; } diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts index e014aa985a82d..f0222de02697d 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts @@ -12,16 +12,9 @@ import { } from '../get_monitor_availability'; import { setupMockEsCompositeQuery } from './helper'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; -import { GetMonitorAvailabilityParams } from '../../../../common/runtime_types'; +import { GetMonitorAvailabilityParams, makePing, Ping } from '../../../../common/runtime_types'; interface AvailabilityTopHit { - _source: { - monitor: { - name: string; - }; - url: { - full: string; - }; - }; + _source: Ping; } interface AvailabilityDoc { @@ -46,11 +39,10 @@ interface AvailabilityDoc { const genBucketItem = ({ monitorId, location, - name, - url, up, down, availabilityRatio, + monitorInfo, }: GetMonitorAvailabilityResult): AvailabilityDoc => ({ key: { monitorId, @@ -61,14 +53,7 @@ const genBucketItem = ({ hits: { hits: [ { - _source: { - monitor: { - name, - }, - url: { - full: url, - }, - }, + _source: monitorInfo, }, ], }, @@ -148,10 +133,6 @@ describe('monitor availability', () => { }, "fields": Object { "top_hits": Object { - "_source": Array [ - "monitor.name", - "url.full", - ], "size": 1, "sort": Array [ Object { @@ -271,29 +252,26 @@ describe('monitor availability', () => { { monitorId: 'foo', location: 'harrisburg', - name: 'Foo', - url: 'http://foo.com', up: 456, down: 234, availabilityRatio: 0.660869565217391, + monitorInfo: makePing({}), }, { monitorId: 'foo', location: 'faribanks', - name: 'Foo', - url: 'http://foo.com', up: 450, down: 240, availabilityRatio: 0.652173913043478, + monitorInfo: makePing({}), }, { monitorId: 'bar', location: 'fairbanks', - name: 'Bar', - url: 'http://bar.com', up: 468, down: 212, availabilityRatio: 0.688235294117647, + monitorInfo: makePing({}), }, ], }, @@ -327,10 +305,6 @@ describe('monitor availability', () => { }, "fields": Object { "top_hits": Object { - "_source": Array [ - "monitor.name", - "url.full", - ], "size": 1, "sort": Array [ Object { @@ -417,27 +391,63 @@ describe('monitor availability', () => { "down": 234, "location": "harrisburg", "monitorId": "foo", - "name": "Foo", + "monitorInfo": Object { + "docId": "myDocId", + "monitor": Object { + "duration": Object { + "us": 100000, + }, + "id": "myId", + "ip": "127.0.0.1", + "name": undefined, + "status": "up", + "type": "myType", + }, + "timestamp": "2020-07-07T01:14:08Z", + }, "up": 456, - "url": "http://foo.com", }, Object { "availabilityRatio": 0.652173913043478, "down": 240, "location": "faribanks", "monitorId": "foo", - "name": "Foo", + "monitorInfo": Object { + "docId": "myDocId", + "monitor": Object { + "duration": Object { + "us": 100000, + }, + "id": "myId", + "ip": "127.0.0.1", + "name": undefined, + "status": "up", + "type": "myType", + }, + "timestamp": "2020-07-07T01:14:08Z", + }, "up": 450, - "url": "http://foo.com", }, Object { "availabilityRatio": 0.688235294117647, "down": 212, "location": "fairbanks", "monitorId": "bar", - "name": "Bar", + "monitorInfo": Object { + "docId": "myDocId", + "monitor": Object { + "duration": Object { + "us": 100000, + }, + "id": "myId", + "ip": "127.0.0.1", + "name": undefined, + "status": "up", + "type": "myType", + }, + "timestamp": "2020-07-07T01:14:08Z", + }, "up": 468, - "url": "http://bar.com", }, ] `); @@ -459,20 +469,18 @@ describe('monitor availability', () => { { monitorId: 'foo', location: 'harrisburg', - name: 'Foo', - url: 'http://foo.com', up: 243, down: 11, availabilityRatio: 0.956692913385827, + monitorInfo: makePing({}), }, { monitorId: 'foo', location: 'fairbanks', - name: 'Foo', - url: 'http://foo.com', up: 251, down: 13, availabilityRatio: 0.950757575757576, + monitorInfo: makePing({}), }, ], }, @@ -481,20 +489,18 @@ describe('monitor availability', () => { { monitorId: 'baz', location: 'harrisburg', - name: 'Baz', - url: 'http://baz.com', up: 341, down: 3, availabilityRatio: 0.991279069767442, + monitorInfo: makePing({}), }, { monitorId: 'baz', location: 'fairbanks', - name: 'Baz', - url: 'http://baz.com', up: 365, down: 5, availabilityRatio: 0.986486486486486, + monitorInfo: makePing({}), }, ], }, @@ -515,36 +521,84 @@ describe('monitor availability', () => { "down": 11, "location": "harrisburg", "monitorId": "foo", - "name": "Foo", + "monitorInfo": Object { + "docId": "myDocId", + "monitor": Object { + "duration": Object { + "us": 100000, + }, + "id": "myId", + "ip": "127.0.0.1", + "name": undefined, + "status": "up", + "type": "myType", + }, + "timestamp": "2020-07-07T01:14:08Z", + }, "up": 243, - "url": "http://foo.com", }, Object { "availabilityRatio": 0.950757575757576, "down": 13, "location": "fairbanks", "monitorId": "foo", - "name": "Foo", + "monitorInfo": Object { + "docId": "myDocId", + "monitor": Object { + "duration": Object { + "us": 100000, + }, + "id": "myId", + "ip": "127.0.0.1", + "name": undefined, + "status": "up", + "type": "myType", + }, + "timestamp": "2020-07-07T01:14:08Z", + }, "up": 251, - "url": "http://foo.com", }, Object { "availabilityRatio": 0.991279069767442, "down": 3, "location": "harrisburg", "monitorId": "baz", - "name": "Baz", + "monitorInfo": Object { + "docId": "myDocId", + "monitor": Object { + "duration": Object { + "us": 100000, + }, + "id": "myId", + "ip": "127.0.0.1", + "name": undefined, + "status": "up", + "type": "myType", + }, + "timestamp": "2020-07-07T01:14:08Z", + }, "up": 341, - "url": "http://baz.com", }, Object { "availabilityRatio": 0.986486486486486, "down": 5, "location": "fairbanks", "monitorId": "baz", - "name": "Baz", + "monitorInfo": Object { + "docId": "myDocId", + "monitor": Object { + "duration": Object { + "us": 100000, + }, + "id": "myId", + "ip": "127.0.0.1", + "name": undefined, + "status": "up", + "type": "myType", + }, + "timestamp": "2020-07-07T01:14:08Z", + }, "up": 365, - "url": "http://baz.com", }, ] `); @@ -565,10 +619,6 @@ describe('monitor availability', () => { }, "fields": Object { "top_hits": Object { - "_source": Array [ - "monitor.name", - "url.full", - ], "size": 1, "sort": Array [ Object { @@ -663,10 +713,6 @@ describe('monitor availability', () => { }, "fields": Object { "top_hits": Object { - "_source": Array [ - "monitor.name", - "url.full", - ], "size": 1, "sort": Array [ Object { @@ -833,18 +879,30 @@ describe('monitor availability', () => { "down": 2450, "location": "fairbanks", "monitorId": "test-node-service", - "name": "Test Node Service", + "monitorInfo": Object { + "monitor": Object { + "name": "Test Node Service", + }, + "url": Object { + "full": "http://localhost:12349", + }, + }, "up": 821, - "url": "http://localhost:12349", }, Object { "availabilityRatio": 0.5804076040417879, "down": 2450, "location": "harrisburg", "monitorId": "test-node-service", - "name": "Test Node Service", + "monitorInfo": Object { + "monitor": Object { + "name": "Test Node Service", + }, + "url": Object { + "full": "http://localhost:12349", + }, + }, "up": 3389, - "url": "http://localhost:12349", }, ] `); diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts index f12f1527fb56c..7dba71a8126e2 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts @@ -9,14 +9,14 @@ import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; import { setupMockEsCompositeQuery } from './helper'; export interface BucketItemCriteria { - monitor_id: string; + monitorId: string; status: string; location: string; doc_count: number; } interface BucketKey { - monitor_id: string; + monitorId: string; status: string; location: string; } @@ -27,19 +27,17 @@ interface BucketItem { } const genBucketItem = ({ - // eslint-disable-next-line @typescript-eslint/naming-convention - monitor_id, + monitorId, status, location, - // eslint-disable-next-line @typescript-eslint/naming-convention - doc_count, + doc_count: count, }: BucketItemCriteria): BucketItem => ({ key: { - monitor_id, + monitorId, status, location, }, - doc_count, + doc_count: count, }); describe('getMonitorStatus', () => { @@ -48,37 +46,37 @@ describe('getMonitorStatus', () => { [], genBucketItem ); - const exampleFilter = `{ - "bool": { - "should": [ + const exampleFilter = { + bool: { + should: [ { - "bool": { - "should": [ + bool: { + should: [ { - "match_phrase": { - "monitor.id": "apm-dev" - } - } + match_phrase: { + 'monitor.id': 'apm-dev', + }, + }, ], - "minimum_should_match": 1 - } + minimum_should_match: 1, + }, }, { - "bool": { - "should": [ + bool: { + should: [ { - "match_phrase": { - "monitor.id": "auto-http-0X8D6082B94BBE3B8A" - } - } + match_phrase: { + 'monitor.id': 'auto-http-0X8D6082B94BBE3B8A', + }, + }, ], - "minimum_should_match": 1 - } - } + minimum_should_match: 1, + }, + }, ], - "minimum_should_match": 1 - } - }`; + minimum_should_match: 1, + }, + }; await getMonitorStatus({ callES, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, @@ -98,11 +96,18 @@ describe('getMonitorStatus', () => { "body": Object { "aggs": Object { "monitors": Object { + "aggs": Object { + "fields": Object { + "top_hits": Object { + "size": 1, + }, + }, + }, "composite": Object { "size": 2000, "sources": Array [ Object { - "monitor_id": Object { + "monitorId": Object { "terms": Object { "field": "monitor.id", }, @@ -203,11 +208,18 @@ describe('getMonitorStatus', () => { "body": Object { "aggs": Object { "monitors": Object { + "aggs": Object { + "fields": Object { + "top_hits": Object { + "size": 1, + }, + }, + }, "composite": Object { "size": 2000, "sources": Array [ Object { - "monitor_id": Object { + "monitorId": Object { "terms": Object { "field": "monitor.id", }, @@ -280,19 +292,19 @@ describe('getMonitorStatus', () => { { bucketCriteria: [ { - monitor_id: 'foo', + monitorId: 'foo', status: 'down', location: 'fairbanks', doc_count: 43, }, { - monitor_id: 'bar', + monitorId: 'bar', status: 'down', location: 'harrisburg', doc_count: 53, }, { - monitor_id: 'foo', + monitorId: 'foo', status: 'down', location: 'harrisburg', doc_count: 44, @@ -324,11 +336,18 @@ describe('getMonitorStatus', () => { "body": Object { "aggs": Object { "monitors": Object { + "aggs": Object { + "fields": Object { + "top_hits": Object { + "size": 1, + }, + }, + }, "composite": Object { "size": 2000, "sources": Array [ Object { - "monitor_id": Object { + "monitorId": Object { "terms": Object { "field": "monitor.id", }, @@ -377,25 +396,29 @@ describe('getMonitorStatus', () => { "index": "heartbeat-8*", } `); + expect(result.length).toBe(3); expect(result).toMatchInlineSnapshot(` Array [ Object { "count": 43, "location": "fairbanks", - "monitor_id": "foo", + "monitorId": "foo", + "monitorInfo": undefined, "status": "down", }, Object { "count": 53, "location": "harrisburg", - "monitor_id": "bar", + "monitorId": "bar", + "monitorInfo": undefined, "status": "down", }, Object { "count": 44, "location": "harrisburg", - "monitor_id": "foo", + "monitorId": "foo", + "monitorInfo": undefined, "status": "down", }, ] @@ -406,25 +429,25 @@ describe('getMonitorStatus', () => { const criteria = [ { after_key: { - monitor_id: 'foo', + monitorId: 'foo', location: 'harrisburg', status: 'down', }, bucketCriteria: [ { - monitor_id: 'foo', + monitorId: 'foo', status: 'down', location: 'fairbanks', doc_count: 43, }, { - monitor_id: 'bar', + monitorId: 'bar', status: 'down', location: 'harrisburg', doc_count: 53, }, { - monitor_id: 'foo', + monitorId: 'foo', status: 'down', location: 'harrisburg', doc_count: 44, @@ -433,25 +456,25 @@ describe('getMonitorStatus', () => { }, { after_key: { - monitor_id: 'bar', + monitorId: 'bar', status: 'down', location: 'fairbanks', }, bucketCriteria: [ { - monitor_id: 'sna', + monitorId: 'sna', status: 'down', location: 'fairbanks', doc_count: 21, }, { - monitor_id: 'fu', + monitorId: 'fu', status: 'down', location: 'fairbanks', doc_count: 21, }, { - monitor_id: 'bar', + monitorId: 'bar', status: 'down', location: 'fairbanks', doc_count: 45, @@ -461,13 +484,13 @@ describe('getMonitorStatus', () => { { bucketCriteria: [ { - monitor_id: 'sna', + monitorId: 'sna', status: 'down', location: 'harrisburg', doc_count: 21, }, { - monitor_id: 'fu', + monitorId: 'fu', status: 'down', location: 'harrisburg', doc_count: 21, @@ -489,54 +512,63 @@ describe('getMonitorStatus', () => { to: 'now-1m', }, }); + expect(result.length).toBe(8); expect(result).toMatchInlineSnapshot(` Array [ Object { "count": 43, "location": "fairbanks", - "monitor_id": "foo", + "monitorId": "foo", + "monitorInfo": undefined, "status": "down", }, Object { "count": 53, "location": "harrisburg", - "monitor_id": "bar", + "monitorId": "bar", + "monitorInfo": undefined, "status": "down", }, Object { "count": 44, "location": "harrisburg", - "monitor_id": "foo", + "monitorId": "foo", + "monitorInfo": undefined, "status": "down", }, Object { "count": 21, "location": "fairbanks", - "monitor_id": "sna", + "monitorId": "sna", + "monitorInfo": undefined, "status": "down", }, Object { "count": 21, "location": "fairbanks", - "monitor_id": "fu", + "monitorId": "fu", + "monitorInfo": undefined, "status": "down", }, Object { "count": 45, "location": "fairbanks", - "monitor_id": "bar", + "monitorId": "bar", + "monitorInfo": undefined, "status": "down", }, Object { "count": 21, "location": "harrisburg", - "monitor_id": "sna", + "monitorId": "sna", + "monitorInfo": undefined, "status": "down", }, Object { "count": 21, "location": "harrisburg", - "monitor_id": "fu", + "monitorId": "fu", + "monitorInfo": undefined, "status": "down", }, ] diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_availability.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_availability.ts index 798cefc404e1f..0801fc5769363 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_availability.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_availability.ts @@ -5,7 +5,7 @@ */ import { UMElasticsearchQueryFn } from '../adapters'; -import { GetMonitorAvailabilityParams } from '../../../common/runtime_types'; +import { GetMonitorAvailabilityParams, Ping } from '../../../common/runtime_types'; export interface AvailabilityKey { monitorId: string; @@ -14,20 +14,18 @@ export interface AvailabilityKey { export interface GetMonitorAvailabilityResult { monitorId: string; - location: string; - name: string; - url: string; up: number; down: number; + location: string; availabilityRatio: number | null; + monitorInfo: Ping; } export const formatBuckets = async (buckets: any[]): Promise => // eslint-disable-next-line @typescript-eslint/naming-convention buckets.map(({ key, fields, up_sum, down_sum, ratio }: any) => ({ ...key, - name: fields?.hits?.hits?.[0]?._source?.monitor.name, - url: fields?.hits?.hits?.[0]?._source?.url.full, + monitorInfo: fields?.hits?.hits?.[0]?._source, up: up_sum.value, down: down_sum.value, availabilityRatio: ratio.value, @@ -94,7 +92,6 @@ export const getMonitorAvailability: UMElasticsearchQueryFn< fields: { top_hits: { size: 1, - _source: ['monitor.name', 'url.full'], sort: [ { '@timestamp': { @@ -143,8 +140,8 @@ export const getMonitorAvailability: UMElasticsearchQueryFn< }; if (filters) { - const parsedFilters = JSON.parse(filters); - esParams.body.query.bool = { ...esParams.body.query.bool, ...parsedFilters.bool }; + const parsedFilter = JSON.parse(filters); + esParams.body.query.bool = { ...esParams.body.query.bool, ...parsedFilter.bool }; } if (afterKey) { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts index a52bbfc8f2442..0788880994200 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts @@ -4,20 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ +import { JsonObject } from 'src/plugins/kibana_utils/public'; import { UMElasticsearchQueryFn } from '../adapters'; +import { Ping } from '../../../common/runtime_types/ping'; export interface GetMonitorStatusParams { - filters?: string; + filters?: JsonObject; locations: string[]; numTimes: number; timerange: { from: string; to: string }; } export interface GetMonitorStatusResult { - monitor_id: string; + monitorId: string; status: string; location: string; count: number; + monitorInfo: Ping; } interface MonitorStatusKey { @@ -26,15 +29,6 @@ interface MonitorStatusKey { location: string; } -const formatBuckets = async ( - buckets: any[], - numTimes: number -): Promise => { - return buckets - .filter((monitor: any) => monitor?.doc_count > numTimes) - .map(({ key, doc_count: count }: any) => ({ ...key, count })); -}; - const getLocationClause = (locations: string[]) => ({ bool: { should: [ @@ -51,10 +45,10 @@ export const getMonitorStatus: UMElasticsearchQueryFn< GetMonitorStatusParams, GetMonitorStatusResult[] > = async ({ callES, dynamicSettings, filters, locations, numTimes, timerange: { from, to } }) => { - const queryResults: Array> = []; let afterKey: MonitorStatusKey | undefined; const STATUS = 'down'; + let monitors: any = []; do { // today this value is hardcoded. In the future we may support // multiple status types for this alert, and this will become a parameter @@ -87,7 +81,7 @@ export const getMonitorStatus: UMElasticsearchQueryFn< size: 2000, sources: [ { - monitor_id: { + monitorId: { terms: { field: 'monitor.id', }, @@ -110,18 +104,20 @@ export const getMonitorStatus: UMElasticsearchQueryFn< }, ], }, + aggs: { + fields: { + top_hits: { + size: 1, + }, + }, + }, }, }, }, }; - /** - * `filters` are an unparsed JSON string. We parse them and append the bool fields of the query - * to the bool of the parsed filters. - */ - if (filters) { - const parsedFilters = JSON.parse(filters); - esParams.body.query.bool = Object.assign({}, esParams.body.query.bool, parsedFilters.bool); + if (filters?.bool) { + esParams.body.query.bool = Object.assign({}, esParams.body.query.bool, filters.bool); } /** @@ -142,8 +138,14 @@ export const getMonitorStatus: UMElasticsearchQueryFn< const result = await callES('search', esParams); afterKey = result?.aggregations?.monitors?.after_key; - queryResults.push(formatBuckets(result?.aggregations?.monitors?.buckets || [], numTimes)); + monitors = monitors.concat(result?.aggregations?.monitors?.buckets || []); } while (afterKey !== undefined); - return (await Promise.all(queryResults)).reduce((acc, cur) => acc.concat(cur), []); + return monitors + .filter((monitor: any) => monitor?.doc_count > numTimes) + .map(({ key, doc_count: count, fields }: any) => ({ + ...key, + count, + monitorInfo: fields?.hits?.hits?.[0]?._source, + })); }; diff --git a/x-pack/plugins/uptime/server/lib/requests/index.ts b/x-pack/plugins/uptime/server/lib/requests/index.ts index 415b3d2f4b4a1..8fa4561268e8f 100644 --- a/x-pack/plugins/uptime/server/lib/requests/index.ts +++ b/x-pack/plugins/uptime/server/lib/requests/index.ts @@ -4,19 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ -export { getCerts } from './get_certs'; -export { getFilterBar, GetFilterBarParams } from './get_filter_bar'; -export { getUptimeIndexPattern as getIndexPattern } from './get_index_pattern'; -export { getLatestMonitor, GetLatestMonitorParams } from './get_latest_monitor'; -export { getMonitorAvailability } from './get_monitor_availability'; -export { getMonitorDurationChart, GetMonitorChartsParams } from './get_monitor_duration'; -export { getMonitorDetails, GetMonitorDetailsParams } from './get_monitor_details'; -export { getMonitorLocations, GetMonitorLocationsParams } from './get_monitor_locations'; -export { getMonitorStates, GetMonitorStatesParams } from './get_monitor_states'; -export { getMonitorStatus, GetMonitorStatusParams } from './get_monitor_status'; -export * from './get_monitor_status'; -export { getPings } from './get_pings'; -export { getPingHistogram, GetPingHistogramParams } from './get_ping_histogram'; -export { UptimeRequests } from './uptime_requests'; -export { getSnapshotCount, GetSnapshotCountParams } from './get_snapshot_counts'; -export { getIndexStatus } from './get_index_status'; +import { getCerts } from './get_certs'; +import { getFilterBar } from './get_filter_bar'; +import { getUptimeIndexPattern as getIndexPattern } from './get_index_pattern'; +import { getLatestMonitor } from './get_latest_monitor'; +import { getMonitorAvailability } from './get_monitor_availability'; +import { getMonitorDurationChart } from './get_monitor_duration'; +import { getMonitorDetails } from './get_monitor_details'; +import { getMonitorLocations } from './get_monitor_locations'; +import { getMonitorStates } from './get_monitor_states'; +import { getMonitorStatus } from './get_monitor_status'; +import { getPings } from './get_pings'; +import { getPingHistogram } from './get_ping_histogram'; +import { getSnapshotCount } from './get_snapshot_counts'; +import { getIndexStatus } from './get_index_status'; + +export const requests = { + getCerts, + getFilterBar, + getIndexPattern, + getLatestMonitor, + getMonitorAvailability, + getMonitorDurationChart, + getMonitorDetails, + getMonitorLocations, + getMonitorStates, + getMonitorStatus, + getPings, + getPingHistogram, + getSnapshotCount, + getIndexStatus, +}; + +export type UptimeRequests = typeof requests; diff --git a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts deleted file mode 100644 index 2a9420a275570..0000000000000 --- a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { UMElasticsearchQueryFn } from '../adapters'; -import { - OverviewFilters, - GetMonitorAvailabilityParams, - MonitorDetails, - MonitorLocations, - Snapshot, - StatesIndexStatus, - HistogramResult, - Ping, - PingsResponse, - GetCertsParams, - GetPingsParams, - CertResult, - MonitorSummariesResult, -} from '../../../common/runtime_types'; -import { MonitorDurationResult } from '../../../common/types'; - -import { - GetFilterBarParams, - GetLatestMonitorParams, - GetMonitorChartsParams, - GetMonitorDetailsParams, - GetMonitorLocationsParams, - GetMonitorStatesParams, - GetPingHistogramParams, - GetMonitorStatusParams, - GetMonitorStatusResult, -} from '.'; -import { GetSnapshotCountParams } from './get_snapshot_counts'; -import { IIndexPattern } from '../../../../../../src/plugins/data/server'; -import { GetMonitorAvailabilityResult } from './get_monitor_availability'; - -type ESQ = UMElasticsearchQueryFn; - -export interface UptimeRequests { - getCerts: ESQ; - getFilterBar: ESQ; - getIndexPattern: ESQ<{}, IIndexPattern | undefined>; - getLatestMonitor: ESQ; - getMonitorAvailability: ESQ; - getMonitorDurationChart: ESQ; - getMonitorDetails: ESQ; - getMonitorLocations: ESQ; - getMonitorStates: ESQ; - getMonitorStatus: ESQ; - getPings: ESQ; - getPingHistogram: ESQ; - getSnapshotCount: ESQ; - getIndexStatus: ESQ<{}, StatesIndexStatus>; -}