From a76c3788c40548de875d782e04dae4290dbef69f Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Fri, 14 Aug 2020 14:26:52 -0400 Subject: [PATCH 01/22] [ML] DF Analytics creation: display explain error message (#75006) (#75060) * display explain error message to the user * fix translation * update error message --- .../configuration_step_form.tsx | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx index 2ae1996647463..25baff98556a6 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx @@ -213,13 +213,12 @@ export const ConfigurationStepForm: FC = ({ (errorMessage.includes('must have at most') || errorMessage.includes('must have at least')) ) { maxDistinctValuesErrorMessage = errorMessage; - } - - if (errorMessage.includes('status_exception') && errorMessage.includes('unsupported type')) { + } else if ( + errorMessage.includes('status_exception') && + errorMessage.includes('unsupported type') + ) { unsupportedFieldsErrorMessage = errorMessage; - } - - if ( + } else if ( errorMessage.includes('status_exception') && errorMessage.includes('Unable to estimate memory usage as no documents') ) { @@ -231,6 +230,16 @@ export const ConfigurationStepForm: FC = ({ }, }) ); + } else { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.ml.dataframe.analytics.create.unableToFetchExplainDataMessage', + { + defaultMessage: 'An error occurred fetching analysis fields data.', + } + ), + text: errorMessage, + }); } const fallbackModelMemoryLimit = From c7f82e102fd1493ba65901fbe18f340c4cff9810 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Fri, 14 Aug 2020 20:47:01 +0200 Subject: [PATCH 02/22] [Uptime] Singular alert (#74659) (#75063) Co-authored-by: Elastic Machine 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 2a40ab9b081ea..6593665351bf3 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -18894,10 +18894,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": "想定応答時間", @@ -18910,11 +18906,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": "現在ダウンしているモニターを要約する生成されたメッセージ。", @@ -18941,7 +18932,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 8882eb6a7b3a4..6799c379d7765 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -18902,10 +18902,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": "预期响应时间", @@ -18918,11 +18914,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": "生成的消息,汇总当前关闭的监测", @@ -18949,7 +18940,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-7*", }, + "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-7*", }, + "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-7*", }, - "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-7*", }, - "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 f26e453c55192..94bbfaa5a540d 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 e96db9bb95238..50fd6e42752df 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-7*", } `); + 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>; -} From 2a5ee780ebcc741427ff6565043bc57ad62cf4a5 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 14 Aug 2020 13:16:20 -0600 Subject: [PATCH 03/22] [Maps] add map configurations to docker list (#75035) (#75065) --- .../os_packages/docker_generator/resources/bin/kibana-docker | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker index 2da2177b54e81..dda039018b31f 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker @@ -177,6 +177,8 @@ kibana_vars=( xpack.ingestManager.fleet.tlsCheckDisabled xpack.ingestManager.registryUrl xpack.license_management.enabled + xpack.maps.enabled + xpack.maps.showMapVisualizationTypes xpack.ml.enabled xpack.reporting.capture.browser.autoDownload xpack.reporting.capture.browser.chromium.disableSandbox From 3879409aaabb10734e3d884dfee5faa827493cc8 Mon Sep 17 00:00:00 2001 From: Lee Drengenberg Date: Fri, 14 Aug 2020 14:34:05 -0500 Subject: [PATCH 04/22] [7.x] [functional test][saved objects] update tests for additional copy saved objects to space (#74907) (#75069) Co-authored-by: Larry Gregory Co-authored-by: Elastic Machine --- .../apps/management/_import_objects.js | 21 ++--- .../management/saved_objects_page.ts | 91 +++++++++++++++++-- .../apps/spaces/copy_saved_objects.ts | 4 +- .../copy_saved_objects_to_space_page.ts | 26 +----- 4 files changed, 99 insertions(+), 43 deletions(-) diff --git a/test/functional/apps/management/_import_objects.js b/test/functional/apps/management/_import_objects.js index 03db3a2b108f2..5fbeb978f9a1c 100644 --- a/test/functional/apps/management/_import_objects.js +++ b/test/functional/apps/management/_import_objects.js @@ -49,12 +49,13 @@ export default function ({ getService, getPageObjects }) { await PageObjects.savedObjects.checkImportSucceeded(); await PageObjects.savedObjects.clickImportDone(); - // get all the elements in the table, and index them by the 'title' visible text field - const elements = keyBy(await PageObjects.savedObjects.getElementsInTable(), 'title'); log.debug("check that 'Log Agents' is in table as a visualization"); - expect(elements['Log Agents'].objectType).to.eql('visualization'); + expect(await PageObjects.savedObjects.getObjectTypeByTitle('Log Agents')).to.eql( + 'visualization' + ); + + await PageObjects.savedObjects.clickRelationshipsByTitle('logstash-*'); - await elements['logstash-*'].relationshipsElement.click(); const flyout = keyBy(await PageObjects.savedObjects.getRelationshipFlyout(), 'title'); log.debug( "check that 'Shared-Item Visualization AreaChart' shows 'logstash-*' as it's Parent" @@ -150,8 +151,7 @@ export default function ({ getService, getPageObjects }) { }); it('should not import saved objects linked to saved searches when saved search index pattern does not exist', async function () { - const elements = keyBy(await PageObjects.savedObjects.getElementsInTable(), 'title'); - await elements['logstash-*'].checkbox.click(); + await PageObjects.savedObjects.clickCheckboxByTitle('logstash-*'); await PageObjects.savedObjects.clickDelete(); await PageObjects.savedObjects.importFile( @@ -182,8 +182,7 @@ export default function ({ getService, getPageObjects }) { it('should import saved objects with index patterns when index patterns does not exists', async () => { // First, we need to delete the index pattern - const elements = keyBy(await PageObjects.savedObjects.getElementsInTable(), 'title'); - await elements['logstash-*'].checkbox.click(); + await PageObjects.savedObjects.clickCheckboxByTitle('logstash-*'); await PageObjects.savedObjects.clickDelete(); // Then, import the objects @@ -321,8 +320,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.savedObjects.clickImportDone(); // Second, we need to delete the index pattern - const elements = keyBy(await PageObjects.savedObjects.getElementsInTable(), 'title'); - await elements['logstash-*'].checkbox.click(); + await PageObjects.savedObjects.clickCheckboxByTitle('logstash-*'); await PageObjects.savedObjects.clickDelete(); // Last, import a saved object connected to the saved search @@ -353,8 +351,7 @@ export default function ({ getService, getPageObjects }) { it('should import saved objects with index patterns when index patterns does not exists', async () => { // First, we need to delete the index pattern - const elements = keyBy(await PageObjects.savedObjects.getElementsInTable(), 'title'); - await elements['logstash-*'].checkbox.click(); + await PageObjects.savedObjects.clickCheckboxByTitle('logstash-*'); await PageObjects.savedObjects.clickDelete(); // Then, import the objects diff --git a/test/functional/page_objects/management/saved_objects_page.ts b/test/functional/page_objects/management/saved_objects_page.ts index 03d21aa4aa52f..ad82ea9b6fbc1 100644 --- a/test/functional/page_objects/management/saved_objects_page.ts +++ b/test/functional/page_objects/management/saved_objects_page.ts @@ -17,6 +17,7 @@ * under the License. */ +import { keyBy } from 'lodash'; import { map as mapAsync } from 'bluebird'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -34,6 +35,8 @@ export function SavedObjectsPageProvider({ getService, getPageObjects }: FtrProv await searchBox.clearValue(); await searchBox.type(objectName); await searchBox.pressKeys(browser.keys.ENTER); + await PageObjects.header.waitUntilLoadingHasFinished(); + await this.waitTableIsLoaded(); } async importFile(path: string, overwriteAll = true) { @@ -99,6 +102,56 @@ export function SavedObjectsPageProvider({ getService, getPageObjects }: FtrProv }); } + async clickRelationshipsByTitle(title: string) { + const table = keyBy(await this.getElementsInTable(), 'title'); + // should we check if table size > 0 and log error if not? + if (table[title].menuElement) { + log.debug(`we found a context menu element for (${title}) so click it`); + await table[title].menuElement?.click(); + // Wait for context menu to render + const menuPanel = await find.byCssSelector('.euiContextMenuPanel'); + await (await menuPanel.findByTestSubject('savedObjectsTableAction-relationships')).click(); + } else { + log.debug( + `we didn't find a menu element so should be a relastionships element for (${title}) to click` + ); + // or the action elements are on the row without the menu + await table[title].relationshipsElement?.click(); + } + } + + async clickCopyToSpaceByTitle(title: string) { + const table = keyBy(await this.getElementsInTable(), 'title'); + // should we check if table size > 0 and log error if not? + if (table[title].menuElement) { + log.debug(`we found a context menu element for (${title}) so click it`); + await table[title].menuElement?.click(); + // Wait for context menu to render + const menuPanel = await find.byCssSelector('.euiContextMenuPanel'); + await ( + await menuPanel.findByTestSubject('savedObjectsTableAction-copy_saved_objects_to_space') + ).click(); + } else { + log.debug( + `we didn't find a menu element so should be a "copy to space" element for (${title}) to click` + ); + // or the action elements are on the row without the menu + await table[title].copySaveObjectsElement?.click(); + } + } + + async clickCheckboxByTitle(title: string) { + const table = keyBy(await this.getElementsInTable(), 'title'); + // should we check if table size > 0 and log error if not? + await table[title].checkbox.click(); + } + + async getObjectTypeByTitle(title: string) { + const table = keyBy(await this.getElementsInTable(), 'title'); + // should we check if table size > 0 and log error if not? + return table[title].objectType; + } + async getElementsInTable() { const rows = await testSubjects.findAll('~savedObjectsTableRow'); return mapAsync(rows, async (row) => { @@ -107,23 +160,45 @@ export function SavedObjectsPageProvider({ getService, getPageObjects }: FtrProv const objectType = await row.findByTestSubject('objectType'); const titleElement = await row.findByTestSubject('savedObjectsTableRowTitle'); // not all rows have inspect button - Advanced Settings objects don't - let inspectElement; - const innerHtml = await row.getAttribute('innerHTML'); - if (innerHtml.includes('Inspect')) { + // Advanced Settings has 2 actions, + // data-test-subj="savedObjectsTableAction-relationships" + // data-test-subj="savedObjectsTableAction-copy_saved_objects_to_space" + // Some other objects have the ... + // data-test-subj="euiCollapsedItemActionsButton" + // Maybe some objects still have the inspect element visible? + // !!! Also note that since we don't have spaces on OSS, the actions for the same object can be different depending on OSS or not + let menuElement = null; + let inspectElement = null; + let relationshipsElement = null; + let copySaveObjectsElement = null; + const actions = await row.findByClassName('euiTableRowCell--hasActions'); + // getting the innerHTML and checking if it 'includes' a string is faster than a timeout looking for each element + const actionsHTML = await actions.getAttribute('innerHTML'); + if (actionsHTML.includes('euiCollapsedItemActionsButton')) { + menuElement = await row.findByTestSubject('euiCollapsedItemActionsButton'); + } + if (actionsHTML.includes('savedObjectsTableAction-inspect')) { inspectElement = await row.findByTestSubject('savedObjectsTableAction-inspect'); - } else { - inspectElement = null; } - const relationshipsElement = await row.findByTestSubject( - 'savedObjectsTableAction-relationships' - ); + if (actionsHTML.includes('savedObjectsTableAction-relationships')) { + relationshipsElement = await row.findByTestSubject( + 'savedObjectsTableAction-relationships' + ); + } + if (actionsHTML.includes('savedObjectsTableAction-copy_saved_objects_to_space')) { + copySaveObjectsElement = await row.findByTestSubject( + 'savedObjectsTableAction-copy_saved_objects_to_space' + ); + } return { checkbox, objectType: await objectType.getAttribute('aria-label'), titleElement, title: await titleElement.getVisibleText(), + menuElement, inspectElement, relationshipsElement, + copySaveObjectsElement, }; }); } diff --git a/x-pack/test/functional/apps/spaces/copy_saved_objects.ts b/x-pack/test/functional/apps/spaces/copy_saved_objects.ts index 074b6fc528157..05d497c235dad 100644 --- a/x-pack/test/functional/apps/spaces/copy_saved_objects.ts +++ b/x-pack/test/functional/apps/spaces/copy_saved_objects.ts @@ -15,8 +15,7 @@ export default function spaceSelectorFunctonalTests({ const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['security', 'settings', 'copySavedObjectsToSpace']); - // TODO: Flakey again https://github.com/elastic/kibana/issues/44575#issuecomment-528864287 - describe.skip('Copy Saved Objects to Space', function () { + describe('Copy Saved Objects to Space', function () { before(async () => { await esArchiver.load('spaces/copy_saved_objects'); @@ -32,6 +31,7 @@ export default function spaceSelectorFunctonalTests({ disabledFeatures: [], }); + await PageObjects.security.forceLogout(); await PageObjects.security.login(undefined, undefined, { expectSpaceSelector: true, }); diff --git a/x-pack/test/functional/page_objects/copy_saved_objects_to_space_page.ts b/x-pack/test/functional/page_objects/copy_saved_objects_to_space_page.ts index 03596aa68dbc6..629a86520389d 100644 --- a/x-pack/test/functional/page_objects/copy_saved_objects_to_space_page.ts +++ b/x-pack/test/functional/page_objects/copy_saved_objects_to_space_page.ts @@ -15,31 +15,15 @@ export function CopySavedObjectsToSpacePageProvider({ getPageObjects, }: FtrProviderContext) { const testSubjects = getService('testSubjects'); - const find = getService('find'); - const { savedObjects } = getPageObjects(['savedObjects']); + const { savedObjects, common } = getPageObjects(['savedObjects', 'common']); return { async openCopyToSpaceFlyoutForObject(objectName: string) { + // This searchForObject narrows down the objects to those matching ANY of the words in the objectName. + // Hopefully the one we want is on the first page of results. await savedObjects.searchForObject(objectName); - - // Click action button to show context menu - await find.clickByCssSelector( - 'table.euiTable tbody tr.euiTableRow td.euiTableRowCell:last-child .euiButtonIcon' - ); - - // Wait for context menu to render - await find.existsByCssSelector('.euiContextMenuPanel'); - - const actions = await find.allByCssSelector('.euiContextMenuItem'); - - for (const action of actions) { - const actionText = await action.getVisibleText(); - if (actionText === 'Copy to space') { - await action.click(); - break; - } - } - + await common.sleep(1000); + await savedObjects.clickCopyToSpaceByTitle(objectName); await testSubjects.existOrFail('copy-to-space-flyout'); }, From 2ece4f12a4322237a6c78b221c367f6c86b17b91 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Fri, 14 Aug 2020 16:41:47 -0400 Subject: [PATCH 05/22] [7.x] [CI] Add pipeline task queue framework and merge workers into one (#71268) (#74276) --- .ci/Dockerfile | 38 +++ .ci/Jenkinsfile_baseline_capture | 28 +- .ci/runbld_no_junit.yml | 2 +- .gitignore | 2 + Jenkinsfile | 44 +--- packages/kbn-dev-utils/src/run/help.test.ts | 6 +- src/dev/ci_setup/checkout_sibling_es.sh | 12 +- src/dev/ci_setup/setup_env.sh | 4 +- src/dev/notice/generate_notice_from_source.ts | 2 + tasks/test_jest.js | 6 +- test/scripts/checks/doc_api_changes.sh | 5 + test/scripts/checks/file_casing.sh | 5 + test/scripts/checks/i18n.sh | 5 + test/scripts/checks/licenses.sh | 5 + test/scripts/checks/lock_file_symlinks.sh | 5 + test/scripts/checks/telemetry.sh | 5 + test/scripts/checks/test_hardening.sh | 5 + test/scripts/checks/test_projects.sh | 5 + test/scripts/checks/ts_projects.sh | 5 + test/scripts/checks/type_check.sh | 5 + .../checks/verify_dependency_versions.sh | 5 + test/scripts/checks/verify_notice.sh | 5 + test/scripts/jenkins_accessibility.sh | 2 +- .../jenkins_build_kbn_sample_panel_action.sh | 0 test/scripts/jenkins_build_kibana.sh | 13 +- test/scripts/jenkins_build_plugins.sh | 12 + test/scripts/jenkins_ci_group.sh | 2 +- test/scripts/jenkins_firefox_smoke.sh | 2 +- test/scripts/jenkins_plugin_functional.sh | 15 ++ .../jenkins_security_solution_cypress.sh | 8 +- .../jenkins_setup_parallel_workspace.sh | 32 +++ test/scripts/jenkins_test_setup.sh | 6 + test/scripts/jenkins_test_setup_oss.sh | 15 +- test/scripts/jenkins_test_setup_xpack.sh | 15 +- test/scripts/jenkins_xpack_accessibility.sh | 2 +- test/scripts/jenkins_xpack_build_kibana.sh | 17 +- test/scripts/jenkins_xpack_build_plugins.sh | 14 + ...nkins_xpack_saved_objects_field_metrics.sh | 2 +- .../jenkins_xpack_visual_regression.sh | 8 +- test/scripts/lint/eslint.sh | 5 + test/scripts/lint/sasslint.sh | 5 + test/scripts/test/api_integration.sh | 5 + test/scripts/test/jest_integration.sh | 5 + test/scripts/test/jest_unit.sh | 5 + test/scripts/test/karma_ci.sh | 5 + test/scripts/test/mocha.sh | 5 + test/scripts/test/safer_lodash_set.sh | 5 + test/scripts/test/xpack_jest_unit.sh | 6 + test/scripts/test/xpack_karma.sh | 6 + .../test/xpack_list_cyclic_dependency.sh | 6 + .../test/xpack_siem_cyclic_dependency.sh | 6 + vars/catchErrors.groovy | 11 +- vars/kibanaPipeline.groovy | 241 +++++++++++++++--- vars/task.groovy | 5 + vars/tasks.groovy | 119 +++++++++ vars/withTaskQueue.groovy | 154 +++++++++++ vars/workers.groovy | 12 +- .../canvas/storybook/storyshots.test.tsx | 7 + 58 files changed, 835 insertions(+), 147 deletions(-) create mode 100644 .ci/Dockerfile create mode 100755 test/scripts/checks/doc_api_changes.sh create mode 100755 test/scripts/checks/file_casing.sh create mode 100755 test/scripts/checks/i18n.sh create mode 100755 test/scripts/checks/licenses.sh create mode 100755 test/scripts/checks/lock_file_symlinks.sh create mode 100755 test/scripts/checks/telemetry.sh create mode 100755 test/scripts/checks/test_hardening.sh create mode 100755 test/scripts/checks/test_projects.sh create mode 100755 test/scripts/checks/ts_projects.sh create mode 100755 test/scripts/checks/type_check.sh create mode 100755 test/scripts/checks/verify_dependency_versions.sh create mode 100755 test/scripts/checks/verify_notice.sh mode change 100644 => 100755 test/scripts/jenkins_build_kbn_sample_panel_action.sh create mode 100755 test/scripts/jenkins_build_plugins.sh create mode 100755 test/scripts/jenkins_plugin_functional.sh mode change 100644 => 100755 test/scripts/jenkins_security_solution_cypress.sh create mode 100755 test/scripts/jenkins_setup_parallel_workspace.sh mode change 100644 => 100755 test/scripts/jenkins_test_setup.sh mode change 100644 => 100755 test/scripts/jenkins_test_setup_oss.sh mode change 100644 => 100755 test/scripts/jenkins_test_setup_xpack.sh create mode 100755 test/scripts/jenkins_xpack_build_plugins.sh create mode 100755 test/scripts/lint/eslint.sh create mode 100755 test/scripts/lint/sasslint.sh create mode 100755 test/scripts/test/api_integration.sh create mode 100755 test/scripts/test/jest_integration.sh create mode 100755 test/scripts/test/jest_unit.sh create mode 100755 test/scripts/test/karma_ci.sh create mode 100755 test/scripts/test/mocha.sh create mode 100755 test/scripts/test/safer_lodash_set.sh create mode 100755 test/scripts/test/xpack_jest_unit.sh create mode 100755 test/scripts/test/xpack_karma.sh create mode 100755 test/scripts/test/xpack_list_cyclic_dependency.sh create mode 100755 test/scripts/test/xpack_siem_cyclic_dependency.sh create mode 100644 vars/task.groovy create mode 100644 vars/tasks.groovy create mode 100644 vars/withTaskQueue.groovy diff --git a/.ci/Dockerfile b/.ci/Dockerfile new file mode 100644 index 0000000000000..d90d9f4710b5b --- /dev/null +++ b/.ci/Dockerfile @@ -0,0 +1,38 @@ +# NOTE: This Dockerfile is ONLY used to run certain tasks in CI. It is not used to run Kibana or as a distributable. +# If you're looking for the Kibana Docker image distributable, please see: src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts + +ARG NODE_VERSION=10.21.0 + +FROM node:${NODE_VERSION} AS base + +RUN apt-get update && \ + apt-get -y install xvfb gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 \ + libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 \ + libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 \ + libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 \ + libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget openjdk-8-jre && \ + rm -rf /var/lib/apt/lists/* + +RUN curl -sSL https://dl.google.com/linux/linux_signing_key.pub | apt-key add - \ + && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ + && apt-get update \ + && apt-get install -y rsync jq bsdtar google-chrome-stable \ + --no-install-recommends \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +RUN LATEST_VAULT_RELEASE=$(curl -s https://api.github.com/repos/hashicorp/vault/tags | jq --raw-output .[0].name[1:]) \ + && curl -L https://releases.hashicorp.com/vault/${LATEST_VAULT_RELEASE}/vault_${LATEST_VAULT_RELEASE}_linux_amd64.zip -o vault.zip \ + && unzip vault.zip \ + && rm vault.zip \ + && chmod +x vault \ + && mv vault /usr/local/bin/vault + +RUN groupadd -r kibana && useradd -r -g kibana kibana && mkdir /home/kibana && chown kibana:kibana /home/kibana + +COPY ./bash_standard_lib.sh /usr/local/bin/bash_standard_lib.sh +RUN chmod +x /usr/local/bin/bash_standard_lib.sh + +COPY ./runbld /usr/local/bin/runbld +RUN chmod +x /usr/local/bin/runbld + +USER kibana diff --git a/.ci/Jenkinsfile_baseline_capture b/.ci/Jenkinsfile_baseline_capture index b0d3591821642..9a49c19b94df2 100644 --- a/.ci/Jenkinsfile_baseline_capture +++ b/.ci/Jenkinsfile_baseline_capture @@ -7,18 +7,22 @@ kibanaPipeline(timeoutMinutes: 120) { githubCommitStatus.trackBuild(params.commit, 'kibana-ci-baseline') { ciStats.trackBuild { catchError { - parallel([ - 'oss-visualRegression': { - workers.ci(name: 'oss-visualRegression', size: 's-highmem', ramDisk: true) { - kibanaPipeline.functionalTestProcess('oss-visualRegression', './test/scripts/jenkins_visual_regression.sh')(1) - } - }, - 'xpack-visualRegression': { - workers.ci(name: 'xpack-visualRegression', size: 's-highmem', ramDisk: true) { - kibanaPipeline.functionalTestProcess('xpack-visualRegression', './test/scripts/jenkins_xpack_visual_regression.sh')(1) - } - }, - ]) + withEnv([ + 'CI_PARALLEL_PROCESS_NUMBER=1' + ]) { + parallel([ + 'oss-visualRegression': { + workers.ci(name: 'oss-visualRegression', size: 's-highmem', ramDisk: true) { + kibanaPipeline.functionalTestProcess('oss-visualRegression', './test/scripts/jenkins_visual_regression.sh')() + } + }, + 'xpack-visualRegression': { + workers.ci(name: 'xpack-visualRegression', size: 's-highmem', ramDisk: true) { + kibanaPipeline.functionalTestProcess('xpack-visualRegression', './test/scripts/jenkins_xpack_visual_regression.sh')() + } + }, + ]) + } } kibanaPipeline.sendMail() diff --git a/.ci/runbld_no_junit.yml b/.ci/runbld_no_junit.yml index 67b5002c1c437..1bcb7e22a2648 100644 --- a/.ci/runbld_no_junit.yml +++ b/.ci/runbld_no_junit.yml @@ -3,4 +3,4 @@ profiles: - ".*": # Match any job tests: - junit-filename-pattern: "8d8bd494-d909-4e67-a052-7e8b5aaeb5e4" # A bogus path that should never exist + junit-filename-pattern: false diff --git a/.gitignore b/.gitignore index dfd02de7b1186..1d12ef2a9cff3 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,8 @@ npm-debug.log* .tern-project .nyc_output .ci/pipeline-library/build/ +.ci/runbld +.ci/bash_standard_lib.sh .gradle # apm plugin diff --git a/Jenkinsfile b/Jenkinsfile index ad1d244c78874..3b68cde206573 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -9,49 +9,7 @@ kibanaPipeline(timeoutMinutes: 155, checkPrChanges: true, setCommitStatus: true) ciStats.trackBuild { catchError { retryable.enable() - parallel([ - 'kibana-intake-agent': workers.intake('kibana-intake', './test/scripts/jenkins_unit.sh'), - 'x-pack-intake-agent': workers.intake('x-pack-intake', './test/scripts/jenkins_xpack.sh'), - 'kibana-oss-agent': workers.functional('kibana-oss-tests', { kibanaPipeline.buildOss() }, [ - 'oss-firefoxSmoke': kibanaPipeline.functionalTestProcess('kibana-firefoxSmoke', './test/scripts/jenkins_firefox_smoke.sh'), - 'oss-ciGroup1': kibanaPipeline.ossCiGroupProcess(1), - 'oss-ciGroup2': kibanaPipeline.ossCiGroupProcess(2), - 'oss-ciGroup3': kibanaPipeline.ossCiGroupProcess(3), - 'oss-ciGroup4': kibanaPipeline.ossCiGroupProcess(4), - 'oss-ciGroup5': kibanaPipeline.ossCiGroupProcess(5), - 'oss-ciGroup6': kibanaPipeline.ossCiGroupProcess(6), - 'oss-ciGroup7': kibanaPipeline.ossCiGroupProcess(7), - 'oss-ciGroup8': kibanaPipeline.ossCiGroupProcess(8), - 'oss-ciGroup9': kibanaPipeline.ossCiGroupProcess(9), - 'oss-ciGroup10': kibanaPipeline.ossCiGroupProcess(10), - 'oss-ciGroup11': kibanaPipeline.ossCiGroupProcess(11), - 'oss-ciGroup12': kibanaPipeline.ossCiGroupProcess(12), - 'oss-accessibility': kibanaPipeline.functionalTestProcess('kibana-accessibility', './test/scripts/jenkins_accessibility.sh'), - // 'oss-visualRegression': kibanaPipeline.functionalTestProcess('visualRegression', './test/scripts/jenkins_visual_regression.sh'), - ]), - 'kibana-xpack-agent': workers.functional('kibana-xpack-tests', { kibanaPipeline.buildXpack() }, [ - 'xpack-firefoxSmoke': kibanaPipeline.functionalTestProcess('xpack-firefoxSmoke', './test/scripts/jenkins_xpack_firefox_smoke.sh'), - 'xpack-ciGroup1': kibanaPipeline.xpackCiGroupProcess(1), - 'xpack-ciGroup2': kibanaPipeline.xpackCiGroupProcess(2), - 'xpack-ciGroup3': kibanaPipeline.xpackCiGroupProcess(3), - 'xpack-ciGroup4': kibanaPipeline.xpackCiGroupProcess(4), - 'xpack-ciGroup5': kibanaPipeline.xpackCiGroupProcess(5), - 'xpack-ciGroup6': kibanaPipeline.xpackCiGroupProcess(6), - 'xpack-ciGroup7': kibanaPipeline.xpackCiGroupProcess(7), - 'xpack-ciGroup8': kibanaPipeline.xpackCiGroupProcess(8), - 'xpack-ciGroup9': kibanaPipeline.xpackCiGroupProcess(9), - 'xpack-ciGroup10': kibanaPipeline.xpackCiGroupProcess(10), - 'xpack-accessibility': kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh'), - 'xpack-savedObjectsFieldMetrics': kibanaPipeline.functionalTestProcess('xpack-savedObjectsFieldMetrics', './test/scripts/jenkins_xpack_saved_objects_field_metrics.sh'), - 'xpack-securitySolutionCypress': { processNumber -> - whenChanged(['x-pack/plugins/security_solution/', 'x-pack/test/security_solution_cypress/', 'x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/', 'x-pack/plugins/triggers_actions_ui/public/application/context/actions_connectors_context.tsx']) { - kibanaPipeline.functionalTestProcess('xpack-securitySolutionCypress', './test/scripts/jenkins_security_solution_cypress.sh')(processNumber) - } - }, - - // 'xpack-visualRegression': kibanaPipeline.functionalTestProcess('xpack-visualRegression', './test/scripts/jenkins_xpack_visual_regression.sh'), - ]), - ]) + kibanaPipeline.allCiTasks() } } } diff --git a/packages/kbn-dev-utils/src/run/help.test.ts b/packages/kbn-dev-utils/src/run/help.test.ts index 27be7ad28b81a..300f1cba7eb7d 100644 --- a/packages/kbn-dev-utils/src/run/help.test.ts +++ b/packages/kbn-dev-utils/src/run/help.test.ts @@ -57,7 +57,7 @@ const barCommand: Command = { usage: 'bar [...names]', }; -describe('getHelp()', () => { +describe.skip('getHelp()', () => { it('returns the expected output', () => { expect( getHelp({ @@ -95,7 +95,7 @@ describe('getHelp()', () => { }); }); -describe('getCommandLevelHelp()', () => { +describe.skip('getCommandLevelHelp()', () => { it('returns the expected output', () => { expect( getCommandLevelHelp({ @@ -141,7 +141,7 @@ describe('getCommandLevelHelp()', () => { }); }); -describe('getHelpForAllCommands()', () => { +describe.skip('getHelpForAllCommands()', () => { it('returns the expected output', () => { expect( getHelpForAllCommands({ diff --git a/src/dev/ci_setup/checkout_sibling_es.sh b/src/dev/ci_setup/checkout_sibling_es.sh index 915759d4214f9..3832ec9b4076a 100755 --- a/src/dev/ci_setup/checkout_sibling_es.sh +++ b/src/dev/ci_setup/checkout_sibling_es.sh @@ -7,10 +7,11 @@ function checkout_sibling { targetDir=$2 useExistingParamName=$3 useExisting="$(eval "echo "\$$useExistingParamName"")" + repoAddress="https://github.com/" if [ -z ${useExisting:+x} ]; then if [ -d "$targetDir" ]; then - echo "I expected a clean workspace but an '${project}' sibling directory already exists in [$PARENT_DIR]!" + echo "I expected a clean workspace but an '${project}' sibling directory already exists in [$WORKSPACE]!" echo echo "Either define '${useExistingParamName}' or remove the existing '${project}' sibling." exit 1 @@ -21,8 +22,9 @@ function checkout_sibling { cloneBranch="" function clone_target_is_valid { + echo " -> checking for '${cloneBranch}' branch at ${cloneAuthor}/${project}" - if [[ -n "$(git ls-remote --heads "git@github.com:${cloneAuthor}/${project}.git" ${cloneBranch} 2>/dev/null)" ]]; then + if [[ -n "$(git ls-remote --heads "${repoAddress}${cloneAuthor}/${project}.git" ${cloneBranch} 2>/dev/null)" ]]; then return 0 else return 1 @@ -71,7 +73,7 @@ function checkout_sibling { fi echo " -> checking out '${cloneBranch}' branch from ${cloneAuthor}/${project}..." - git clone -b "$cloneBranch" "git@github.com:${cloneAuthor}/${project}.git" "$targetDir" --depth=1 + git clone -b "$cloneBranch" "${repoAddress}${cloneAuthor}/${project}.git" "$targetDir" --depth=1 echo " -> checked out ${project} revision: $(git -C "${targetDir}" rev-parse HEAD)" echo } @@ -87,12 +89,12 @@ function checkout_sibling { fi } -checkout_sibling "elasticsearch" "${PARENT_DIR}/elasticsearch" "USE_EXISTING_ES" +checkout_sibling "elasticsearch" "${WORKSPACE}/elasticsearch" "USE_EXISTING_ES" export TEST_ES_FROM=${TEST_ES_FROM:-snapshot} # Set the JAVA_HOME based on the Java property file in the ES repo # This assumes the naming convention used on CI (ex: ~/.java/java10) -ES_DIR="$PARENT_DIR/elasticsearch" +ES_DIR="$WORKSPACE/elasticsearch" ES_JAVA_PROP_PATH=$ES_DIR/.ci/java-versions.properties diff --git a/src/dev/ci_setup/setup_env.sh b/src/dev/ci_setup/setup_env.sh index 86927b694679a..72ec73ad810e6 100644 --- a/src/dev/ci_setup/setup_env.sh +++ b/src/dev/ci_setup/setup_env.sh @@ -53,6 +53,8 @@ export PARENT_DIR="$parentDir" kbnBranch="$(jq -r .branch "$KIBANA_DIR/package.json")" export KIBANA_PKG_BRANCH="$kbnBranch" +export WORKSPACE="${WORKSPACE:-$PARENT_DIR}" + ### ### download node ### @@ -162,7 +164,7 @@ export -f checks-reporter-with-killswitch source "$KIBANA_DIR/src/dev/ci_setup/load_env_keys.sh" -ES_DIR="$PARENT_DIR/elasticsearch" +ES_DIR="$WORKSPACE/elasticsearch" ES_JAVA_PROP_PATH=$ES_DIR/.ci/java-versions.properties if [[ -d "$ES_DIR" && -f "$ES_JAVA_PROP_PATH" ]]; then diff --git a/src/dev/notice/generate_notice_from_source.ts b/src/dev/notice/generate_notice_from_source.ts index 37bbcce72e497..0bef5bc5f32d4 100644 --- a/src/dev/notice/generate_notice_from_source.ts +++ b/src/dev/notice/generate_notice_from_source.ts @@ -49,8 +49,10 @@ export async function generateNoticeFromSource({ productName, directory, log }: ignore: [ '{node_modules,build,dist,data,built_assets}/**', 'packages/*/{node_modules,build,dist}/**', + 'src/plugins/*/{node_modules,build,dist}/**', 'x-pack/{node_modules,build,dist,data}/**', 'x-pack/packages/*/{node_modules,build,dist}/**', + 'x-pack/plugins/*/{node_modules,build,dist}/**', '**/target/**', ], }; diff --git a/tasks/test_jest.js b/tasks/test_jest.js index d8f51806e8ddc..810ed42324840 100644 --- a/tasks/test_jest.js +++ b/tasks/test_jest.js @@ -22,7 +22,7 @@ const { resolve } = require('path'); module.exports = function (grunt) { grunt.registerTask('test:jest', function () { const done = this.async(); - runJest(resolve(__dirname, '../scripts/jest.js')).then(done, done); + runJest(resolve(__dirname, '../scripts/jest.js'), ['--maxWorkers=10']).then(done, done); }); grunt.registerTask('test:jest_integration', function () { @@ -30,10 +30,10 @@ module.exports = function (grunt) { runJest(resolve(__dirname, '../scripts/jest_integration.js')).then(done, done); }); - function runJest(jestScript) { + function runJest(jestScript, args = []) { const serverCmd = { cmd: 'node', - args: [jestScript, '--ci'], + args: [jestScript, '--ci', ...args], opts: { stdio: 'inherit' }, }; diff --git a/test/scripts/checks/doc_api_changes.sh b/test/scripts/checks/doc_api_changes.sh new file mode 100755 index 0000000000000..503d12b2f6d73 --- /dev/null +++ b/test/scripts/checks/doc_api_changes.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:checkDocApiChanges diff --git a/test/scripts/checks/file_casing.sh b/test/scripts/checks/file_casing.sh new file mode 100755 index 0000000000000..513664263791b --- /dev/null +++ b/test/scripts/checks/file_casing.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:checkFileCasing diff --git a/test/scripts/checks/i18n.sh b/test/scripts/checks/i18n.sh new file mode 100755 index 0000000000000..7a6fd46c46c76 --- /dev/null +++ b/test/scripts/checks/i18n.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:i18nCheck diff --git a/test/scripts/checks/licenses.sh b/test/scripts/checks/licenses.sh new file mode 100755 index 0000000000000..a08d7d07a24a1 --- /dev/null +++ b/test/scripts/checks/licenses.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:licenses diff --git a/test/scripts/checks/lock_file_symlinks.sh b/test/scripts/checks/lock_file_symlinks.sh new file mode 100755 index 0000000000000..1d43d32c9feb8 --- /dev/null +++ b/test/scripts/checks/lock_file_symlinks.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:checkLockfileSymlinks diff --git a/test/scripts/checks/telemetry.sh b/test/scripts/checks/telemetry.sh new file mode 100755 index 0000000000000..c74ec295b385c --- /dev/null +++ b/test/scripts/checks/telemetry.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:telemetryCheck diff --git a/test/scripts/checks/test_hardening.sh b/test/scripts/checks/test_hardening.sh new file mode 100755 index 0000000000000..9184758577654 --- /dev/null +++ b/test/scripts/checks/test_hardening.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:test_hardening diff --git a/test/scripts/checks/test_projects.sh b/test/scripts/checks/test_projects.sh new file mode 100755 index 0000000000000..5f9aafe80e10e --- /dev/null +++ b/test/scripts/checks/test_projects.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:test_projects diff --git a/test/scripts/checks/ts_projects.sh b/test/scripts/checks/ts_projects.sh new file mode 100755 index 0000000000000..d667c753baec2 --- /dev/null +++ b/test/scripts/checks/ts_projects.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:checkTsProjects diff --git a/test/scripts/checks/type_check.sh b/test/scripts/checks/type_check.sh new file mode 100755 index 0000000000000..07c49638134be --- /dev/null +++ b/test/scripts/checks/type_check.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:typeCheck diff --git a/test/scripts/checks/verify_dependency_versions.sh b/test/scripts/checks/verify_dependency_versions.sh new file mode 100755 index 0000000000000..b73a71e7ff7fd --- /dev/null +++ b/test/scripts/checks/verify_dependency_versions.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:verifyDependencyVersions diff --git a/test/scripts/checks/verify_notice.sh b/test/scripts/checks/verify_notice.sh new file mode 100755 index 0000000000000..9f8343e540861 --- /dev/null +++ b/test/scripts/checks/verify_notice.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:verifyNotice diff --git a/test/scripts/jenkins_accessibility.sh b/test/scripts/jenkins_accessibility.sh index c122d71b58edb..fa7cbd41d7078 100755 --- a/test/scripts/jenkins_accessibility.sh +++ b/test/scripts/jenkins_accessibility.sh @@ -5,5 +5,5 @@ source test/scripts/jenkins_test_setup_oss.sh checks-reporter-with-killswitch "Kibana accessibility tests" \ node scripts/functional_tests \ --debug --bail \ - --kibana-install-dir "$installDir" \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ --config test/accessibility/config.ts; diff --git a/test/scripts/jenkins_build_kbn_sample_panel_action.sh b/test/scripts/jenkins_build_kbn_sample_panel_action.sh old mode 100644 new mode 100755 diff --git a/test/scripts/jenkins_build_kibana.sh b/test/scripts/jenkins_build_kibana.sh index 0960e12ed99e9..55eb06a43864c 100755 --- a/test/scripts/jenkins_build_kibana.sh +++ b/test/scripts/jenkins_build_kibana.sh @@ -2,13 +2,9 @@ source src/dev/ci_setup/setup_env.sh -echo " -> building kibana platform plugins" -node scripts/build_kibana_platform_plugins \ - --oss \ - --filter '!alertingExample' \ - --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ - --scan-dir "$KIBANA_DIR/test/interpreter_functional/plugins" \ - --verbose; +if [[ ! "$TASK_QUEUE_PROCESS_ID" ]]; then + ./test/scripts/jenkins_build_plugins.sh +fi # doesn't persist, also set in kibanaPipeline.groovy export KBN_NP_PLUGINS_BUILT=true @@ -18,3 +14,6 @@ yarn run grunt functionalTests:ensureAllTestsInCiGroup; echo " -> building and extracting OSS Kibana distributable for use in functional tests" node scripts/build --debug --oss + +mkdir -p "$WORKSPACE/kibana-build-oss" +cp -pR build/oss/kibana-*-SNAPSHOT-linux-x86_64/. $WORKSPACE/kibana-build-oss/ diff --git a/test/scripts/jenkins_build_plugins.sh b/test/scripts/jenkins_build_plugins.sh new file mode 100755 index 0000000000000..0c3ee4e3f261f --- /dev/null +++ b/test/scripts/jenkins_build_plugins.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +echo " -> building kibana platform plugins" +node scripts/build_kibana_platform_plugins \ + --oss \ + --filter '!alertingExample' \ + --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ + --scan-dir "$KIBANA_DIR/test/interpreter_functional/plugins" \ + --workers 6 \ + --verbose diff --git a/test/scripts/jenkins_ci_group.sh b/test/scripts/jenkins_ci_group.sh index 9110a13c452c3..2bda9dc367852 100755 --- a/test/scripts/jenkins_ci_group.sh +++ b/test/scripts/jenkins_ci_group.sh @@ -4,7 +4,7 @@ source test/scripts/jenkins_test_setup_oss.sh checks-reporter-with-killswitch "Functional tests / Group ${CI_GROUP}" yarn run grunt "run:functionalTests_ciGroup${CI_GROUP}"; -if [ "$CI_GROUP" == "1" ]; then +if [[ ! "$TASK_QUEUE_PROCESS_ID" && "$CI_GROUP" == "1" ]]; then source test/scripts/jenkins_build_kbn_sample_panel_action.sh yarn run grunt run:pluginFunctionalTestsRelease --from=source; yarn run grunt run:exampleFunctionalTestsRelease --from=source; diff --git a/test/scripts/jenkins_firefox_smoke.sh b/test/scripts/jenkins_firefox_smoke.sh index 2bba6e06d76d7..247ab360b7912 100755 --- a/test/scripts/jenkins_firefox_smoke.sh +++ b/test/scripts/jenkins_firefox_smoke.sh @@ -5,6 +5,6 @@ source test/scripts/jenkins_test_setup_oss.sh checks-reporter-with-killswitch "Firefox smoke test" \ node scripts/functional_tests \ --bail --debug \ - --kibana-install-dir "$installDir" \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ --include-tag "includeFirefox" \ --config test/functional/config.firefox.js; diff --git a/test/scripts/jenkins_plugin_functional.sh b/test/scripts/jenkins_plugin_functional.sh new file mode 100755 index 0000000000000..1d691d98982de --- /dev/null +++ b/test/scripts/jenkins_plugin_functional.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +source test/scripts/jenkins_test_setup_oss.sh + +cd test/plugin_functional/plugins/kbn_sample_panel_action; +if [[ ! -d "target" ]]; then + yarn build; +fi +cd -; + +pwd + +yarn run grunt run:pluginFunctionalTestsRelease --from=source; +yarn run grunt run:exampleFunctionalTestsRelease --from=source; +yarn run grunt run:interpreterFunctionalTestsRelease; diff --git a/test/scripts/jenkins_security_solution_cypress.sh b/test/scripts/jenkins_security_solution_cypress.sh old mode 100644 new mode 100755 index 204911a3eedaa..a5a1a2103801f --- a/test/scripts/jenkins_security_solution_cypress.sh +++ b/test/scripts/jenkins_security_solution_cypress.sh @@ -1,12 +1,6 @@ #!/usr/bin/env bash -source test/scripts/jenkins_test_setup.sh - -installDir="$PARENT_DIR/install/kibana" -destDir="${installDir}-${CI_WORKER_NUMBER}" -cp -R "$installDir" "$destDir" - -export KIBANA_INSTALL_DIR="$destDir" +source test/scripts/jenkins_test_setup_xpack.sh echo " -> Running security solution cypress tests" cd "$XPACK_DIR" diff --git a/test/scripts/jenkins_setup_parallel_workspace.sh b/test/scripts/jenkins_setup_parallel_workspace.sh new file mode 100755 index 0000000000000..5274d05572e71 --- /dev/null +++ b/test/scripts/jenkins_setup_parallel_workspace.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -e + +CURRENT_DIR=$(pwd) + +# Copy everything except node_modules into the current workspace +rsync -a ${WORKSPACE}/kibana/* . --exclude node_modules +rsync -a ${WORKSPACE}/kibana/.??* . + +# Symlink all non-root, non-fixture node_modules into our new workspace +cd ${WORKSPACE}/kibana +find . -type d -name node_modules -not -path '*__fixtures__*' -not -path './node_modules*' -prune -print0 | xargs -0I % ln -s "${WORKSPACE}/kibana/%" "${CURRENT_DIR}/%" +find . -type d -wholename '*__fixtures__*node_modules' -not -path './node_modules*' -prune -print0 | xargs -0I % cp -R "${WORKSPACE}/kibana/%" "${CURRENT_DIR}/%" +cd "${CURRENT_DIR}" + +# Symlink all of the individual root-level node_modules into the node_modules/ directory +mkdir -p node_modules +ln -s ${WORKSPACE}/kibana/node_modules/* node_modules/ +ln -s ${WORKSPACE}/kibana/node_modules/.??* node_modules/ + +# Copy a few node_modules instead of symlinking them. They don't work correctly if symlinked +unlink node_modules/@kbn +unlink node_modules/css-loader +unlink node_modules/style-loader + +# packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts will fail if this is a symlink +unlink node_modules/val-loader + +cp -R ${WORKSPACE}/kibana/node_modules/@kbn node_modules/ +cp -R ${WORKSPACE}/kibana/node_modules/css-loader node_modules/ +cp -R ${WORKSPACE}/kibana/node_modules/style-loader node_modules/ +cp -R ${WORKSPACE}/kibana/node_modules/val-loader node_modules/ diff --git a/test/scripts/jenkins_test_setup.sh b/test/scripts/jenkins_test_setup.sh old mode 100644 new mode 100755 index 49ee8a6b526ca..05b88aa2dd0a2 --- a/test/scripts/jenkins_test_setup.sh +++ b/test/scripts/jenkins_test_setup.sh @@ -14,3 +14,9 @@ trap 'post_work' EXIT export TEST_BROWSER_HEADLESS=1 source src/dev/ci_setup/setup_env.sh + +# For parallel workspaces, we should copy the .es directory from the root, because it should already have downloaded snapshots in it +# This isn't part of jenkins_setup_parallel_workspace.sh just because not all tasks require ES +if [[ ! -d .es && -d "$WORKSPACE/kibana/.es" ]]; then + cp -R $WORKSPACE/kibana/.es ./ +fi diff --git a/test/scripts/jenkins_test_setup_oss.sh b/test/scripts/jenkins_test_setup_oss.sh old mode 100644 new mode 100755 index 7bbb867526384..b7eac33f35176 --- a/test/scripts/jenkins_test_setup_oss.sh +++ b/test/scripts/jenkins_test_setup_oss.sh @@ -2,10 +2,17 @@ source test/scripts/jenkins_test_setup.sh -if [[ -z "$CODE_COVERAGE" ]] ; then - installDir="$(realpath $PARENT_DIR/kibana/build/oss/kibana-*-SNAPSHOT-linux-x86_64)" - destDir=${installDir}-${CI_PARALLEL_PROCESS_NUMBER} - cp -R "$installDir" "$destDir" +if [[ -z "$CODE_COVERAGE" ]]; then + + destDir="build/kibana-build-oss" + if [[ ! "$TASK_QUEUE_PROCESS_ID" ]]; then + destDir="${destDir}-${CI_PARALLEL_PROCESS_NUMBER}" + fi + + if [[ ! -d $destDir ]]; then + mkdir -p $destDir + cp -pR "$WORKSPACE/kibana-build-oss/." $destDir/ + fi export KIBANA_INSTALL_DIR="$destDir" fi diff --git a/test/scripts/jenkins_test_setup_xpack.sh b/test/scripts/jenkins_test_setup_xpack.sh old mode 100644 new mode 100755 index a72e9749ebbd5..74a3de77e3a76 --- a/test/scripts/jenkins_test_setup_xpack.sh +++ b/test/scripts/jenkins_test_setup_xpack.sh @@ -3,11 +3,18 @@ source test/scripts/jenkins_test_setup.sh if [[ -z "$CODE_COVERAGE" ]]; then - installDir="$PARENT_DIR/install/kibana" - destDir="${installDir}-${CI_PARALLEL_PROCESS_NUMBER}" - cp -R "$installDir" "$destDir" - export KIBANA_INSTALL_DIR="$destDir" + destDir="build/kibana-build-xpack" + if [[ ! "$TASK_QUEUE_PROCESS_ID" ]]; then + destDir="${destDir}-${CI_PARALLEL_PROCESS_NUMBER}" + fi + + if [[ ! -d $destDir ]]; then + mkdir -p $destDir + cp -pR "$WORKSPACE/kibana-build-xpack/." $destDir/ + fi + + export KIBANA_INSTALL_DIR="$(realpath $destDir)" cd "$XPACK_DIR" fi diff --git a/test/scripts/jenkins_xpack_accessibility.sh b/test/scripts/jenkins_xpack_accessibility.sh index a3c03dd780886..3afd4bfb76396 100755 --- a/test/scripts/jenkins_xpack_accessibility.sh +++ b/test/scripts/jenkins_xpack_accessibility.sh @@ -5,5 +5,5 @@ source test/scripts/jenkins_test_setup_xpack.sh checks-reporter-with-killswitch "X-Pack accessibility tests" \ node scripts/functional_tests \ --debug --bail \ - --kibana-install-dir "$installDir" \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ --config test/accessibility/config.ts; diff --git a/test/scripts/jenkins_xpack_build_kibana.sh b/test/scripts/jenkins_xpack_build_kibana.sh index def6f1f4346ff..912130d973eb4 100755 --- a/test/scripts/jenkins_xpack_build_kibana.sh +++ b/test/scripts/jenkins_xpack_build_kibana.sh @@ -3,15 +3,9 @@ cd "$KIBANA_DIR" source src/dev/ci_setup/setup_env.sh -echo " -> building kibana platform plugins" -node scripts/build_kibana_platform_plugins \ - --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ - --scan-dir "$XPACK_DIR/test/plugin_functional/plugins" \ - --scan-dir "$XPACK_DIR/test/functional_with_es_ssl/fixtures/plugins" \ - --scan-dir "$XPACK_DIR/test/alerting_api_integration/plugins" \ - --scan-dir "$XPACK_DIR/test/plugin_api_integration/plugins" \ - --scan-dir "$XPACK_DIR/test/plugin_api_perf/plugins" \ - --verbose; +if [[ ! "$TASK_QUEUE_PROCESS_ID" ]]; then + ./test/scripts/jenkins_xpack_build_plugins.sh +fi # doesn't persist, also set in kibanaPipeline.groovy export KBN_NP_PLUGINS_BUILT=true @@ -34,6 +28,9 @@ echo " -> building and extracting default Kibana distributable for use in functi cd "$KIBANA_DIR" node scripts/build --debug --no-oss linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" -installDir="$PARENT_DIR/install/kibana" +installDir="$KIBANA_DIR/install/kibana" mkdir -p "$installDir" tar -xzf "$linuxBuild" -C "$installDir" --strip=1 + +mkdir -p "$WORKSPACE/kibana-build-xpack" +cp -pR install/kibana/. $WORKSPACE/kibana-build-xpack/ diff --git a/test/scripts/jenkins_xpack_build_plugins.sh b/test/scripts/jenkins_xpack_build_plugins.sh new file mode 100755 index 0000000000000..3fd3d02de1304 --- /dev/null +++ b/test/scripts/jenkins_xpack_build_plugins.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +echo " -> building kibana platform plugins" +node scripts/build_kibana_platform_plugins \ + --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ + --scan-dir "$XPACK_DIR/test/plugin_functional/plugins" \ + --scan-dir "$XPACK_DIR/test/functional_with_es_ssl/fixtures/plugins" \ + --scan-dir "$XPACK_DIR/test/alerting_api_integration/plugins" \ + --scan-dir "$XPACK_DIR/test/plugin_api_integration/plugins" \ + --scan-dir "$XPACK_DIR/test/plugin_api_perf/plugins" \ + --workers 12 \ + --verbose diff --git a/test/scripts/jenkins_xpack_saved_objects_field_metrics.sh b/test/scripts/jenkins_xpack_saved_objects_field_metrics.sh index d3ca8839a7dab..e3b0fe778bdfb 100755 --- a/test/scripts/jenkins_xpack_saved_objects_field_metrics.sh +++ b/test/scripts/jenkins_xpack_saved_objects_field_metrics.sh @@ -5,5 +5,5 @@ source test/scripts/jenkins_test_setup_xpack.sh checks-reporter-with-killswitch "Capture Kibana Saved Objects field count metrics" \ node scripts/functional_tests \ --debug --bail \ - --kibana-install-dir "$installDir" \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ --config test/saved_objects_field_count/config.ts; diff --git a/test/scripts/jenkins_xpack_visual_regression.sh b/test/scripts/jenkins_xpack_visual_regression.sh index 7fb7d7b71b2e4..55d4a524820c5 100755 --- a/test/scripts/jenkins_xpack_visual_regression.sh +++ b/test/scripts/jenkins_xpack_visual_regression.sh @@ -7,19 +7,19 @@ echo " -> building and extracting default Kibana distributable" cd "$KIBANA_DIR" node scripts/build --debug --no-oss linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" -installDir="$PARENT_DIR/install/kibana" +installDir="$KIBANA_DIR/install/kibana" mkdir -p "$installDir" tar -xzf "$linuxBuild" -C "$installDir" --strip=1 +mkdir -p "$WORKSPACE/kibana-build-xpack" +cp -pR install/kibana/. $WORKSPACE/kibana-build-xpack/ + # cd "$KIBANA_DIR" # source "test/scripts/jenkins_xpack_page_load_metrics.sh" cd "$KIBANA_DIR" source "test/scripts/jenkins_xpack_saved_objects_field_metrics.sh" -cd "$KIBANA_DIR" -source "test/scripts/jenkins_xpack_saved_objects_field_metrics.sh" - echo " -> running visual regression tests from x-pack directory" cd "$XPACK_DIR" yarn percy exec -t 10000 -- -- \ diff --git a/test/scripts/lint/eslint.sh b/test/scripts/lint/eslint.sh new file mode 100755 index 0000000000000..c3211300b96c5 --- /dev/null +++ b/test/scripts/lint/eslint.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:eslint diff --git a/test/scripts/lint/sasslint.sh b/test/scripts/lint/sasslint.sh new file mode 100755 index 0000000000000..b9c683bcb049e --- /dev/null +++ b/test/scripts/lint/sasslint.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:sasslint diff --git a/test/scripts/test/api_integration.sh b/test/scripts/test/api_integration.sh new file mode 100755 index 0000000000000..152c97a3ca7df --- /dev/null +++ b/test/scripts/test/api_integration.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:apiIntegrationTests diff --git a/test/scripts/test/jest_integration.sh b/test/scripts/test/jest_integration.sh new file mode 100755 index 0000000000000..73dbbddfb38f6 --- /dev/null +++ b/test/scripts/test/jest_integration.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:test_jest_integration diff --git a/test/scripts/test/jest_unit.sh b/test/scripts/test/jest_unit.sh new file mode 100755 index 0000000000000..e25452698cebc --- /dev/null +++ b/test/scripts/test/jest_unit.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:test_jest diff --git a/test/scripts/test/karma_ci.sh b/test/scripts/test/karma_ci.sh new file mode 100755 index 0000000000000..e9985300ba19d --- /dev/null +++ b/test/scripts/test/karma_ci.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:test_karma_ci diff --git a/test/scripts/test/mocha.sh b/test/scripts/test/mocha.sh new file mode 100755 index 0000000000000..43c00f0a09dcf --- /dev/null +++ b/test/scripts/test/mocha.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:mocha diff --git a/test/scripts/test/safer_lodash_set.sh b/test/scripts/test/safer_lodash_set.sh new file mode 100755 index 0000000000000..4d7f9c28210d1 --- /dev/null +++ b/test/scripts/test/safer_lodash_set.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +yarn run grunt run:test_package_safer_lodash_set diff --git a/test/scripts/test/xpack_jest_unit.sh b/test/scripts/test/xpack_jest_unit.sh new file mode 100755 index 0000000000000..93d70ec355391 --- /dev/null +++ b/test/scripts/test/xpack_jest_unit.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +cd x-pack +checks-reporter-with-killswitch "X-Pack Jest" node --max-old-space-size=6144 scripts/jest --ci --verbose --maxWorkers=10 diff --git a/test/scripts/test/xpack_karma.sh b/test/scripts/test/xpack_karma.sh new file mode 100755 index 0000000000000..9078f01f1b870 --- /dev/null +++ b/test/scripts/test/xpack_karma.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +cd x-pack +checks-reporter-with-killswitch "X-Pack Karma Tests" yarn test:karma diff --git a/test/scripts/test/xpack_list_cyclic_dependency.sh b/test/scripts/test/xpack_list_cyclic_dependency.sh new file mode 100755 index 0000000000000..493fe9f58d322 --- /dev/null +++ b/test/scripts/test/xpack_list_cyclic_dependency.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +cd x-pack +checks-reporter-with-killswitch "X-Pack List cyclic dependency test" node plugins/lists/scripts/check_circular_deps diff --git a/test/scripts/test/xpack_siem_cyclic_dependency.sh b/test/scripts/test/xpack_siem_cyclic_dependency.sh new file mode 100755 index 0000000000000..b21301f25ad08 --- /dev/null +++ b/test/scripts/test/xpack_siem_cyclic_dependency.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +cd x-pack +checks-reporter-with-killswitch "X-Pack SIEM cyclic dependency test" node plugins/security_solution/scripts/check_circular_deps diff --git a/vars/catchErrors.groovy b/vars/catchErrors.groovy index 460a90b8ec0c0..2a1b55d832606 100644 --- a/vars/catchErrors.groovy +++ b/vars/catchErrors.groovy @@ -1,8 +1,15 @@ // Basically, this is a shortcut for catchError(catchInterruptions: false) {} // By default, catchError will swallow aborts/timeouts, which we almost never want +// Also, by wrapping it in an additional try/catch, we cut down on spam in Pipeline Steps def call(Map params = [:], Closure closure) { - params.catchInterruptions = false - return catchError(params, closure) + try { + closure() + } catch (ex) { + params.catchInterruptions = false + catchError(params) { + throw ex + } + } } return this diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index 2ff7dca085d31..19f67336b9a5f 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -2,18 +2,61 @@ def withPostBuildReporting(Closure closure) { try { closure() } finally { - catchErrors { - runErrorReporter() + def parallelWorkspaces = [] + try { + parallelWorkspaces = getParallelWorkspaces() + } catch(ex) { + print ex } catchErrors { - runbld.junit() + runErrorReporter([pwd()] + parallelWorkspaces) } catchErrors { publishJunit() } + + catchErrors { + def parallelWorkspace = "${env.WORKSPACE}/parallel" + if (fileExists(parallelWorkspace)) { + dir(parallelWorkspace) { + def workspaceTasks = [:] + + parallelWorkspaces.each { workspaceDir -> + workspaceTasks[workspaceDir] = { + dir(workspaceDir) { + catchErrors { + runbld.junit() + } + } + } + } + + if (workspaceTasks) { + parallel(workspaceTasks) + } + } + } + } + } +} + +def getParallelWorkspaces() { + def workspaces = [] + def parallelWorkspace = "${env.WORKSPACE}/parallel" + if (fileExists(parallelWorkspace)) { + dir(parallelWorkspace) { + // findFiles only returns files if you use glob, so look for a file that should be in every valid workspace + workspaces = findFiles(glob: '*/kibana/package.json') + .collect { + // get the paths to the kibana directories for the parallel workspaces + return parallelWorkspace + '/' + it.path.tokenize('/').dropRight(1).join('/') + } + } } + + return workspaces } def notifyOnError(Closure closure) { @@ -35,36 +78,43 @@ def notifyOnError(Closure closure) { } } -def functionalTestProcess(String name, Closure closure) { - return { processNumber -> - def kibanaPort = "61${processNumber}1" - def esPort = "61${processNumber}2" - def esTransportPort = "61${processNumber}3" - def ingestManagementPackageRegistryPort = "61${processNumber}4" +def withFunctionalTestEnv(List additionalEnvs = [], Closure closure) { + // This can go away once everything that uses the deprecated workers.parallelProcesses() is moved to task queue + def parallelId = env.TASK_QUEUE_PROCESS_ID ?: env.CI_PARALLEL_PROCESS_NUMBER - withEnv([ - "CI_PARALLEL_PROCESS_NUMBER=${processNumber}", - "TEST_KIBANA_HOST=localhost", - "TEST_KIBANA_PORT=${kibanaPort}", - "TEST_KIBANA_URL=http://elastic:changeme@localhost:${kibanaPort}", - "TEST_ES_URL=http://elastic:changeme@localhost:${esPort}", - "TEST_ES_TRANSPORT_PORT=${esTransportPort}", - "INGEST_MANAGEMENT_PACKAGE_REGISTRY_PORT=${ingestManagementPackageRegistryPort}", - "IS_PIPELINE_JOB=1", - "JOB=${name}", - "KBN_NP_PLUGINS_BUILT=true", - ]) { - notifyOnError { - closure() - } - } + def kibanaPort = "61${parallelId}1" + def esPort = "61${parallelId}2" + def esTransportPort = "61${parallelId}3" + def ingestManagementPackageRegistryPort = "61${parallelId}4" + + withEnv([ + "CI_GROUP=${parallelId}", + "REMOVE_KIBANA_INSTALL_DIR=1", + "CI_PARALLEL_PROCESS_NUMBER=${parallelId}", + "TEST_KIBANA_HOST=localhost", + "TEST_KIBANA_PORT=${kibanaPort}", + "TEST_KIBANA_URL=http://elastic:changeme@localhost:${kibanaPort}", + "TEST_ES_URL=http://elastic:changeme@localhost:${esPort}", + "TEST_ES_TRANSPORT_PORT=${esTransportPort}", + "KBN_NP_PLUGINS_BUILT=true", + "INGEST_MANAGEMENT_PACKAGE_REGISTRY_PORT=${ingestManagementPackageRegistryPort}", + ] + additionalEnvs) { + closure() + } +} + +def functionalTestProcess(String name, Closure closure) { + return { + withFunctionalTestEnv(["JOB=${name}"], closure) } } def functionalTestProcess(String name, String script) { return functionalTestProcess(name) { - retryable(name) { - runbld(script, "Execute ${name}") + notifyOnError { + retryable(name) { + runbld(script, "Execute ${name}") + } } } } @@ -110,11 +160,17 @@ def withGcsArtifactUpload(workerName, closure) { def ARTIFACT_PATTERNS = [ '**/target/public/.kbn-optimizer-cache', 'target/kibana-*', + 'target/test-metrics/*', 'target/kibana-security-solution/**/*.png', 'target/junit/**/*', - 'test/**/screenshots/**/*.png', + 'target/test-suites-ci-plan.json', + 'test/**/screenshots/session/*.png', + 'test/**/screenshots/failure/*.png', + 'test/**/screenshots/diff/*.png', 'test/functional/failure_debug/html/*.html', - 'x-pack/test/**/screenshots/**/*.png', + 'x-pack/test/**/screenshots/session/*.png', + 'x-pack/test/**/screenshots/failure/*.png', + 'x-pack/test/**/screenshots/diff/*.png', 'x-pack/test/functional/failure_debug/html/*.html', 'x-pack/test/functional/apps/reporting/reports/session/*.pdf', ] @@ -129,6 +185,12 @@ def withGcsArtifactUpload(workerName, closure) { ARTIFACT_PATTERNS.each { pattern -> uploadGcsArtifact(uploadPrefix, pattern) } + + dir(env.WORKSPACE) { + ARTIFACT_PATTERNS.each { pattern -> + uploadGcsArtifact(uploadPrefix, "parallel/*/kibana/${pattern}") + } + } } } }) @@ -136,6 +198,10 @@ def withGcsArtifactUpload(workerName, closure) { def publishJunit() { junit(testResults: 'target/junit/**/*.xml', allowEmptyResults: true, keepLongStdio: true) + + dir(env.WORKSPACE) { + junit(testResults: 'parallel/*/kibana/target/junit/**/*.xml', allowEmptyResults: true, keepLongStdio: true) + } } def sendMail() { @@ -201,26 +267,36 @@ def doSetup() { } } -def buildOss() { +def buildOss(maxWorkers = '') { notifyOnError { - runbld("./test/scripts/jenkins_build_kibana.sh", "Build OSS/Default Kibana") + withEnv(["KBN_OPTIMIZER_MAX_WORKERS=${maxWorkers}"]) { + runbld("./test/scripts/jenkins_build_kibana.sh", "Build OSS/Default Kibana") + } } } -def buildXpack() { +def buildXpack(maxWorkers = '') { notifyOnError { - runbld("./test/scripts/jenkins_xpack_build_kibana.sh", "Build X-Pack Kibana") + withEnv(["KBN_OPTIMIZER_MAX_WORKERS=${maxWorkers}"]) { + runbld("./test/scripts/jenkins_xpack_build_kibana.sh", "Build X-Pack Kibana") + } } } def runErrorReporter() { + return runErrorReporter([pwd()]) +} + +def runErrorReporter(workspaces) { def status = buildUtils.getBuildStatus() def dryRun = status != "ABORTED" ? "" : "--no-github-update" + def globs = workspaces.collect { "'${it}/target/junit/**/*.xml'" }.join(" ") + bash( """ source src/dev/ci_setup/setup_env.sh - node scripts/report_failed_tests ${dryRun} target/junit/**/*.xml + node scripts/report_failed_tests ${dryRun} ${globs} """, "Report failed tests, if necessary" ) @@ -259,6 +335,102 @@ def call(Map params = [:], Closure closure) { } } +// Creates a task queue using withTaskQueue, and copies the bootstrapped kibana repo into each process's workspace +// Note that node_modules are mostly symlinked to save time/space. See test/scripts/jenkins_setup_parallel_workspace.sh +def withCiTaskQueue(Map options = [:], Closure closure) { + def setupClosure = { + // This can't use runbld, because it expects the source to be there, which isn't yet + bash("${env.WORKSPACE}/kibana/test/scripts/jenkins_setup_parallel_workspace.sh", "Set up duplicate workspace for parallel process") + } + + def config = [parallel: 24, setup: setupClosure] + options + + withTaskQueue(config) { + closure.call() + } +} + +def scriptTask(description, script) { + return { + withFunctionalTestEnv { + notifyOnError { + runbld(script, description) + } + } + } +} + +def scriptTaskDocker(description, script) { + return { + withDocker(scriptTask(description, script)) + } +} + +def buildDocker() { + sh( + script: """ + cp /usr/local/bin/runbld .ci/ + cp /usr/local/bin/bash_standard_lib.sh .ci/ + cd .ci + docker build -t kibana-ci -f ./Dockerfile . + """, + label: 'Build CI Docker image' + ) +} + +def withDocker(Closure closure) { + docker + .image('kibana-ci') + .inside( + "-v /etc/runbld:/etc/runbld:ro -v '${env.JENKINS_HOME}:${env.JENKINS_HOME}' -v '/dev/shm/workspace:/dev/shm/workspace' --shm-size 2GB --cpus 4", + closure + ) +} + +def buildOssPlugins() { + runbld('./test/scripts/jenkins_build_plugins.sh', 'Build OSS Plugins') +} + +def buildXpackPlugins() { + runbld('./test/scripts/jenkins_xpack_build_plugins.sh', 'Build X-Pack Plugins') +} + +def withTasks(Map params = [worker: [:]], Closure closure) { + catchErrors { + def config = [name: 'ci-worker', size: 'xxl', ramDisk: true] + (params.worker ?: [:]) + + workers.ci(config) { + withCiTaskQueue(parallel: 24) { + parallel([ + docker: { + retry(2) { + buildDocker() + } + }, + + // There are integration tests etc that require the plugins to be built first, so let's go ahead and build them before set up the parallel workspaces + ossPlugins: { buildOssPlugins() }, + xpackPlugins: { buildXpackPlugins() }, + ]) + + catchErrors { + closure() + } + } + } + } +} + +def allCiTasks() { + withTasks { + tasks.check() + tasks.lint() + tasks.test() + tasks.functionalOss() + tasks.functionalXpack() + } +} + def pipelineLibraryTests() { whenChanged(['vars/', '.ci/pipeline-library/']) { workers.base(size: 'flyweight', bootstrapped: false, ramDisk: false) { @@ -269,5 +441,4 @@ def pipelineLibraryTests() { } } - return this diff --git a/vars/task.groovy b/vars/task.groovy new file mode 100644 index 0000000000000..0c07b519b6fef --- /dev/null +++ b/vars/task.groovy @@ -0,0 +1,5 @@ +def call(Closure closure) { + withTaskQueue.addTask(closure) +} + +return this diff --git a/vars/tasks.groovy b/vars/tasks.groovy new file mode 100644 index 0000000000000..52641ce31f0be --- /dev/null +++ b/vars/tasks.groovy @@ -0,0 +1,119 @@ +def call(List closures) { + withTaskQueue.addTasks(closures) +} + +def check() { + tasks([ + kibanaPipeline.scriptTask('Check Telemetry Schema', 'test/scripts/checks/telemetry.sh'), + kibanaPipeline.scriptTask('Check TypeScript Projects', 'test/scripts/checks/ts_projects.sh'), + kibanaPipeline.scriptTask('Check Doc API Changes', 'test/scripts/checks/doc_api_changes.sh'), + kibanaPipeline.scriptTask('Check Types', 'test/scripts/checks/type_check.sh'), + kibanaPipeline.scriptTask('Check i18n', 'test/scripts/checks/i18n.sh'), + kibanaPipeline.scriptTask('Check File Casing', 'test/scripts/checks/file_casing.sh'), + kibanaPipeline.scriptTask('Check Lockfile Symlinks', 'test/scripts/checks/lock_file_symlinks.sh'), + kibanaPipeline.scriptTask('Check Licenses', 'test/scripts/checks/licenses.sh'), + kibanaPipeline.scriptTask('Verify Dependency Versions', 'test/scripts/checks/verify_dependency_versions.sh'), + kibanaPipeline.scriptTask('Verify NOTICE', 'test/scripts/checks/verify_notice.sh'), + kibanaPipeline.scriptTask('Test Projects', 'test/scripts/checks/test_projects.sh'), + kibanaPipeline.scriptTask('Test Hardening', 'test/scripts/checks/test_hardening.sh'), + ]) +} + +def lint() { + tasks([ + kibanaPipeline.scriptTask('Lint: eslint', 'test/scripts/lint/eslint.sh'), + kibanaPipeline.scriptTask('Lint: sasslint', 'test/scripts/lint/sasslint.sh'), + ]) +} + +def test() { + tasks([ + // These 2 tasks require isolation because of hard-coded, conflicting ports and such, so let's use Docker here + kibanaPipeline.scriptTaskDocker('Jest Integration Tests', 'test/scripts/test/jest_integration.sh'), + kibanaPipeline.scriptTaskDocker('Mocha Tests', 'test/scripts/test/mocha.sh'), + + kibanaPipeline.scriptTask('Jest Unit Tests', 'test/scripts/test/jest_unit.sh'), + kibanaPipeline.scriptTask('API Integration Tests', 'test/scripts/test/api_integration.sh'), + kibanaPipeline.scriptTask('@elastic/safer-lodash-set Tests', 'test/scripts/test/safer_lodash_set.sh'), + kibanaPipeline.scriptTask('X-Pack SIEM cyclic dependency', 'test/scripts/test/xpack_siem_cyclic_dependency.sh'), + kibanaPipeline.scriptTask('X-Pack List cyclic dependency', 'test/scripts/test/xpack_list_cyclic_dependency.sh'), + kibanaPipeline.scriptTask('X-Pack Jest Unit Tests', 'test/scripts/test/xpack_jest_unit.sh'), + ]) +} + +def functionalOss(Map params = [:]) { + def config = params ?: [ciGroups: true, firefox: true, accessibility: true, pluginFunctional: true, visualRegression: false] + + task { + kibanaPipeline.buildOss(6) + + if (config.ciGroups) { + def ciGroups = 1..12 + tasks(ciGroups.collect { kibanaPipeline.ossCiGroupProcess(it) }) + } + + if (config.firefox) { + task(kibanaPipeline.functionalTestProcess('oss-firefox', './test/scripts/jenkins_firefox_smoke.sh')) + } + + if (config.accessibility) { + task(kibanaPipeline.functionalTestProcess('oss-accessibility', './test/scripts/jenkins_accessibility.sh')) + } + + if (config.pluginFunctional) { + task(kibanaPipeline.functionalTestProcess('oss-pluginFunctional', './test/scripts/jenkins_plugin_functional.sh')) + } + + if (config.visualRegression) { + task(kibanaPipeline.functionalTestProcess('oss-visualRegression', './test/scripts/jenkins_visual_regression.sh')) + } + } +} + +def functionalXpack(Map params = [:]) { + def config = params ?: [ + ciGroups: true, + firefox: true, + accessibility: true, + pluginFunctional: true, + savedObjectsFieldMetrics:true, + pageLoadMetrics: false, + visualRegression: false, + ] + + task { + kibanaPipeline.buildXpack(10) + + if (config.ciGroups) { + def ciGroups = 1..10 + tasks(ciGroups.collect { kibanaPipeline.xpackCiGroupProcess(it) }) + } + + if (config.firefox) { + task(kibanaPipeline.functionalTestProcess('xpack-firefox', './test/scripts/jenkins_xpack_firefox_smoke.sh')) + } + + if (config.accessibility) { + task(kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh')) + } + + if (config.visualRegression) { + task(kibanaPipeline.functionalTestProcess('xpack-visualRegression', './test/scripts/jenkins_xpack_visual_regression.sh')) + } + + if (config.savedObjectsFieldMetrics) { + task(kibanaPipeline.functionalTestProcess('xpack-savedObjectsFieldMetrics', './test/scripts/jenkins_xpack_saved_objects_field_metrics.sh')) + } + + whenChanged([ + 'x-pack/plugins/security_solution/', + 'x-pack/test/security_solution_cypress/', + 'x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/', + 'x-pack/plugins/triggers_actions_ui/public/application/context/actions_connectors_context.tsx', + ]) { + task(kibanaPipeline.functionalTestProcess('xpack-securitySolutionCypress', './test/scripts/jenkins_security_solution_cypress.sh')) + } + } +} + +return this diff --git a/vars/withTaskQueue.groovy b/vars/withTaskQueue.groovy new file mode 100644 index 0000000000000..8132d6264744f --- /dev/null +++ b/vars/withTaskQueue.groovy @@ -0,0 +1,154 @@ +import groovy.transform.Field + +public static @Field TASK_QUEUES = [:] +public static @Field TASK_QUEUES_COUNTER = 0 + +/** + withTaskQueue creates a queue of "tasks" (just plain closures to execute), and executes them with your desired level of concurrency. + This way, you can define, for example, 40 things that need to execute, then only allow 10 of them to execute at once. + + Each "process" will execute in a separate, unique, empty directory. + If you want each process to have a bootstrapped kibana repo, check out kibanaPipeline.withCiTaskQueue + + Using the queue currently requires an agent/worker. + + Usage: + + withTaskQueue(parallel: 10) { + task { print "This is a task" } + + // This is the same as calling task() multiple times + tasks([ { print "Another task" }, { print "And another task" } ]) + + // Tasks can queue up subsequent tasks + task { + buildThing() + task { print "I depend on buildThing()" } + } + } + + You can also define a setup task that each process should execute one time before executing tasks: + withTaskQueue(parallel: 10, setup: { sh "my-setup-scrupt.sh" }) { + ... + } + +*/ +def call(Map options = [:], Closure closure) { + def config = [ parallel: 10 ] + options + def counter = ++TASK_QUEUES_COUNTER + + // We're basically abusing withEnv() to create a "scope" for all steps inside of a withTaskQueue block + // This way, we could have multiple task queue instances in the same pipeline + withEnv(["TASK_QUEUE_ID=${counter}"]) { + withTaskQueue.TASK_QUEUES[env.TASK_QUEUE_ID] = [ + tasks: [], + tmpFile: sh(script: 'mktemp', returnStdout: true).trim() + ] + + closure.call() + + def processesExecuting = 0 + def processes = [:] + def iterationId = 0 + + for(def i = 1; i <= config.parallel; i++) { + def j = i + processes["task-queue-process-${j}"] = { + catchErrors { + withEnv([ + "TASK_QUEUE_PROCESS_ID=${j}", + "TASK_QUEUE_ITERATION_ID=${++iterationId}" + ]) { + dir("${WORKSPACE}/parallel/${j}/kibana") { + if (config.setup) { + config.setup.call(j) + } + + def isDone = false + while(!isDone) { // TODO some kind of timeout? + catchErrors { + if (!getTasks().isEmpty()) { + processesExecuting++ + catchErrors { + def task + try { + task = getTasks().pop() + } catch (java.util.NoSuchElementException ex) { + return + } + + task.call() + } + processesExecuting-- + // If a task finishes, and no new tasks were queued up, and nothing else is executing + // Then all of the processes should wake up and exit + if (processesExecuting < 1 && getTasks().isEmpty()) { + taskNotify() + } + return + } + + if (processesExecuting > 0) { + taskSleep() + return + } + + // Queue is empty, no processes are executing + isDone = true + } + } + } + } + } + } + } + parallel(processes) + } +} + +// If we sleep in a loop using Groovy code, Pipeline Steps is flooded with Sleep steps +// So, instead, we just watch a file and `touch` it whenever something happens that could modify the queue +// There's a 20 minute timeout just in case something goes wrong, +// in which case this method will get called again if the process is actually supposed to be waiting. +def taskSleep() { + sh(script: """#!/bin/bash + TIMESTAMP=\$(date '+%s' -d "0 seconds ago") + for (( i=1; i<=240; i++ )) + do + if [ "\$(stat -c %Y '${getTmpFile()}')" -ge "\$TIMESTAMP" ] + then + break + else + sleep 5 + if [[ \$i == 240 ]]; then + echo "Waited for new tasks for 20 minutes, exiting in case something went wrong" + fi + fi + done + """, label: "Waiting for new tasks...") +} + +// Used to let the task queue processes know that either a new task has been queued up, or work is complete +def taskNotify() { + sh "touch '${getTmpFile()}'" +} + +def getTasks() { + return withTaskQueue.TASK_QUEUES[env.TASK_QUEUE_ID].tasks +} + +def getTmpFile() { + return withTaskQueue.TASK_QUEUES[env.TASK_QUEUE_ID].tmpFile +} + +def addTask(Closure closure) { + getTasks() << closure + taskNotify() +} + +def addTasks(List closures) { + closures.reverse().each { + getTasks() << it + } + taskNotify() +} diff --git a/vars/workers.groovy b/vars/workers.groovy index f5a28c97c6812..e582e996a78b5 100644 --- a/vars/workers.groovy +++ b/vars/workers.groovy @@ -13,6 +13,8 @@ def label(size) { return 'docker && tests-l' case 'xl': return 'docker && tests-xl' + case 'xl-highmem': + return 'docker && tests-xl-highmem' case 'xxl': return 'docker && tests-xxl' } @@ -55,6 +57,11 @@ def base(Map params, Closure closure) { } } + sh( + script: "mkdir -p ${env.WORKSPACE}/tmp", + label: "Create custom temp directory" + ) + def checkoutInfo = [:] if (config.scm) { @@ -89,6 +96,7 @@ def base(Map params, Closure closure) { "PR_AUTHOR=${env.ghprbPullAuthorLogin ?: ''}", "TEST_BROWSER_HEADLESS=1", "GIT_BRANCH=${checkoutInfo.branch}", + "TMPDIR=${env.WORKSPACE}/tmp", // For Chrome and anything else that respects it ]) { withCredentials([ string(credentialsId: 'vault-addr', variable: 'VAULT_ADDR'), @@ -169,7 +177,9 @@ def parallelProcesses(Map params) { sleep(delay) } - processClosure(processNumber) + withEnv(["CI_PARALLEL_PROCESS_NUMBER=${processNumber}"]) { + processClosure() + } } } diff --git a/x-pack/plugins/canvas/storybook/storyshots.test.tsx b/x-pack/plugins/canvas/storybook/storyshots.test.tsx index b51a85edaa67b..85ec7baf18c62 100644 --- a/x-pack/plugins/canvas/storybook/storyshots.test.tsx +++ b/x-pack/plugins/canvas/storybook/storyshots.test.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import fs from 'fs'; import { ReactChildren } from 'react'; import path from 'path'; import moment from 'moment'; @@ -94,6 +95,12 @@ jest.mock('../shareable_runtime/components/rendered_element'); // @ts-expect-error RenderedElement.mockImplementation(() => 'RenderedElement'); +// Some of the code requires that this directory exists, but the tests don't actually require any css to be present +const cssDir = path.resolve(__dirname, '../../../../built_assets/css'); +if (!fs.existsSync(cssDir)) { + fs.mkdirSync(cssDir, { recursive: true }); +} + addSerializer(styleSheetSerializer); // Initialize Storyshots and build the Jest Snapshots From 700b94392542e2ae0543ee4a4f898e4fd4b3effd Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Fri, 14 Aug 2020 13:44:44 -0700 Subject: [PATCH 06/22] [Reporting/Flaky Test] Skip test for paging list of reports (#75075) --- .../test/functional/apps/reporting_management/report_listing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/reporting_management/report_listing.ts b/x-pack/test/functional/apps/reporting_management/report_listing.ts index 7065dc9e0471e..1c71b624f78dc 100644 --- a/x-pack/test/functional/apps/reporting_management/report_listing.ts +++ b/x-pack/test/functional/apps/reporting_management/report_listing.ts @@ -67,7 +67,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - it('Paginates historical reports', async () => { + it.skip('Paginates historical reports', async () => { // wait for first row of page 1 await testSubjects.find('checkboxSelectRow-k9a9xlwl0gpe1457b10rraq3'); From 3199af17675184b0409dac5e379f6ee2787d2ffd Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Fri, 14 Aug 2020 17:37:30 -0400 Subject: [PATCH 07/22] skip flaky suite (#74814) --- .../open_timeline/open_timeline_modal/index.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx index 5ce53607817eb..36ab5979f623c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx @@ -46,7 +46,8 @@ jest.mock('../use_timeline_status', () => { }; }); -describe('OpenTimelineModal', () => { +// Failing: See https://github.com/elastic/kibana/issues/74814 +describe.skip('OpenTimelineModal', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); const mockInstallPrepackagedTimelines = jest.fn(); beforeEach(() => { From 304f9d2de8046b127eb1427528e93d10a8314a84 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Fri, 14 Aug 2020 15:54:44 -0700 Subject: [PATCH 08/22] [7.x] [Reporting] Update more Server Types for TaskManager (#74915) (#75003) * [Reporting] Update more Server Types for TaskManager (#74915) * [Reporting] Update more Server Types for TaskManager * remove some task manager references * more strict * more strict 2 * simplify * fix test * fix test * routing validation unused types cleanup * remove more casting in route handlers * feedback changes * original comment was fine Co-authored-by: Elastic Machine # Conflicts: # x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts # x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts # x-pack/plugins/reporting/server/lib/store/store.ts * fix test * fix test --- x-pack/plugins/reporting/server/core.ts | 8 +- .../server/export_types/csv/create_job.ts | 4 +- .../export_types/csv/execute_job.test.ts | 112 +++---- .../server/export_types/csv/execute_job.ts | 4 +- .../export_types/csv/generate_csv/index.ts | 4 + .../server/export_types/csv/index.ts | 8 +- .../server/export_types/csv/types.d.ts | 6 +- .../csv_from_savedobject/execute_job.ts | 2 +- .../export_types/png/create_job/index.ts | 4 +- .../export_types/png/execute_job/index.ts | 4 +- .../server/export_types/png/index.ts | 8 +- .../server/export_types/png/types.d.ts | 7 +- .../printable_pdf/create_job/index.ts | 4 +- .../printable_pdf/execute_job/index.ts | 4 +- .../export_types/printable_pdf/index.ts | 8 +- .../export_types/printable_pdf/types.d.ts | 6 +- .../reporting/server/lib/create_worker.ts | 6 +- .../reporting/server/lib/enqueue_job.ts | 38 ++- .../server/lib/esqueue/constants/index.js | 4 +- .../reporting/server/lib/esqueue/worker.js | 48 +-- x-pack/plugins/reporting/server/lib/index.ts | 3 +- .../lib/{esqueue/constants => }/statuses.ts | 0 .../reporting/server/lib/store/mapping.ts | 2 +- .../reporting/server/lib/store/report.test.ts | 113 +++++-- .../reporting/server/lib/store/report.ts | 144 +++++++-- .../reporting/server/lib/store/store.test.ts | 304 ++++++++++++++---- .../reporting/server/lib/store/store.ts | 194 +++++++---- x-pack/plugins/reporting/server/plugin.ts | 10 +- .../server/routes/generate_from_jobparams.ts | 15 +- .../generate_from_savedobject_immediate.ts | 10 +- .../server/routes/generation.test.ts | 3 +- .../reporting/server/routes/generation.ts | 10 +- .../plugins/reporting/server/routes/legacy.ts | 6 +- .../server/routes/lib/get_document_payload.ts | 7 +- .../routes/lib/get_job_params_from_request.ts | 16 +- .../reporting/server/routes/types.d.ts | 4 +- .../create_mock_reportingplugin.ts | 19 +- x-pack/plugins/reporting/server/types.ts | 46 +-- 38 files changed, 780 insertions(+), 415 deletions(-) rename x-pack/plugins/reporting/server/lib/{esqueue/constants => }/statuses.ts (100%) diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts index 95dc7586ad4a6..25594e1c0140b 100644 --- a/x-pack/plugins/reporting/server/core.ts +++ b/x-pack/plugins/reporting/server/core.ts @@ -23,7 +23,6 @@ import { HeadlessChromiumDriverFactory } from './browsers/chromium/driver_factor import { screenshotsObservableFactory } from './lib/screenshots'; import { checkLicense, getExportTypesRegistry } from './lib'; import { ESQueueInstance } from './lib/create_queue'; -import { EnqueueJobFn } from './lib/enqueue_job'; import { ReportingStore } from './lib/store'; export interface ReportingInternalSetup { @@ -36,7 +35,6 @@ export interface ReportingInternalSetup { export interface ReportingInternalStart { browserDriverFactory: HeadlessChromiumDriverFactory; - enqueueJob: EnqueueJobFn; esqueue: ESQueueInstance; store: ReportingStore; savedObjects: SavedObjectsServiceStart; @@ -115,7 +113,7 @@ export class ReportingCore { /* * Gives async access to the startDeps */ - private async getPluginStartDeps() { + public async getPluginStartDeps() { if (this.pluginStartDeps) { return this.pluginStartDeps; } @@ -131,10 +129,6 @@ export class ReportingCore { return (await this.getPluginStartDeps()).esqueue; } - public async getEnqueueJob() { - return (await this.getPluginStartDeps()).enqueueJob; - } - public async getLicenseInfo() { const { licensing } = this.getPluginSetupDeps(); return await licensing.license$ diff --git a/x-pack/plugins/reporting/server/export_types/csv/create_job.ts b/x-pack/plugins/reporting/server/export_types/csv/create_job.ts index 5e8ce923a79e0..252968e386b53 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/create_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/create_job.ts @@ -5,10 +5,10 @@ */ import { cryptoFactory } from '../../lib'; -import { ESQueueCreateJobFn, ScheduleTaskFnFactory } from '../../types'; +import { CreateJobFn, ScheduleTaskFnFactory } from '../../types'; import { JobParamsDiscoverCsv } from './types'; -export const scheduleTaskFnFactory: ScheduleTaskFnFactory> = function createJobFactoryFn(reporting) { const config = reporting.getConfig(); diff --git a/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts index 29c0850f03698..e01273d237af4 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts @@ -5,7 +5,7 @@ */ import nodeCrypto from '@elastic/node-crypto'; -import { IUiSettingsClient, ElasticsearchServiceSetup } from 'kibana/server'; +import { ElasticsearchServiceSetup, IUiSettingsClient } from 'kibana/server'; // @ts-ignore import Puid from 'puid'; import sinon from 'sinon'; @@ -20,8 +20,8 @@ import { CSV_BOM_CHARS } from '../../../common/constants'; import { LevelLogger } from '../../lib'; import { setFieldFormats } from '../../services'; import { createMockReportingCore } from '../../test_helpers'; -import { ScheduledTaskParamsCSV } from './types'; import { runTaskFnFactory } from './execute_job'; +import { ScheduledTaskParamsCSV } from './types'; const delay = (ms: number) => new Promise((resolve) => setTimeout(() => resolve(), ms)); @@ -125,7 +125,7 @@ describe('CSV Execute Job', function () { describe('basic Elasticsearch call behavior', function () { it('should decrypt encrypted headers and pass to callAsCurrentUser', async function () { - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); await runTask( 'job456', getScheduledTaskParams({ @@ -145,7 +145,7 @@ describe('CSV Execute Job', function () { testBody: true, }; - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const job = getScheduledTaskParams({ headers: encryptedHeaders, fields: [], @@ -172,7 +172,7 @@ describe('CSV Execute Job', function () { _scroll_id: scrollId, }); callAsCurrentUserStub.onSecondCall().resolves(defaultElasticsearchResponse); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); await runTask( 'job456', getScheduledTaskParams({ @@ -190,7 +190,7 @@ describe('CSV Execute Job', function () { }); it('should not execute scroll if there are no hits from the search', async function () { - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); await runTask( 'job456', getScheduledTaskParams({ @@ -224,7 +224,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); await runTask( 'job456', getScheduledTaskParams({ @@ -263,7 +263,7 @@ describe('CSV Execute Job', function () { _scroll_id: lastScrollId, }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); await runTask( 'job456', getScheduledTaskParams({ @@ -295,7 +295,7 @@ describe('CSV Execute Job', function () { _scroll_id: lastScrollId, }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -322,7 +322,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -347,7 +347,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['=SUM(A1:A2)', 'two'], @@ -373,7 +373,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -399,7 +399,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['=SUM(A1:A2)', 'two'], @@ -425,7 +425,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -452,7 +452,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -473,7 +473,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -496,7 +496,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -517,7 +517,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -533,7 +533,7 @@ describe('CSV Execute Job', function () { describe('Elasticsearch call errors', function () { it('should reject Promise if search call errors out', async function () { callAsCurrentUserStub.rejects(new Error()); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: [], @@ -552,7 +552,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); callAsCurrentUserStub.onSecondCall().rejects(new Error()); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: [], @@ -573,7 +573,7 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: [], @@ -592,7 +592,7 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: [], @@ -618,7 +618,7 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: [], @@ -644,7 +644,7 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: [], @@ -679,7 +679,7 @@ describe('CSV Execute Job', function () { }); it('should stop calling Elasticsearch when cancellationToken.cancel is called', async function () { - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); runTask( 'job345', getScheduledTaskParams({ @@ -698,7 +698,7 @@ describe('CSV Execute Job', function () { }); it(`shouldn't call clearScroll if it never got a scrollId`, async function () { - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); runTask( 'job345', getScheduledTaskParams({ @@ -716,7 +716,7 @@ describe('CSV Execute Job', function () { }); it('should call clearScroll if it got a scrollId', async function () { - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); runTask( 'job345', getScheduledTaskParams({ @@ -738,7 +738,7 @@ describe('CSV Execute Job', function () { describe('csv content', function () { it('should write column headers to output, even if there are no results', async function () { - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -750,7 +750,7 @@ describe('CSV Execute Job', function () { it('should use custom uiSettings csv:separator for header', async function () { mockUiSettingsClient.get.withArgs(CSV_SEPARATOR_SETTING).returns(';'); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -762,7 +762,7 @@ describe('CSV Execute Job', function () { it('should escape column headers if uiSettings csv:quoteValues is true', async function () { mockUiSettingsClient.get.withArgs(CSV_QUOTE_VALUES_SETTING).returns(true); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], @@ -774,7 +774,7 @@ describe('CSV Execute Job', function () { it(`shouldn't escape column headers if uiSettings csv:quoteValues is false`, async function () { mockUiSettingsClient.get.withArgs(CSV_QUOTE_VALUES_SETTING).returns(false); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], @@ -785,7 +785,7 @@ describe('CSV Execute Job', function () { }); it('should write column headers to output, when there are results', async function () { - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ one: '1', two: '2' }], @@ -799,13 +799,14 @@ describe('CSV Execute Job', function () { searchRequest: { index: null, body: null }, }); const { content } = await runTask('job123', jobParams, cancellationToken); - const lines = content.split('\n'); + expect(content).not.toBe(null); + const lines = content!.split('\n'); const headerLine = lines[0]; expect(headerLine).toBe('one,two'); }); it('should use comma separated values of non-nested fields from _source', async function () { - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -820,13 +821,14 @@ describe('CSV Execute Job', function () { searchRequest: { index: null, body: null }, }); const { content } = await runTask('job123', jobParams, cancellationToken); - const lines = content.split('\n'); + expect(content).not.toBe(null); + const lines = content!.split('\n'); const valuesLine = lines[1]; expect(valuesLine).toBe('foo,bar'); }); it('should concatenate the hits from multiple responses', async function () { - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -847,14 +849,15 @@ describe('CSV Execute Job', function () { searchRequest: { index: null, body: null }, }); const { content } = await runTask('job123', jobParams, cancellationToken); - const lines = content.split('\n'); + expect(content).not.toBe(null); + const lines = content!.split('\n'); expect(lines[1]).toBe('foo,bar'); expect(lines[2]).toBe('baz,qux'); }); it('should use field formatters to format fields', async function () { - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -878,7 +881,8 @@ describe('CSV Execute Job', function () { }, }); const { content } = await runTask('job123', jobParams, cancellationToken); - const lines = content.split('\n'); + expect(content).not.toBe(null); + const lines = content!.split('\n'); expect(lines[1]).toBe('FOO,bar'); }); @@ -890,13 +894,13 @@ describe('CSV Execute Job', function () { // tests use these 'simple' characters to make the math easier describe('when only the headers exceed the maxSizeBytes', function () { - let content: string; - let maxSizeReached: boolean; + let content: string | null; + let maxSizeReached: boolean | undefined; beforeEach(async function () { configGetStub.withArgs('csv', 'maxSizeBytes').returns(1); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -920,13 +924,13 @@ describe('CSV Execute Job', function () { }); describe('when headers are equal to maxSizeBytes', function () { - let content: string; - let maxSizeReached: boolean; + let content: string | null; + let maxSizeReached: boolean | undefined; beforeEach(async function () { configGetStub.withArgs('csv', 'maxSizeBytes').returns(9); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -950,8 +954,8 @@ describe('CSV Execute Job', function () { }); describe('when the data exceeds the maxSizeBytes', function () { - let content: string; - let maxSizeReached: boolean; + let content: string | null; + let maxSizeReached: boolean | undefined; beforeEach(async function () { configGetStub.withArgs('csv', 'maxSizeBytes').returns(9); @@ -963,7 +967,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -988,8 +992,8 @@ describe('CSV Execute Job', function () { }); describe('when headers and data equal the maxSizeBytes', function () { - let content: string; - let maxSizeReached: boolean; + let content: string | null; + let maxSizeReached: boolean | undefined; beforeEach(async function () { mockReportingCore.getUiSettingsServiceFactory = () => @@ -1003,7 +1007,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1040,7 +1044,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1066,7 +1070,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1092,7 +1096,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const runTask = runTaskFnFactory(mockReportingCore, mockLogger); const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], diff --git a/x-pack/plugins/reporting/server/export_types/csv/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv/execute_job.ts index f0c41a6a49703..802f4a81777c5 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/execute_job.ts @@ -10,7 +10,7 @@ import Hapi from 'hapi'; import { KibanaRequest } from '../../../../../../src/core/server'; import { CONTENT_TYPE_CSV, CSV_JOB_TYPE } from '../../../common/constants'; import { cryptoFactory, LevelLogger } from '../../lib'; -import { ESQueueWorkerExecuteFn, RunTaskFnFactory } from '../../types'; +import { WorkerExecuteFn, RunTaskFnFactory } from '../../types'; import { ScheduledTaskParamsCSV } from './types'; import { createGenerateCsv } from './generate_csv'; @@ -54,7 +54,7 @@ const getRequest = async (headers: string | undefined, crypto: Crypto, logger: L } as Hapi.Request); }; -export const runTaskFnFactory: RunTaskFnFactory> = function executeJobFactoryFn(reporting, parentLogger) { const config = reporting.getConfig(); diff --git a/x-pack/plugins/reporting/server/export_types/csv/generate_csv/index.ts b/x-pack/plugins/reporting/server/export_types/csv/generate_csv/index.ts index 8da27100ac31c..06aa2434afc3f 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/generate_csv/index.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/generate_csv/index.ts @@ -113,6 +113,10 @@ export function createGenerateCsv(logger: LevelLogger) { break; } + if (cancellationToken.isCancelled()) { + break; + } + const flattened = flattenHit(hit); const rows = formatCsvValues(flattened); const rowsHaveFormulas = diff --git a/x-pack/plugins/reporting/server/export_types/csv/index.ts b/x-pack/plugins/reporting/server/export_types/csv/index.ts index dffc874831dc2..4bca42e0661e5 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/index.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/index.ts @@ -13,17 +13,17 @@ import { LICENSE_TYPE_TRIAL, } from '../../../common/constants'; import { CSV_JOB_TYPE as jobType } from '../../../constants'; -import { ESQueueCreateJobFn, ESQueueWorkerExecuteFn, ExportTypeDefinition } from '../../types'; -import { metadata } from './metadata'; +import { CreateJobFn, WorkerExecuteFn, ExportTypeDefinition } from '../../types'; import { scheduleTaskFnFactory } from './create_job'; import { runTaskFnFactory } from './execute_job'; +import { metadata } from './metadata'; import { JobParamsDiscoverCsv, ScheduledTaskParamsCSV } from './types'; export const getExportType = (): ExportTypeDefinition< JobParamsDiscoverCsv, - ESQueueCreateJobFn, + CreateJobFn, ScheduledTaskParamsCSV, - ESQueueWorkerExecuteFn + WorkerExecuteFn > => ({ ...metadata, jobType, diff --git a/x-pack/plugins/reporting/server/export_types/csv/types.d.ts b/x-pack/plugins/reporting/server/export_types/csv/types.d.ts index 9e86a5bb254a3..e0d09d04a3d3a 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/types.d.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ScheduledTaskParams } from '../../types'; +import { CreateJobBaseParams, ScheduledTaskParams } from '../../types'; export type RawValue = string | object | null | undefined; @@ -28,10 +28,8 @@ export interface IndexPatternSavedObject { }; } -export interface JobParamsDiscoverCsv { - browserTimezone: string; +export interface JobParamsDiscoverCsv extends CreateJobBaseParams { indexPatternId: string; - objectType: string; title: string; searchRequest: SearchRequest; fields: string[]; diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts index 0cc9ec16ed71b..ec7e0a21f0498 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts @@ -41,7 +41,7 @@ export const runTaskFnFactory: RunTaskFnFactory = function e // jobID is only for "queued" jobs // Use the jobID as a logging tag or "immediate" const { jobParams } = jobPayload; - const jobLogger = logger.clone([jobId === null ? 'immediate' : jobId]); + const jobLogger = logger.clone(['immediate']); const generateCsv = createGenerateCsv(jobLogger); const { panel, visType } = jobParams as JobParamsPanelCsv & { panel: SearchPanel }; diff --git a/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts index 9227354520b6e..2252177e98085 100644 --- a/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts @@ -5,11 +5,11 @@ */ import { cryptoFactory } from '../../../lib'; -import { ESQueueCreateJobFn, ScheduleTaskFnFactory } from '../../../types'; +import { CreateJobFn, ScheduleTaskFnFactory } from '../../../types'; import { validateUrls } from '../../common'; import { JobParamsPNG } from '../types'; -export const scheduleTaskFnFactory: ScheduleTaskFnFactory> = function createJobFactoryFn(reporting) { const config = reporting.getConfig(); diff --git a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts index 9c7134736f4f6..35cd4139df413 100644 --- a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts @@ -8,7 +8,7 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; import { PNG_JOB_TYPE } from '../../../../common/constants'; -import { ESQueueWorkerExecuteFn, RunTaskFnFactory, TaskRunResult } from '../../..//types'; +import { WorkerExecuteFn, RunTaskFnFactory, TaskRunResult } from '../../..//types'; import { decryptJobHeaders, getConditionalHeaders, @@ -18,7 +18,7 @@ import { import { generatePngObservableFactory } from '../lib/generate_png'; import { ScheduledTaskParamsPNG } from '../types'; -type QueuedPngExecutorFactory = RunTaskFnFactory>; +type QueuedPngExecutorFactory = RunTaskFnFactory>; export const runTaskFnFactory: QueuedPngExecutorFactory = function executeJobFactoryFn( reporting, diff --git a/x-pack/plugins/reporting/server/export_types/png/index.ts b/x-pack/plugins/reporting/server/export_types/png/index.ts index 25b4dbd60535b..c966dedb6b076 100644 --- a/x-pack/plugins/reporting/server/export_types/png/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/index.ts @@ -12,17 +12,17 @@ import { LICENSE_TYPE_TRIAL, PNG_JOB_TYPE as jobType, } from '../../../common/constants'; -import { ESQueueCreateJobFn, ESQueueWorkerExecuteFn, ExportTypeDefinition } from '../../types'; -import { metadata } from './metadata'; +import { CreateJobFn, WorkerExecuteFn, ExportTypeDefinition } from '../../types'; import { scheduleTaskFnFactory } from './create_job'; import { runTaskFnFactory } from './execute_job'; +import { metadata } from './metadata'; import { JobParamsPNG, ScheduledTaskParamsPNG } from './types'; export const getExportType = (): ExportTypeDefinition< JobParamsPNG, - ESQueueCreateJobFn, + CreateJobFn, ScheduledTaskParamsPNG, - ESQueueWorkerExecuteFn + WorkerExecuteFn > => ({ ...metadata, jobType, diff --git a/x-pack/plugins/reporting/server/export_types/png/types.d.ts b/x-pack/plugins/reporting/server/export_types/png/types.d.ts index 4c40f55f0f0d6..1ddee8419df30 100644 --- a/x-pack/plugins/reporting/server/export_types/png/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/png/types.d.ts @@ -4,16 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ScheduledTaskParams } from '../../../server/types'; +import { CreateJobBaseParams, ScheduledTaskParams } from '../../../server/types'; import { LayoutInstance, LayoutParams } from '../../lib/layouts'; // Job params: structure of incoming user request data -export interface JobParamsPNG { - objectType: string; +export interface JobParamsPNG extends CreateJobBaseParams { title: string; relativeUrl: string; - browserTimezone: string; - layout: LayoutInstance; } // Job payload: structure of stored job data provided by create_job diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts index 2295ea4ba92aa..0c842b9f98e06 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts @@ -6,13 +6,13 @@ import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { cryptoFactory } from '../../../lib'; -import { ESQueueCreateJobFn, ScheduleTaskFnFactory } from '../../../types'; +import { CreateJobFn, ScheduleTaskFnFactory } from '../../../types'; import { validateUrls } from '../../common'; import { JobParamsPDF } from '../types'; // @ts-ignore no module def (deprecated module) import { compatibilityShimFactory } from './compatibility_shim'; -export const scheduleTaskFnFactory: ScheduleTaskFnFactory> = function createJobFactoryFn(reporting, logger) { const config = reporting.getConfig(); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts index eb15c0a71ca3f..5ace1c987adb5 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts @@ -8,7 +8,7 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; import { PDF_JOB_TYPE } from '../../../../common/constants'; -import { ESQueueWorkerExecuteFn, RunTaskFnFactory, TaskRunResult } from '../../../types'; +import { WorkerExecuteFn, RunTaskFnFactory, TaskRunResult } from '../../../types'; import { decryptJobHeaders, getConditionalHeaders, @@ -19,7 +19,7 @@ import { import { generatePdfObservableFactory } from '../lib/generate_pdf'; import { ScheduledTaskParamsPDF } from '../types'; -type QueuedPdfExecutorFactory = RunTaskFnFactory>; +type QueuedPdfExecutorFactory = RunTaskFnFactory>; export const runTaskFnFactory: QueuedPdfExecutorFactory = function executeJobFactoryFn( reporting, diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts index e5115c243c697..7f21d36c4b72c 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts @@ -12,17 +12,17 @@ import { LICENSE_TYPE_TRIAL, PDF_JOB_TYPE as jobType, } from '../../../common/constants'; -import { ESQueueCreateJobFn, ESQueueWorkerExecuteFn, ExportTypeDefinition } from '../../types'; -import { metadata } from './metadata'; +import { CreateJobFn, WorkerExecuteFn, ExportTypeDefinition } from '../../types'; import { scheduleTaskFnFactory } from './create_job'; import { runTaskFnFactory } from './execute_job'; +import { metadata } from './metadata'; import { JobParamsPDF, ScheduledTaskParamsPDF } from './types'; export const getExportType = (): ExportTypeDefinition< JobParamsPDF, - ESQueueCreateJobFn, + CreateJobFn, ScheduledTaskParamsPDF, - ESQueueWorkerExecuteFn + WorkerExecuteFn > => ({ ...metadata, jobType, diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts index b33c1f7ab60b5..be7bb470860c6 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts @@ -4,15 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ScheduledTaskParams } from '../../../server/types'; +import { CreateJobBaseParams, ScheduledTaskParams } from '../../../server/types'; import { LayoutInstance, LayoutParams } from '../../lib/layouts'; // Job params: structure of incoming user request data, after being parsed from RISON -export interface JobParamsPDF { - objectType: string; +export interface JobParamsPDF extends CreateJobBaseParams { title: string; relativeUrls: string[]; - browserTimezone: string; layout: LayoutInstance; } diff --git a/x-pack/plugins/reporting/server/lib/create_worker.ts b/x-pack/plugins/reporting/server/lib/create_worker.ts index 837be1f44a093..5b0f1ddb2f157 100644 --- a/x-pack/plugins/reporting/server/lib/create_worker.ts +++ b/x-pack/plugins/reporting/server/lib/create_worker.ts @@ -8,7 +8,7 @@ import { CancellationToken } from '../../common'; import { PLUGIN_ID } from '../../common/constants'; import { ReportingCore } from '../../server'; import { LevelLogger } from '../../server/lib'; -import { ESQueueWorkerExecuteFn, ExportTypeDefinition, JobSource } from '../../server/types'; +import { ExportTypeDefinition, JobSource, WorkerExecuteFn } from '../../server/types'; import { ESQueueInstance } from './create_queue'; // @ts-ignore untyped dependency import { events as esqueueEvents } from './esqueue'; @@ -22,10 +22,10 @@ export function createWorkerFactory(reporting: ReportingCore, log // Once more document types are added, this will need to be passed in return async function createWorker(queue: ESQueueInstance) { // export type / execute job map - const jobExecutors: Map> = new Map(); + const jobExecutors: Map> = new Map(); for (const exportType of reporting.getExportTypesRegistry().getAll() as Array< - ExportTypeDefinition> + ExportTypeDefinition> >) { const jobExecutor = exportType.runTaskFnFactory(reporting, logger); jobExecutors.set(exportType.jobType, jobExecutor); diff --git a/x-pack/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/plugins/reporting/server/lib/enqueue_job.ts index d1554a03b9389..31960c782b7b9 100644 --- a/x-pack/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/plugins/reporting/server/lib/enqueue_job.ts @@ -5,15 +5,15 @@ */ import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { ReportingCore } from '../'; import { AuthenticatedUser } from '../../../security/server'; -import { ESQueueCreateJobFn } from '../../server/types'; -import { ReportingCore } from '../core'; +import { CreateJobBaseParams, CreateJobFn } from '../types'; import { LevelLogger } from './'; -import { ReportingStore, Report } from './store'; +import { Report } from './store'; export type EnqueueJobFn = ( exportTypeId: string, - jobParams: unknown, + jobParams: CreateJobBaseParams, user: AuthenticatedUser | null, context: RequestHandlerContext, request: KibanaRequest @@ -21,41 +21,39 @@ export type EnqueueJobFn = ( export function enqueueJobFactory( reporting: ReportingCore, - store: ReportingStore, parentLogger: LevelLogger ): EnqueueJobFn { - const config = reporting.getConfig(); - const queueTimeout = config.get('queue', 'timeout'); - const browserType = config.get('capture', 'browser', 'type'); - const maxAttempts = config.get('capture', 'maxAttempts'); const logger = parentLogger.clone(['queue-job']); return async function enqueueJob( exportTypeId: string, - jobParams: unknown, + jobParams: CreateJobBaseParams, user: AuthenticatedUser | null, context: RequestHandlerContext, request: KibanaRequest ) { - type ScheduleTaskFnType = ESQueueCreateJobFn; + type ScheduleTaskFnType = CreateJobFn; - const username = user ? user.username : false; + const username: string | null = user ? user.username : null; const exportType = reporting.getExportTypesRegistry().getById(exportTypeId); if (exportType == null) { throw new Error(`Export type ${exportTypeId} does not exist in the registry!`); } - const scheduleTask = exportType.scheduleTaskFnFactory(reporting, logger) as ScheduleTaskFnType; + const [scheduleTask, { store }] = await Promise.all([ + exportType.scheduleTaskFnFactory(reporting, logger) as ScheduleTaskFnType, + reporting.getPluginStartDeps(), + ]); + + // add encrytped headers const payload = await scheduleTask(jobParams, context, request); - const options = { - timeout: queueTimeout, - created_by: username, - browser_type: browserType, - max_attempts: maxAttempts, - }; + // store the pending report, puts it in the Reporting Management UI table + const report = await store.addReport(exportType.jobType, username, payload); + + logger.info(`Scheduled ${exportType.name} report: ${report._id}`); - return await store.addReport(exportType.jobType, payload, options); + return report; }; } diff --git a/x-pack/plugins/reporting/server/lib/esqueue/constants/index.js b/x-pack/plugins/reporting/server/lib/esqueue/constants/index.js index 5fcff3531851a..7f7383bb8611d 100644 --- a/x-pack/plugins/reporting/server/lib/esqueue/constants/index.js +++ b/x-pack/plugins/reporting/server/lib/esqueue/constants/index.js @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { events } from './events'; -import { statuses } from './statuses'; +import { statuses } from '../../statuses'; import { defaultSettings } from './default_settings'; +import { events } from './events'; export const constants = { ...events, diff --git a/x-pack/plugins/reporting/server/lib/esqueue/worker.js b/x-pack/plugins/reporting/server/lib/esqueue/worker.js index f24cb6cd55307..f96b4f56411b6 100644 --- a/x-pack/plugins/reporting/server/lib/esqueue/worker.js +++ b/x-pack/plugins/reporting/server/lib/esqueue/worker.js @@ -158,26 +158,18 @@ export class Worker extends events.EventEmitter { kibana_name: this.kibanaName, }; - return this.queue.store - .updateReport({ - index: job._index, - id: job._id, - if_seq_no: job._seq_no, - if_primary_term: job._primary_term, - body: { doc }, - }) - .then((response) => { - this.info(`Job marked as claimed: ${getUpdatedDocPath(response)}`); - const updatedJob = { - ...job, - ...response, - }; - updatedJob._source = { - ...job._source, - ...doc, - }; - return updatedJob; - }); + return this.queue.store.setReportClaimed(job, doc).then((response) => { + this.info(`Job marked as claimed: ${getUpdatedDocPath(response)}`); + const updatedJob = { + ...job, + ...response, + }; + updatedJob._source = { + ...job._source, + ...doc, + }; + return updatedJob; + }); } _failJob(job, output = false) { @@ -198,13 +190,7 @@ export class Worker extends events.EventEmitter { }); return this.queue.store - .updateReport({ - index: job._index, - id: job._id, - if_seq_no: job._seq_no, - if_primary_term: job._primary_term, - body: { doc }, - }) + .setReportFailed(job, doc) .then((response) => { this.info(`Job marked as failed: ${getUpdatedDocPath(response)}`); }) @@ -295,13 +281,7 @@ export class Worker extends events.EventEmitter { }; return this.queue.store - .updateReport({ - index: job._index, - id: job._id, - if_seq_no: job._seq_no, - if_primary_term: job._primary_term, - body: { doc }, - }) + .setReportCompleted(job, doc) .then((response) => { const eventOutput = { job: formatJobObject(job), diff --git a/x-pack/plugins/reporting/server/lib/index.ts b/x-pack/plugins/reporting/server/lib/index.ts index e4adb1188e3fc..f3a09cffbb104 100644 --- a/x-pack/plugins/reporting/server/lib/index.ts +++ b/x-pack/plugins/reporting/server/lib/index.ts @@ -8,8 +8,9 @@ export { checkLicense } from './check_license'; export { createQueueFactory } from './create_queue'; export { cryptoFactory } from './crypto'; export { enqueueJobFactory } from './enqueue_job'; -export { getExportTypesRegistry } from './export_types_registry'; +export { ExportTypesRegistry, getExportTypesRegistry } from './export_types_registry'; export { LevelLogger } from './level_logger'; +export { statuses } from './statuses'; export { ReportingStore } from './store'; export { startTrace } from './trace'; export { runValidations } from './validate'; diff --git a/x-pack/plugins/reporting/server/lib/esqueue/constants/statuses.ts b/x-pack/plugins/reporting/server/lib/statuses.ts similarity index 100% rename from x-pack/plugins/reporting/server/lib/esqueue/constants/statuses.ts rename to x-pack/plugins/reporting/server/lib/statuses.ts diff --git a/x-pack/plugins/reporting/server/lib/store/mapping.ts b/x-pack/plugins/reporting/server/lib/store/mapping.ts index a819923e2f105..d08b928cdca4b 100644 --- a/x-pack/plugins/reporting/server/lib/store/mapping.ts +++ b/x-pack/plugins/reporting/server/lib/store/mapping.ts @@ -45,7 +45,7 @@ export const mapping = { priority: { type: 'byte' }, timeout: { type: 'long' }, process_expiration: { type: 'date' }, - created_by: { type: 'keyword' }, + created_by: { type: 'keyword' }, // `null` if security is disabled created_at: { type: 'date' }, started_at: { type: 'date' }, completed_at: { type: 'date' }, diff --git a/x-pack/plugins/reporting/server/lib/store/report.test.ts b/x-pack/plugins/reporting/server/lib/store/report.test.ts index 83444494e61d3..9ac5d1f87c387 100644 --- a/x-pack/plugins/reporting/server/lib/store/report.test.ts +++ b/x-pack/plugins/reporting/server/lib/store/report.test.ts @@ -8,46 +8,59 @@ import { Report } from './report'; describe('Class Report', () => { it('constructs Report instance', () => { - const opts = { - index: '.reporting-test-index-12345', + const report = new Report({ + _index: '.reporting-test-index-12345', jobtype: 'test-report', created_by: 'created_by_test_string', browser_type: 'browser_type_test_string', max_attempts: 50, - payload: { payload_test_field: 1 }, + payload: { headers: 'payload_test_field', objectType: 'testOt' }, timeout: 30000, priority: 1, - }; - const report = new Report(opts); - expect(report.toJSON()).toMatchObject({ - _primary_term: undefined, - _seq_no: undefined, + }); + + expect(report.toEsDocsJSON()).toMatchObject({ + _index: '.reporting-test-index-12345', + _source: { + attempts: 0, + browser_type: 'browser_type_test_string', + completed_at: undefined, + created_at: undefined, + created_by: 'created_by_test_string', + jobtype: 'test-report', + max_attempts: 50, + meta: undefined, + payload: { headers: 'payload_test_field', objectType: 'testOt' }, + priority: 1, + started_at: undefined, + status: 'pending', + timeout: 30000, + }, + }); + expect(report.toApiJSON()).toMatchObject({ browser_type: 'browser_type_test_string', created_by: 'created_by_test_string', jobtype: 'test-report', max_attempts: 50, - payload: { - payload_test_field: 1, - }, + payload: { headers: 'payload_test_field', objectType: 'testOt' }, priority: 1, timeout: 30000, }); - expect(report.id).toBeDefined(); + expect(report._id).toBeDefined(); }); - it('updateWithDoc method syncs takes fields to sync ES metadata', () => { - const opts = { - index: '.reporting-test-index-12345', + it('updateWithEsDoc method syncs fields to sync ES metadata', () => { + const report = new Report({ + _index: '.reporting-test-index-12345', jobtype: 'test-report', created_by: 'created_by_test_string', browser_type: 'browser_type_test_string', max_attempts: 50, - payload: { payload_test_field: 1 }, + payload: { headers: 'payload_test_field', objectType: 'testOt' }, timeout: 30000, priority: 1, - }; - const report = new Report(opts); + }); const metadata = { _index: '.reporting-test-update', @@ -55,23 +68,53 @@ describe('Class Report', () => { _primary_term: 77, _seq_no: 99, }; - report.updateWithDoc(metadata); - - expect(report.toJSON()).toMatchObject({ - index: '.reporting-test-update', - _primary_term: 77, - _seq_no: 99, - browser_type: 'browser_type_test_string', - created_by: 'created_by_test_string', - jobtype: 'test-report', - max_attempts: 50, - payload: { - payload_test_field: 1, - }, - priority: 1, - timeout: 30000, - }); + report.updateWithEsDoc(metadata); - expect(report._id).toBe('12342p9o387549o2345'); + expect(report.toEsDocsJSON()).toMatchInlineSnapshot(` + Object { + "_id": "12342p9o387549o2345", + "_index": ".reporting-test-update", + "_source": Object { + "attempts": 0, + "browser_type": "browser_type_test_string", + "completed_at": undefined, + "created_at": undefined, + "created_by": "created_by_test_string", + "jobtype": "test-report", + "max_attempts": 50, + "meta": undefined, + "payload": Object { + "headers": "payload_test_field", + "objectType": "testOt", + }, + "priority": 1, + "started_at": undefined, + "status": "pending", + "timeout": 30000, + }, + } + `); + expect(report.toApiJSON()).toMatchInlineSnapshot(` + Object { + "attempts": 0, + "browser_type": "browser_type_test_string", + "completed_at": undefined, + "created_at": undefined, + "created_by": "created_by_test_string", + "id": "12342p9o387549o2345", + "index": ".reporting-test-update", + "jobtype": "test-report", + "max_attempts": 50, + "meta": undefined, + "payload": Object { + "headers": "payload_test_field", + "objectType": "testOt", + }, + "priority": 1, + "started_at": undefined, + "status": "pending", + "timeout": 30000, + } + `); }); }); diff --git a/x-pack/plugins/reporting/server/lib/store/report.ts b/x-pack/plugins/reporting/server/lib/store/report.ts index cc9967e64b6eb..5ff71ae7a7182 100644 --- a/x-pack/plugins/reporting/server/lib/store/report.ts +++ b/x-pack/plugins/reporting/server/lib/store/report.ts @@ -6,80 +6,158 @@ // @ts-ignore no module definition import Puid from 'puid'; +import { JobStatuses } from '../../../constants'; +import { LayoutInstance } from '../layouts'; -interface Payload { - id?: string; - index: string; +/* + * The document created by Reporting to store in the .reporting index + */ +interface ReportingDocument { + _id: string; + _index: string; + _seq_no: unknown; + _primary_term: unknown; jobtype: string; - created_by: string | boolean; - payload: unknown; + created_by: string | null; + payload: { + headers: string; // encrypted headers + objectType: string; + layout?: LayoutInstance; + }; + meta: unknown; browser_type: string; - priority: number; max_attempts: number; timeout: number; + + status: string; + attempts: number; + output?: unknown; + started_at?: string; + completed_at?: string; + created_at?: string; + priority?: number; + process_expiration?: string; } +/* + * The document created by Reporting to store as task parameters for Task + * Manager to reference the report in .reporting + */ const puid = new Puid(); -export class Report { - public readonly jobtype: string; - public readonly created_by: string | boolean; - public readonly payload: unknown; - public readonly browser_type: string; - public readonly id: string; +export class Report implements Partial { + public _index?: string; + public _id: string; + public _primary_term?: unknown; // set by ES + public _seq_no: unknown; // set by ES - public readonly priority: number; - // queue stuff, to be removed with Task Manager integration + public readonly jobtype: string; + public readonly created_at?: string; + public readonly created_by?: string | null; + public readonly payload: { + headers: string; // encrypted headers + objectType: string; + layout?: LayoutInstance; + }; + public readonly meta: unknown; public readonly max_attempts: number; - public readonly timeout: number; + public readonly browser_type?: string; - public _index: string; - public _id?: string; // set by ES - public _primary_term?: unknown; // set by ES - public _seq_no: unknown; // set by ES + public readonly status: string; + public readonly attempts: number; + public readonly output?: unknown; + public readonly started_at?: string; + public readonly completed_at?: string; + public readonly process_expiration?: string; + public readonly priority?: number; + public readonly timeout?: number; /* * Create an unsaved report */ - constructor(opts: Payload) { - this.jobtype = opts.jobtype; + constructor(opts: Partial) { + this._id = opts._id != null ? opts._id : puid.generate(); + this._index = opts._index; + this._primary_term = opts._primary_term; + this._seq_no = opts._seq_no; + + this.payload = opts.payload!; + this.jobtype = opts.jobtype!; + this.max_attempts = opts.max_attempts!; + this.attempts = opts.attempts || 0; + + this.process_expiration = opts.process_expiration; + this.timeout = opts.timeout; + + this.created_at = opts.created_at; this.created_by = opts.created_by; - this.payload = opts.payload; + this.meta = opts.meta; this.browser_type = opts.browser_type; this.priority = opts.priority; - this.max_attempts = opts.max_attempts; - this.timeout = opts.timeout; - this.id = puid.generate(); - this._index = opts.index; + this.status = opts.status || JobStatuses.PENDING; + this.output = opts.output || null; } /* * Update the report with "live" storage metadata */ - updateWithDoc(doc: Partial) { - if (doc._index) { - this._index = doc._index; // can not be undefined + updateWithEsDoc(doc: Partial) { + if (doc._index == null || doc._id == null) { + throw new Error(`Report object from ES has missing fields!`); } this._id = doc._id; + this._index = doc._index; this._primary_term = doc._primary_term; this._seq_no = doc._seq_no; } - toJSON() { + /* + * Data structure for writing to Elasticsearch index + */ + toEsDocsJSON() { + return { + _id: this._id, + _index: this._index, + _source: { + jobtype: this.jobtype, + created_at: this.created_at, + created_by: this.created_by, + payload: this.payload, + meta: this.meta, + timeout: this.timeout, + max_attempts: this.max_attempts, + priority: this.priority, + browser_type: this.browser_type, + status: this.status, + attempts: this.attempts, + started_at: this.started_at, + completed_at: this.completed_at, + }, + }; + } + + /* + * Data structure for API responses + */ + toApiJSON() { return { - id: this.id, + id: this._id, index: this._index, - _seq_no: this._seq_no, - _primary_term: this._primary_term, jobtype: this.jobtype, + created_at: this.created_at, created_by: this.created_by, payload: this.payload, + meta: this.meta, timeout: this.timeout, max_attempts: this.max_attempts, priority: this.priority, browser_type: this.browser_type, + status: this.status, + attempts: this.attempts, + started_at: this.started_at, + completed_at: this.completed_at, }; } } diff --git a/x-pack/plugins/reporting/server/lib/store/store.test.ts b/x-pack/plugins/reporting/server/lib/store/store.test.ts index 4868a1dfdd8f3..c66e2dd7742c4 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.test.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.test.ts @@ -5,11 +5,12 @@ */ import sinon from 'sinon'; +import { ElasticsearchServiceSetup } from 'src/core/server'; import { ReportingConfig, ReportingCore } from '../..'; import { createMockReportingCore } from '../../test_helpers'; import { createMockLevelLogger } from '../../test_helpers/create_mock_levellogger'; +import { Report } from './report'; import { ReportingStore } from './store'; -import { ElasticsearchServiceSetup } from 'src/core/server'; const getMockConfig = (mockConfigGet: sinon.SinonStub) => ({ get: mockConfigGet, @@ -31,11 +32,13 @@ describe('ReportingStore', () => { mockConfig = getMockConfig(mockConfigGet); mockCore = await createMockReportingCore(mockConfig); + callClusterStub.reset(); callClusterStub.withArgs('indices.exists').resolves({}); callClusterStub.withArgs('indices.create').resolves({}); - callClusterStub.withArgs('index').resolves({}); + callClusterStub.withArgs('index').resolves({ _id: 'stub-id', _index: 'stub-index' }); callClusterStub.withArgs('indices.refresh').resolves({}); callClusterStub.withArgs('update').resolves({}); + callClusterStub.withArgs('get').resolves({}); mockCore.getElasticsearchService = () => (mockElasticsearch as unknown) as ElasticsearchServiceSetup; @@ -45,25 +48,25 @@ describe('ReportingStore', () => { it('returns Report object', async () => { const store = new ReportingStore(mockCore, mockLogger); const reportType = 'unknowntype'; - const reportPayload = {}; - const reportOptions = { - timeout: 10000, - created_by: 'created_by_string', - browser_type: 'browser_type_string', - max_attempts: 1, + const reportPayload = { + browserTimezone: 'UTC', + headers: 'rp_headers_1', + objectType: 'testOt', }; - await expect( - store.addReport(reportType, reportPayload, reportOptions) - ).resolves.toMatchObject({ + await expect(store.addReport(reportType, 'username1', reportPayload)).resolves.toMatchObject({ _primary_term: undefined, _seq_no: undefined, - browser_type: 'browser_type_string', - created_by: 'created_by_string', + attempts: 0, + browser_type: undefined, + completed_at: undefined, + created_by: 'username1', jobtype: 'unknowntype', - max_attempts: 1, + max_attempts: undefined, payload: {}, priority: 10, - timeout: 10000, + started_at: undefined, + status: 'pending', + timeout: undefined, }); }); @@ -76,35 +79,31 @@ describe('ReportingStore', () => { const store = new ReportingStore(mockCore, mockLogger); const reportType = 'unknowntype'; - const reportPayload = {}; - const reportOptions = { - timeout: 10000, - created_by: 'created_by_string', - browser_type: 'browser_type_string', - max_attempts: 1, + const reportPayload = { + browserTimezone: 'UTC', + headers: 'rp_headers_2', + objectType: 'testOt', }; - expect( - store.addReport(reportType, reportPayload, reportOptions) - ).rejects.toMatchInlineSnapshot(`[Error: Invalid index interval: centurially]`); + expect(store.addReport(reportType, 'user1', reportPayload)).rejects.toMatchInlineSnapshot( + `[Error: Invalid index interval: centurially]` + ); }); it('handles error creating the index', async () => { // setup callClusterStub.withArgs('indices.exists').resolves(false); - callClusterStub.withArgs('indices.create').rejects(new Error('error')); + callClusterStub.withArgs('indices.create').rejects(new Error('horrible error')); const store = new ReportingStore(mockCore, mockLogger); const reportType = 'unknowntype'; - const reportPayload = {}; - const reportOptions = { - timeout: 10000, - created_by: 'created_by_string', - browser_type: 'browser_type_string', - max_attempts: 1, + const reportPayload = { + browserTimezone: 'UTC', + headers: 'rp_headers_3', + objectType: 'testOt', }; await expect( - store.addReport(reportType, reportPayload, reportOptions) - ).rejects.toMatchInlineSnapshot(`[Error: error]`); + store.addReport(reportType, 'user1', reportPayload) + ).rejects.toMatchInlineSnapshot(`[Error: horrible error]`); }); /* Creating the index will fail, if there were multiple jobs staged in @@ -116,20 +115,18 @@ describe('ReportingStore', () => { it('ignores index creation error if the index already exists and continues adding the report', async () => { // setup callClusterStub.withArgs('indices.exists').resolves(false); - callClusterStub.withArgs('indices.create').rejects(new Error('error')); + callClusterStub.withArgs('indices.create').rejects(new Error('devastating error')); const store = new ReportingStore(mockCore, mockLogger); const reportType = 'unknowntype'; - const reportPayload = {}; - const reportOptions = { - timeout: 10000, - created_by: 'created_by_string', - browser_type: 'browser_type_string', - max_attempts: 1, + const reportPayload = { + browserTimezone: 'UTC', + headers: 'rp_headers_4', + objectType: 'testOt', }; await expect( - store.addReport(reportType, reportPayload, reportOptions) - ).rejects.toMatchInlineSnapshot(`[Error: error]`); + store.addReport(reportType, 'user1', reportPayload) + ).rejects.toMatchInlineSnapshot(`[Error: devastating error]`); }); it('skips creating the index if already exists', async () => { @@ -141,26 +138,223 @@ describe('ReportingStore', () => { const store = new ReportingStore(mockCore, mockLogger); const reportType = 'unknowntype'; - const reportPayload = {}; - const reportOptions = { - timeout: 10000, - created_by: 'created_by_string', - browser_type: 'browser_type_string', - max_attempts: 1, + const reportPayload = { + browserTimezone: 'UTC', + headers: 'rp_headers_5', + objectType: 'testOt', }; - await expect( - store.addReport(reportType, reportPayload, reportOptions) - ).resolves.toMatchObject({ + await expect(store.addReport(reportType, 'user1', reportPayload)).resolves.toMatchObject({ + _primary_term: undefined, + _seq_no: undefined, + attempts: 0, + browser_type: undefined, + completed_at: undefined, + created_by: 'user1', + jobtype: 'unknowntype', + max_attempts: undefined, + payload: {}, + priority: 10, + started_at: undefined, + status: 'pending', + timeout: undefined, + }); + }); + + it('allows username string to be `null`', async () => { + // setup + callClusterStub.withArgs('indices.exists').resolves(false); + callClusterStub + .withArgs('indices.create') + .rejects(new Error('resource_already_exists_exception')); // will be triggered but ignored + + const store = new ReportingStore(mockCore, mockLogger); + const reportType = 'unknowntype'; + const reportPayload = { + browserTimezone: 'UTC', + headers: 'rp_test_headers', + objectType: 'testOt', + }; + await expect(store.addReport(reportType, null, reportPayload)).resolves.toMatchObject({ _primary_term: undefined, _seq_no: undefined, - browser_type: 'browser_type_string', - created_by: 'created_by_string', + attempts: 0, + browser_type: undefined, + completed_at: undefined, + created_by: null, jobtype: 'unknowntype', - max_attempts: 1, + max_attempts: undefined, payload: {}, priority: 10, - timeout: 10000, + started_at: undefined, + status: 'pending', + timeout: undefined, }); }); }); + + it('setReportClaimed sets the status of a record to processing', async () => { + const store = new ReportingStore(mockCore, mockLogger); + const report = new Report({ + _id: 'id-of-processing', + _index: '.reporting-test-index-12345', + jobtype: 'test-report', + created_by: 'created_by_test_string', + browser_type: 'browser_type_test_string', + max_attempts: 50, + payload: { + headers: 'rp_test_headers', + objectType: 'testOt', + }, + timeout: 30000, + priority: 1, + }); + + await store.setReportClaimed(report, { testDoc: 'test' } as any); + + const updateCall = callClusterStub.getCalls().find((call) => call.args[0] === 'update'); + expect(updateCall && updateCall.args).toMatchInlineSnapshot(` + Array [ + "update", + Object { + "body": Object { + "doc": Object { + "status": "processing", + "testDoc": "test", + }, + }, + "id": "id-of-processing", + "if_primary_term": undefined, + "if_seq_no": undefined, + "index": ".reporting-test-index-12345", + }, + ] + `); + }); + + it('setReportFailed sets the status of a record to failed', async () => { + const store = new ReportingStore(mockCore, mockLogger); + const report = new Report({ + _id: 'id-of-failure', + _index: '.reporting-test-index-12345', + jobtype: 'test-report', + created_by: 'created_by_test_string', + browser_type: 'browser_type_test_string', + max_attempts: 50, + payload: { + headers: 'rp_test_headers', + objectType: 'testOt', + }, + timeout: 30000, + priority: 1, + }); + + await store.setReportFailed(report, { errors: 'yes' } as any); + + const updateCall = callClusterStub.getCalls().find((call) => call.args[0] === 'update'); + expect(updateCall && updateCall.args).toMatchInlineSnapshot(` + Array [ + "update", + Object { + "body": Object { + "doc": Object { + "errors": "yes", + "status": "failed", + }, + }, + "id": "id-of-failure", + "if_primary_term": undefined, + "if_seq_no": undefined, + "index": ".reporting-test-index-12345", + }, + ] + `); + }); + + it('setReportCompleted sets the status of a record to completed', async () => { + const store = new ReportingStore(mockCore, mockLogger); + const report = new Report({ + _id: 'vastly-great-report-id', + _index: '.reporting-test-index-12345', + jobtype: 'test-report', + created_by: 'created_by_test_string', + browser_type: 'browser_type_test_string', + max_attempts: 50, + payload: { + headers: 'rp_test_headers', + objectType: 'testOt', + }, + timeout: 30000, + priority: 1, + }); + + await store.setReportCompleted(report, { certainly_completed: 'yes' } as any); + + const updateCall = callClusterStub.getCalls().find((call) => call.args[0] === 'update'); + expect(updateCall && updateCall.args).toMatchInlineSnapshot(` + Array [ + "update", + Object { + "body": Object { + "doc": Object { + "certainly_completed": "yes", + "status": "completed", + }, + }, + "id": "vastly-great-report-id", + "if_primary_term": undefined, + "if_seq_no": undefined, + "index": ".reporting-test-index-12345", + }, + ] + `); + }); + + it('setReportCompleted sets the status of a record to completed_with_warnings', async () => { + const store = new ReportingStore(mockCore, mockLogger); + const report = new Report({ + _id: 'vastly-great-report-id', + _index: '.reporting-test-index-12345', + jobtype: 'test-report', + created_by: 'created_by_test_string', + browser_type: 'browser_type_test_string', + max_attempts: 50, + payload: { + headers: 'rp_test_headers', + objectType: 'testOt', + }, + timeout: 30000, + priority: 1, + }); + + await store.setReportCompleted(report, { + certainly_completed: 'pretty_much', + output: { + warnings: [`those pants don't go with that shirt`], + }, + } as any); + + const updateCall = callClusterStub.getCalls().find((call) => call.args[0] === 'update'); + expect(updateCall && updateCall.args).toMatchInlineSnapshot(` + Array [ + "update", + Object { + "body": Object { + "doc": Object { + "certainly_completed": "pretty_much", + "output": Object { + "warnings": Array [ + "those pants don't go with that shirt", + ], + }, + "status": "completed_with_warnings", + }, + }, + "id": "vastly-great-report-id", + "if_primary_term": undefined, + "if_seq_no": undefined, + "index": ".reporting-test-index-12345", + }, + ] + `); + }); }); diff --git a/x-pack/plugins/reporting/server/lib/store/store.ts b/x-pack/plugins/reporting/server/lib/store/store.ts index c92d4ca61ab00..7ed0860c83811 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.ts @@ -5,36 +5,24 @@ */ import { ElasticsearchServiceSetup } from 'src/core/server'; -import { LevelLogger } from '../'; +import { LevelLogger, statuses } from '../'; import { ReportingCore } from '../../'; +import { CreateJobBaseParams, CreateJobBaseParamsEncryptedFields } from '../../types'; import { indexTimestamp } from './index_timestamp'; -import { LayoutInstance } from '../layouts'; import { mapping } from './mapping'; import { Report } from './report'; - -export const statuses = { - JOB_STATUS_PENDING: 'pending', - JOB_STATUS_PROCESSING: 'processing', - JOB_STATUS_COMPLETED: 'completed', - JOB_STATUS_WARNINGS: 'completed_with_warnings', - JOB_STATUS_FAILED: 'failed', - JOB_STATUS_CANCELLED: 'cancelled', -}; - -interface AddReportOpts { +interface JobSettings { timeout: number; - created_by: string | boolean; browser_type: string; max_attempts: number; + priority: number; } -interface UpdateQuery { - index: string; - id: string; - if_seq_no: unknown; - if_primary_term: unknown; - body: { doc: Partial }; -} +const checkReportIsEditable = (report: Report) => { + if (!report._id || !report._index) { + throw new Error(`Report object is not synced with ES!`); + } +}; /* * A class to give an interface to historical reports in the reporting.index @@ -43,9 +31,9 @@ interface UpdateQuery { * - interface for downloading the report */ export class ReportingStore { - public readonly indexPrefix: string; - public readonly indexInterval: string; - + private readonly indexPrefix: string; + private readonly indexInterval: string; + private readonly jobSettings: JobSettings; private client: ElasticsearchServiceSetup['legacy']['client']; private logger: LevelLogger; @@ -56,12 +44,18 @@ export class ReportingStore { this.client = elasticsearch.legacy.client; this.indexPrefix = config.get('index'); this.indexInterval = config.get('queue', 'indexInterval'); + this.jobSettings = { + timeout: config.get('queue', 'timeout'), + browser_type: config.get('capture', 'browser', 'type'), + max_attempts: config.get('capture', 'maxAttempts'), + priority: 10, // unused + }; this.logger = logger; } private async createIndex(indexName: string) { - return this.client + return await this.client .callAsInternalUser('indices.exists', { index: indexName, }) @@ -95,75 +89,157 @@ export class ReportingStore { return; } + this.logger.error(err); throw err; }); }); } - private async saveReport(report: Report) { - const payload = report.payload as { type: string; layout: LayoutInstance }; + /* + * Called from addReport, which handles any errors + */ + private async indexReport(report: Report) { + const params = report.payload; + + // Queing is handled by TM. These queueing-based fields for reference in Report Info panel + const infoFields = { + timeout: report.timeout, + process_expiration: new Date(0), // use epoch so the job query works + created_at: new Date(), + attempts: 0, + max_attempts: report.max_attempts, + status: statuses.JOB_STATUS_PENDING, + browser_type: report.browser_type, + }; const indexParams = { index: report._index, - id: report.id, + id: report._id, body: { + ...infoFields, jobtype: report.jobtype, meta: { // We are copying these values out of payload because these fields are indexed and can be aggregated on // for tracking stats, while payload contents are not. - objectType: payload.type, - layout: payload.layout ? payload.layout.id : 'none', + objectType: (params as any).type, // params.type for legacy (7.x) + layout: params.layout ? params.layout.id : 'none', }, payload: report.payload, created_by: report.created_by, - timeout: report.timeout, - process_expiration: new Date(0), // use epoch so the job query works - created_at: new Date(), - attempts: 0, - max_attempts: report.max_attempts, - status: statuses.JOB_STATUS_PENDING, - browser_type: report.browser_type, }, }; - return this.client.callAsInternalUser('index', indexParams); + return await this.client.callAsInternalUser('index', indexParams); } + /* + * Called from addReport, which handles any errors + */ private async refreshIndex(index: string) { - return this.client.callAsInternalUser('indices.refresh', { index }); + return await this.client.callAsInternalUser('indices.refresh', { index }); } - public async addReport(type: string, payload: unknown, options: AddReportOpts): Promise { + public async addReport( + type: string, + username: string | null, + payload: CreateJobBaseParams & CreateJobBaseParamsEncryptedFields + ): Promise { const timestamp = indexTimestamp(this.indexInterval); const index = `${this.indexPrefix}-${timestamp}`; await this.createIndex(index); const report = new Report({ - index, + _index: index, payload, jobtype: type, - created_by: options.created_by, - browser_type: options.browser_type, - max_attempts: options.max_attempts, - timeout: options.timeout, - priority: 10, // unused + created_by: username, + ...this.jobSettings, }); - const doc = await this.saveReport(report); - report.updateWithDoc(doc); + try { + const doc = await this.indexReport(report); + report.updateWithEsDoc(doc); - await this.refreshIndex(index); - this.logger.info(`Successfully queued pending job: ${report._index}/${report.id}`); + await this.refreshIndex(index); + this.logger.debug(`Successfully stored pending job: ${report._index}/${report._id}`); - return report; + return report; + } catch (err) { + this.logger.error(`Error in addReport!`); + this.logger.error(err); + throw err; + } } - public async updateReport(query: UpdateQuery): Promise { - return this.client.callAsInternalUser('update', { - index: query.index, - id: query.id, - if_seq_no: query.if_seq_no, - if_primary_term: query.if_primary_term, - body: { doc: query.body.doc }, - }); + public async setReportClaimed(report: Report, stats: Partial): Promise { + const doc = { + ...stats, + status: statuses.JOB_STATUS_PROCESSING, + }; + + try { + checkReportIsEditable(report); + + return await this.client.callAsInternalUser('update', { + id: report._id, + index: report._index, + if_seq_no: report._seq_no, + if_primary_term: report._primary_term, + body: { doc }, + }); + } catch (err) { + this.logger.error('Error in setting report processing status!'); + this.logger.error(err); + throw err; + } + } + + public async setReportFailed(report: Report, stats: Partial): Promise { + const doc = { + ...stats, + status: statuses.JOB_STATUS_FAILED, + }; + + try { + checkReportIsEditable(report); + + return await this.client.callAsInternalUser('update', { + id: report._id, + index: report._index, + if_seq_no: report._seq_no, + if_primary_term: report._primary_term, + body: { doc }, + }); + } catch (err) { + this.logger.error('Error in setting report failed status!'); + this.logger.error(err); + throw err; + } + } + + public async setReportCompleted(report: Report, stats: Partial): Promise { + try { + const { output } = stats as { output: any }; + const status = + output && output.warnings && output.warnings.length > 0 + ? statuses.JOB_STATUS_WARNINGS + : statuses.JOB_STATUS_COMPLETED; + const doc = { + ...stats, + status, + }; + checkReportIsEditable(report); + + return await this.client.callAsInternalUser('update', { + id: report._id, + index: report._index, + if_seq_no: report._seq_no, + if_primary_term: report._primary_term, + body: { doc }, + }); + } catch (err) { + this.logger.error('Error in setting report complete status!'); + this.logger.error(err); + throw err; + } } } diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts index cedc9dc14a237..20e22c2db00e3 100644 --- a/x-pack/plugins/reporting/server/plugin.ts +++ b/x-pack/plugins/reporting/server/plugin.ts @@ -8,13 +8,7 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core import { ReportingCore } from './'; import { initializeBrowserDriverFactory } from './browsers'; import { buildConfig, ReportingConfigType } from './config'; -import { - createQueueFactory, - enqueueJobFactory, - LevelLogger, - runValidations, - ReportingStore, -} from './lib'; +import { createQueueFactory, LevelLogger, runValidations, ReportingStore } from './lib'; import { registerRoutes } from './routes'; import { setFieldFormats } from './services'; import { ReportingSetup, ReportingSetupDeps, ReportingStart, ReportingStartDeps } from './types'; @@ -94,14 +88,12 @@ export class ReportingPlugin const browserDriverFactory = await initializeBrowserDriverFactory(config, logger); const store = new ReportingStore(reportingCore, logger); const esqueue = await createQueueFactory(reportingCore, store, logger); // starts polling for pending jobs - const enqueueJob = enqueueJobFactory(reportingCore, store, logger); // called from generation routes reportingCore.pluginStart({ browserDriverFactory, savedObjects: core.savedObjects, uiSettings: core.uiSettings, esqueue, - enqueueJob, store, }); diff --git a/x-pack/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/plugins/reporting/server/routes/generate_from_jobparams.ts index 2a12a64d67a35..f4959b56dfea1 100644 --- a/x-pack/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -10,6 +10,7 @@ import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routi import { HandlerErrorFunction, HandlerFunction } from './types'; import { ReportingCore } from '../'; import { API_BASE_URL } from '../../common/constants'; +import { CreateJobBaseParams } from '../types'; const BASE_GENERATE = `${API_BASE_URL}/generate`; @@ -44,13 +45,13 @@ export function registerGenerateFromJobParams( }, }, userHandler(async (user, context, req, res) => { - let jobParamsRison: string | null; + let jobParamsRison: null | string = null; if (req.body) { - const { jobParams: jobParamsPayload } = req.body as { jobParams: string }; - jobParamsRison = jobParamsPayload; - } else { - const { jobParams: queryJobParams } = req.query as { jobParams: string }; + const { jobParams: jobParamsPayload } = req.body; + jobParamsRison = jobParamsPayload ? jobParamsPayload : null; + } else if (req.query?.jobParams) { + const { jobParams: queryJobParams } = req.query; if (queryJobParams) { jobParamsRison = queryJobParams; } else { @@ -65,11 +66,11 @@ export function registerGenerateFromJobParams( }); } - const { exportType } = req.params as { exportType: string }; + const { exportType } = req.params; let jobParams; try { - jobParams = rison.decode(jobParamsRison) as object | null; + jobParams = rison.decode(jobParamsRison) as CreateJobBaseParams | null; if (!jobParams) { return res.customError({ statusCode: 400, diff --git a/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index 8250ca462049b..a0a8f25de7fc4 100644 --- a/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -5,16 +5,24 @@ */ import { schema } from '@kbn/config-schema'; +import { KibanaRequest } from 'src/core/server'; import { ReportingCore } from '../'; import { API_BASE_GENERATE_V1 } from '../../common/constants'; import { scheduleTaskFnFactory } from '../export_types/csv_from_savedobject/create_job'; import { runTaskFnFactory } from '../export_types/csv_from_savedobject/execute_job'; +import { JobParamsPostPayloadPanelCsv } from '../export_types/csv_from_savedobject/types'; import { LevelLogger as Logger } from '../lib'; import { TaskRunResult } from '../types'; import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; import { getJobParamsFromRequest } from './lib/get_job_params_from_request'; import { HandlerErrorFunction } from './types'; +export type CsvFromSavedObjectRequest = KibanaRequest< + { savedObjectType: string; savedObjectId: string }, + unknown, + JobParamsPostPayloadPanelCsv +>; + /* * This function registers API Endpoints for immediate Reporting jobs. The API inputs are: * - saved object type and ID @@ -56,7 +64,7 @@ export function registerGenerateCsvFromSavedObjectImmediate( }), }, }, - userHandler(async (user, context, req, res) => { + userHandler(async (user, context, req: CsvFromSavedObjectRequest, res) => { const logger = parentLogger.clone(['savedobject-csv']); const jobParams = getJobParamsFromRequest(req, { isImmediate: true }); const scheduleTaskFn = scheduleTaskFnFactory(reporting, logger); diff --git a/x-pack/plugins/reporting/server/routes/generation.test.ts b/x-pack/plugins/reporting/server/routes/generation.test.ts index 87a696948ad84..cef4da9aabbd4 100644 --- a/x-pack/plugins/reporting/server/routes/generation.test.ts +++ b/x-pack/plugins/reporting/server/routes/generation.test.ts @@ -138,8 +138,7 @@ describe('POST /api/reporting/generate', () => { }); it('returns 500 if job handler throws an error', async () => { - // throw an error from enqueueJob - core.getEnqueueJob = jest.fn().mockRejectedValue('Sorry, this tests says no'); + callClusterStub.withArgs('index').rejects('silly'); registerJobGenerationRoutes(core, mockLogger); diff --git a/x-pack/plugins/reporting/server/routes/generation.ts b/x-pack/plugins/reporting/server/routes/generation.ts index 2b58f21565f26..dd244e21bd4ce 100644 --- a/x-pack/plugins/reporting/server/routes/generation.ts +++ b/x-pack/plugins/reporting/server/routes/generation.ts @@ -10,6 +10,7 @@ import { kibanaResponseFactory } from 'src/core/server'; import { ReportingCore } from '../'; import { API_BASE_URL } from '../../common/constants'; import { LevelLogger as Logger } from '../lib'; +import { enqueueJobFactory } from '../lib/enqueue_job'; import { registerGenerateFromJobParams } from './generate_from_jobparams'; import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; import { registerLegacy } from './legacy'; @@ -44,11 +45,10 @@ export function registerJobGenerationRoutes(reporting: ReportingCore, logger: Lo } try { - const enqueueJob = await reporting.getEnqueueJob(); - const job = await enqueueJob(exportTypeId, jobParams, user, context, req); + const enqueueJob = enqueueJobFactory(reporting, logger); + const report = await enqueueJob(exportTypeId, jobParams, user, context, req); // return the queue's job information - const jobJson = job.toJSON(); const downloadBaseUrl = getDownloadBaseUrl(reporting); return res.ok({ @@ -56,8 +56,8 @@ export function registerJobGenerationRoutes(reporting: ReportingCore, logger: Lo 'content-type': 'application/json', }, body: { - path: `${downloadBaseUrl}/${jobJson.id}`, - job: jobJson, + path: `${downloadBaseUrl}/${report._id}`, + job: report.toApiJSON(), }, }); } catch (err) { diff --git a/x-pack/plugins/reporting/server/routes/legacy.ts b/x-pack/plugins/reporting/server/routes/legacy.ts index d432f9396872c..baa4e3648c85b 100644 --- a/x-pack/plugins/reporting/server/routes/legacy.ts +++ b/x-pack/plugins/reporting/server/routes/legacy.ts @@ -42,7 +42,10 @@ export function registerLegacy( logger.warn(message, ['deprecation']); try { - const { savedObjectId }: { savedObjectId: string } = req.params as any; + const { + savedObjectId, + browserTimezone, + }: { savedObjectId: string; browserTimezone: string } = req.params as any; const queryString = querystring.stringify(req.query as any); return await handler( @@ -51,6 +54,7 @@ export function registerLegacy( { objectType, savedObjectId, + browserTimezone, queryString, }, context, diff --git a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts index d384cbb878a0e..84a98d6d1f1d7 100644 --- a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts +++ b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts @@ -8,8 +8,7 @@ import contentDisposition from 'content-disposition'; import { get } from 'lodash'; import { CSV_JOB_TYPE } from '../../../common/constants'; -import { statuses } from '../../lib/esqueue/constants/statuses'; -import { ExportTypesRegistry } from '../../lib/export_types_registry'; +import { ExportTypesRegistry, statuses } from '../../lib'; import { ExportTypeDefinition, JobSource, TaskRunResult } from '../../types'; type ExportTypeType = ExportTypeDefinition; @@ -18,11 +17,11 @@ interface ErrorFromPayload { message: string; } -// A camelCase version of TaskRunResult +// interface of the API result interface Payload { statusCode: number; content: string | Buffer | ErrorFromPayload; - contentType: string; + contentType: string | null; headers: Record; } diff --git a/x-pack/plugins/reporting/server/routes/lib/get_job_params_from_request.ts b/x-pack/plugins/reporting/server/routes/lib/get_job_params_from_request.ts index e5c1f38241349..bfa15a4022a4d 100644 --- a/x-pack/plugins/reporting/server/routes/lib/get_job_params_from_request.ts +++ b/x-pack/plugins/reporting/server/routes/lib/get_job_params_from_request.ts @@ -4,21 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest } from 'src/core/server'; -import { - JobParamsPanelCsv, - JobParamsPostPayloadPanelCsv, -} from '../../export_types/csv_from_savedobject/types'; +import { JobParamsPanelCsv } from '../../export_types/csv_from_savedobject/types'; +import { CsvFromSavedObjectRequest } from '../generate_from_savedobject_immediate'; export function getJobParamsFromRequest( - request: KibanaRequest, + request: CsvFromSavedObjectRequest, opts: { isImmediate: boolean } ): JobParamsPanelCsv { - const { savedObjectType, savedObjectId } = request.params as { - savedObjectType: string; - savedObjectId: string; - }; - const { timerange, state } = request.body as JobParamsPostPayloadPanelCsv; + const { savedObjectType, savedObjectId } = request.params; + const { timerange, state } = request.body; const post = timerange || state ? { timerange, state } : undefined; diff --git a/x-pack/plugins/reporting/server/routes/types.d.ts b/x-pack/plugins/reporting/server/routes/types.d.ts index 607ce34ab9465..c92c9fb7eef74 100644 --- a/x-pack/plugins/reporting/server/routes/types.d.ts +++ b/x-pack/plugins/reporting/server/routes/types.d.ts @@ -6,12 +6,12 @@ import { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server'; import { AuthenticatedUser } from '../../../security/server'; -import { ScheduledTaskParams } from '../types'; +import { CreateJobBaseParams, ScheduledTaskParams } from '../types'; export type HandlerFunction = ( user: AuthenticatedUser | null, exportType: string, - jobParams: object, + jobParams: CreateJobBaseParams, context: RequestHandlerContext, req: KibanaRequest, res: KibanaResponseFactory diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts index 95b06aa39f07e..c508ee6974ca0 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts @@ -8,7 +8,6 @@ jest.mock('../routes'); jest.mock('../usage'); jest.mock('../browsers'); jest.mock('../lib/create_queue'); -jest.mock('../lib/enqueue_job'); jest.mock('../lib/validate'); import * as Rx from 'rxjs'; @@ -19,10 +18,9 @@ import { initializeBrowserDriverFactory, } from '../browsers'; import { ReportingInternalSetup, ReportingInternalStart } from '../core'; -import { ReportingStartDeps } from '../types'; import { ReportingStore } from '../lib'; +import { ReportingStartDeps } from '../types'; import { createMockLevelLogger } from './create_mock_levellogger'; -import { Report } from '../lib/store'; (initializeBrowserDriverFactory as jest.Mock< Promise @@ -30,10 +28,13 @@ import { Report } from '../lib/store'; (chromium as any).createDriverFactory.mockImplementation(() => ({})); -const createMockPluginSetup = (setupMock?: any): ReportingInternalSetup => { +const createMockPluginSetup = ( + mockReportingCore: ReportingCore, + setupMock?: any +): ReportingInternalSetup => { return { elasticsearch: setupMock.elasticsearch || { legacy: { client: {} } }, - basePath: setupMock.basePath, + basePath: setupMock.basePath || '/all-about-that-basepath', router: setupMock.router, security: setupMock.security, licensing: { license$: Rx.of({ isAvailable: true, isActive: true, type: 'basic' }) } as any, @@ -48,7 +49,6 @@ const createMockPluginStart = ( const store = new ReportingStore(mockReportingCore, logger); return { browserDriverFactory: startMock.browserDriverFactory, - enqueueJob: startMock.enqueueJob || jest.fn().mockResolvedValue(new Report({} as any)), esqueue: startMock.esqueue, savedObjects: startMock.savedObjects || { getScopedClient: jest.fn() }, uiSettings: startMock.uiSettings || { asScopedToClient: () => ({ get: jest.fn() }) }, @@ -72,15 +72,14 @@ export const createMockReportingCore = async ( setupDepsMock: ReportingInternalSetup | undefined = undefined, startDepsMock: ReportingInternalStart | undefined = undefined ) => { - if (!setupDepsMock) { - setupDepsMock = createMockPluginSetup({}); - } - const mockReportingCore = { getConfig: () => config, getElasticsearchService: () => setupDepsMock?.elasticsearch, } as ReportingCore; + if (!setupDepsMock) { + setupDepsMock = createMockPluginSetup(mockReportingCore, {}); + } if (!startDepsMock) { startDepsMock = createMockPluginStart(mockReportingCore, {}); } diff --git a/x-pack/plugins/reporting/server/types.ts b/x-pack/plugins/reporting/server/types.ts index ff597b53ea0b0..061e15f883c0f 100644 --- a/x-pack/plugins/reporting/server/types.ts +++ b/x-pack/plugins/reporting/server/types.ts @@ -19,24 +19,12 @@ import { LevelLogger } from './lib'; import { LayoutInstance } from './lib/layouts'; /* - * Routing / API types + * Routing types */ -interface ListQuery { - page: string; - size: string; - ids?: string; // optional field forbids us from extending RequestQuery -} - -interface GenerateQuery { - jobParams: string; -} - -export type ReportingRequestQuery = ListQuery | GenerateQuery; - export interface ReportingRequestPre { management: { - jobTypes: any; + jobTypes: string[]; }; user: string; } @@ -54,12 +42,14 @@ export interface TimeRangeParams { max?: Date | string | number | null; } +// the "raw" data coming from the client, unencrypted export interface JobParamPostPayload { timerange?: TimeRangeParams; } +// the pre-processed, encrypted data ready for storage export interface ScheduledTaskParams { - headers?: string; // serialized encrypted headers + headers: string; // serialized encrypted headers jobParams: JobParamsType; title: string; type: string; @@ -77,10 +67,10 @@ export interface JobSource { } export interface TaskRunResult { - content_type: string; + content_type: string | null; content: string | null; - size: number; csv_contains_formulas?: boolean; + size: number; max_size_reached?: boolean; warnings?: string[]; } @@ -177,17 +167,31 @@ export type ReportingSetup = object; export type CaptureConfig = ReportingConfigType['capture']; export type ScrollConfig = ReportingConfigType['csv']['scroll']; -export type ESQueueCreateJobFn = ( +export interface CreateJobBaseParams { + browserTimezone: string; + layout?: LayoutInstance; // for screenshot type reports + objectType: string; + savedObjectId?: string; // legacy (7.x) only + queryString?: string; // legacy (7.x) only +} + +export interface CreateJobBaseParamsEncryptedFields extends CreateJobBaseParams { + basePath?: string; // for screenshot type reports + headers: string; // encrypted headers +} + +export type CreateJobFn = ( jobParams: JobParamsType, context: RequestHandlerContext, request: KibanaRequest -) => Promise; +) => Promise; -export type ESQueueWorkerExecuteFn = ( +// rename me +export type WorkerExecuteFn = ( jobId: string, job: ScheduledTaskParamsType, cancellationToken: CancellationToken -) => Promise; +) => Promise; export type ScheduleTaskFnFactory = ( reporting: ReportingCore, From 1572b419bddb77dac0fdf27f328649be08fa627e Mon Sep 17 00:00:00 2001 From: Corey Robertson Date: Fri, 14 Aug 2020 19:31:33 -0400 Subject: [PATCH 09/22] [Canvas][tech-debt] Convert renderers (#74134) (#74790) * Convert renderers to typescript * Fix typo * Fix type issues * Fixes * Fix issue with data table render Co-authored-by: Elastic Machine Co-authored-by: Elastic Machine --- .../functions/browser/markdown.ts | 2 +- .../functions/common/containerStyle.ts | 1 - .../functions/common/image.ts | 3 +- .../functions/common/index.ts | 2 +- .../canvas_plugin_src/functions/common/pie.ts | 2 +- .../functions/common/progress.ts | 6 ++- .../functions/common/repeat_image.test.js | 2 +- .../{repeatImage.ts => repeat_image.ts} | 9 +++- .../functions/common/revealImage.ts | 10 +++- .../functions/common/shape.ts | 2 +- .../functions/common/table.ts | 4 +- .../lib/{elastic_logo.js => elastic_logo.ts} | 0 ...{elastic_outline.js => elastic_outline.ts} | 0 .../canvas/canvas_plugin_src/plugin.ts | 1 - .../renderers/{core.js => core.ts} | 0 .../renderers/{debug.js => debug.tsx} | 3 +- .../renderers/embeddable/embeddable.tsx | 13 +++-- .../renderers/error/{index.js => index.tsx} | 11 +++-- .../filters/advanced_filter/index.tsx | 2 +- .../renderers/filters/{index.js => index.ts} | 0 .../renderers/{image.js => image.tsx} | 4 +- .../renderers/{index.js => index.ts} | 0 .../markdown/{index.js => index.tsx} | 8 +-- .../renderers/pie/{index.js => index.tsx} | 26 +++++++--- .../renderers/plot/{index.js => index.ts} | 12 +++-- .../renderers/progress/{index.js => index.ts} | 49 ++++++++++--------- .../progress/shapes/{index.js => index.ts} | 0 .../{repeat_image.js => repeat_image.ts} | 14 +++--- .../reveal_image/{index.js => index.ts} | 19 ++++--- .../renderers/shape/{index.js => index.ts} | 28 ++++++----- .../renderers/{table.js => table.tsx} | 20 +++++--- .../renderers/{text.js => text.tsx} | 7 +-- x-pack/plugins/canvas/common/lib/index.ts | 2 - .../{missing_asset.js => missing_asset.ts} | 0 .../common/lib/{url.test.js => url.test.ts} | 0 .../canvas/common/lib/{url.js => url.ts} | 2 +- .../i18n/functions/dict/repeat_image.ts | 2 +- .../components/error/{error.js => error.tsx} | 10 +++- .../components/error/{index.js => index.ts} | 0 .../{show_debugging.js => show_debugging.tsx} | 17 +++---- x-pack/plugins/canvas/public/plugin_api.ts | 12 +++-- .../public/services/stubs/expressions.ts | 13 ++--- .../__stories__/rendered_element.stories.tsx | 1 - .../components/rendered_element.tsx | 4 +- x-pack/plugins/canvas/types/renderers.ts | 3 ++ 45 files changed, 197 insertions(+), 129 deletions(-) rename x-pack/plugins/canvas/canvas_plugin_src/functions/common/{repeatImage.ts => repeat_image.ts} (93%) rename x-pack/plugins/canvas/canvas_plugin_src/lib/{elastic_logo.js => elastic_logo.ts} (100%) rename x-pack/plugins/canvas/canvas_plugin_src/lib/{elastic_outline.js => elastic_outline.ts} (100%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/{core.js => core.ts} (100%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/{debug.js => debug.tsx} (91%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/error/{index.js => index.tsx} (84%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/{index.js => index.ts} (100%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/{image.js => image.tsx} (86%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/{index.js => index.ts} (100%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/markdown/{index.js => index.tsx} (78%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/{index.js => index.tsx} (74%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/{index.js => index.ts} (82%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/{index.js => index.ts} (64%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/{index.js => index.ts} (100%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/{repeat_image.js => repeat_image.ts} (81%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/{index.js => index.ts} (80%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/{index.js => index.ts} (73%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/{table.js => table.tsx} (61%) rename x-pack/plugins/canvas/canvas_plugin_src/renderers/{text.js => text.tsx} (71%) rename x-pack/plugins/canvas/common/lib/{missing_asset.js => missing_asset.ts} (100%) rename x-pack/plugins/canvas/common/lib/{url.test.js => url.test.ts} (100%) rename x-pack/plugins/canvas/common/lib/{url.js => url.ts} (90%) rename x-pack/plugins/canvas/public/components/error/{error.js => error.tsx} (86%) rename x-pack/plugins/canvas/public/components/error/{index.js => index.ts} (100%) rename x-pack/plugins/canvas/public/components/error/{show_debugging.js => show_debugging.tsx} (64%) diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts index e44fb903ef042..947106fd9397a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts @@ -22,7 +22,7 @@ interface Arguments { openLinksInNewTab: boolean; } -interface Return { +export interface Return { content: string; font: Style; openLinksInNewTab: boolean; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts index 09ce2b2bf1755..93e4a3636b914 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts @@ -6,7 +6,6 @@ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { ContainerStyle, Overflow, BackgroundRepeat, BackgroundSize } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; -// @ts-expect-error untyped local import { isValidUrl } from '../../../common/lib/url'; interface Output extends ContainerStyle { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts index 3ef956b41ce20..21157660ed414 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts @@ -8,7 +8,6 @@ import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; // @ts-expect-error untyped local import { resolveWithMissingImage } from '../../../common/lib/resolve_dataurl'; -// @ts-expect-error .png file import { elasticLogo } from '../../lib/elastic_logo'; export enum ImageMode { @@ -22,7 +21,7 @@ interface Arguments { mode: ImageMode | null; } -interface Return { +export interface Return { type: 'image'; mode: string; dataurl: string; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts index 79538941bbbfa..5ec831efbe35f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts @@ -46,7 +46,7 @@ import { render } from './render'; import { replace } from './replace'; import { rounddate } from './rounddate'; import { rowCount } from './rowCount'; -import { repeatImage } from './repeatImage'; +import { repeatImage } from './repeat_image'; import { revealImage } from './revealImage'; import { seriesStyle } from './seriesStyle'; import { shape } from './shape'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts index c32c553fffc1b..16eee349475ef 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts @@ -57,7 +57,7 @@ interface PieData { color?: string; } -interface Pie { +export interface Pie { font: Style; data: PieData[]; options: PieOptions; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.ts index 6fc1e509cd5e6..4c4dba3ef7a3b 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.ts @@ -20,7 +20,7 @@ export enum Shape { WHEEL = 'wheel', } -interface Arguments { +export interface Arguments { barColor: string; barWeight: number; font: Style; @@ -31,6 +31,10 @@ interface Arguments { valueWeight: number; } +export type Output = Arguments & { + value: number; +}; + export function progress(): ExpressionFunctionDefinition< 'progress', number, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeat_image.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeat_image.test.js index 723c5eb4c6823..f7c1ecc94a240 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeat_image.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeat_image.test.js @@ -7,7 +7,7 @@ import { functionWrapper } from '../../../__tests__/helpers/function_wrapper'; import { elasticOutline } from '../../lib/elastic_outline'; import { elasticLogo } from '../../lib/elastic_logo'; -import { repeatImage } from './repeatImage'; +import { repeatImage } from './repeat_image'; describe('repeatImage', () => { const fn = functionWrapper(repeatImage); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeatImage.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeat_image.ts similarity index 93% rename from x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeatImage.ts rename to x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeat_image.ts index 9e296f2b9a92a..cbb118c0db807 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeatImage.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeat_image.ts @@ -7,7 +7,6 @@ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; // @ts-expect-error untyped local import { resolveWithMissingImage } from '../../../common/lib/resolve_dataurl'; -// @ts-expect-error .png file import { elasticOutline } from '../../lib/elastic_outline'; import { Render } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; @@ -19,6 +18,14 @@ interface Arguments { emptyImage: string | null; } +export interface Return { + count: number; + image: string; + size: number; + max: number; + emptyImage: string | null; +} + export function repeatImage(): ExpressionFunctionDefinition< 'repeatImage', number, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts index 3e721cc49b411..9288a417f9d29 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts @@ -7,7 +7,6 @@ import { ExpressionFunctionDefinition, ExpressionValueRender } from 'src/plugins/expressions'; // @ts-expect-error untyped local import { resolveWithMissingImage } from '../../../common/lib/resolve_dataurl'; -// @ts-expect-error .png file import { elasticOutline } from '../../lib/elastic_outline'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; @@ -24,11 +23,18 @@ interface Arguments { origin: Origin; } +export interface Output { + image: string; + emptyImage: string; + origin: Origin; + percent: number; +} + export function revealImage(): ExpressionFunctionDefinition< 'revealImage', number, Arguments, - ExpressionValueRender + ExpressionValueRender > { const { help, args: argHelp } = getFunctionHelp().revealImage; const errors = getFunctionErrors().revealImage; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/shape.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/shape.ts index a3fedebd36cfe..e7178ab4eef5b 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/shape.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/shape.ts @@ -34,7 +34,7 @@ interface Arguments { maintainAspect: boolean; } -interface Output extends Arguments { +export interface Output extends Arguments { type: 'shape'; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/table.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/table.ts index 689f3f969d1c8..744d426d3bc6c 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/table.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/table.ts @@ -15,11 +15,13 @@ interface Arguments { showHeader: boolean; } +export type Return = { datatable: Datatable } & Arguments; + export function table(): ExpressionFunctionDefinition< 'table', Datatable, Arguments, - Render + Render > { const { help, args: argHelp } = getFunctionHelp().table; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/lib/elastic_logo.js b/x-pack/plugins/canvas/canvas_plugin_src/lib/elastic_logo.ts similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/lib/elastic_logo.js rename to x-pack/plugins/canvas/canvas_plugin_src/lib/elastic_logo.ts diff --git a/x-pack/plugins/canvas/canvas_plugin_src/lib/elastic_outline.js b/x-pack/plugins/canvas/canvas_plugin_src/lib/elastic_outline.ts similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/lib/elastic_outline.js rename to x-pack/plugins/canvas/canvas_plugin_src/lib/elastic_outline.ts diff --git a/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts b/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts index 59f0287805eac..55f5319bbadb7 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts @@ -12,7 +12,6 @@ import { Start as InspectorStart } from '../../../../src/plugins/inspector/publi import { functions } from './functions/browser'; import { typeFunctions } from './expression_types'; -// @ts-expect-error: untyped local import { renderFunctions, renderFunctionFactories } from './renderers'; import { initializeElements } from './elements'; // @ts-expect-error untyped local diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/core.js rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/debug.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/debug.tsx similarity index 91% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/debug.js rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/debug.tsx index 79ec0078fa98a..b4fbba96e8dfb 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/debug.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/debug.tsx @@ -8,10 +8,11 @@ import ReactDOM from 'react-dom'; import React from 'react'; import { Debug } from '../../public/components/debug'; import { RendererStrings } from '../../i18n'; +import { RendererFactory } from '../../types'; const { debug: strings } = RendererStrings; -export const debug = () => ({ +export const debug: RendererFactory = () => ({ name: 'debug', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx index ad368a912cd8c..641580d9c58a5 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx @@ -17,7 +17,7 @@ import { EmbeddableExpression } from '../../expression_types/embeddable'; import { RendererStrings } from '../../../i18n'; import { embeddableInputToExpression } from './embeddable_input_to_expression'; import { EmbeddableInput } from '../../expression_types'; -import { RendererHandlers } from '../../../types'; +import { RendererFactory } from '../../../types'; import { CANVAS_EMBEDDABLE_CLASSNAME } from '../../../common/lib'; const { embeddable: strings } = RendererStrings; @@ -43,18 +43,17 @@ const renderEmbeddableFactory = (core: CoreStart, plugins: StartDeps) => { }; }; -export const embeddableRendererFactory = (core: CoreStart, plugins: StartDeps) => { +export const embeddableRendererFactory = ( + core: CoreStart, + plugins: StartDeps +): RendererFactory> => { const renderEmbeddable = renderEmbeddableFactory(core, plugins); return () => ({ name: 'embeddable', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), reuseDomNode: true, - render: async ( - domNode: HTMLElement, - { input, embeddableType }: EmbeddableExpression, - handlers: RendererHandlers - ) => { + render: async (domNode, { input, embeddableType }, handlers) => { const uniqueId = handlers.getElementId(); if (!embeddablesRegistry[uniqueId]) { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/index.tsx similarity index 84% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/error/index.js rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/error/index.tsx index b7e3fc300a189..a9296bd9a1241 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/index.tsx @@ -5,15 +5,20 @@ */ import ReactDOM from 'react-dom'; -import React from 'react'; +import React, { MouseEventHandler } from 'react'; import { EuiIcon } from '@elastic/eui'; import { Error } from '../../../public/components/error'; import { Popover } from '../../../public/components/popover'; import { RendererStrings } from '../../../i18n'; +import { RendererFactory } from '../../../types'; + +interface Config { + error: Error; +} const { error: strings } = RendererStrings; -export const error = () => ({ +export const error: RendererFactory = () => ({ name: 'error', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), @@ -21,7 +26,7 @@ export const error = () => ({ render(domNode, config, handlers) { const draw = () => { const buttonSize = Math.min(domNode.clientHeight, domNode.clientWidth); - const button = (handleClick) => ( + const button = (handleClick: MouseEventHandler) => ( ({ +export const advancedFilter: RendererFactory<{}> = () => ({ name: 'advanced_filter', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/index.ts similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/index.js rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/index.ts diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/image.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/image.tsx similarity index 86% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/image.js rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/image.tsx index fd76f0106f7d5..cbd81f0143da8 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/image.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/image.tsx @@ -8,11 +8,13 @@ import ReactDOM from 'react-dom'; import React from 'react'; import { elasticLogo } from '../lib/elastic_logo'; import { isValidUrl } from '../../common/lib/url'; +import { Return as Arguments } from '../functions/common/image'; import { RendererStrings } from '../../i18n'; +import { RendererFactory } from '../../types'; const { image: strings } = RendererStrings; -export const image = () => ({ +export const image: RendererFactory = () => ({ name: 'image', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/index.ts similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/index.js rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/index.ts diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/markdown/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/markdown/index.tsx similarity index 78% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/markdown/index.js rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/markdown/index.tsx index a7b0f620cf710..1ce05b77a5109 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/markdown/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/markdown/index.tsx @@ -4,14 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { CSSProperties } from 'react'; import ReactDOM from 'react-dom'; import { RendererStrings } from '../../../i18n'; +import { Return as Config } from '../../functions/browser/markdown'; import { Markdown } from '../../../../../../src/plugins/kibana_react/public'; +import { RendererFactory } from '../../../types'; const { markdown: strings } = RendererStrings; -export const markdown = () => ({ +export const markdown: RendererFactory = () => ({ name: 'markdown', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), @@ -22,7 +24,7 @@ export const markdown = () => ({ ReactDOM.render( , diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/index.tsx similarity index 74% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/index.js rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/index.tsx index cb423af30094c..622e73ccf2223 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/index.tsx @@ -10,11 +10,14 @@ import '../../lib/flot-charts'; import { debounce, includes } from 'lodash'; import { RendererStrings } from '../../../i18n'; +// @ts-expect-error Untyped local: Will not convert import { pie as piePlugin } from './plugins/pie'; +import { Pie } from '../../functions/common/pie'; +import { RendererFactory } from '../../../types'; const { pie: strings } = RendererStrings; -export const pie = () => ({ +export const pie: RendererFactory = () => ({ name: 'pie', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), @@ -27,7 +30,10 @@ export const pie = () => ({ config.options.legend.labelBoxBorderColor = 'transparent'; if (config.font) { - const labelFormatter = (label, slice) => { + const labelFormatter = ( + label: string, + slice: jquery.flot.dataSeries & { percent: number } + ) => { // font color defaults to slice color if not specified const fontSpec = { ...config.font.spec, color: config.font.spec.color || slice.color }; const labelDiv = document.createElement('div'); @@ -36,23 +42,28 @@ export const pie = () => ({ const lineBreak = document.createElement('br'); const percentText = document.createTextNode(`${Math.round(slice.percent)}%`); - labelDiv.appendChild(labelSpan); + if (labelSpan) { + labelDiv.appendChild(labelSpan); + } labelDiv.appendChild(lineBreak); labelDiv.appendChild(percentText); return labelDiv.outerHTML; }; + // @ts-ignore ignoring missing propery config.options.series.pie.label.formatter = labelFormatter; - const legendFormatter = (label) => { + const legendFormatter = (label: string) => { const labelSpan = document.createElement('span'); Object.assign(labelSpan.style, config.font.spec); labelSpan.textContent = label; return labelSpan.outerHTML; }; + // @ts-ignore ignoring missing propery config.options.legend.labelFormatter = legendFormatter; } - let plot; + let plot: jquery.flot.plot; + function draw() { if (domNode.clientHeight < 1 || domNode.clientWidth < 1) { return; @@ -63,10 +74,11 @@ export const pie = () => ({ if (!config.data || !config.data.length) { $(domNode).empty(); } else { - plot = $.plot($(domNode), config.data, config.options); + // Casting config.options to any here as the flot typings do not appear to be accurate. + // For example, it does not have colors as a valid option. + plot = $.plot($(domNode), config.data, config.options as any); } } catch (e) { - console.log(e); // Nope } } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/index.ts similarity index 82% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/index.js rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/index.ts index ab523f1526e16..8c84f54f8746b 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/index.ts @@ -10,12 +10,15 @@ import '../../lib/flot-charts'; import { debounce, includes } from 'lodash'; import { RendererStrings } from '../../../i18n'; +import { RendererFactory, RendererSpec } from '../../../types'; +// @ts-expect-error Not going to convert import { size } from './plugins/size'; +// @ts-expect-error Not going to convert import { text } from './plugins/text'; const { plot: strings } = RendererStrings; -const render = (domNode, config, handlers) => { +const render: RendererSpec['render'] = (domNode, config, handlers) => { // TODO: OH NOES if (!includes($.plot.plugins, size)) { $.plot.plugins.push(size); @@ -24,14 +27,14 @@ const render = (domNode, config, handlers) => { $.plot.plugins.push(text); } - let plot; + let plot: jquery.flot.plot; function draw() { if (domNode.clientHeight < 1 || domNode.clientWidth < 1) { return; } if (config.font) { - const legendFormatter = (label) => { + const legendFormatter = (label: string) => { const labelSpan = document.createElement('span'); Object.assign(labelSpan.style, config.font.spec); labelSpan.textContent = label; @@ -67,9 +70,10 @@ const render = (domNode, config, handlers) => { return handlers.done(); }; -export const plot = () => ({ +export const plot: RendererFactory = () => ({ name: 'plot', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), + reuseDomNode: false, render, }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/index.ts similarity index 64% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/index.js rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/index.ts index 67d0abb65837d..ea57655c230b9 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/index.ts @@ -7,10 +7,12 @@ import { getId } from '../../../public/lib/get_id'; import { RendererStrings } from '../../../i18n'; import { shapes } from './shapes'; +import { Output as Arguments } from '../../functions/common/progress'; +import { RendererFactory } from '../../../types'; const { progress: strings } = RendererStrings; -export const progress = () => ({ +export const progress: RendererFactory = () => ({ name: 'progress', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), @@ -23,12 +25,13 @@ export const progress = () => ({ if (shapeDef) { const parser = new DOMParser(); - const [shapeSvg] = parser - .parseFromString(shapes[shape], 'image/svg+xml') - .getElementsByTagName('svg'); + const shapeSvg = parser + .parseFromString(shapeDef, 'image/svg+xml') + .getElementsByTagName('svg') + .item(0)!; const initialViewBox = shapeSvg - .getAttribute('viewBox') + .getAttribute('viewBox')! .split(' ') .map((v) => parseInt(v, 10)); let [minX, minY, width, height] = initialViewBox; @@ -51,35 +54,35 @@ export const progress = () => ({ const svgId = getId('svg'); shapeSvg.id = svgId; - const [bar] = shapeSvg.getElementsByTagName('path'); + const bar = shapeSvg.getElementsByTagName('path').item(0)!; bar.setAttribute('className', 'canvasProgress__background'); bar.setAttribute('fill', 'none'); bar.setAttribute('stroke', barColor); bar.setAttribute('stroke-width', `${barWeight}px`); - const value = bar.cloneNode(true); - value.setAttribute('className', 'canvasProgress__value'); - value.setAttribute('stroke', valueColor); - value.setAttribute('stroke-width', `${valueWeight}px`); + const valueSvg = bar.cloneNode(true) as SVGPathElement; + valueSvg.setAttribute('className', 'canvasProgress__value'); + valueSvg.setAttribute('stroke', valueColor); + valueSvg.setAttribute('stroke-width', `${valueWeight}px`); - const length = value.getTotalLength(); + const length = valueSvg.getTotalLength(); const to = length * (1 - percent); - value.setAttribute('stroke-dasharray', length); - value.setAttribute('stroke-dashoffset', Math.max(0, to)); + valueSvg.setAttribute('stroke-dasharray', String(length)); + valueSvg.setAttribute('stroke-dashoffset', String(Math.max(0, to))); - shapeSvg.appendChild(value); + shapeSvg.appendChild(valueSvg); - const [text] = shapeSvg.getElementsByTagName('text'); + const text = shapeSvg.getElementsByTagName('text').item(0); if (label && text) { - text.textContent = label; + text.textContent = String(label); text.setAttribute('className', 'canvasProgress__label'); if (shape === 'horizontalPill') { - text.setAttribute('x', parseInt(text.getAttribute('x'), 10) + offset / 2); + text.setAttribute('x', String(parseInt(text.getAttribute('x')!, 10) + offset / 2)); } if (shape === 'verticalPill') { - text.setAttribute('y', parseInt(text.getAttribute('y'), 10) - offset / 2); + text.setAttribute('y', String(parseInt(text.getAttribute('y')!, 10) - offset / 2)); } Object.assign(text.style, font.spec); @@ -89,7 +92,7 @@ export const progress = () => ({ const { width: labelWidth, height: labelHeight } = text.getBBox(); if (shape === 'horizontalBar' || shape === 'horizontalPill') { - text.setAttribute('x', parseInt(text.getAttribute('x'), 10)); + text.setAttribute('x', String(parseInt(text.getAttribute('x')!, 10))); width += labelWidth; } if (shape === 'verticalBar' || shape === 'verticalPill') { @@ -103,8 +106,8 @@ export const progress = () => ({ } shapeSvg.setAttribute('viewBox', [minX, minY, width, height].join(' ')); - shapeSvg.setAttribute('width', domNode.offsetWidth); - shapeSvg.setAttribute('height', domNode.offsetHeight); + shapeSvg.setAttribute('width', String(domNode.offsetWidth)); + shapeSvg.setAttribute('height', String(domNode.offsetHeight)); if (domNode.firstChild) { domNode.removeChild(domNode.firstChild); @@ -112,8 +115,8 @@ export const progress = () => ({ domNode.appendChild(shapeSvg); handlers.onResize(() => { - shapeSvg.setAttribute('width', domNode.offsetWidth); - shapeSvg.setAttribute('height', domNode.offsetHeight); + shapeSvg.setAttribute('width', String(domNode.offsetWidth)); + shapeSvg.setAttribute('height', String(domNode.offsetHeight)); }); } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/index.ts similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/index.js rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/index.ts diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/repeat_image.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/repeat_image.ts similarity index 81% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/repeat_image.js rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/repeat_image.ts index 2e48c8a2d5ec3..ff37ca7a0e3dd 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/repeat_image.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/repeat_image.ts @@ -9,25 +9,27 @@ import { times } from 'lodash'; import { elasticOutline } from '../lib/elastic_outline'; import { isValidUrl } from '../../common/lib/url'; import { RendererStrings, ErrorStrings } from '../../i18n'; +import { Return as Arguments } from '../functions/common/repeat_image'; +import { RendererFactory } from '../../types'; const { repeatImage: strings } = RendererStrings; const { RepeatImage: errors } = ErrorStrings; -export const repeatImage = () => ({ +export const repeatImage: RendererFactory = () => ({ name: 'repeatImage', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), reuseDomNode: true, render(domNode, config, handlers) { const settings = { - count: 10, ...config, image: isValidUrl(config.image) ? config.image : elasticOutline, + emptyImage: config.emptyImage || '', }; const container = $('
'); - function setSize(img) { + function setSize(img: HTMLImageElement) { if (img.naturalHeight > img.naturalWidth) { img.height = settings.size; } else { @@ -36,7 +38,7 @@ export const repeatImage = () => ({ } function finish() { - $(domNode).html(container); + $(domNode).append(container); handlers.done(); } @@ -46,7 +48,7 @@ export const repeatImage = () => ({ if (settings.max && settings.count > settings.max) { settings.count = settings.max; } - times(settings.count, () => container.append(img.cloneNode(true))); + times(settings.count, () => container.append($(img).clone())); if (isValidUrl(settings.emptyImage)) { if (settings.max == null) { @@ -56,7 +58,7 @@ export const repeatImage = () => ({ const emptyImage = new Image(); emptyImage.onload = function () { setSize(emptyImage); - times(settings.max - settings.count, () => container.append(emptyImage.cloneNode(true))); + times(settings.max - settings.count, () => container.append($(emptyImage).clone())); finish(); }; emptyImage.src = settings.emptyImage; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/index.ts similarity index 80% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/index.js rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/index.ts index 96c8d80794c0c..c52556f8564c1 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/index.ts @@ -7,10 +7,12 @@ import { elasticOutline } from '../../lib/elastic_outline'; import { isValidUrl } from '../../../common/lib/url'; import { RendererStrings } from '../../../i18n'; +import { RendererFactory } from '../../../types'; +import { Output as Arguments } from '../../functions/common/revealImage'; const { revealImage: strings } = RendererStrings; -export const revealImage = () => ({ +export const revealImage: RendererFactory = () => ({ name: 'revealImage', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), @@ -23,16 +25,17 @@ export const revealImage = () => ({ domNode.className = 'revealImage'; // set up the overlay image - img.onload = function () { + function onLoad() { setSize(); finish(); - }; + } + img.onload = onLoad; img.className = 'revealImage__image'; img.style.clipPath = getClipPath(config.percent, config.origin); - img.style['-webkit-clip-path'] = getClipPath(config.percent, config.origin); + img.style.setProperty('-webkit-clip-path', getClipPath(config.percent, config.origin)); img.src = isValidUrl(config.image) ? config.image : elasticOutline; - handlers.onResize(img.onload); + handlers.onResize(onLoad); // set up the underlay, "empty" image aligner.className = 'revealImageAligner'; @@ -52,9 +55,9 @@ export const revealImage = () => ({ handlers.done(); } - function getClipPath(percent, origin = 'bottom') { - const directions = { bottom: 0, left: 1, top: 2, right: 3 }; - const values = [0, 0, 0, 0]; + function getClipPath(percent: number, origin = 'bottom') { + const directions: Record = { bottom: 0, left: 1, top: 2, right: 3 }; + const values: Array = [0, 0, 0, 0]; values[directions[origin]] = `${100 - percent * 100}%`; return `inset(${values.join(' ')})`; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/index.ts similarity index 73% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/index.js rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/index.ts index 5684c8c4602b5..7c61ff69b18e0 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/index.ts @@ -6,22 +6,26 @@ import { RendererStrings } from '../../../i18n'; import { shapes } from './shapes'; +import { RendererFactory } from '../../../types'; +import { Output } from '../../functions/common/shape'; const { shape: strings } = RendererStrings; -export const shape = () => ({ +export const shape: RendererFactory = () => ({ name: 'shape', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), reuseDomNode: true, render(domNode, config, handlers) { - const { shape, fill, border, borderWidth, maintainAspect } = config; + const { shape: shapeType, fill, border, borderWidth, maintainAspect } = config; + const parser = new DOMParser(); - const [shapeSvg] = parser - .parseFromString(shapes[shape], 'image/svg+xml') - .getElementsByTagName('svg'); + const shapeSvg = parser + .parseFromString(shapes[shapeType], 'image/svg+xml') + .getElementsByTagName('svg') + .item(0)!; - const shapeContent = shapeSvg.firstElementChild; + const shapeContent = shapeSvg.firstElementChild!; if (fill) { shapeContent.setAttribute('fill', fill); @@ -30,15 +34,15 @@ export const shape = () => ({ shapeContent.setAttribute('stroke', border); } const strokeWidth = Math.max(borderWidth, 0); - shapeContent.setAttribute('stroke-width', strokeWidth); - shapeContent.setAttribute('stroke-miterlimit', 999); + shapeContent.setAttribute('stroke-width', String(strokeWidth)); + shapeContent.setAttribute('stroke-miterlimit', '999'); shapeContent.setAttribute('vector-effect', 'non-scaling-stroke'); shapeSvg.setAttribute('preserveAspectRatio', maintainAspect ? 'xMidYMid meet' : 'none'); shapeSvg.setAttribute('overflow', 'visible'); const initialViewBox = shapeSvg - .getAttribute('viewBox') + .getAttribute('viewBox')! .split(' ') .map((v) => parseInt(v, 10)); @@ -66,8 +70,8 @@ export const shape = () => ({ shapeHeight = 0; } - shapeSvg.setAttribute('width', width); - shapeSvg.setAttribute('height', height); + shapeSvg.setAttribute('width', String(width)); + shapeSvg.setAttribute('height', String(height)); shapeSvg.setAttribute('viewBox', [minX, minY, shapeWidth, shapeHeight].join(' ')); const oldShape = domNode.firstElementChild; @@ -75,7 +79,7 @@ export const shape = () => ({ domNode.removeChild(oldShape); } - domNode.style.lineHeight = 0; + domNode.style.lineHeight = '0'; domNode.appendChild(shapeSvg); }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/table.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/table.tsx similarity index 61% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/table.js rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/table.tsx index 971ba577643ed..ada159e07f6ae 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/table.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/table.tsx @@ -6,22 +6,30 @@ import ReactDOM from 'react-dom'; import React from 'react'; -import { get } from 'lodash'; -import { Datatable } from '../../public/components/datatable'; +import { Datatable as DatatableComponent } from '../../public/components/datatable'; import { RendererStrings } from '../../i18n'; +import { RendererFactory, Style, Datatable } from '../../types'; const { dropdownFilter: strings } = RendererStrings; -export const table = () => ({ +interface TableArguments { + font?: Style; + paginate: boolean; + perPage: number; + showHeader: boolean; + datatable: Datatable; +} + +export const table: RendererFactory = () => ({ name: 'table', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), reuseDomNode: true, render(domNode, config, handlers) { - const { datatable, paginate, perPage, font, showHeader } = config; + const { datatable, paginate, perPage, font = { spec: {} }, showHeader } = config; ReactDOM.render( -
- + ({ +export const text: RendererFactory<{ text: string }> = () => ({ name: 'text', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), reuseDomNode: true, - render(domNode, { text }, handlers) { - ReactDOM.render(
{text}
, domNode, () => handlers.done()); + render(domNode, { text: textString }, handlers) { + ReactDOM.render(
{textString}
, domNode, () => handlers.done()); handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); }, }); diff --git a/x-pack/plugins/canvas/common/lib/index.ts b/x-pack/plugins/canvas/common/lib/index.ts index 6bd7e0bc9948f..055f6ce7739b7 100644 --- a/x-pack/plugins/canvas/common/lib/index.ts +++ b/x-pack/plugins/canvas/common/lib/index.ts @@ -24,12 +24,10 @@ export * from './get_legend_config'; export * from './handlebars'; export * from './hex_to_rgb'; export * from './httpurl'; -// @ts-expect-error missing local definition export * from './missing_asset'; export * from './palettes'; export * from './pivot_object_array'; // @ts-expect-error missing local definition export * from './resolve_dataurl'; export * from './unquote_string'; -// @ts-expect-error missing local definition export * from './url'; diff --git a/x-pack/plugins/canvas/common/lib/missing_asset.js b/x-pack/plugins/canvas/common/lib/missing_asset.ts similarity index 100% rename from x-pack/plugins/canvas/common/lib/missing_asset.js rename to x-pack/plugins/canvas/common/lib/missing_asset.ts diff --git a/x-pack/plugins/canvas/common/lib/url.test.js b/x-pack/plugins/canvas/common/lib/url.test.ts similarity index 100% rename from x-pack/plugins/canvas/common/lib/url.test.js rename to x-pack/plugins/canvas/common/lib/url.test.ts diff --git a/x-pack/plugins/canvas/common/lib/url.js b/x-pack/plugins/canvas/common/lib/url.ts similarity index 90% rename from x-pack/plugins/canvas/common/lib/url.js rename to x-pack/plugins/canvas/common/lib/url.ts index bed5e30cbff3b..d2e031159d969 100644 --- a/x-pack/plugins/canvas/common/lib/url.js +++ b/x-pack/plugins/canvas/common/lib/url.ts @@ -7,6 +7,6 @@ import { isValidDataUrl } from '../../common/lib/dataurl'; import { isValidHttpUrl } from '../../common/lib/httpurl'; -export function isValidUrl(url) { +export function isValidUrl(url: string) { return isValidDataUrl(url) || isValidHttpUrl(url); } diff --git a/x-pack/plugins/canvas/i18n/functions/dict/repeat_image.ts b/x-pack/plugins/canvas/i18n/functions/dict/repeat_image.ts index aafaec11c4d1d..222947779a758 100644 --- a/x-pack/plugins/canvas/i18n/functions/dict/repeat_image.ts +++ b/x-pack/plugins/canvas/i18n/functions/dict/repeat_image.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { repeatImage } from '../../../canvas_plugin_src/functions/common/repeatImage'; +import { repeatImage } from '../../../canvas_plugin_src/functions/common/repeat_image'; import { FunctionHelp } from '../function_help'; import { FunctionFactory } from '../../../types'; import { CONTEXT, BASE64, URL } from '../../constants'; diff --git a/x-pack/plugins/canvas/public/components/error/error.js b/x-pack/plugins/canvas/public/components/error/error.tsx similarity index 86% rename from x-pack/plugins/canvas/public/components/error/error.js rename to x-pack/plugins/canvas/public/components/error/error.tsx index 2f82e7d669bd5..93650df93cbeb 100644 --- a/x-pack/plugins/canvas/public/components/error/error.js +++ b/x-pack/plugins/canvas/public/components/error/error.tsx @@ -4,16 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; import { EuiCallOut } from '@elastic/eui'; import { get } from 'lodash'; import { ComponentStrings } from '../../../i18n'; import { ShowDebugging } from './show_debugging'; +export interface Props { + payload: { + error: Error; + }; +} + const { Error: strings } = ComponentStrings; -export const Error = ({ payload }) => { +export const Error: FC = ({ payload }) => { const message = get(payload, 'error.message'); return ( diff --git a/x-pack/plugins/canvas/public/components/error/index.js b/x-pack/plugins/canvas/public/components/error/index.ts similarity index 100% rename from x-pack/plugins/canvas/public/components/error/index.js rename to x-pack/plugins/canvas/public/components/error/index.ts diff --git a/x-pack/plugins/canvas/public/components/error/show_debugging.js b/x-pack/plugins/canvas/public/components/error/show_debugging.tsx similarity index 64% rename from x-pack/plugins/canvas/public/components/error/show_debugging.js rename to x-pack/plugins/canvas/public/components/error/show_debugging.tsx index 102ebc3f8a7e8..0d5d74903828b 100644 --- a/x-pack/plugins/canvas/public/components/error/show_debugging.js +++ b/x-pack/plugins/canvas/public/components/error/show_debugging.tsx @@ -4,14 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC, useState } from 'react'; import PropTypes from 'prop-types'; -import { withState } from 'recompose'; import { EuiButtonEmpty } from '@elastic/eui'; import { Debug } from '../debug'; +import { Props } from './error'; -const ShowDebuggingComponent = ({ payload, expanded, setExpanded }) => - process.env.NODE_ENV === 'production' ? null : ( +export const ShowDebugging: FC = ({ payload }) => { + const [expanded, setExpanded] = useState(false); + + return process.env.NODE_ENV === 'production' ? null : (
)}
); +}; -ShowDebuggingComponent.propTypes = { - expanded: PropTypes.bool.isRequired, - setExpanded: PropTypes.func.isRequired, +ShowDebugging.propTypes = { payload: PropTypes.object.isRequired, }; - -export const ShowDebugging = withState('expanded', 'setExpanded', false)(ShowDebuggingComponent); diff --git a/x-pack/plugins/canvas/public/plugin_api.ts b/x-pack/plugins/canvas/public/plugin_api.ts index 4074d240c06e9..62e82df4b0d04 100644 --- a/x-pack/plugins/canvas/public/plugin_api.ts +++ b/x-pack/plugins/canvas/public/plugin_api.ts @@ -6,7 +6,8 @@ import { AnyExpressionFunctionDefinition, AnyExpressionTypeDefinition, - RendererFactory, + AnyExpressionRenderDefinition, + AnyRendererFactory, } from '../types'; import { ElementFactory } from '../types'; import { ExpressionsSetup } from '../../../../src/plugins/expressions/public'; @@ -19,7 +20,7 @@ export interface CanvasApi { addElements: AddToRegistry; addFunctions: AddToRegistry<() => AnyExpressionFunctionDefinition>; addModelUIs: AddToRegistry; - addRenderers: AddToRegistry; + addRenderers: AddToRegistry; addTagUIs: AddToRegistry; addTransformUIs: AddToRegistry; addTransitions: AddToRegistry; @@ -65,8 +66,11 @@ export function getPluginApi( }); }, addRenderers: (renderers) => { - renderers.forEach((r: any) => { - expressionsPluginSetup.registerRenderer(r); + renderers.forEach((r) => { + // There is an issue of the canvas render definition not matching the expression render definition + // due to our handlers needing additional methods. For now, we are going to cast to get to the proper + // type, but we should work with AppArch to figure out how the Handlers can be genericized + expressionsPluginSetup.registerRenderer((r as unknown) as AnyExpressionRenderDefinition); }); }, diff --git a/x-pack/plugins/canvas/public/services/stubs/expressions.ts b/x-pack/plugins/canvas/public/services/stubs/expressions.ts index 26a90670106d0..ee332e20c4ca3 100644 --- a/x-pack/plugins/canvas/public/services/stubs/expressions.ts +++ b/x-pack/plugins/canvas/public/services/stubs/expressions.ts @@ -4,13 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AnyExpressionRenderDefinition } from 'src/plugins/expressions'; import { ExpressionsService } from '../'; -import { - plugin, - ExpressionRenderDefinition, -} from '../../../../../../src/plugins/expressions/public'; +import { plugin } from '../../../../../../src/plugins/expressions/public'; import { functions as functionDefinitions } from '../../../canvas_plugin_src/functions/common'; -// @ts-expect-error untyped local import { renderFunctions } from '../../../canvas_plugin_src/renderers/core'; const placeholder = {} as any; @@ -22,6 +19,6 @@ const setup = expressionsPlugin.setup(placeholder, { export const expressionsService: ExpressionsService = setup.fork(); functionDefinitions.forEach((fn) => expressionsService.registerFunction(fn)); -renderFunctions.forEach((fn: ExpressionRenderDefinition) => - expressionsService.registerRenderer(fn) -); +renderFunctions.forEach((fn) => { + expressionsService.registerRenderer((fn as unknown) as AnyExpressionRenderDefinition); +}); diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__stories__/rendered_element.stories.tsx b/x-pack/plugins/canvas/shareable_runtime/components/__stories__/rendered_element.stories.tsx index 899edee7f0481..aba510b8e736a 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/__stories__/rendered_element.stories.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/__stories__/rendered_element.stories.tsx @@ -7,7 +7,6 @@ import { storiesOf } from '@storybook/react'; import React from 'react'; import { ExampleContext } from '../../test/context_example'; -// @ts-expect-error import { image } from '../../../canvas_plugin_src/renderers/image'; import { sharedWorkpads } from '../../test'; import { RenderedElement, RenderedElementComponent } from '../rendered_element'; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx b/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx index 6bcc0db92f1cc..2291eff0db0ec 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx @@ -12,7 +12,7 @@ import { Positionable } from '../../public/components/positionable/positionable' import { elementToShape } from '../../public/components/workpad_page/utils'; import { CanvasRenderedElement } from '../types'; import { CanvasShareableContext, useCanvasShareableState } from '../context'; -import { RendererSpec } from '../../types'; +import { AnyRendererSpec } from '../../types'; import { createHandlers } from '../../public/lib/create_handlers'; import css from './rendered_element.module.scss'; @@ -33,7 +33,7 @@ export interface Props { * The Expression function that evaluates the state of the Element and renders * it to the Page. */ - fn: RendererSpec; + fn: AnyRendererSpec; } /** diff --git a/x-pack/plugins/canvas/types/renderers.ts b/x-pack/plugins/canvas/types/renderers.ts index 772a16aa94c60..7dcb94507b632 100644 --- a/x-pack/plugins/canvas/types/renderers.ts +++ b/x-pack/plugins/canvas/types/renderers.ts @@ -47,3 +47,6 @@ export interface RendererSpec { } export type RendererFactory = () => RendererSpec; + +export type AnyRendererFactory = RendererFactory; +export type AnyRendererSpec = RendererSpec; From 767419df5ba7b5a741ceeceb05213d3dedda992f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Sat, 15 Aug 2020 01:40:55 +0200 Subject: [PATCH 10/22] [ILM] TS conversion of Edit policy components (#74747) (#75089) * [ILM] Convert node allocation component to TS and use hooks * [ILM] Fix jest tests * [ILM] Fix i18n check * [ILM] Implement code review suggestions * [ILM] Fix type check, docs link and button maxWidth in NodeAllocation component * Fix internaliation error * [ILM] Convert node details flyout to TS * [ILM] Fix useState declaration * [ILM] Fix useState declaration * [ILM] Fix jest test * [ILM] Change error message when unable to load node attributes * [ILM] Change error message when unable to load node details * [ILM] Delete a period in error callout * [ILM] Delete a period in error callout * [ILM] Convert node details flyout to TS * [ILM] Fix i18n check * [ILM] Fix useState declaration * [ILM] Fix useState declaration * [ILM] Fix jest test * [ILM] Change error message when unable to load node details * [ILM] Delete a period in error callout * [ILM] edit components * [ILM] Fix review suggestions Co-authored-by: Elastic Machine Co-authored-by: Elastic Machine --- .../components/active_badge.tsx} | 0 .../cold_phase/cold_phase.container.js | 22 ----- .../components/cold_phase/index.js | 7 -- .../delete_phase/delete_phase.container.js | 21 ---- .../components/delete_phase/index.js | 7 -- .../{ => components}/form_errors.tsx | 0 .../hot_phase/hot_phase.container.js | 22 ----- .../edit_policy/components/hot_phase/index.js | 7 -- .../components/index.ts} | 9 +- .../components/learn_more_link.tsx | 2 +- .../{min_age_input.js => min_age_input.tsx} | 27 +++-- .../{node_allocation => }/node_allocation.tsx | 15 +-- .../node_attrs_details.tsx | 2 +- .../components/node_attrs_details/index.ts | 7 -- .../components/optional_label.tsx} | 0 .../components/phase_error_message.tsx} | 2 +- .../components/policy_json_flyout.js | 95 ------------------ .../components/policy_json_flyout.tsx | 98 +++++++++++++++++++ ...iority_input.js => set_priority_input.tsx} | 22 ++++- .../snapshot_policies.tsx | 2 +- .../components/snapshot_policies/index.ts | 7 -- .../components/warm_phase/index.js | 7 -- .../warm_phase/warm_phase.container.js | 22 ----- .../edit_policy/edit_policy.container.js | 4 + .../sections/edit_policy/edit_policy.js | 29 ++++-- .../cold_phase.js => phases/cold_phase.tsx} | 32 +++--- .../delete_phase.tsx} | 38 ++++--- .../hot_phase.js => phases/hot_phase.tsx} | 27 ++--- .../node_allocation => phases}/index.ts | 5 +- .../warm_phase.js => phases/warm_phase.tsx} | 34 ++++--- .../add_policy_to_template_confirm_modal.js | 2 +- .../public/application/store/actions/nodes.js | 3 - 32 files changed, 267 insertions(+), 310 deletions(-) rename x-pack/plugins/index_lifecycle_management/public/application/sections/{components/active_badge.js => edit_policy/components/active_badge.tsx} (100%) delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.container.js delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/index.js delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.container.js delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/index.js rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{ => components}/form_errors.tsx (100%) delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.container.js delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/index.js rename x-pack/plugins/index_lifecycle_management/public/application/sections/{components/index.js => edit_policy/components/index.ts} (54%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/{ => edit_policy}/components/learn_more_link.tsx (92%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/{min_age_input.js => min_age_input.tsx} (91%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/{node_allocation => }/node_allocation.tsx (93%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/{node_attrs_details => }/node_attrs_details.tsx (97%) delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details/index.ts rename x-pack/plugins/index_lifecycle_management/public/application/sections/{components/optional_label.js => edit_policy/components/optional_label.tsx} (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/{components/phase_error_message.js => edit_policy/components/phase_error_message.tsx} (87%) delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.js create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/{set_priority_input.js => set_priority_input.tsx} (80%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/{snapshot_policies => }/snapshot_policies.tsx (98%) delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/snapshot_policies/index.ts delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/index.js delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.container.js rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{components/cold_phase/cold_phase.js => phases/cold_phase.tsx} (92%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{components/delete_phase/delete_phase.js => phases/delete_phase.tsx} (88%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{components/hot_phase/hot_phase.js => phases/hot_phase.tsx} (96%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{components/node_allocation => phases}/index.ts (58%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{components/warm_phase/warm_phase.js => phases/warm_phase.tsx} (95%) diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/active_badge.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/active_badge.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/components/active_badge.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/active_badge.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.container.js deleted file mode 100644 index d4605ceb43499..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.container.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { connect } from 'react-redux'; - -import { getPhase } from '../../../../store/selectors'; -import { setPhaseData } from '../../../../store/actions'; -import { PHASE_COLD, PHASE_HOT, PHASE_ROLLOVER_ENABLED } from '../../../../constants'; -import { ColdPhase as PresentationComponent } from './cold_phase'; - -export const ColdPhase = connect( - (state) => ({ - phaseData: getPhase(state, PHASE_COLD), - hotPhaseRolloverEnabled: getPhase(state, PHASE_HOT)[PHASE_ROLLOVER_ENABLED], - }), - { - setPhaseData: (key, value) => setPhaseData(PHASE_COLD, key, value), - } -)(PresentationComponent); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/index.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/index.js deleted file mode 100644 index e0d70ceb57726..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { ColdPhase } from './cold_phase.container'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.container.js deleted file mode 100644 index 84bd17e3637e8..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.container.js +++ /dev/null @@ -1,21 +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 { connect } from 'react-redux'; -import { getPhase } from '../../../../store/selectors'; -import { setPhaseData } from '../../../../store/actions'; -import { PHASE_DELETE, PHASE_HOT, PHASE_ROLLOVER_ENABLED } from '../../../../constants'; -import { DeletePhase as PresentationComponent } from './delete_phase'; - -export const DeletePhase = connect( - (state) => ({ - phaseData: getPhase(state, PHASE_DELETE), - hotPhaseRolloverEnabled: getPhase(state, PHASE_HOT)[PHASE_ROLLOVER_ENABLED], - }), - { - setPhaseData: (key, value) => setPhaseData(PHASE_DELETE, key, value), - } -)(PresentationComponent); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/index.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/index.js deleted file mode 100644 index 5f909ab2c0f79..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { DeletePhase } from './delete_phase.container'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_errors.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/form_errors.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_errors.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/form_errors.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.container.js deleted file mode 100644 index 5f1451afdcc31..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.container.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { connect } from 'react-redux'; - -import { getPhase } from '../../../../store/selectors'; -import { setPhaseData } from '../../../../store/actions'; -import { PHASE_HOT, PHASE_WARM, WARM_PHASE_ON_ROLLOVER } from '../../../../constants'; -import { HotPhase as PresentationComponent } from './hot_phase'; - -export const HotPhase = connect( - (state) => ({ - phaseData: getPhase(state, PHASE_HOT), - }), - { - setPhaseData: (key, value) => setPhaseData(PHASE_HOT, key, value), - setWarmPhaseOnRollover: (value) => setPhaseData(PHASE_WARM, WARM_PHASE_ON_ROLLOVER, value), - } -)(PresentationComponent); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/index.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/index.js deleted file mode 100644 index 114e34c3ef4d0..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { HotPhase } from './hot_phase.container'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/index.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts similarity index 54% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/components/index.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts index a2ae37780b9f9..e933c46e98491 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/index.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts @@ -5,6 +5,13 @@ */ export { ActiveBadge } from './active_badge'; +export { ErrableFormRow } from './form_errors'; export { LearnMoreLink } from './learn_more_link'; -export { PhaseErrorMessage } from './phase_error_message'; +export { MinAgeInput } from './min_age_input'; +export { NodeAllocation } from './node_allocation'; +export { NodeAttrsDetails } from './node_attrs_details'; export { OptionalLabel } from './optional_label'; +export { PhaseErrorMessage } from './phase_error_message'; +export { PolicyJsonFlyout } from './policy_json_flyout'; +export { SetPriorityInput } from './set_priority_input'; +export { SnapshotPolicies } from './snapshot_policies'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/learn_more_link.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/learn_more_link.tsx similarity index 92% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/components/learn_more_link.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/learn_more_link.tsx index 623ff982438d7..5ada49b318018 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/learn_more_link.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/learn_more_link.tsx @@ -8,7 +8,7 @@ import React, { ReactNode } from 'react'; import { EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { createDocLink } from '../../services/documentation'; +import { createDocLink } from '../../../services/documentation'; interface Props { docPath: string; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.tsx similarity index 91% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.tsx index d90ad9378efd4..c9732f2311758 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.tsx @@ -16,10 +16,10 @@ import { PHASE_COLD, PHASE_DELETE, } from '../../../constants'; -import { LearnMoreLink } from '../../components'; -import { ErrableFormRow } from '../form_errors'; +import { LearnMoreLink } from './learn_more_link'; +import { ErrableFormRow } from './form_errors'; -function getTimingLabelForPhase(phase) { +function getTimingLabelForPhase(phase: string) { // NOTE: Hot phase isn't necessary, because indices begin in the hot phase. switch (phase) { case PHASE_WARM: @@ -39,7 +39,7 @@ function getTimingLabelForPhase(phase) { } } -function getUnitsAriaLabelForPhase(phase) { +function getUnitsAriaLabelForPhase(phase: string) { // NOTE: Hot phase isn't necessary, because indices begin in the hot phase. switch (phase) { case PHASE_WARM: @@ -68,9 +68,24 @@ function getUnitsAriaLabelForPhase(phase) { } } -export const MinAgeInput = (props) => { - const { rolloverEnabled, errors, phaseData, phase, setPhaseData, isShowingErrors } = props; +interface Props { + rolloverEnabled: boolean; + errors: Record; + phase: string; + // TODO add types for phaseData and setPhaseData after policy is typed + phaseData: any; + setPhaseData: (dataKey: string, value: any) => void; + isShowingErrors: boolean; +} +export const MinAgeInput: React.FunctionComponent = ({ + rolloverEnabled, + errors, + phaseData, + phase, + setPhaseData, + isShowingErrors, +}) => { let daysOptionLabel; let hoursOptionLabel; let minutesOptionLabel; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/node_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation.tsx similarity index 93% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/node_allocation.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation.tsx index 31261de45c743..576483a5ab9c2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/node_allocation.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation.tsx @@ -16,17 +16,18 @@ import { EuiButton, } from '@elastic/eui'; -import { PHASE_NODE_ATTRS } from '../../../../constants'; -import { LearnMoreLink } from '../../../components/learn_more_link'; -import { ErrableFormRow } from '../../form_errors'; -import { useLoadNodes } from '../../../../services/api'; -import { NodeAttrsDetails } from '../node_attrs_details'; +import { PHASE_NODE_ATTRS } from '../../../constants'; +import { LearnMoreLink } from './learn_more_link'; +import { ErrableFormRow } from './form_errors'; +import { useLoadNodes } from '../../../services/api'; +import { NodeAttrsDetails } from './node_attrs_details'; interface Props { phase: string; - setPhaseData: (dataKey: string, value: any) => void; - errors: any; + errors: Record; + // TODO add types for phaseData and setPhaseData after policy is typed phaseData: any; + setPhaseData: (dataKey: string, value: any) => void; isShowingErrors: boolean; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details/node_attrs_details.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details.tsx similarity index 97% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details/node_attrs_details.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details.tsx index 6fcbd94dc5e9a..cd87cc324a414 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details/node_attrs_details.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details.tsx @@ -20,7 +20,7 @@ import { EuiButton, } from '@elastic/eui'; -import { useLoadNodeDetails } from '../../../../services/api'; +import { useLoadNodeDetails } from '../../../services/api'; interface Props { close: () => void; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details/index.ts deleted file mode 100644 index 056d2f2f600f3..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details/index.ts +++ /dev/null @@ -1,7 +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. - */ - -export { NodeAttrsDetails } from './node_attrs_details'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/optional_label.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/optional_label.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/components/optional_label.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/optional_label.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/phase_error_message.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx similarity index 87% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/components/phase_error_message.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx index 904ac7c25f2f9..750f68543f221 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/phase_error_message.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiBadge } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -export const PhaseErrorMessage = ({ isShowingErrors }) => { +export const PhaseErrorMessage = ({ isShowingErrors }: { isShowingErrors: boolean }) => { return isShowingErrors ? ( '}`; - const request = `${endpoint}\n${this.getEsJson(lifecycle)}`; - - return ( - - - -

- {policyName ? ( - - ) : ( - - )} -

-
-
- - - -

- -

-
- - - - - {request} - -
- - - - - - -
- ); - } -} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx new file mode 100644 index 0000000000000..aaf4aa6e6222d --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { + EuiButtonEmpty, + EuiCodeBlock, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +interface Props { + close: () => void; + // TODO add types for lifecycle after policy is typed + lifecycle: any; + policyName: string; +} + +export const PolicyJsonFlyout: React.FunctionComponent = ({ + close, + lifecycle, + policyName, +}) => { + // @ts-ignore until store is typed + const getEsJson = ({ phases }) => { + return JSON.stringify( + { + policy: { + phases, + }, + }, + null, + 2 + ); + }; + + const endpoint = `PUT _ilm/policy/${policyName || ''}`; + const request = `${endpoint}\n${getEsJson(lifecycle)}`; + + return ( + + + +

+ {policyName ? ( + + ) : ( + + )} +

+
+
+ + + +

+ +

+
+ + + + + {request} + +
+ + + + + + +
+ ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.tsx similarity index 80% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.tsx index bdcc1e23b4230..0034de85fce17 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.tsx @@ -8,12 +8,26 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFieldNumber, EuiTextColor, EuiDescribedFormGroup } from '@elastic/eui'; import { PHASE_INDEX_PRIORITY } from '../../../constants'; -import { LearnMoreLink, OptionalLabel } from '../../components'; -import { ErrableFormRow } from '../form_errors'; -export const SetPriorityInput = (props) => { - const { errors, phaseData, phase, setPhaseData, isShowingErrors } = props; +import { LearnMoreLink } from './'; +import { OptionalLabel } from './'; +import { ErrableFormRow } from './'; +interface Props { + errors: Record; + // TODO add types for phaseData and setPhaseData after policy is typed + phase: string; + phaseData: any; + setPhaseData: (dataKey: string, value: any) => void; + isShowingErrors: boolean; +} +export const SetPriorityInput: React.FunctionComponent = ({ + errors, + phaseData, + phase, + setPhaseData, + isShowingErrors, +}) => { return ( ({ - phaseData: getPhase(state, PHASE_WARM), - hotPhaseRolloverEnabled: getPhase(state, PHASE_HOT)[PHASE_ROLLOVER_ENABLED], - }), - { - setPhaseData: (key, value) => setPhaseData(PHASE_WARM, key, value), - } -)(PresentationComponent); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.js index 1c6ced8953211..e7f20a66d09f0 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.js @@ -15,6 +15,7 @@ import { isPolicyListLoaded, getIsNewPolicy, getSelectedOriginalPolicyName, + getPhases, } from '../../store/selectors'; import { @@ -23,6 +24,7 @@ import { setSaveAsNewPolicy, saveLifecyclePolicy, fetchPolicies, + setPhaseData, } from '../../store/actions'; import { findFirstError } from '../../services/find_errors'; @@ -42,6 +44,7 @@ export const EditPolicy = connect( isPolicyListLoaded: isPolicyListLoaded(state), isNewPolicy: getIsNewPolicy(state), originalPolicyName: getSelectedOriginalPolicyName(state), + phases: getPhases(state), }; }, { @@ -50,5 +53,6 @@ export const EditPolicy = connect( setSaveAsNewPolicy, saveLifecyclePolicy, fetchPolicies, + setPhaseData, } )(PresentationComponent); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js index d9d8866a2e2cc..a29ecd07c5e45 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js @@ -33,17 +33,15 @@ import { PHASE_DELETE, PHASE_WARM, STRUCTURE_POLICY_NAME, + WARM_PHASE_ON_ROLLOVER, + PHASE_ROLLOVER_ENABLED, } from '../../constants'; import { toasts } from '../../services/notification'; import { findFirstError } from '../../services/find_errors'; -import { LearnMoreLink } from '../components'; -import { PolicyJsonFlyout } from './components/policy_json_flyout'; -import { ErrableFormRow } from './form_errors'; -import { HotPhase } from './components/hot_phase'; -import { WarmPhase } from './components/warm_phase'; -import { DeletePhase } from './components/delete_phase'; -import { ColdPhase } from './components/cold_phase'; +import { LearnMoreLink, PolicyJsonFlyout, ErrableFormRow } from './components'; + +import { HotPhase, WarmPhase, ColdPhase, DeletePhase } from './phases'; export class EditPolicy extends Component { static propTypes = { @@ -137,6 +135,8 @@ export class EditPolicy extends Component { isNewPolicy, lifecycle, originalPolicyName, + phases, + setPhaseData, } = this.props; const selectedPolicyName = selectedPolicy.name; const { isShowingErrors, isShowingPolicyJsonFlyout } = this.state; @@ -275,9 +275,13 @@ export class EditPolicy extends Component { setPhaseData(PHASE_HOT, key, value)} + phaseData={phases[PHASE_HOT]} + setWarmPhaseOnRollover={(value) => + setPhaseData(PHASE_WARM, WARM_PHASE_ON_ROLLOVER, value) + } /> @@ -285,6 +289,9 @@ export class EditPolicy extends Component { setPhaseData(PHASE_WARM, key, value)} + phaseData={phases[PHASE_WARM]} + hotPhaseRolloverEnabled={phases[PHASE_HOT][PHASE_ROLLOVER_ENABLED]} /> @@ -292,6 +299,9 @@ export class EditPolicy extends Component { setPhaseData(PHASE_COLD, key, value)} + phaseData={phases[PHASE_COLD]} + hotPhaseRolloverEnabled={phases[PHASE_HOT][PHASE_ROLLOVER_ENABLED]} /> @@ -300,6 +310,9 @@ export class EditPolicy extends Component { errors={errors[PHASE_DELETE]} isShowingErrors={isShowingErrors && !!findFirstError(errors[PHASE_DELETE], false)} getUrlForApp={this.props.getUrlForApp} + setPhaseData={(key, value) => setPhaseData(PHASE_DELETE, key, value)} + phaseData={phases[PHASE_DELETE]} + hotPhaseRolloverEnabled={phases[PHASE_HOT][PHASE_ROLLOVER_ENABLED]} /> diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/cold_phase.tsx similarity index 92% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/cold_phase.tsx index 200bf0e767d9d..babbbf7638ebe 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/cold_phase.tsx @@ -5,7 +5,6 @@ */ import React, { PureComponent, Fragment } from 'react'; -import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -24,20 +23,27 @@ import { PHASE_ENABLED, PHASE_REPLICA_COUNT, PHASE_FREEZE_ENABLED, -} from '../../../../constants'; -import { LearnMoreLink, ActiveBadge, PhaseErrorMessage, OptionalLabel } from '../../../components'; -import { ErrableFormRow } from '../../form_errors'; -import { MinAgeInput } from '../min_age_input'; -import { NodeAllocation } from '../node_allocation'; -import { SetPriorityInput } from '../set_priority_input'; +} from '../../../constants'; +import { + LearnMoreLink, + ActiveBadge, + PhaseErrorMessage, + OptionalLabel, + ErrableFormRow, + MinAgeInput, + NodeAllocation, + SetPriorityInput, +} from '../components'; -export class ColdPhase extends PureComponent { - static propTypes = { - setPhaseData: PropTypes.func.isRequired, +interface Props { + setPhaseData: (key: string, value: any) => void; + phaseData: any; + isShowingErrors: boolean; + errors: Record; + hotPhaseRolloverEnabled: boolean; +} - isShowingErrors: PropTypes.bool.isRequired, - errors: PropTypes.object.isRequired, - }; +export class ColdPhase extends PureComponent { render() { const { setPhaseData, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/delete_phase.tsx similarity index 88% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/delete_phase.tsx index 2b12eec953e11..0143cc4af24e3 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/delete_phase.tsx @@ -5,22 +5,35 @@ */ import React, { PureComponent, Fragment } from 'react'; -import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiDescribedFormGroup, EuiSwitch, EuiTextColor, EuiFormRow } from '@elastic/eui'; -import { PHASE_DELETE, PHASE_ENABLED, PHASE_WAIT_FOR_SNAPSHOT_POLICY } from '../../../../constants'; -import { ActiveBadge, LearnMoreLink, OptionalLabel, PhaseErrorMessage } from '../../../components'; -import { MinAgeInput } from '../min_age_input'; -import { SnapshotPolicies } from '../snapshot_policies'; +import { PHASE_DELETE, PHASE_ENABLED, PHASE_WAIT_FOR_SNAPSHOT_POLICY } from '../../../constants'; +import { + ActiveBadge, + LearnMoreLink, + OptionalLabel, + PhaseErrorMessage, + MinAgeInput, + SnapshotPolicies, +} from '../components'; -export class DeletePhase extends PureComponent { - static propTypes = { - setPhaseData: PropTypes.func.isRequired, - isShowingErrors: PropTypes.bool.isRequired, - errors: PropTypes.object.isRequired, - }; +interface Props { + setPhaseData: (key: string, value: any) => void; + phaseData: any; + isShowingErrors: boolean; + errors: Record; + hotPhaseRolloverEnabled: boolean; + getUrlForApp: ( + appId: string, + options?: { + path?: string; + absolute?: boolean; + } + ) => string; +} +export class DeletePhase extends PureComponent { render() { const { setPhaseData, @@ -28,6 +41,7 @@ export class DeletePhase extends PureComponent { errors, isShowingErrors, hotPhaseRolloverEnabled, + getUrlForApp, } = this.props; return ( @@ -123,7 +137,7 @@ export class DeletePhase extends PureComponent { setPhaseData(PHASE_WAIT_FOR_SNAPSHOT_POLICY, value)} - getUrlForApp={this.props.getUrlForApp} + getUrlForApp={getUrlForApp} /> diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/hot_phase.tsx similarity index 96% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/hot_phase.tsx index b420442198712..dbd48f3a85634 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/hot_phase.tsx @@ -5,7 +5,6 @@ */ import React, { Fragment, PureComponent } from 'react'; -import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -28,18 +27,24 @@ import { PHASE_ROLLOVER_MAX_SIZE_STORED, PHASE_ROLLOVER_MAX_SIZE_STORED_UNITS, PHASE_ROLLOVER_ENABLED, -} from '../../../../constants'; -import { LearnMoreLink, ActiveBadge, PhaseErrorMessage } from '../../../components'; -import { ErrableFormRow } from '../../form_errors'; -import { SetPriorityInput } from '../set_priority_input'; +} from '../../../constants'; +import { + LearnMoreLink, + ActiveBadge, + PhaseErrorMessage, + ErrableFormRow, + SetPriorityInput, +} from '../components'; -export class HotPhase extends PureComponent { - static propTypes = { - setPhaseData: PropTypes.func.isRequired, - isShowingErrors: PropTypes.bool.isRequired, - errors: PropTypes.object.isRequired, - }; +interface Props { + errors: Record; + isShowingErrors: boolean; + phaseData: any; + setPhaseData: (key: string, value: any) => void; + setWarmPhaseOnRollover: (value: boolean) => void; +} +export class HotPhase extends PureComponent { render() { const { setPhaseData, phaseData, isShowingErrors, errors, setWarmPhaseOnRollover } = this.props; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/index.ts similarity index 58% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/index.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/index.ts index 4675ab46ee501..8d1ace5950497 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/index.ts @@ -4,4 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export { NodeAllocation } from './node_allocation'; +export { HotPhase } from './hot_phase'; +export { WarmPhase } from './warm_phase'; +export { ColdPhase } from './cold_phase'; +export { DeletePhase } from './delete_phase'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/warm_phase.tsx similarity index 95% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/warm_phase.tsx index 60b5ab4781b6d..6ed81bf8f45d5 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/warm_phase.tsx @@ -5,7 +5,6 @@ */ import React, { Fragment, PureComponent } from 'react'; -import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { @@ -28,21 +27,26 @@ import { PHASE_PRIMARY_SHARD_COUNT, PHASE_REPLICA_COUNT, PHASE_SHRINK_ENABLED, -} from '../../../../constants'; -import { LearnMoreLink, ActiveBadge, PhaseErrorMessage, OptionalLabel } from '../../../components'; -import { ErrableFormRow } from '../../form_errors'; -import { SetPriorityInput } from '../set_priority_input'; -import { NodeAllocation } from '../node_allocation'; -import { MinAgeInput } from '../min_age_input'; - -export class WarmPhase extends PureComponent { - static propTypes = { - setPhaseData: PropTypes.func.isRequired, - - isShowingErrors: PropTypes.bool.isRequired, - errors: PropTypes.object.isRequired, - }; +} from '../../../constants'; +import { + LearnMoreLink, + ActiveBadge, + PhaseErrorMessage, + OptionalLabel, + ErrableFormRow, + SetPriorityInput, + NodeAllocation, + MinAgeInput, +} from '../components'; +interface Props { + setPhaseData: (key: string, value: any) => void; + phaseData: any; + isShowingErrors: boolean; + errors: Record; + hotPhaseRolloverEnabled: boolean; +} +export class WarmPhase extends PureComponent { render() { const { setPhaseData, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/add_policy_to_template_confirm_modal.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/add_policy_to_template_confirm_modal.js index 8e53569047d8f..47134ad097720 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/add_policy_to_template_confirm_modal.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/add_policy_to_template_confirm_modal.js @@ -23,7 +23,7 @@ import { import { toasts } from '../../../../services/notification'; import { addLifecyclePolicyToTemplate, loadIndexTemplates } from '../../../../services/api'; import { showApiError } from '../../../../services/api_errors'; -import { LearnMoreLink } from '../../../components/learn_more_link'; +import { LearnMoreLink } from '../../../edit_policy/components'; export class AddPolicyToTemplateConfirmModal extends Component { state = { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/actions/nodes.js b/x-pack/plugins/index_lifecycle_management/public/application/store/actions/nodes.js index 3f1c00db621a7..45a8e63f70e83 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/actions/nodes.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/store/actions/nodes.js @@ -4,8 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ import { createAction } from 'redux-actions'; -import { SET_SELECTED_NODE_ATTRS } from '../../constants'; - -export const setSelectedNodeAttrs = createAction(SET_SELECTED_NODE_ATTRS); export const setSelectedPrimaryShardCount = createAction('SET_SELECTED_PRIMARY_SHARED_COUNT'); export const setSelectedReplicaCount = createAction('SET_SELECTED_REPLICA_COUNT'); From 2e5e06bad623f4757d342da02f018b14fd5829ce Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Fri, 14 Aug 2020 17:55:16 -0600 Subject: [PATCH 11/22] Upgrade EUI to v27.4.0 (#74004) (#75064) * eui to 27.1.0 * eui to 27.2.0 * buttoniconside type * euiselectable type * update onScroll callback and polyfill size references * findTestSubject ts * buttoncontent and collapsiblenav src snapshot updates * update prop retrieval * xpack snapshots * jest updates * type fixes * more snapshots * virtual list changes * more virtualization changes * merge * fix functional tests * data-test-subj for indexPatter-switcher * storyshots * eui to 27.3.1 * Fix unit tests * Fix broken unit test * Updated snapshots * Fixed types * search for value in euiselectable before selection * select the correct element * mock virtualized dep * ts fix * reinstate storyshot * ts fix Co-authored-by: Chandler Prall Co-authored-by: Elastic Machine Co-authored-by: Chandler Prall Co-authored-by: Elastic Machine --- package.json | 2 +- packages/kbn-ui-shared-deps/package.json | 2 +- .../collapsible_nav.test.tsx.snap | 9557 ++++++++++------- .../header/__snapshots__/header.test.tsx.snap | 1724 ++- .../__snapshots__/modal_service.test.tsx.snap | 4 +- .../components/field/field.test.tsx | 1 - .../components/form/form.test.tsx | 1 - .../components/search/search.test.tsx | 1 - .../dashboard_empty_screen.test.tsx.snap | 65 +- .../dashboard_empty_screen.test.tsx | 1 - .../viewport/dashboard_viewport.test.tsx | 1 - .../tests/dashboard_container.test.tsx | 1 - .../shard_failure_open_modal_button.test.tsx | 1 - .../components/action_bar/action_bar.test.tsx | 1 - .../pager/tool_bar_pager_buttons.test.tsx | 1 - .../table_header/table_header.test.tsx | 1 - .../context_error_message.test.tsx | 1 - .../application/components/doc/doc.test.tsx | 1 - .../components/doc_viewer/doc_viewer.test.tsx | 1 - .../hits_counter/hits_counter.test.tsx | 1 - .../loading_spinner/loading_spinner.test.tsx | 1 - .../sidebar/discover_field.test.tsx | 1 - .../sidebar/discover_field_search.test.tsx | 1 - .../sidebar/discover_index_pattern.test.tsx | 4 +- .../sidebar/discover_sidebar.test.tsx | 1 - .../skip_bottom_button.test.tsx | 2 - .../components/table/table.test.tsx | 1 - .../timechart_header.test.tsx | 1 - .../lib/embeddables/embeddable_root.test.tsx | 1 - .../lib/panel/embeddable_panel.test.tsx | 1 - .../add_panel/add_panel_flyout.test.tsx | 3 +- .../customize_panel_action.test.ts | 2 - .../tests/customize_panel_modal.test.tsx | 1 - .../saved_objects_installer.test.js.snap | 78 +- .../header/__snapshots__/header.test.tsx.snap | 2 +- .../empty_state/empty_state.test.tsx | 1 - .../components/editor/controls_tab.test.tsx | 1 - .../editor/list_control_editor.test.tsx | 1 - .../editor/range_control_editor.test.tsx | 1 - .../components/vis/input_control_vis.test.tsx | 1 - .../inspector_panel.test.tsx.snap | 63 +- .../public/top_nav_menu/top_nav_menu_data.tsx | 4 +- .../__snapshots__/header.test.tsx.snap | 165 +- .../objects_table/components/table.test.tsx | 1 - .../components/color_picker.test.tsx | 1 - .../legend/__snapshots__/legend.test.tsx.snap | 2 +- .../__snapshots__/new_vis_modal.test.tsx.snap | 4284 +++++--- .../apps/dashboard/dashboard_filter_bar.js | 2 +- .../input_control_vis/chained_controls.js | 2 +- .../input_control_vis/dynamic_options.js | 8 +- test/functional/page_objects/discover_page.ts | 1 + .../plugins/kbn_tp_run_pipeline/package.json | 2 +- .../kbn_sample_panel_action/package.json | 2 +- .../kbn_tp_custom_visualizations/package.json | 2 +- x-pack/package.json | 2 +- .../__test__/__snapshots__/List.test.tsx.snap | 18 +- .../ServiceOverview.test.tsx.snap | 5 +- .../TransactionActionMenu.test.tsx.snap | 12 +- .../time_filter.stories.storyshot | 40 +- .../asset_manager.stories.storyshot | 16 +- .../custom_element_modal.stories.storyshot | 44 +- .../element_card.stories.storyshot | 8 +- .../keyboard_shortcuts_doc.stories.storyshot | 5 + .../element_grid.stories.storyshot | 6 +- .../saved_elements_modal.stories.storyshot | 29 +- .../text_style_picker.stories.storyshot | 60 +- .../__snapshots__/toolbar.stories.storyshot | 19 +- .../delete_var.stories.storyshot | 7 +- .../__snapshots__/edit_var.stories.storyshot | 34 +- .../__snapshots__/edit_menu.stories.storyshot | 12 +- .../element_menu.stories.storyshot | 5 +- .../__snapshots__/pdf_panel.stories.storyshot | 7 +- .../share_menu.stories.storyshot | 2 +- .../__snapshots__/view_menu.stories.storyshot | 8 +- .../workpad_templates.stories.storyshot | 22 +- .../__snapshots__/shareable.test.tsx.snap | 10 +- .../__snapshots__/canvas.stories.storyshot | 6 +- .../__tests__/__snapshots__/app.test.tsx.snap | 2 +- .../__snapshots__/footer.stories.storyshot | 4 +- .../page_controls.stories.storyshot | 6 +- .../autoplay_settings.stories.storyshot | 6 +- .../__snapshots__/settings.test.tsx.snap | 2 +- .../__snapshots__/policy_table.test.js.snap | 5 +- .../layerpanel.test.tsx | 23 +- .../__snapshots__/add_license.test.js.snap | 4 +- .../request_trial_extension.test.js.snap | 8 +- .../revert_to_basic.test.js.snap | 6 +- .../__snapshots__/start_trial.test.js.snap | 8 +- .../upload_license.test.tsx.snap | 1399 ++- .../pipeline_list/pipelines_table.test.js | 2 +- .../upgrade_failure.test.js.snap | 195 +- .../entity_control/entity_control.tsx | 3 +- .../__snapshots__/no_data.test.js.snap | 8 +- .../collection_enabled.test.js.snap | 48 +- .../collection_interval.test.js.snap | 116 +- .../__snapshots__/reason_found.test.js.snap | 2 +- .../__snapshots__/summary_status.test.js.snap | 10 - .../remote_cluster_form.test.js.snap | 113 +- .../report_info_button.test.tsx.snap | 1272 ++- .../job_create/navigation/navigation.js | 2 +- .../overwritten_session_page.test.tsx.snap | 54 +- .../role_combo_box/role_combo_box.test.tsx | 37 +- .../cypress/tasks/create_new_case.ts | 2 +- .../__snapshots__/index.test.tsx.snap | 6 + .../note_card_body.test.tsx.snap | 6 + .../open_timeline_modal/index.test.tsx | 11 +- .../components/timeline/body/index.test.tsx | 2 +- .../timeline/selectable_timeline/index.tsx | 56 +- .../client_integration/policy_add.test.ts | 10 + .../public/custom_time_range_action.test.ts | 1 - .../public/custom_time_range_badge.test.ts | 1 - .../fingerprint_col.test.tsx.snap | 4 +- .../uptime_date_picker.test.tsx.snap | 12 +- .../__snapshots__/license_info.test.tsx.snap | 2 +- .../__snapshots__/ml_flyout.test.tsx.snap | 16 +- .../ml_integerations.test.tsx.snap | 7 +- .../__snapshots__/ml_job_link.test.tsx.snap | 2 +- .../__snapshots__/ml_manage_job.test.tsx.snap | 7 +- .../location_status_tags.test.tsx.snap | 6 +- .../location_missing.test.tsx.snap | 5 +- .../__snapshots__/empty_state.test.tsx.snap | 285 +- .../filter_popover.test.tsx.snap | 7 +- .../__tests__/filter_popover.test.tsx | 13 +- .../filter_status_button.test.tsx.snap | 4 +- .../__snapshots__/monitor_list.test.tsx.snap | 19 +- .../__snapshots__/status_filter.test.tsx.snap | 12 +- .../__snapshots__/page_header.test.tsx.snap | 41 +- .../threshold_watch_edit.tsx | 2 +- .../functional/apps/maps/add_layer_panel.js | 5 +- .../functional/page_objects/graph_page.ts | 3 + yarn.lock | 137 +- 131 files changed, 12036 insertions(+), 8372 deletions(-) diff --git a/package.json b/package.json index 19e365b2951f1..8c2052492f129 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "7.9.0-rc.2", "@elastic/ems-client": "7.9.3", - "@elastic/eui": "26.3.1", + "@elastic/eui": "27.4.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "^2.5.0", diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 72eed25a36abf..cf57130b7060c 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@elastic/charts": "19.8.1", - "@elastic/eui": "26.3.1", + "@elastic/eui": "27.4.0", "@elastic/numeral": "^2.5.0", "@kbn/i18n": "1.0.0", "@kbn/monaco": "1.0.0", diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index 9ecbc055e3320..72d62730fa698 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -378,7 +378,9 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
- - -
- } - onActivation={[Function]} - onDeactivation={[Function]} - persistentFocus={false} - > - - + + } + onActivation={[Function]} + onDeactivation={[Function]} + persistentFocus={false} + returnFocus={[Function]} + shards={Array []} + sideCar={ + Object { + "assignMedium": [Function], + "assignSyncMedium": [Function], + "options": Object { + "async": true, + "ssr": false, + }, + "read": [Function], + "useMedium": [Function], + } + } + > + -
- + + Custom link + + + + +
- -
-
+
- +
+ + Home + + + + +
-
-
- -
-
-
-
+ + +
+ -
-
-
-
-
-
- -
-
-
-
-
-
    -
  • - - - discover - - -
  • - visualize + recent 1
  • @@ -1442,16 +1364,18 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` class="euiListGroupItem euiListGroupItem--small euiListGroupItem--subdued euiListGroupItem-isClickable" > - dashboard + recent 2 @@ -1461,2983 +1385,3573 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
+
- -
-
-
-
+ + +
+
+
- + + visualize + + + +
  • + + + dashboard + + +
  • + +
    -
    -
    - +
    +
    +
    +
    -

    - Security -

    +
  • + + + metrics + + +
  • +
  • + + + logs + + +
  • +
    - - +
    +
    -
    -
    + +
    +
    +
    - + + siem + + + + +
    -
    -
    - -
    -
    -
    -
    + + +
    +
    +
    - + + monitoring + + + + +
    -
    -
    - + + canvas + + + + +
    -
    -
    -
      -
    • - -
    • -
    +
    + + Dock navigation + + + + +
    -
    - - -
    - } - onActivation={[Function]} - onDeactivation={[Function]} - persistentFocus={false} - /> - - - - - - -
    -
    - -
    - -
    + - - - -
    -
    - - -
    - - - - -

    - Recently viewed -

    -
    -
    - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading" - data-test-subj="collapsibleNavGroup-recentlyViewed" - id="mockId" - initialIsOpen={true} - onToggle={[Function]} - paddingSize="none" - > -
    -
    -
    +
    +
    +
    - -
    +
      - -

      - Recently viewed -

      -
      -
    -
    +
    + + Home + + + + +
    - - - -
    -
    - -
    +
    - -
    + + +
    +
    +
    +
    +
    -
  • - - + + recent 1 + + +
  • +
  • + - recent 2 - - -
  • - - - + + recent 2 + + + + +
    +
    +
    - - - - -
    -
    - -
    -
    - -
    - - - - - - - -

    - Kibana -

    -
    -
    - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-subj="collapsibleNavGroup-kibana" - id="mockId" - initialIsOpen={true} - onToggle={[Function]} - paddingSize="none" - > -
    -
    - -
    -
    - -
    + + +
    -
    - + +
    - -
    - - - - - - - - - - -

    - Observability -

    -
    -
    - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-subj="collapsibleNavGroup-observability" - id="mockId" - initialIsOpen={true} - onToggle={[Function]} - paddingSize="none" - > -
    -
    - -
    -
    - -
    + + +
    -
    - +
    - - + +
    +
    - - - -
    -
    - - - - - - - -

    - Security -

    -
    -
    - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-subj="collapsibleNavGroup-security" - id="mockId" - initialIsOpen={true} - onToggle={[Function]} - paddingSize="none" - > -
    -
    - -
    -
    - -
    + + +
    -
    - +
    - - + +
    +
    - - - -
    -
    - - - - -

    - Management -

    -
    -
    - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" - data-test-subj="collapsibleNavGroup-management" - id="mockId" - initialIsOpen={true} - onToggle={[Function]} - paddingSize="none" - > -
    -
    - -
    -
    - -
    + + +
    -
    - +
    - - + +
    +
    - - - -
    -
    - -
    -
    - - - -
    -
    -
    - - -
    -
    - -
      - -
      + canvas + + + +
    +
    +
    +
    +
    +
      +
    • + , - } - } - color="subdued" - data-test-subj="collapsible-nav-lock" - iconType="lockOpen" - label="Dock navigation" - onClick={[Function]} - size="xs" + +
    • +
    +
    +
    + + + + + } + onActivation={[Function]} + onDeactivation={[Function]} + persistentFocus={false} + returnFocus={[Function]} + shards={Array []} + > + + - -
    - -
    - - - - - -`; - -exports[`CollapsibleNav renders the default nav 1`] = ` - - - -`; - -exports[`CollapsibleNav renders the default nav 2`] = ` - - - - - - - -
    - -
    -
    -
    - - - -
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
      +
    • + +
    • +
    +
    +
    +
    + + +
    + } + onActivation={[Function]} + onDeactivation={[Function]} + persistentFocus={false} + returnFocus={[Function]} + shards={Array []} + /> + + + +
    - -
    +
    + +
    +
    + + + +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    + + + +
    +
    +
    +
    +
    + + + + +

    + Recently viewed +

    +
    +
    + + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading" + data-test-subj="collapsibleNavGroup-recentlyViewed" + id="mockId" + initialIsOpen={true} + onToggle={[Function]} + paddingSize="none" + > +
    +
    + +
    +
    + +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    - - - -
    -
    -
    -
    - - - - - -

    - Recently viewed -

    -
    -
    - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading" - data-test-subj="collapsibleNavGroup-recentlyViewed" - id="mockId" - initialIsOpen={true} - onToggle={[Function]} - paddingSize="none" - > -
    -
    -
    +
    + +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    +
    +
    + - + + + + + +

    + Observability +

    +
    +
    + + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-subj="collapsibleNavGroup-observability" + id="mockId" + initialIsOpen={true} + onToggle={[Function]} + paddingSize="none" >
    - - - +
    + +
    +
    + +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    + +
    + - + + + + + +

    + Security +

    +
    +
    +
    + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-subj="collapsibleNavGroup-security" + id="mockId" + initialIsOpen={true} + onToggle={[Function]} + paddingSize="none" >
    - -
    + +
    +
    + +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    + +
    + + +

    - Recently viewed + Management

    -
    -
    - - - - - -
    - -
    -
    + + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-subj="collapsibleNavGroup-management" + id="mockId" + initialIsOpen={true} + onToggle={[Function]} + paddingSize="none" >
    - -
    - + +
    + + + + +
    + +
    + +

    + Management +

    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    -

    - No recently viewed items -

    +
    + + + +
    - -
    - +
    + +
    -
    -
    -
    -
    - - - - -
    -
    - -
    - - -
    -
    + + - -
      - -
      - - Dock navigation - - , - } - } - color="subdued" - data-test-subj="collapsible-nav-lock" - iconType="lockOpen" - label="Dock navigation" - onClick={[Function]} - size="xs" + -
    • -
    • + +
    +
    +
    +
    +
    + + +
    +
    + +
      + +
      + + Dock navigation + + , + } + } + color="subdued" + data-test-subj="collapsible-nav-lock" + iconType="lockOpen" + label="Dock navigation" + onClick={[Function]} + size="xs" > - Dock navigation - - - - -
    -
    -
    +
  • + +
  • + + + +
    +
    + + - - - -
    - - - - - - - -
    + + +
    + + + + close + + + + + + + + +
    + +
    + + - + > + + + + + + + + + +
    @@ -4446,7 +4960,242 @@ exports[`CollapsibleNav renders the default nav 2`] = ` `; -exports[`CollapsibleNav renders the default nav 3`] = ` +exports[`CollapsibleNav renders the default nav 1`] = ` + + + +`; + +exports[`CollapsibleNav renders the default nav 2`] = ` @@ -4682,12 +5431,16 @@ exports[`CollapsibleNav renders the default nav 3`] = ` event="keydown" handler={[Function]} /> + - -
    -
    -
    - +
    +
    +
    -
    -
    + -
    - -
    -
    -
    -
    -
    -
    -
    -

    - No recently viewed items -

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    + + + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading" + data-test-subj="collapsibleNavGroup-recentlyViewed" id="mockId" + initialIsOpen={true} + onToggle={[Function]} + paddingSize="none" >
    -
      -
    • - -
    • -
    -
    -
    -
    - - -
    - } - onActivation={[Function]} - onDeactivation={[Function]} - persistentFocus={false} - > - - -
    + + +
    + + + + close + + + + + + + + +
    + +
    - -
    + - -
    - - - - , + } + } + color="subdued" + data-test-subj="collapsible-nav-lock" + iconType="lock" + label="Undock navigation" + onClick={[Function]} + size="xs" + > +
  • + +
  • + + + +
    +
    + + + + + + - - close - - - - - - - - -
    - + + + +
    + + + + close + + + + + + + + +
    + +
    + + +
    diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index ce56b19f82cd0..a1920154d9f71 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -9016,953 +9016,264 @@ exports[`Header renders 3`] = ` onTouchEnd={[Function]} onTouchStart={[Function]} > - -
    -
    -
    - - -
    + lockProps={ + Object { + "allowPinchZoom": undefined, + "enabled": false, + "inert": undefined, + "shards": undefined, + "sideCar": [Function], + } } - onActivation={[Function]} - onDeactivation={[Function]} + noFocusGuards={false} persistentFocus={false} + returnFocus={true} + sideCar={[Function]} > - - -
    +
    - -
    + + +
    +
    +
    - - - -
    -
    - -
    - - - - - -

    - Recently viewed -

    -
    -
    - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading" - data-test-subj="collapsibleNavGroup-recentlyViewed" - id="mockId" - initialIsOpen={true} - onToggle={[Function]} - paddingSize="none" - > -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    +
    + + -
    + + +

    + Recently viewed +

    +
    +
    + + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading" + data-test-subj="collapsibleNavGroup-recentlyViewed" + id="mockId" + initialIsOpen={true} + onToggle={[Function]} + paddingSize="none" > - -
      - -
    • + +
      + + + - -
    • -
      -
    -
    -
    -
    - - - +
    + +

    + Recently viewed +

    +
    +
    +
    + + + + + +
    + +
    +
    +
    + + + +
    +
    +
    +
    +
    + + + + +
    +
    +
    -
    - -
      - -
      - - Undock navigation - - , - } - } - color="subdued" - data-test-subj="collapsible-nav-lock" - iconType="lock" - label="Undock navigation" - onClick={[Function]} - size="xs" + -
    • - +
    • + +
    +
    +
    +
    + + + +
    +
    + +
      + +
      + + Undock navigation + + , + } + } + color="subdued" + data-test-subj="collapsible-nav-lock" + iconType="lock" + label="Undock navigation" + onClick={[Function]} + size="xs" > - Undock navigation - - - - -
    -
    -
    +
  • + +
  • + + + +
    + +
    +
    - - - -
    - - - - - - - -
    - + + + +
    + + + + close + + + + + + + + +
    + +
    + + +
    diff --git a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap index b17e7d0fec773..fb00ddc38c6dc 100644 --- a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap +++ b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap @@ -31,7 +31,7 @@ Array [ ] `; -exports[`ModalService openConfirm() renders a mountpoint confirm message 2`] = `"
    Modal content
    "`; +exports[`ModalService openConfirm() renders a mountpoint confirm message 2`] = `"
    Modal content
    "`; exports[`ModalService openConfirm() renders a string confirm message 1`] = ` Array [ @@ -53,7 +53,7 @@ Array [ ] `; -exports[`ModalService openConfirm() renders a string confirm message 2`] = `"

    Some message

    "`; +exports[`ModalService openConfirm() renders a string confirm message 2`] = `"

    Some message

    "`; exports[`ModalService openConfirm() with a currently active confirm replaces the current confirm with the new one 1`] = ` Array [ diff --git a/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx b/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx index 72992c190e8ae..5b33b0e0ea120 100644 --- a/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx @@ -25,7 +25,6 @@ import { FieldSetting } from '../../types'; import { UiSettingsType, StringValidation } from '../../../../../../core/public'; import { notificationServiceMock, docLinksServiceMock } from '../../../../../../core/public/mocks'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { Field, getEditableValue } from './field'; diff --git a/src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx b/src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx index 0e942665b23a9..e42432d0bc319 100644 --- a/src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx @@ -21,7 +21,6 @@ import React from 'react'; import { shallowWithI18nProvider, mountWithI18nProvider } from 'test_utils/enzyme_helpers'; import { UiSettingsType } from '../../../../../../core/public'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { notificationServiceMock } from '../../../../../../core/public/mocks'; diff --git a/src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx b/src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx index 0e3fbb3cf97fa..01f54cce60319 100644 --- a/src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx @@ -20,7 +20,6 @@ import React from 'react'; import { shallowWithI18nProvider, mountWithI18nProvider } from 'test_utils/enzyme_helpers'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { Query } from '@elastic/eui'; diff --git a/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap index 698c124d2d805..201c6e83a4a44 100644 --- a/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -671,35 +671,54 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = ` iconType="plusInCircle" size="s" > - + + +
    + + + Create new + + + + +

    diff --git a/src/plugins/dashboard/public/application/dashboard_empty_screen.test.tsx b/src/plugins/dashboard/public/application/dashboard_empty_screen.test.tsx index 933475d354cfa..0a49e524d3350 100644 --- a/src/plugins/dashboard/public/application/dashboard_empty_screen.test.tsx +++ b/src/plugins/dashboard/public/application/dashboard_empty_screen.test.tsx @@ -19,7 +19,6 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { coreMock } from '../../../../core/public/mocks'; diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx index 1e07c610b0ef2..60395bce678c2 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx @@ -17,7 +17,6 @@ * under the License. */ -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import React from 'react'; import { skip } from 'rxjs/operators'; diff --git a/src/plugins/dashboard/public/application/tests/dashboard_container.test.tsx b/src/plugins/dashboard/public/application/tests/dashboard_container.test.tsx index 6eb85faeea014..24075e0a634ba 100644 --- a/src/plugins/dashboard/public/application/tests/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/application/tests/dashboard_container.test.tsx @@ -17,7 +17,6 @@ * under the License. */ -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import React from 'react'; import { mount } from 'enzyme'; diff --git a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.test.tsx b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.test.tsx index 18b1237895f79..aee8d1f4eac4d 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.test.tsx +++ b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.test.tsx @@ -22,7 +22,6 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ShardFailureOpenModalButton } from './shard_failure_open_modal_button'; import { shardFailureRequest } from './__mocks__/shard_failure_request'; import { shardFailureResponse } from './__mocks__/shard_failure_response'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; describe('ShardFailureOpenModalButton', () => { diff --git a/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.test.tsx b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.test.tsx index 8976f8ea3c107..ab7adba193d87 100644 --- a/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.test.tsx +++ b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.test.tsx @@ -20,7 +20,6 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ActionBar, ActionBarProps } from './action_bar'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE } from '../../query_parameters/constants'; diff --git a/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx index 524161c77cbf8..2cd829d89f78e 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx @@ -20,7 +20,6 @@ import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { ToolBarPagerButtons } from './tool_bar_pager_buttons'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; test('it renders ToolBarPagerButtons', () => { diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx index b201bea26503e..224e249a274cd 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx @@ -20,7 +20,6 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { TableHeader } from './table_header'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { SortOrder } from './helpers'; import { IndexPattern, IFieldType } from '../../../../../kibana_services'; diff --git a/src/plugins/discover/public/application/components/context_error_message/context_error_message.test.tsx b/src/plugins/discover/public/application/components/context_error_message/context_error_message.test.tsx index 1c9439bc34e58..1cc8247957512 100644 --- a/src/plugins/discover/public/application/components/context_error_message/context_error_message.test.tsx +++ b/src/plugins/discover/public/application/components/context_error_message/context_error_message.test.tsx @@ -22,7 +22,6 @@ import { ReactWrapper } from 'enzyme'; import { ContextErrorMessage } from './context_error_message'; // @ts-ignore import { FAILURE_REASONS, LOADING_STATUS } from '../../angular/context/query'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; describe('loading spinner', function () { diff --git a/src/plugins/discover/public/application/components/doc/doc.test.tsx b/src/plugins/discover/public/application/components/doc/doc.test.tsx index 0bc621714c70f..c9fa551f61aca 100644 --- a/src/plugins/discover/public/application/components/doc/doc.test.tsx +++ b/src/plugins/discover/public/application/components/doc/doc.test.tsx @@ -20,7 +20,6 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ReactWrapper } from 'enzyme'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { Doc, DocProps } from './doc'; diff --git a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx index 3710ce72533db..9115915690324 100644 --- a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx +++ b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx @@ -19,7 +19,6 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; import { DocViewer } from './doc_viewer'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { getDocViewsRegistry } from '../../../kibana_services'; import { DocViewRenderProps } from '../../doc_views/doc_views_types'; diff --git a/src/plugins/discover/public/application/components/hits_counter/hits_counter.test.tsx b/src/plugins/discover/public/application/components/hits_counter/hits_counter.test.tsx index 84ad19dbddcbf..c2eb4f08cf549 100644 --- a/src/plugins/discover/public/application/components/hits_counter/hits_counter.test.tsx +++ b/src/plugins/discover/public/application/components/hits_counter/hits_counter.test.tsx @@ -20,7 +20,6 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ReactWrapper } from 'enzyme'; import { HitsCounter, HitsCounterProps } from './hits_counter'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; describe('hits counter', function () { diff --git a/src/plugins/discover/public/application/components/loading_spinner/loading_spinner.test.tsx b/src/plugins/discover/public/application/components/loading_spinner/loading_spinner.test.tsx index 3321ac764a05b..e996da5fe0e3c 100644 --- a/src/plugins/discover/public/application/components/loading_spinner/loading_spinner.test.tsx +++ b/src/plugins/discover/public/application/components/loading_spinner/loading_spinner.test.tsx @@ -20,7 +20,6 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ReactWrapper } from 'enzyme'; import { LoadingSpinner } from './loading_spinner'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; describe('loading spinner', function () { diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx index 3f12a8c0fa769..e1abbfd7657d0 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx @@ -18,7 +18,6 @@ */ import React from 'react'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; // @ts-ignore import StubIndexPattern from 'test_utils/stub_index_pattern'; diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_search.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_search.test.tsx index 654df5bfa9ee9..625d6833406eb 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_search.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_search.test.tsx @@ -19,7 +19,6 @@ import React, { EventHandler, MouseEvent as ReactMouseEvent } from 'react'; import { act } from 'react-dom/test-utils'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { DiscoverFieldSearch, Props } from './discover_field_search'; import { EuiButtonGroupProps, EuiPopover } from '@elastic/eui'; diff --git a/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.test.tsx index 24e6f5993a0a5..a1b231c2d4479 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.test.tsx @@ -24,7 +24,7 @@ import { ShallowWrapper } from 'enzyme'; import { ChangeIndexPattern } from './change_indexpattern'; import { SavedObject } from 'kibana/server'; import { DiscoverIndexPattern } from './discover_index_pattern'; -import { EuiSelectable, EuiSelectableList } from '@elastic/eui'; +import { EuiSelectable } from '@elastic/eui'; import { IIndexPattern } from 'src/plugins/data/public'; const indexPattern = { @@ -57,7 +57,7 @@ function getIndexPatternPickerList(instance: ShallowWrapper) { } function getIndexPatternPickerOptions(instance: ShallowWrapper) { - return getIndexPatternPickerList(instance).dive().find(EuiSelectableList).prop('options'); + return getIndexPatternPickerList(instance).prop('options'); } function selectIndexPatternPickerOption(instance: ShallowWrapper, selectedLabel: string) { diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx index 90ade60d2073d..9572f35641d69 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx @@ -19,7 +19,6 @@ import _ from 'lodash'; import { ReactWrapper } from 'enzyme'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; // @ts-ignore import StubIndexPattern from 'test_utils/stub_index_pattern'; diff --git a/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.test.tsx b/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.test.tsx index bf417f9f1890b..fdb0ff973dcf0 100644 --- a/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.test.tsx +++ b/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.test.tsx @@ -20,8 +20,6 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ReactWrapper } from 'enzyme'; import { SkipBottomButton, SkipBottomButtonProps } from './skip_bottom_button'; -// @ts-ignore -import { findTestSubject } from '@elastic/eui/lib/test'; describe('Skip to Bottom Button', function () { let props: SkipBottomButtonProps; diff --git a/src/plugins/discover/public/application/components/table/table.test.tsx b/src/plugins/discover/public/application/components/table/table.test.tsx index 29659b3969365..5b840a25d8beb 100644 --- a/src/plugins/discover/public/application/components/table/table.test.tsx +++ b/src/plugins/discover/public/application/components/table/table.test.tsx @@ -18,7 +18,6 @@ */ import React from 'react'; import { mount } from 'enzyme'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { DocViewTable } from './table'; import { indexPatterns, IndexPattern } from '../../../../../data/public'; diff --git a/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx b/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx index 964f94ca9d9b2..a4c10e749d868 100644 --- a/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx +++ b/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx @@ -21,7 +21,6 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ReactWrapper } from 'enzyme'; import { TimechartHeader, TimechartHeaderProps } from './timechart_header'; import { EuiIconTip } from '@elastic/eui'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; describe('timechart header', function () { diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_root.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_root.test.tsx index 131069909dd2a..cb900884fde97 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_root.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_root.test.tsx @@ -20,7 +20,6 @@ import React from 'react'; import { HelloWorldEmbeddable } from '../../../../../../examples/embeddable_examples/public'; import { EmbeddableRoot } from './embeddable_root'; import { mount } from 'enzyme'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; test('EmbeddableRoot renders an embeddable', async () => { diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index 341a51d7348b2..fcf79c1d6b211 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -21,7 +21,6 @@ import React from 'react'; import { mount } from 'enzyme'; import { nextTick } from 'test_utils/enzyme_helpers'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { I18nProvider } from '@kbn/i18n/react'; import { CONTEXT_MENU_TRIGGER } from '../triggers'; diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx index 34a176400dbb9..95aee3d9cb335 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx @@ -30,7 +30,6 @@ import { ContainerInput } from '../../../../containers'; import { mountWithIntl as mount } from 'test_utils/enzyme_helpers'; import { ReactWrapper } from 'enzyme'; import { coreMock } from '../../../../../../../../core/public/mocks'; -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { embeddablePluginMock } from '../../../../../mocks'; @@ -125,7 +124,7 @@ test('selecting embeddable in "Create new ..." list calls createNewEmbeddable()' notifications={core.notifications} SavedObjectFinder={(props) => } /> - ) as ReactWrapper; + ) as ReactWrapper; const spy = jest.fn(); component.instance().createNewEmbeddable = spy; diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts index 6fddcbc84faf7..dbfa55a4e0f13 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts @@ -18,8 +18,6 @@ */ import { Container, isErrorEmbeddable } from '../../../..'; -// @ts-ignore -import { findTestSubject } from '@elastic/eui/lib/test'; import { nextTick } from 'test_utils/enzyme_helpers'; import { CustomizePanelTitleAction } from './customize_panel_action'; import { diff --git a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx index e094afe528498..d12a55dd827e6 100644 --- a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx +++ b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx @@ -17,7 +17,6 @@ * under the License. */ -// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import * as React from 'react'; import { Container, isErrorEmbeddable } from '../lib'; diff --git a/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap b/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap index 1e7b3d5c6284c..9a9e055d54d2f 100644 --- a/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap +++ b/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap @@ -245,22 +245,41 @@ exports[`bulkCreate should display error message when bulkCreate request fails 1 isLoading={false} onClick={[Function]} > - + + + Load Kibana objects + + +
    + +
    @@ -565,22 +584,41 @@ exports[`bulkCreate should display success message when bulkCreate is successful isLoading={false} onClick={[Function]} > - + + + Load Kibana objects + + + + + diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/__snapshots__/header.test.tsx.snap b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/__snapshots__/header.test.tsx.snap index 6261ea2c90793..5218ebd1b4ad4 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/__snapshots__/header.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/__snapshots__/header.test.tsx.snap @@ -33,7 +33,7 @@ exports[`Header should render normally 1`] = ` type="button" > diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx index a1653c5289255..992a2fcf0c89d 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx @@ -17,7 +17,7 @@ * under the License. */ -import { ButtonIconSide } from '@elastic/eui'; +import { EuiButtonProps } from '@elastic/eui'; export type TopNavMenuAction = (anchorElement: HTMLElement) => void; @@ -32,7 +32,7 @@ export interface TopNavMenuData { tooltip?: string | (() => string | undefined); emphasize?: boolean; iconType?: string; - iconSide?: ButtonIconSide; + iconSide?: EuiButtonProps['iconSide']; } export interface RegisteredTopNavMenuData extends TopNavMenuData { diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap index d56776c2be9d7..f5c2d3efd56f7 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap @@ -58,45 +58,63 @@ exports[`Intro component renders correctly 1`] = ` iconType="eye" size="s" > - - -
    My Canvas Workpad
    " `; exports[`Canvas Shareable Workpad API Placed successfully with height specified 1`] = `"
    "`; @@ -21,7 +21,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with height specified
    markdown mock
    markdown mock
    My Canvas Workpad
    " +
    markdown mock
    My Canvas Workpad
    " `; exports[`Canvas Shareable Workpad API Placed successfully with page specified 1`] = `"
    "`; @@ -33,7 +33,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with page specified 2`
    markdown mock
    markdown mock
    My Canvas Workpad
    " +
    markdown mock
    My Canvas Workpad
    " `; exports[`Canvas Shareable Workpad API Placed successfully with width and height specified 1`] = `"
    "`; @@ -45,7 +45,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with width and height
    markdown mock
    markdown mock
    My Canvas Workpad
    " +
    markdown mock
    My Canvas Workpad
    " `; exports[`Canvas Shareable Workpad API Placed successfully with width specified 1`] = `"
    "`; @@ -57,5 +57,5 @@ exports[`Canvas Shareable Workpad API Placed successfully with width specified 2
    markdown mock
    markdown mock
    My Canvas Workpad
    " +
    markdown mock
    My Canvas Workpad
    " `; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot index 81e75ff5ee0d9..e0970b57ed971 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot +++ b/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot @@ -1398,7 +1398,7 @@ exports[`Storyshots shareables/Canvas component 1`] = ` type="button" > App renders properly 1`] = `
    markdown mock
    markdown mock
    My Canvas Workpad
    " +
    markdown mock
    My Canvas Workpad
    " `; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/__stories__/__snapshots__/footer.stories.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/footer/__stories__/__snapshots__/footer.stories.storyshot index 6ff39b723a49a..2d3a9c460272c 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/footer/__stories__/__snapshots__/footer.stories.storyshot +++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/__stories__/__snapshots__/footer.stories.storyshot @@ -1351,7 +1351,7 @@ exports[`Storyshots shareables/Footer contextual: austin 1`] = ` type="button" > can navigate Autoplay Settings 2`] = ` type="submit" > - - -
    + +
    + + - + > + + + + + + + + + + @@ -1076,21 +1469,31 @@ exports[`UploadLicense should display a modal when license requires acknowledgem onClick={[Function]} rel="noreferrer" > - - - Cancel - + + Cancel + + - +
    @@ -1107,28 +1510,48 @@ exports[`UploadLicense should display a modal when license requires acknowledgem isLoading={false} onClick={[Function]} > - + + + Upload + + + + + +
    @@ -1734,21 +2157,31 @@ exports[`UploadLicense should display an error when ES says license is expired 1 onClick={[Function]} rel="noreferrer" > - - - Cancel - + + Cancel + + -
    + @@ -1765,28 +2198,48 @@ exports[`UploadLicense should display an error when ES says license is expired 1 isLoading={false} onClick={[Function]} > - + + + Upload + + +
    + + + @@ -2392,21 +2845,31 @@ exports[`UploadLicense should display an error when ES says license is invalid 1 onClick={[Function]} rel="noreferrer" > - - - Cancel - + + Cancel + + -
    + @@ -2423,28 +2886,48 @@ exports[`UploadLicense should display an error when ES says license is invalid 1 isLoading={false} onClick={[Function]} > - + + + Upload + + +
    + + + @@ -3050,21 +3533,31 @@ exports[`UploadLicense should display an error when submitting invalid JSON 1`] onClick={[Function]} rel="noreferrer" > - - - Cancel - + + Cancel + + -
    + @@ -3081,28 +3574,48 @@ exports[`UploadLicense should display an error when submitting invalid JSON 1`] isLoading={false} onClick={[Function]} > - + + + Upload + + +
    + + + @@ -3708,21 +4221,31 @@ exports[`UploadLicense should display error when ES returns error 1`] = ` onClick={[Function]} rel="noreferrer" > - - - Cancel - + + Cancel + + -
    + @@ -3739,28 +4262,48 @@ exports[`UploadLicense should display error when ES returns error 1`] = ` isLoading={false} onClick={[Function]} > - + + + Upload + + +
    + + + diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_list/pipelines_table.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipelines_table.test.js index 2c25ae69ae722..6c2f0d02801c4 100644 --- a/x-pack/plugins/logstash/public/application/components/pipeline_list/pipelines_table.test.js +++ b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipelines_table.test.js @@ -45,7 +45,7 @@ describe('PipelinesTable component', () => { it('calls clone when cloned button clicked', () => { props.pipelines = [{ id: 'testPipeline', isCentrallyManaged: true }]; const wrapper = mountWithIntl(); - wrapper.find('[iconType="copy"]').simulate('click'); + wrapper.find('[iconType="copy"]').first().simulate('click'); expect(clonePipeline).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/logstash/public/application/components/upgrade_failure/__snapshots__/upgrade_failure.test.js.snap b/x-pack/plugins/logstash/public/application/components/upgrade_failure/__snapshots__/upgrade_failure.test.js.snap index 5f54513c427dd..d0f1bed8e8e9e 100644 --- a/x-pack/plugins/logstash/public/application/components/upgrade_failure/__snapshots__/upgrade_failure.test.js.snap +++ b/x-pack/plugins/logstash/public/application/components/upgrade_failure/__snapshots__/upgrade_failure.test.js.snap @@ -265,21 +265,38 @@ exports[`UpgradeFailure component passes expected text for new pipeline 1`] = ` fill={true} onClick={[MockFunction]} > - + + + Try again + + + + + @@ -298,21 +315,31 @@ exports[`UpgradeFailure component passes expected text for new pipeline 1`] = ` onClick={[MockFunction]} type="button" > - - - Go back - + + Go back + + -
    + @@ -594,21 +621,38 @@ exports[`UpgradeFailure component passes expected text for not manual upgrade 1` fill={true} onClick={[MockFunction]} > - + + + Upgrade + + + + + @@ -627,21 +671,31 @@ exports[`UpgradeFailure component passes expected text for not manual upgrade 1` onClick={[MockFunction]} type="button" > - - - Go back - + + Go back + + -
    + @@ -923,21 +977,38 @@ exports[`UpgradeFailure component passes expected text for not new pipeline 1`] fill={true} onClick={[MockFunction]} > - + + + Try again + + + + + @@ -956,21 +1027,31 @@ exports[`UpgradeFailure component passes expected text for not new pipeline 1`] onClick={[MockFunction]} type="button" > - - - Go back - + + Go back + + -
    + diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx index c144525699d81..93bb62fa1fc58 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx @@ -16,7 +16,6 @@ import { EuiFormRow, EuiToolTip, } from '@elastic/eui'; -import { EuiSelectableOption } from '@elastic/eui/src/components/selectable/selectable_option'; export interface Entity { fieldName: string; @@ -113,7 +112,7 @@ export class EntityControl extends Component { + renderOption = (option: EuiComboBoxOptionOption) => { const { label } = option; return label === EMPTY_FIELD_VALUE_LABEL ? {label} : label; }; diff --git a/x-pack/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap b/x-pack/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap index 973aca6b92669..cfe7fd1ca54ac 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap @@ -47,7 +47,7 @@ exports[`NoData should show a default message if reason is unknown 1`] = ` type="button" > - + + + Turn on monitoring + + + + + + diff --git a/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_interval/__tests__/__snapshots__/collection_interval.test.js.snap b/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_interval/__tests__/__snapshots__/collection_interval.test.js.snap index 49cbe092e0e20..782be3a073016 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_interval/__tests__/__snapshots__/collection_interval.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_interval/__tests__/__snapshots__/collection_interval.test.js.snap @@ -430,37 +430,59 @@ exports[`ExplainCollectionInterval collection interval setting updates should sh onClick={[Function]} type="button" > - + + + + + + Turn on monitoring + + + + + + @@ -728,28 +750,48 @@ exports[`ExplainCollectionInterval should explain about xpack.monitoring.collect onClick={[Function]} type="button" > - + + + Turn on monitoring + + + + + + diff --git a/x-pack/plugins/monitoring/public/components/no_data/reasons/__tests__/__snapshots__/reason_found.test.js.snap b/x-pack/plugins/monitoring/public/components/no_data/reasons/__tests__/__snapshots__/reason_found.test.js.snap index 898be82b139d1..e7b88e23c5f68 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/reasons/__tests__/__snapshots__/reason_found.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/no_data/reasons/__tests__/__snapshots__/reason_found.test.js.snap @@ -61,7 +61,7 @@ Array [ type="button" >  Yellow

    -

    - Status [object Object] -

     Green

    -

    - Status [object Object] -

    - + +
    + + + + Save + + + + + +
    @@ -1326,21 +1348,31 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u onClick={[Function]} type="button" > - - - Show request - + + Show request + + - +
    @@ -1743,11 +1775,10 @@ Array [ type="button" > - -
    +
    + + - + > + + + + + + + + + +
    @@ -449,7 +717,9 @@ Array [
    @@ -844,27 +897,44 @@ exports[`EmptyState component doesn't render child components when count is fals color="primary" href="/app/uptime#/settings" > - - - - - Update index pattern settings - - - - + + + Update index pattern settings + + +
    + + + @@ -1256,27 +1326,45 @@ exports[`EmptyState component notifies when index does not exist 1`] = ` fill={true} href="/app/home#/tutorial/uptimeMonitors" > - - - - - View setup instructions - - - - + + + View setup instructions + + +
    + + + @@ -1288,27 +1376,44 @@ exports[`EmptyState component notifies when index does not exist 1`] = ` color="primary" href="/app/uptime#/settings" > - - - - - Update index pattern settings - - - - + + + Update index pattern settings + + +
    + + + diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/__tests__/__snapshots__/filter_popover.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/filter_group/__tests__/__snapshots__/filter_popover.test.tsx.snap index 2677fd828c957..63ba8bcf9d26a 100644 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/__tests__/__snapshots__/filter_popover.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/__tests__/__snapshots__/filter_popover.test.tsx.snap @@ -94,15 +94,14 @@ exports[`FilterPopover component returns selected items on popover close 1`] = ` class="euiPopover__anchor" >