From 093d6499b073b7fd2d3a99ae0107abe2fb75cf4c Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 31 Mar 2022 13:17:51 -0600 Subject: [PATCH 01/65] [maps] clarify how layer filters are applied to term joins (#128502) * [maps] clarify how layer filters are applied to term joins * typo * clean up * change to present tense * fix typo * Update docs/maps/search.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> --- docs/maps/search.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index a170bcc414d3b..2330bf52abc29 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -133,7 +133,7 @@ If the map is a dashboard panel with drilldowns, you can apply a phrase filter t You can apply a search request to individual layers by setting `Filters` in the layer details panel. Click the *Add filter* button to add a filter to a layer. -NOTE: Layer filters are not applied to *term joins*. You can apply a search request to *term joins* by setting the *where* clause in the join definition. +NOTE: Layer filters are not applied to the right side of *term joins*. You can apply a search request to the right side of *term joins* by setting the *where* clause in the join definition. For example, suppose you have a layer with a term join where the left side is roads and the right side is traffic volume measurements. A layer filter of `roadType is "highway"` is applied to the roads index, but not to the traffic volume measurements index. [role="screenshot"] image::maps/images/layer_search.png[] From d9bf9713311c86cdc5b9da547cb4e346feb056c7 Mon Sep 17 00:00:00 2001 From: Corey Robertson Date: Thu, 31 Mar 2022 15:49:07 -0400 Subject: [PATCH 02/65] [Controls] Fix off screen popover (#128983) * Fix off screen popover --- .../control_types/time_slider/time_slider.component.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/controls/public/control_types/time_slider/time_slider.component.tsx b/src/plugins/controls/public/control_types/time_slider/time_slider.component.tsx index 9ce7cf825e863..5aab250136ac1 100644 --- a/src/plugins/controls/public/control_types/time_slider/time_slider.component.tsx +++ b/src/plugins/controls/public/control_types/time_slider/time_slider.component.tsx @@ -188,7 +188,7 @@ export const TimeSlider: FC = (props) => { panelPaddingSize="s" anchorPosition="downCenter" disableFocusTrap - repositionOnScroll + attachToAnchor={false} > {isValidRange(range) ? ( Date: Thu, 31 Mar 2022 15:19:16 -0500 Subject: [PATCH 03/65] [Security Solution] fix blocklist path validation (#129124) --- .../src/path_validations/index.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/kbn-securitysolution-utils/src/path_validations/index.ts b/packages/kbn-securitysolution-utils/src/path_validations/index.ts index 665b1a0838346..4e296cb90584c 100644 --- a/packages/kbn-securitysolution-utils/src/path_validations/index.ts +++ b/packages/kbn-securitysolution-utils/src/path_validations/index.ts @@ -34,7 +34,10 @@ export type TrustedAppConditionEntryField = | 'process.executable.caseless' | 'process.Ext.code_signature'; export type BlocklistConditionEntryField = 'file.hash.*' | 'file.path' | 'file.Ext.code_signature'; -export type AllConditionEntryFields = TrustedAppConditionEntryField | BlocklistConditionEntryField; +export type AllConditionEntryFields = + | TrustedAppConditionEntryField + | BlocklistConditionEntryField + | 'file.path.text'; export const enum OperatingSystem { LINUX = 'linux', @@ -105,11 +108,16 @@ export const isPathValid = ({ value, }: { os: OperatingSystem; - field: AllConditionEntryFields | 'file.path.text'; + field: AllConditionEntryFields; type: EntryTypes; value: string; }): boolean => { - if (field === ConditionEntryField.PATH || field === 'file.path.text') { + const pathFields: AllConditionEntryFields[] = [ + 'process.executable.caseless', + 'file.path', + 'file.path.text', + ]; + if (pathFields.includes(field)) { if (type === 'wildcard') { return os === OperatingSystem.WINDOWS ? isWindowsWildcardPathValid(value) From 7fc501267d2163611678f76664b152fed3c8b41a Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Thu, 31 Mar 2022 15:10:23 -0700 Subject: [PATCH 04/65] [Reporting] Fix event.duration to use nanoseconds (#129111) * [Reporting] Fix event.duration to use nanoseconds * comment polish --- .../plugins/reporting/server/lib/event_logger/logger.test.ts | 2 +- x-pack/plugins/reporting/server/lib/event_logger/logger.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/reporting/server/lib/event_logger/logger.test.ts b/x-pack/plugins/reporting/server/lib/event_logger/logger.test.ts index b389dd715f616..13c932c0efe1a 100644 --- a/x-pack/plugins/reporting/server/lib/event_logger/logger.test.ts +++ b/x-pack/plugins/reporting/server/lib/event_logger/logger.test.ts @@ -175,7 +175,7 @@ describe('Event Logger', () => { expect([result.event, result.kibana.reporting, result.message]).toMatchInlineSnapshot(` Array [ Object { - "duration": 5500, + "duration": 5500000000, "timezone": "UTC", }, Object { diff --git a/x-pack/plugins/reporting/server/lib/event_logger/logger.ts b/x-pack/plugins/reporting/server/lib/event_logger/logger.ts index 82a089192b2fb..6a3df43136c4f 100644 --- a/x-pack/plugins/reporting/server/lib/event_logger/logger.ts +++ b/x-pack/plugins/reporting/server/lib/event_logger/logger.ts @@ -149,11 +149,12 @@ export function reportingEventLoggerFactory(logger: Logger) { logClaimTask({ queueDurationMs }: ExecutionClaimMetrics): ClaimedTask { const message = `claimed report ${this.report._id}`; + const queueDurationNs = queueDurationMs * 1000000; const event = deepMerge( { message, kibana: { reporting: { actionType: ActionType.CLAIM_TASK } }, - event: { duration: queueDurationMs }, + event: { duration: queueDurationNs }, // this field is nanoseconds by ECS definition } as Partial, this.eventObj ); From 6a44f1f7253e6d602112a266ca3adf695d6119c2 Mon Sep 17 00:00:00 2001 From: Constance Date: Thu, 31 Mar 2022 16:47:09 -0700 Subject: [PATCH 05/65] Upgrade EUI to v53.0.1 (#128825) * Upgrade EUI to v53.0.1 * Update i18n EUI mappings with new super date picker tokens * Update deprecated prettyDuration usage * Fix misc tests/type updates caused by EuiSuperDatePicker i18n release - Fix mock typeof: this changed because the exported EuiSuperDatePicker is no longer a class, but a functional component wrapper around a class - CI test: update now-capitalized datepicker select copy - Jest tests: update snapshots, find()s (`Memo()` selector change is due to the exported EuiSuperDatePicker change) * [kibana-app-services feedback] use renderToString Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 2 +- .../__snapshots__/i18n_service.test.tsx.snap | 82 ++++- src/core/public/i18n/i18n_eui_mapping.test.ts | 26 +- src/core/public/i18n/i18n_eui_mapping.tsx | 322 +++++++++++++++++- src/dev/license_checker/config.ts | 2 +- .../query_bar_top_row.test.tsx | 2 +- .../query_string_input/query_bar_top_row.tsx | 14 +- .../date_picker_wrapper.test.tsx | 4 +- .../execution_log_table.test.tsx.snap | 49 --- .../translations/translations/fr-FR.json | 2 - .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - .../public/custom_time_range_badge.tsx | 14 +- .../apps/maps/saved_object_management.js | 2 +- yarn.lock | 8 +- 15 files changed, 443 insertions(+), 90 deletions(-) diff --git a/package.json b/package.json index d809bb2e025f7..76a84675c2eb2 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "@elastic/datemath": "link:bazel-bin/packages/elastic-datemath", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.2.0-canary.1", "@elastic/ems-client": "8.2.0", - "@elastic/eui": "52.2.0", + "@elastic/eui": "53.0.1", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.1", diff --git a/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap b/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap index aae1b026a4e11..69439d1927745 100644 --- a/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap +++ b/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap @@ -6,6 +6,7 @@ exports[`#start() returns \`Context\` component 1`] = ` i18n={ Object { "mapping": Object { + "euiAbsoluteTab.dateFormatError": [Function], "euiAccordion.isLoading": "Loading", "euiAutoRefresh.autoRefreshLabel": "Auto refresh", "euiAutoRefresh.buttonLabelOff": "Auto refresh is off", @@ -100,6 +101,14 @@ exports[`#start() returns \`Context\` component 1`] = ` "euiDataGridSchema.numberSortTextDesc": "High-Low", "euiDatePopoverButton.invalidTitle": [Function], "euiDatePopoverButton.outdatedTitle": [Function], + "euiDatePopoverContent.absoluteTabLabel": "Absolute", + "euiDatePopoverContent.endDateLabel": "End date", + "euiDatePopoverContent.nowTabButtonEnd": "Set end date and time to now", + "euiDatePopoverContent.nowTabButtonStart": "Set start date and time to now", + "euiDatePopoverContent.nowTabContent": "Setting the time to \\"now\\" means that on every refresh this time will be set to the time of the refresh.", + "euiDatePopoverContent.nowTabLabel": "Now", + "euiDatePopoverContent.relativeTabLabel": "Relative", + "euiDatePopoverContent.startDateLabel": "Start date", "euiDisplaySelector.buttonText": "Display options", "euiDisplaySelector.densityLabel": "Density", "euiDisplaySelector.labelAuto": "Auto fit", @@ -177,6 +186,39 @@ exports[`#start() returns \`Context\` component 1`] = ` "euiPinnableListGroup.pinExtraActionLabel": "Pin item", "euiPinnableListGroup.pinnedExtraActionLabel": "Unpin item", "euiPopover.screenReaderAnnouncement": "You are in a dialog. To close this dialog, hit escape.", + "euiPrettyDuration.durationRoundedToDay": [Function], + "euiPrettyDuration.durationRoundedToHour": [Function], + "euiPrettyDuration.durationRoundedToMinute": [Function], + "euiPrettyDuration.durationRoundedToMonth": [Function], + "euiPrettyDuration.durationRoundedToSecond": [Function], + "euiPrettyDuration.durationRoundedToWeek": [Function], + "euiPrettyDuration.durationRoundedToYear": [Function], + "euiPrettyDuration.fallbackDuration": [Function], + "euiPrettyDuration.invalid": "Invalid date", + "euiPrettyDuration.lastDurationDays": [Function], + "euiPrettyDuration.lastDurationHours": [Function], + "euiPrettyDuration.lastDurationMinutes": [Function], + "euiPrettyDuration.lastDurationMonths": [Function], + "euiPrettyDuration.lastDurationSeconds": [Function], + "euiPrettyDuration.lastDurationWeeks": [Function], + "euiPrettyDuration.lastDurationYears": [Function], + "euiPrettyDuration.nextDurationHours": [Function], + "euiPrettyDuration.nextDurationMinutes": [Function], + "euiPrettyDuration.nextDurationMonths": [Function], + "euiPrettyDuration.nextDurationSeconds": [Function], + "euiPrettyDuration.nextDurationWeeks": [Function], + "euiPrettyDuration.nextDurationYears": [Function], + "euiPrettyDuration.nexttDurationDays": [Function], + "euiPrettyDuration.now": "now", + "euiPrettyInterval.days": [Function], + "euiPrettyInterval.daysShorthand": [Function], + "euiPrettyInterval.hours": [Function], + "euiPrettyInterval.hoursShorthand": [Function], + "euiPrettyInterval.minutes": [Function], + "euiPrettyInterval.minutesShorthand": [Function], + "euiPrettyInterval.off": "Off", + "euiPrettyInterval.seconds": [Function], + "euiPrettyInterval.secondsShorthand": [Function], "euiProgress.valueText": [Function], "euiQuickSelect.applyButton": "Apply", "euiQuickSelect.fullDescription": [Function], @@ -195,8 +237,6 @@ exports[`#start() returns \`Context\` component 1`] = ` "euiRelativeTab.fullDescription": [Function], "euiRelativeTab.numberInputError": "Must be >= 0", "euiRelativeTab.numberInputLabel": "Time span amount", - "euiRelativeTab.relativeDate": [Function], - "euiRelativeTab.roundingLabel": [Function], "euiRelativeTab.unitInputLabel": "Relative time span", "euiResizableButton.horizontalResizerAriaLabel": "Press left or right to adjust panels size", "euiResizableButton.verticalResizerAriaLabel": "Press up or down to adjust panels size", @@ -256,6 +296,44 @@ exports[`#start() returns \`Context\` component 1`] = ` "euiTablePagination.rowsPerPageOption": [Function], "euiTablePagination.rowsPerPageOptionShowAllRows": "Show all rows", "euiTableSortMobile.sorting": "Sorting", + "euiTimeOptions.days": "Days", + "euiTimeOptions.daysAgo": "Days ago", + "euiTimeOptions.daysFromNow": "Days from now", + "euiTimeOptions.hours": "Hours", + "euiTimeOptions.hoursAgo": "Hours ago", + "euiTimeOptions.hoursFromNow": "Hours from now", + "euiTimeOptions.last": "Last", + "euiTimeOptions.minutes": "Minutes", + "euiTimeOptions.minutesAgo": "Minutes ago", + "euiTimeOptions.minutesFromNow": "Minutes from now", + "euiTimeOptions.monthToDate": "Month to date", + "euiTimeOptions.months": "Months", + "euiTimeOptions.monthsAgo": "Months ago", + "euiTimeOptions.monthsFromNow": "Months from now", + "euiTimeOptions.next": "Next", + "euiTimeOptions.roundToDay": "Round to the day", + "euiTimeOptions.roundToHour": "Round to the hour", + "euiTimeOptions.roundToMinute": "Round to the minute", + "euiTimeOptions.roundToMonth": "Round to the month", + "euiTimeOptions.roundToSecond": "Round to the second", + "euiTimeOptions.roundToWeek": "Round to the week", + "euiTimeOptions.roundToYear": "Round to the year", + "euiTimeOptions.seconds": "Seconds", + "euiTimeOptions.secondsAgo": "Seconds ago", + "euiTimeOptions.secondsFromNow": "Seconds from now", + "euiTimeOptions.thisMonth": "This month", + "euiTimeOptions.thisWeek": "This week", + "euiTimeOptions.thisYear": "This year", + "euiTimeOptions.today": "Today", + "euiTimeOptions.weekToDate": "Week to date", + "euiTimeOptions.weeks": "Weeks", + "euiTimeOptions.weeksAgo": "Weeks ago", + "euiTimeOptions.weeksFromNow": "Weeks from now", + "euiTimeOptions.yearToDate": "Year to date", + "euiTimeOptions.years": "Years", + "euiTimeOptions.yearsAgo": "Years ago", + "euiTimeOptions.yearsFromNow": "Years from now", + "euiTimeOptions.yesterday": "Yesterday", "euiToast.dismissToast": "Dismiss toast", "euiToast.newNotification": "A new notification appears", "euiToast.notification": "Notification", diff --git a/src/core/public/i18n/i18n_eui_mapping.test.ts b/src/core/public/i18n/i18n_eui_mapping.test.ts index 2b29fa4d0d66c..aa78345a86de1 100644 --- a/src/core/public/i18n/i18n_eui_mapping.test.ts +++ b/src/core/public/i18n/i18n_eui_mapping.test.ts @@ -76,7 +76,31 @@ describe('@elastic/eui i18n tokens', () => { test('defaultMessage is in sync with defString', () => { // Certain complex tokens (e.g. ones that have a function as a defaultMessage) // need custom i18n handling, and can't be checked for basic defString equality - const tokensToSkip = ['euiColumnSorting.buttonActive', 'euiSelectable.searchResults']; + const tokensToSkip = [ + 'euiColumnSorting.buttonActive', + 'euiSelectable.searchResults', + 'euiPrettyDuration.lastDurationSeconds', + 'euiPrettyDuration.nextDurationSeconds', + 'euiPrettyDuration.lastDurationMinutes', + 'euiPrettyDuration.nextDurationMinutes', + 'euiPrettyDuration.lastDurationHours', + 'euiPrettyDuration.nextDurationHours', + 'euiPrettyDuration.lastDurationDays', + 'euiPrettyDuration.nexttDurationDays', + 'euiPrettyDuration.lastDurationWeeks', + 'euiPrettyDuration.nextDurationWeeks', + 'euiPrettyDuration.lastDurationMonths', + 'euiPrettyDuration.nextDurationMonths', + 'euiPrettyDuration.lastDurationYears', + 'euiPrettyDuration.nextDurationYears', + 'euiPrettyInterval.seconds', + 'euiPrettyInterval.minutes', + 'euiPrettyInterval.hours', + 'euiPrettyInterval.days', + 'euiPrettyInterval.weeks', + 'euiPrettyInterval.months', + 'euiPrettyInterval.years', + ]; if (tokensToSkip.includes(token)) return; // Clean up typical errors from the `@elastic/eui` extraction token tool diff --git a/src/core/public/i18n/i18n_eui_mapping.tsx b/src/core/public/i18n/i18n_eui_mapping.tsx index 7e0fff16d56eb..b58780db3087d 100644 --- a/src/core/public/i18n/i18n_eui_mapping.tsx +++ b/src/core/public/i18n/i18n_eui_mapping.tsx @@ -843,6 +843,279 @@ export const getEuiContextMapping = (): EuiTokensObject => { defaultMessage: '{value}%', values: { value }, }), + 'euiPrettyDuration.lastDurationSeconds': ({ duration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.lastDurationSeconds', { + defaultMessage: 'Last {duration, plural, one {# second} other {# seconds}}', + values: { duration }, + }), + 'euiPrettyDuration.nextDurationSeconds': ({ duration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.nextDurationSeconds', { + defaultMessage: 'Next {duration, plural, one {# second} other {# seconds}}', + values: { duration }, + }), + 'euiPrettyDuration.lastDurationMinutes': ({ duration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.lastDurationMinutes', { + defaultMessage: 'Last {duration, plural, one {# minute} other {# minutes}}', + values: { duration }, + }), + 'euiPrettyDuration.nextDurationMinutes': ({ duration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.nextDurationMinutes', { + defaultMessage: 'Next {duration, plural, one {# minute} other {# minutes}}', + values: { duration }, + }), + 'euiPrettyDuration.lastDurationHours': ({ duration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.lastDurationHours', { + defaultMessage: 'Last {duration, plural, one {# hour} other {# hours}}', + values: { duration }, + }), + 'euiPrettyDuration.nextDurationHours': ({ duration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.nextDurationHours', { + defaultMessage: 'Next {duration, plural, one {# hour} other {# hours}}', + values: { duration }, + }), + 'euiPrettyDuration.lastDurationDays': ({ duration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.lastDurationDays', { + defaultMessage: 'Last {duration, plural, one {# day} other {# days}}', + values: { duration }, + }), + 'euiPrettyDuration.nexttDurationDays': ({ duration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.nexttDurationDays', { + defaultMessage: 'Next {duration, plural, one {# day} other {# days}}', + values: { duration }, + }), + 'euiPrettyDuration.lastDurationWeeks': ({ duration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.lastDurationWeeks', { + defaultMessage: 'Last {duration, plural, one {# week} other {# weeks}}', + values: { duration }, + }), + 'euiPrettyDuration.nextDurationWeeks': ({ duration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.nextDurationWeeks', { + defaultMessage: 'Next {duration, plural, one {# week} other {# weeks}}', + values: { duration }, + }), + 'euiPrettyDuration.lastDurationMonths': ({ duration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.lastDurationMonths', { + defaultMessage: 'Last {duration, plural, one {# month} other {# months}}', + values: { duration }, + }), + 'euiPrettyDuration.nextDurationMonths': ({ duration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.nextDurationMonths', { + defaultMessage: 'Next {duration, plural, one {# month} other {# months}}', + values: { duration }, + }), + 'euiPrettyDuration.lastDurationYears': ({ duration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.lastDurationYears', { + defaultMessage: 'Last {duration, plural, one {# year} other {# years}}', + values: { duration }, + }), + 'euiPrettyDuration.nextDurationYears': ({ duration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.nextDurationYears', { + defaultMessage: 'Next {duration, plural, one {# year} other {# years}}', + values: { duration }, + }), + 'euiPrettyDuration.durationRoundedToSecond': ({ prettyDuration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.durationRoundedToSecond', { + defaultMessage: '{prettyDuration} rounded to the second', + values: { prettyDuration }, + }), + 'euiPrettyDuration.durationRoundedToMinute': ({ prettyDuration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.durationRoundedToMinute', { + defaultMessage: '{prettyDuration} rounded to the minute', + values: { prettyDuration }, + }), + 'euiPrettyDuration.durationRoundedToHour': ({ prettyDuration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.durationRoundedToHour', { + defaultMessage: '{prettyDuration} rounded to the hour', + values: { prettyDuration }, + }), + 'euiPrettyDuration.durationRoundedToDay': ({ prettyDuration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.durationRoundedToDay', { + defaultMessage: '{prettyDuration} rounded to the day', + values: { prettyDuration }, + }), + 'euiPrettyDuration.durationRoundedToWeek': ({ prettyDuration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.durationRoundedToWeek', { + defaultMessage: '{prettyDuration} rounded to the week', + values: { prettyDuration }, + }), + 'euiPrettyDuration.durationRoundedToMonth': ({ prettyDuration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.durationRoundedToMonth', { + defaultMessage: '{prettyDuration} rounded to the month', + values: { prettyDuration }, + }), + 'euiPrettyDuration.durationRoundedToYear': ({ prettyDuration }: EuiValues) => + i18n.translate('core.euiPrettyDuration.durationRoundedToYear', { + defaultMessage: '{prettyDuration} rounded to the year', + values: { prettyDuration }, + }), + 'euiPrettyDuration.now': i18n.translate('core.euiPrettyDuration.now', { + defaultMessage: 'now', + }), + 'euiPrettyDuration.invalid': i18n.translate('core.euiPrettyDuration.invalid', { + defaultMessage: 'Invalid date', + }), + 'euiPrettyDuration.fallbackDuration': ({ displayFrom, displayTo }: EuiValues) => + i18n.translate('core.euiPrettyDuration.fallbackDuration', { + defaultMessage: '{displayFrom} to {displayTo}', + values: { displayFrom, displayTo }, + }), + 'euiPrettyInterval.seconds': ({ interval }: EuiValues) => + i18n.translate('core.euiPrettyInterval.seconds', { + defaultMessage: '{interval, plural, one {# second} other {# seconds}}', + values: { interval }, + }), + 'euiPrettyInterval.minutes': ({ interval }: EuiValues) => + i18n.translate('core.euiPrettyInterval.minutes', { + defaultMessage: '{interval, plural, one {# minute} other {# minutes}}', + values: { interval }, + }), + 'euiPrettyInterval.hours': ({ interval }: EuiValues) => + i18n.translate('core.euiPrettyInterval.hours', { + defaultMessage: '{interval, plural, one {# hour} other {# hours}}', + values: { interval }, + }), + 'euiPrettyInterval.days': ({ interval }: EuiValues) => + i18n.translate('core.euiPrettyInterval.days', { + defaultMessage: '{interval, plural, one {# day} other {# days}}', + values: { interval }, + }), + 'euiPrettyInterval.secondsShorthand': ({ interval }: EuiValues) => + i18n.translate('core.euiPrettyInterval.secondsShorthand', { + defaultMessage: '{interval} s', + values: { interval }, + }), + 'euiPrettyInterval.minutesShorthand': ({ interval }: EuiValues) => + i18n.translate('core.euiPrettyInterval.minutesShorthand', { + defaultMessage: '{interval} m', + values: { interval }, + }), + 'euiPrettyInterval.hoursShorthand': ({ interval }: EuiValues) => + i18n.translate('core.euiPrettyInterval.hoursShorthand', { + defaultMessage: '{interval} h', + values: { interval }, + }), + 'euiPrettyInterval.daysShorthand': ({ interval }: EuiValues) => + i18n.translate('core.euiPrettyInterval.daysShorthand', { + defaultMessage: '{interval} d', + values: { interval }, + }), + 'euiPrettyInterval.off': i18n.translate('core.euiPrettyInterval.off', { + defaultMessage: 'Off', + }), + 'euiTimeOptions.last': i18n.translate('core.euiTimeOptions.last', { + defaultMessage: 'Last', + }), + 'euiTimeOptions.next': i18n.translate('core.euiTimeOptions.next', { + defaultMessage: 'Next', + }), + 'euiTimeOptions.seconds': i18n.translate('core.euiTimeOptions.seconds', { + defaultMessage: 'Seconds', + }), + 'euiTimeOptions.minutes': i18n.translate('core.euiTimeOptions.minutes', { + defaultMessage: 'Minutes', + }), + 'euiTimeOptions.hours': i18n.translate('core.euiTimeOptions.hours', { + defaultMessage: 'Hours', + }), + 'euiTimeOptions.days': i18n.translate('core.euiTimeOptions.days', { + defaultMessage: 'Days', + }), + 'euiTimeOptions.weeks': i18n.translate('core.euiTimeOptions.weeks', { + defaultMessage: 'Weeks', + }), + 'euiTimeOptions.months': i18n.translate('core.euiTimeOptions.months', { + defaultMessage: 'Months', + }), + 'euiTimeOptions.years': i18n.translate('core.euiTimeOptions.years', { + defaultMessage: 'Years', + }), + 'euiTimeOptions.secondsAgo': i18n.translate('core.euiTimeOptions.secondsAgo', { + defaultMessage: 'Seconds ago', + }), + 'euiTimeOptions.minutesAgo': i18n.translate('core.euiTimeOptions.minutesAgo', { + defaultMessage: 'Minutes ago', + }), + 'euiTimeOptions.hoursAgo': i18n.translate('core.euiTimeOptions.hoursAgo', { + defaultMessage: 'Hours ago', + }), + 'euiTimeOptions.daysAgo': i18n.translate('core.euiTimeOptions.daysAgo', { + defaultMessage: 'Days ago', + }), + 'euiTimeOptions.weeksAgo': i18n.translate('core.euiTimeOptions.weeksAgo', { + defaultMessage: 'Weeks ago', + }), + 'euiTimeOptions.monthsAgo': i18n.translate('core.euiTimeOptions.monthsAgo', { + defaultMessage: 'Months ago', + }), + 'euiTimeOptions.yearsAgo': i18n.translate('core.euiTimeOptions.yearsAgo', { + defaultMessage: 'Years ago', + }), + 'euiTimeOptions.secondsFromNow': i18n.translate('core.euiTimeOptions.secondsFromNow', { + defaultMessage: 'Seconds from now', + }), + 'euiTimeOptions.minutesFromNow': i18n.translate('core.euiTimeOptions.minutesFromNow', { + defaultMessage: 'Minutes from now', + }), + 'euiTimeOptions.hoursFromNow': i18n.translate('core.euiTimeOptions.hoursFromNow', { + defaultMessage: 'Hours from now', + }), + 'euiTimeOptions.daysFromNow': i18n.translate('core.euiTimeOptions.daysFromNow', { + defaultMessage: 'Days from now', + }), + 'euiTimeOptions.weeksFromNow': i18n.translate('core.euiTimeOptions.weeksFromNow', { + defaultMessage: 'Weeks from now', + }), + 'euiTimeOptions.monthsFromNow': i18n.translate('core.euiTimeOptions.monthsFromNow', { + defaultMessage: 'Months from now', + }), + 'euiTimeOptions.yearsFromNow': i18n.translate('core.euiTimeOptions.yearsFromNow', { + defaultMessage: 'Years from now', + }), + 'euiTimeOptions.roundToSecond': i18n.translate('core.euiTimeOptions.roundToSecond', { + defaultMessage: 'Round to the second', + }), + 'euiTimeOptions.roundToMinute': i18n.translate('core.euiTimeOptions.roundToMinute', { + defaultMessage: 'Round to the minute', + }), + 'euiTimeOptions.roundToHour': i18n.translate('core.euiTimeOptions.roundToHour', { + defaultMessage: 'Round to the hour', + }), + 'euiTimeOptions.roundToDay': i18n.translate('core.euiTimeOptions.roundToDay', { + defaultMessage: 'Round to the day', + }), + 'euiTimeOptions.roundToWeek': i18n.translate('core.euiTimeOptions.roundToWeek', { + defaultMessage: 'Round to the week', + }), + 'euiTimeOptions.roundToMonth': i18n.translate('core.euiTimeOptions.roundToMonth', { + defaultMessage: 'Round to the month', + }), + 'euiTimeOptions.roundToYear': i18n.translate('core.euiTimeOptions.roundToYear', { + defaultMessage: 'Round to the year', + }), + 'euiTimeOptions.today': i18n.translate('core.euiTimeOptions.today', { + defaultMessage: 'Today', + }), + 'euiTimeOptions.thisWeek': i18n.translate('core.euiTimeOptions.thisWeek', { + defaultMessage: 'This week', + }), + 'euiTimeOptions.thisMonth': i18n.translate('core.euiTimeOptions.thisMonth', { + defaultMessage: 'This month', + }), + 'euiTimeOptions.thisYear': i18n.translate('core.euiTimeOptions.thisYear', { + defaultMessage: 'This year', + }), + 'euiTimeOptions.yesterday': i18n.translate('core.euiTimeOptions.yesterday', { + defaultMessage: 'Yesterday', + }), + 'euiTimeOptions.weekToDate': i18n.translate('core.euiTimeOptions.weekToDate', { + defaultMessage: 'Week to date', + }), + 'euiTimeOptions.monthToDate': i18n.translate('core.euiTimeOptions.monthToDate', { + defaultMessage: 'Month to date', + }), + 'euiTimeOptions.yearToDate': i18n.translate('core.euiTimeOptions.yearToDate', { + defaultMessage: 'Year to date', + }), 'euiQuickSelect.applyButton': i18n.translate('core.euiQuickSelect.applyButton', { defaultMessage: 'Apply', }), @@ -888,21 +1161,50 @@ export const getEuiContextMapping = (): EuiTokensObject => { defaultMessage: 'Refresh is on, interval set to {optionValue} {optionText}.', values: { optionValue, optionText }, }), + 'euiDatePopoverContent.startDateLabel': i18n.translate( + 'core.euiDatePopoverContent.startDateLabel', + { defaultMessage: 'Start date' } + ), + 'euiDatePopoverContent.endDateLabel': i18n.translate( + 'core.euiDatePopoverContent.endDateLabel', + { defaultMessage: 'End date' } + ), + 'euiDatePopoverContent.absoluteTabLabel': i18n.translate( + 'core.euiDatePopoverContent.absoluteTabLabel', + { defaultMessage: 'Absolute' } + ), + 'euiDatePopoverContent.relativeTabLabel': i18n.translate( + 'core.euiDatePopoverContent.relativeTabLabel', + { defaultMessage: 'Relative' } + ), + 'euiDatePopoverContent.nowTabLabel': i18n.translate('core.euiDatePopoverContent.nowTabLabel', { + defaultMessage: 'Now', + }), + 'euiDatePopoverContent.nowTabContent': i18n.translate( + 'core.euiDatePopoverContent.nowTabContent', + { + defaultMessage: + 'Setting the time to "now" means that on every refresh this time will be set to the time of the refresh.', + } + ), + 'euiDatePopoverContent.nowTabButtonStart': i18n.translate( + 'core.euiDatePopoverContent.nowTabButtonStart', + { defaultMessage: 'Set start date and time to now' } + ), + 'euiDatePopoverContent.nowTabButtonEnd': i18n.translate( + 'core.euiDatePopoverContent.nowTabButtonEnd', + { defaultMessage: 'Set end date and time to now' } + ), + 'euiAbsoluteTab.dateFormatError': ({ dateFormat }: EuiValues) => + i18n.translate('core.euiAbsoluteTab.dateFormatError', { + defaultMessage: 'Expected format: {dateFormat}', + values: { dateFormat }, + }), 'euiRelativeTab.fullDescription': ({ unit }: EuiValues) => i18n.translate('core.euiRelativeTab.fullDescription', { defaultMessage: 'The unit is changeable. Currently set to {unit}.', values: { unit }, }), - 'euiRelativeTab.relativeDate': ({ position }: EuiValues) => - i18n.translate('core.euiRelativeTab.relativeDate', { - defaultMessage: '{position} date', - values: { position }, - }), - 'euiRelativeTab.roundingLabel': ({ unit }: EuiValues) => - i18n.translate('core.euiRelativeTab.roundingLabel', { - defaultMessage: 'Round to the {unit}', - values: { unit }, - }), 'euiRelativeTab.unitInputLabel': i18n.translate('core.euiRelativeTab.unitInputLabel', { defaultMessage: 'Relative time span', }), diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index 354121fc88936..cb1c61b0a47e0 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -77,6 +77,6 @@ export const LICENSE_OVERRIDES = { 'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint '@elastic/ems-client@8.2.0': ['Elastic License 2.0'], - '@elastic/eui@52.2.0': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@53.0.1': ['SSPL-1.0 OR Elastic License 2.0'], 'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry }; diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx index 56dd901055fbc..9f99049d3e788 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx @@ -105,7 +105,7 @@ function wrapQueryBarTopRowInContext(testProps: any) { describe('QueryBarTopRowTopRow', () => { const QUERY_INPUT_SELECTOR = 'QueryStringInputUI'; - const TIMEPICKER_SELECTOR = 'EuiSuperDatePicker'; + const TIMEPICKER_SELECTOR = 'Memo(EuiSuperDatePicker)'; const TIMEPICKER_DURATION = '[data-shared-timefilter-duration]'; beforeEach(() => { diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index 75d22137937e9..424347834c3db 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -19,7 +19,7 @@ import { EuiFlexItem, EuiSuperDatePicker, EuiFieldText, - prettyDuration, + usePrettyDuration, EuiIconProps, EuiSuperUpdateButton, OnRefreshProps, @@ -92,12 +92,12 @@ const SharingMetaFields = React.memo(function SharingMetaFields({ return valueAsMoment.toISOString(); } - const dateRangePretty = prettyDuration( - toAbsoluteString(from), - toAbsoluteString(to), - [], - dateFormat - ); + const dateRangePretty = usePrettyDuration({ + timeFrom: toAbsoluteString(from), + timeTo: toAbsoluteString(to), + quickRanges: [], + dateFormat, + }); return (
({ }, })); -const MockedEuiSuperDatePicker = EuiSuperDatePicker as jest.MockedClass; +const MockedEuiSuperDatePicker = EuiSuperDatePicker as jest.MockedFunction< + typeof EuiSuperDatePicker +>; describe('Navigation Menu: ', () => { beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/__snapshots__/execution_log_table.test.tsx.snap b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/__snapshots__/execution_log_table.test.tsx.snap index fdec953b833e6..7a24272b9e1e6 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/__snapshots__/execution_log_table.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/__snapshots__/execution_log_table.test.tsx.snap @@ -22,63 +22,14 @@ exports[`ExecutionLogTable snapshots renders correctly against snapshot 1`] = ` } > diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 77c09a803d2e8..2701e63045eb0 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -1161,8 +1161,6 @@ "core.euiRelativeTab.fullDescription": "L'unité peut être modifiée. Elle est actuellement définie sur {unit}.", "core.euiRelativeTab.numberInputError": "Doit être >= 0.", "core.euiRelativeTab.numberInputLabel": "Nombre d'intervalles", - "core.euiRelativeTab.relativeDate": "Date de {position}", - "core.euiRelativeTab.roundingLabel": "Arrondir à {unit}", "core.euiRelativeTab.unitInputLabel": "Intervalle relatif", "core.euiResizableButton.horizontalResizerAriaLabel": "Utilisez les flèches gauche et droite pour ajuster la taille des panneaux.", "core.euiResizableButton.verticalResizerAriaLabel": "Utilisez les flèches vers le haut et vers le bas pour ajuster la taille des panneaux.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 58568227ea928..1f0117fd7096c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1445,8 +1445,6 @@ "core.euiRelativeTab.fullDescription": "単位は変更可能です。現在 {unit} に設定されています。", "core.euiRelativeTab.numberInputError": "0以上でなければなりません", "core.euiRelativeTab.numberInputLabel": "時間スパンの量", - "core.euiRelativeTab.relativeDate": "{position} 日付", - "core.euiRelativeTab.roundingLabel": "{unit} に四捨五入する", "core.euiRelativeTab.unitInputLabel": "相対的時間スパン", "core.euiResizableButton.horizontalResizerAriaLabel": "左右矢印キーを押してパネルサイズを調整します", "core.euiResizableButton.verticalResizerAriaLabel": "上下矢印キーを押してパネルサイズを調整します", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c03f091153fbb..2e661138cbcac 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1450,8 +1450,6 @@ "core.euiRelativeTab.fullDescription": "单位可更改。当前设置为 {unit}。", "core.euiRelativeTab.numberInputError": "必须 >= 0", "core.euiRelativeTab.numberInputLabel": "时间跨度量", - "core.euiRelativeTab.relativeDate": "{position} 日期", - "core.euiRelativeTab.roundingLabel": "舍入为 {unit}", "core.euiRelativeTab.unitInputLabel": "相对时间跨度", "core.euiResizableButton.horizontalResizerAriaLabel": "按左或右箭头键调整面板大小", "core.euiResizableButton.verticalResizerAriaLabel": "按上或下箭头键调整面板大小", diff --git a/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_badge.tsx b/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_badge.tsx index 28d936475f6b1..0e6217d360fa5 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_badge.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_badge.tsx @@ -6,7 +6,8 @@ */ import React from 'react'; -import { prettyDuration, commonDurationRanges } from '@elastic/eui'; +import { renderToString } from 'react-dom/server'; +import { PrettyDuration } from '@elastic/eui'; import { IEmbeddable, Embeddable, EmbeddableInput } from 'src/plugins/embeddable/public'; import { Action, IncompatibleActionError } from '../../../../src/plugins/ui_actions/public'; import { TimeRange } from '../../../../src/plugins/data/public'; @@ -52,11 +53,12 @@ export class CustomTimeRangeBadge implements Action { } public getDisplayName({ embeddable }: TimeBadgeActionContext) { - return prettyDuration( - embeddable.getInput().timeRange.from, - embeddable.getInput().timeRange.to, - commonDurationRanges, - this.dateFormat + return renderToString( + ); } diff --git a/x-pack/test/functional/apps/maps/saved_object_management.js b/x-pack/test/functional/apps/maps/saved_object_management.js index 01e3e64c7172d..0ae023e645a56 100644 --- a/x-pack/test/functional/apps/maps/saved_object_management.js +++ b/x-pack/test/functional/apps/maps/saved_object_management.js @@ -42,7 +42,7 @@ export default function ({ getPageObjects, getService }) { it('should update global Kibana refresh config to value stored with map', async () => { const kibanaRefreshConfig = await PageObjects.timePicker.getRefreshConfig(); expect(kibanaRefreshConfig.interval).to.equal('1'); - expect(kibanaRefreshConfig.units).to.equal('seconds'); + expect(kibanaRefreshConfig.units).to.equal('Seconds'); expect(kibanaRefreshConfig.isPaused).to.equal(true); }); diff --git a/yarn.lock b/yarn.lock index 9d28bc7349943..310c133f9f204 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1516,10 +1516,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@52.2.0": - version "52.2.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-52.2.0.tgz#761101a29b96a4b5270ef93541dab7bb27f5ca50" - integrity sha512-XboYerntCOTHWHYMWJGzJtu5JYO6pk5IWh0ZHJEQ4SEjmLbTV2bFrVBTO/8uaU7GhV9/RNIo7BU5wHRyYP7z1g== +"@elastic/eui@53.0.1": + version "53.0.1" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-53.0.1.tgz#82359460ae4edab3a538846b6915518cb1214848" + integrity sha512-fPOuDyc7adjHBp+UHOtIJTHdZVKi8MGzyuYxTRN8ZYLHbzcNTaCsogdk+0eb6u4ISRqhk3tudG8Q0znksVHt1w== dependencies: "@types/chroma-js" "^2.0.0" "@types/lodash" "^4.14.160" From e0928efac07bc25dc7c28be8839ff388136a10d3 Mon Sep 17 00:00:00 2001 From: Spencer Date: Thu, 31 Mar 2022 22:51:18 -0600 Subject: [PATCH 06/65] [eslint] autofix fixable lint errors on ci (#129147) --- .buildkite/scripts/steps/lint.sh | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.buildkite/scripts/steps/lint.sh b/.buildkite/scripts/steps/lint.sh index dace6c6f60aef..e94e7be4c7db2 100755 --- a/.buildkite/scripts/steps/lint.sh +++ b/.buildkite/scripts/steps/lint.sh @@ -9,7 +9,25 @@ source .buildkite/scripts/common/util.sh echo '--- Lint: stylelint' checks-reporter-with-killswitch "Lint: stylelint" \ node scripts/stylelint +echo "stylelint ✅" + +# disable "Exit immediately" mode so that we can run eslint, capture it's exit code, and respond appropriately +# after possibly commiting fixed files to the repo +set +e; echo '--- Lint: eslint' checks-reporter-with-killswitch "Lint: eslint" \ - node scripts/eslint --no-cache + node scripts/eslint --no-cache --fix + +eslint_exit=$? + +# re-enable "Exit immediately" mode +set -e; + +check_for_changed_files 'node scripts/eslint --no-cache --fix' true + +if [[ "${eslint_exit}" != "0" ]]; then + exit 1 +fi + +echo "eslint ✅" From 308c3f1fa3f8c6ceeb6cdd73fd9eae4c1a1ed42c Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Fri, 1 Apr 2022 09:39:16 +0200 Subject: [PATCH 07/65] Consolidate ieee754 dependency (#129159) --- yarn.lock | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index 310c133f9f204..cb4bbea65b4a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16431,12 +16431,7 @@ idx@^2.5.6: resolved "https://registry.yarnpkg.com/idx/-/idx-2.5.6.tgz#1f824595070100ae9ad585c86db08dc74f83a59d" integrity sha512-WFXLF7JgPytbMgelpRY46nHz5tyDcedJ76pLV+RJWdb8h33bxFq4bdZau38DhNSzk5eVniBf1K3jwfK+Lb5nYA== -ieee754@^1.1.12, ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -ieee754@^1.1.13, ieee754@^1.2.1: +ieee754@^1.1.12, ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== From 40fa6076b163ebd006098dfbf99f5064a295e7af Mon Sep 17 00:00:00 2001 From: John Dorlus Date: Fri, 1 Apr 2022 04:28:11 -0400 Subject: [PATCH 08/65] Migrate Lens Smoke Test To Use CCS Remote (#127426) --- .../functional/page_objects/visualize_page.ts | 5 +- x-pack/scripts/functional_tests.js | 1 + x-pack/test/functional/apps/lens/index.ts | 149 +++--- .../test/functional/apps/lens/smokescreen.ts | 11 +- x-pack/test/functional/config.ccs.ts | 54 +++ .../kbn_archiver/lens/ccs/default.json | 154 +++++++ .../kbn_archiver/lens/ccs/lens_basic.json | 433 ++++++++++++++++++ .../services/remote_es/remote_es.ts | 23 + .../services/remote_es/remote_es_archiver.ts | 21 + 9 files changed, 791 insertions(+), 60 deletions(-) create mode 100644 x-pack/test/functional/config.ccs.ts create mode 100644 x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/default.json create mode 100644 x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/lens_basic.json create mode 100644 x-pack/test/functional/services/remote_es/remote_es.ts create mode 100644 x-pack/test/functional/services/remote_es/remote_es_archiver.ts diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 06d34094e614c..af4bf6fc40299 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -47,6 +47,9 @@ export class VisualizePageObject extends FtrService { LOGSTASH_NON_TIME_BASED: 'logstash*', }; + remoteEsPrefix = 'ftr-remote:'; + defaultIndexString = 'logstash-*'; + public async initTests(isNewLibrary = false) { await this.kibanaServer.savedObjects.clean({ types: ['visualization'] }); await this.kibanaServer.importExport.load( @@ -54,7 +57,7 @@ export class VisualizePageObject extends FtrService { ); await this.kibanaServer.uiSettings.replace({ - defaultIndex: 'logstash-*', + defaultIndex: this.defaultIndexString, [FORMATS_UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN]: '0,0.[000]b', 'visualization:visualize:legacyPieChartsLibrary': !isNewLibrary, 'visualization:visualize:legacyHeatmapChartsLibrary': !isNewLibrary, diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 977d0c3f1768c..ee99785aa8fad 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -7,6 +7,7 @@ require('../../src/setup_node_env'); require('@kbn/test').runTestsCli([ + require.resolve('../test/functional/config.ccs.ts'), require.resolve('../test/functional/config.js'), require.resolve('../test/functional_basic/config.ts'), require.resolve('../test/security_solution_endpoint/config.ts'), diff --git a/x-pack/test/functional/apps/lens/index.ts b/x-pack/test/functional/apps/lens/index.ts index 76a193c8a8b25..372d17b473e4d 100644 --- a/x-pack/test/functional/apps/lens/index.ts +++ b/x-pack/test/functional/apps/lens/index.ts @@ -5,84 +5,119 @@ * 2.0. */ +import { EsArchiver } from '@kbn/es-archiver'; import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService, loadTestFile, getPageObjects }: FtrProviderContext) { +export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext) => { const browser = getService('browser'); const log = getService('log'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects(['timePicker']); + const config = getService('config'); + let remoteEsArchiver; describe('lens app', () => { + const esArchive = 'x-pack/test/functional/es_archives/logstash_functional'; + const localIndexPatternString = 'logstash-*'; + const remoteIndexPatternString = 'ftr-remote:logstash-*'; + const localFixtures = { + lensBasic: 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json', + lensDefault: 'x-pack/test/functional/fixtures/kbn_archiver/lens/default', + }; + + const remoteFixtures = { + lensBasic: 'x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/lens_basic.json', + lensDefault: 'x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/default', + }; + let esNode: EsArchiver; + let fixtureDirs: { + lensBasic: string; + lensDefault: string; + }; + let indexPatternString: string; before(async () => { - log.debug('Starting lens before method'); + await log.debug('Starting lens before method'); await browser.setWindowSize(1280, 1200); - await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); + try { + config.get('esTestCluster.ccs'); + remoteEsArchiver = getService('remoteEsArchiver' as 'esArchiver'); + esNode = remoteEsArchiver; + fixtureDirs = remoteFixtures; + indexPatternString = remoteIndexPatternString; + } catch (error) { + esNode = esArchiver; + fixtureDirs = localFixtures; + indexPatternString = localIndexPatternString; + } + + await esNode.load(esArchive); // changing the timepicker default here saves us from having to set it in Discover (~8s) await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); - await kibanaServer.uiSettings.update({ defaultIndex: 'logstash-*', 'dateFormat:tz': 'UTC' }); - await kibanaServer.importExport.load( - 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' - ); - await kibanaServer.importExport.load( - 'x-pack/test/functional/fixtures/kbn_archiver/lens/default' - ); + await kibanaServer.uiSettings.update({ + defaultIndex: indexPatternString, + 'dateFormat:tz': 'UTC', + }); + await kibanaServer.importExport.load(fixtureDirs.lensBasic); + await kibanaServer.importExport.load(fixtureDirs.lensDefault); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await esArchiver.unload(esArchive); await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); - await kibanaServer.importExport.unload( - 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' - ); - await kibanaServer.importExport.unload( - 'x-pack/test/functional/fixtures/kbn_archiver/lens/default' - ); + await kibanaServer.importExport.unload(fixtureDirs.lensBasic); + await kibanaServer.importExport.unload(fixtureDirs.lensDefault); }); - describe('', function () { - this.tags(['ciGroup3', 'skipFirefox']); - loadTestFile(require.resolve('./smokescreen')); - loadTestFile(require.resolve('./persistent_context')); - }); + if (config.get('esTestCluster.ccs')) { + describe('', function () { + this.tags(['ciGroup3', 'skipFirefox']); + loadTestFile(require.resolve('./smokescreen')); + }); + } else { + describe('', function () { + this.tags(['ciGroup3', 'skipFirefox']); + loadTestFile(require.resolve('./smokescreen')); + loadTestFile(require.resolve('./persistent_context')); + }); - describe('', function () { - this.tags(['ciGroup16', 'skipFirefox']); + describe('', function () { + this.tags(['ciGroup16', 'skipFirefox']); - loadTestFile(require.resolve('./add_to_dashboard')); - loadTestFile(require.resolve('./table_dashboard')); - loadTestFile(require.resolve('./table')); - loadTestFile(require.resolve('./runtime_fields')); - loadTestFile(require.resolve('./dashboard')); - loadTestFile(require.resolve('./multi_terms')); - loadTestFile(require.resolve('./epoch_millis')); - loadTestFile(require.resolve('./show_underlying_data')); - loadTestFile(require.resolve('./show_underlying_data_dashboard')); - }); + loadTestFile(require.resolve('./add_to_dashboard')); + loadTestFile(require.resolve('./table_dashboard')); + loadTestFile(require.resolve('./table')); + loadTestFile(require.resolve('./runtime_fields')); + loadTestFile(require.resolve('./dashboard')); + loadTestFile(require.resolve('./multi_terms')); + loadTestFile(require.resolve('./epoch_millis')); + loadTestFile(require.resolve('./show_underlying_data')); + loadTestFile(require.resolve('./show_underlying_data_dashboard')); + }); - describe('', function () { - this.tags(['ciGroup4', 'skipFirefox']); + describe('', function () { + this.tags(['ciGroup4', 'skipFirefox']); - loadTestFile(require.resolve('./colors')); - loadTestFile(require.resolve('./chart_data')); - loadTestFile(require.resolve('./time_shift')); - loadTestFile(require.resolve('./drag_and_drop')); - loadTestFile(require.resolve('./disable_auto_apply')); - loadTestFile(require.resolve('./geo_field')); - loadTestFile(require.resolve('./formula')); - loadTestFile(require.resolve('./heatmap')); - loadTestFile(require.resolve('./gauge')); - loadTestFile(require.resolve('./metrics')); - loadTestFile(require.resolve('./reference_lines')); - loadTestFile(require.resolve('./annotations')); - loadTestFile(require.resolve('./inspector')); - loadTestFile(require.resolve('./error_handling')); - loadTestFile(require.resolve('./lens_tagging')); - loadTestFile(require.resolve('./lens_reporting')); - loadTestFile(require.resolve('./tsvb_open_in_lens')); - // has to be last one in the suite because it overrides saved objects - loadTestFile(require.resolve('./rollup')); - }); + loadTestFile(require.resolve('./colors')); + loadTestFile(require.resolve('./chart_data')); + loadTestFile(require.resolve('./time_shift')); + loadTestFile(require.resolve('./drag_and_drop')); + loadTestFile(require.resolve('./disable_auto_apply')); + loadTestFile(require.resolve('./geo_field')); + loadTestFile(require.resolve('./formula')); + loadTestFile(require.resolve('./heatmap')); + loadTestFile(require.resolve('./gauge')); + loadTestFile(require.resolve('./metrics')); + loadTestFile(require.resolve('./reference_lines')); + loadTestFile(require.resolve('./annotations')); + loadTestFile(require.resolve('./inspector')); + loadTestFile(require.resolve('./error_handling')); + loadTestFile(require.resolve('./lens_tagging')); + loadTestFile(require.resolve('./lens_reporting')); + loadTestFile(require.resolve('./tsvb_open_in_lens')); + // has to be last one in the suite because it overrides saved objects + loadTestFile(require.resolve('./rollup')); + }); + } }); -} +}; diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts index 13525ed0ec9c2..721ff1e210326 100644 --- a/x-pack/test/functional/apps/lens/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/smokescreen.ts @@ -17,6 +17,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const elasticChart = getService('elasticChart'); const filterBar = getService('filterBar'); const retry = getService('retry'); + const config = getService('config'); describe('lens smokescreen tests', () => { it('should allow creation of lens xy chart', async () => { @@ -686,8 +687,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should allow to change index pattern', async () => { - await PageObjects.lens.switchFirstLayerIndexPattern('log*'); - expect(await PageObjects.lens.getFirstLayerIndexPattern()).to.equal('log*'); + let indexPatternString; + if (config.get('esTestCluster.ccs')) { + indexPatternString = 'ftr-remote:log*'; + } else { + indexPatternString = 'log*'; + } + await PageObjects.lens.switchFirstLayerIndexPattern(indexPatternString); + expect(await PageObjects.lens.getFirstLayerIndexPattern()).to.equal(indexPatternString); }); it('should show a download button only when the configuration is valid', async () => { diff --git a/x-pack/test/functional/config.ccs.ts b/x-pack/test/functional/config.ccs.ts new file mode 100644 index 0000000000000..e6e0da5293190 --- /dev/null +++ b/x-pack/test/functional/config.ccs.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; +import { RemoteEsArchiverProvider } from './services/remote_es/remote_es_archiver'; +import { RemoteEsProvider } from './services/remote_es/remote_es'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('./config')); + + return { + ...functionalConfig.getAll(), + + testFiles: [require.resolve('./apps/lens')], + + junit: { + reportName: 'X-Pack CCS Tests', + }, + + security: { + ...functionalConfig.get('security'), + remoteEsRoles: { + ccs_remote_search: { + indices: [ + { + names: ['*'], + privileges: ['read', 'view_index_metadata', 'read_cross_cluster'], + }, + ], + }, + }, + defaultRoles: [...(functionalConfig.get('security.defaultRoles') ?? []), 'ccs_remote_search'], + }, + + esTestCluster: { + ...functionalConfig.get('esTestCluster'), + ccs: { + remoteClusterUrl: + process.env.REMOTE_CLUSTER_URL ?? + 'http://elastic:changeme@localhost:' + + `${functionalConfig.get('servers.elasticsearch.port') + 1}`, + }, + }, + services: { + ...functionalConfig.get('services'), + remoteEs: RemoteEsProvider, + remoteEsArchiver: RemoteEsArchiverProvider, + }, + }; +} diff --git a/x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/default.json b/x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/default.json new file mode 100644 index 0000000000000..5fe080700b1ce --- /dev/null +++ b/x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/default.json @@ -0,0 +1,154 @@ +{ + "attributes": { + "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "timeFieldName": "@timestamp", + "title": "ftr-remote:logstash-*" + }, + "coreMigrationVersion": "8.0.0", + "id": "ftr-remote:logstash-*", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2018-12-21T00:43:07.096Z", + "version": "WzEzLDJd" +} + +{ + "attributes": { + "description": "", + "state": { + "datasourceStates": { + "indexpattern": { + "layers": { + "4ba1a1be-6e67-434b-b3a0-f30db8ea5395": { + "columnOrder": [ + "70d52318-354d-47d5-b33b-43d50eb34425", + "bafe3009-1776-4227-a0fe-b0d6ccbb4961", + "3dc0bd55-2087-4e60-aea2-f9910714f7db" + ], + "columns": { + "3dc0bd55-2087-4e60-aea2-f9910714f7db": { + "dataType": "number", + "isBucketed": false, + "label": "Median of bytes", + "operationType": "median", + "scale": "ratio", + "sourceField": "bytes" + }, + "70d52318-354d-47d5-b33b-43d50eb34425": { + "dataType": "string", + "isBucketed": true, + "label": "Top values of response.raw", + "operationType": "terms", + "params": { + "missingBucket": false, + "orderBy": { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "type": "column" + }, + "orderDirection": "desc", + "otherBucket": true, + "size": 3 + }, + "scale": "ordinal", + "sourceField": "response.raw" + }, + "bafe3009-1776-4227-a0fe-b0d6ccbb4961": { + "dataType": "string", + "isBucketed": true, + "label": "Top values of geo.src", + "operationType": "terms", + "params": { + "orderBy": { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "type": "column" + }, + "orderDirection": "desc", + "size": 7 + }, + "scale": "ordinal", + "sourceField": "geo.src" + } + }, + "incompleteColumns": {} + } + } + } + }, + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "indexRefName": "filter-index-pattern-0", + "key": "response", + "negate": true, + "params": { + "query": "200" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "response": "200" + } + } + } + ], + "query": { + "language": "kuery", + "query": "extension.raw : \"jpg\" or extension.raw : \"gif\" " + }, + "visualization": { + "columns": [ + { + "columnId": "bafe3009-1776-4227-a0fe-b0d6ccbb4961", + "isTransposed": false + }, + { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "isTransposed": false + }, + { + "columnId": "70d52318-354d-47d5-b33b-43d50eb34425", + "isTransposed": true + } + ], + "layerId": "4ba1a1be-6e67-434b-b3a0-f30db8ea5395", + "layerType": "data" + } + }, + "title": "lnsTableVis", + "visualizationType": "lnsDatatable" +}, + "coreMigrationVersion": "7.16.0", + "id": "a800e2b0-268c-11ec-b2b6-f1bd289a74d4", + "migrationVersion": { + "lens": "7.15.0" + }, + "references": [ + { + "id": "ftr-remote:logstash-*", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern" + }, + { + "id": "ftr-remote:logstash-*", + "name": "indexpattern-datasource-layer-4ba1a1be-6e67-434b-b3a0-f30db8ea5395", + "type": "index-pattern" + }, + { + "id": "ftr-remote:logstash-*", + "name": "filter-index-pattern-0", + "type": "index-pattern" + } + ], + "type": "lens", + "updated_at": "2020-11-23T19:57:52.834Z", + "version": "WzUyLDJd" +} diff --git a/x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/lens_basic.json b/x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/lens_basic.json new file mode 100644 index 0000000000000..76ffbd3171154 --- /dev/null +++ b/x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/lens_basic.json @@ -0,0 +1,433 @@ +{ + "attributes": { + "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "timeFieldName": "@timestamp", + "title": "ftr-remote:logstash-*" + }, + "coreMigrationVersion": "8.0.0", + "id": "ftr-remote:logstash-*", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2018-12-21T00:43:07.096Z", + "version": "WzEzLDJd" +} + +{ + "attributes": { + "state": { + "datasourceStates": { + "indexpattern": { + "layers": { + "c61a8afb-a185-4fae-a064-fb3846f6c451": { + "columnOrder": [ + "2cd09808-3915-49f4-b3b0-82767eba23f7" + ], + "columns": { + "2cd09808-3915-49f4-b3b0-82767eba23f7": { + "dataType": "number", + "isBucketed": false, + "label": "Maximum of bytes", + "operationType": "max", + "scale": "ratio", + "sourceField": "bytes" + } + } + } + } + } + }, + "filters": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "accessor": "2cd09808-3915-49f4-b3b0-82767eba23f7", + "isHorizontal": false, + "layerId": "c61a8afb-a185-4fae-a064-fb3846f6c451", + "layers": [ + { + "accessors": [ + "d3e62a7a-c259-4fff-a2fc-eebf20b7008a", + "26ef70a9-c837-444c-886e-6bd905ee7335" + ], + "layerId": "c61a8afb-a185-4fae-a064-fb3846f6c451", + "seriesType": "area", + "splitAccessor": "54cd64ed-2a44-4591-af84-b2624504569a", + "xAccessor": "d6e40cea-6299-43b4-9c9d-b4ee305a2ce8" + } + ], + "legend": { + "isVisible": true, + "position": "right" + }, + "preferredSeriesType": "area" + } + }, + "title": "Artistpreviouslyknownaslens", + "visualizationType": "lnsMetric" + }, + "coreMigrationVersion": "8.0.0", + "id": "76fc4200-cf44-11e9-b933-fd84270f3ac1", + "migrationVersion": { + "lens": "7.14.0" + }, + "references": [ + { + "id": "ftr-remote:logstash-*", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern" + }, + { + "id": "ftr-remote:logstash-*", + "name": "indexpattern-datasource-layer-c61a8afb-a185-4fae-a064-fb3846f6c451", + "type": "index-pattern" + } + ], + "type": "lens", + "updated_at": "2019-10-16T00:28:08.979Z", + "version": "WzIwLDJd" +} + +{ + "attributes": { + "state": { + "datasourceStates": { + "indexpattern": { + "layers": { + "4ba1a1be-6e67-434b-b3a0-f30db8ea5395": { + "columnOrder": [ + "7a5d833b-ca6f-4e48-a924-d2a28d365dc3", + "3cf18f28-3495-4d45-a55f-d97f88022099", + "3dc0bd55-2087-4e60-aea2-f9910714f7db" + ], + "columns": { + "3cf18f28-3495-4d45-a55f-d97f88022099": { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": { + "interval": "auto" + }, + "scale": "interval", + "sourceField": "@timestamp" + }, + "3dc0bd55-2087-4e60-aea2-f9910714f7db": { + "dataType": "number", + "isBucketed": false, + "label": "Average of bytes", + "operationType": "average", + "scale": "ratio", + "sourceField": "bytes" + }, + "7a5d833b-ca6f-4e48-a924-d2a28d365dc3": { + "dataType": "ip", + "isBucketed": true, + "label": "Top values of ip", + "operationType": "terms", + "params": { + "orderBy": { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "type": "column" + }, + "orderDirection": "desc", + "size": 3 + }, + "scale": "ordinal", + "sourceField": "ip" + } + } + } + } + } + }, + "filters": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "layers": [ + { + "accessors": [ + "3dc0bd55-2087-4e60-aea2-f9910714f7db" + ], + "layerId": "4ba1a1be-6e67-434b-b3a0-f30db8ea5395", + "seriesType": "bar_stacked", + "splitAccessor": "7a5d833b-ca6f-4e48-a924-d2a28d365dc3", + "xAccessor": "3cf18f28-3495-4d45-a55f-d97f88022099" + } + ], + "legend": { + "isVisible": true, + "position": "right" + }, + "preferredSeriesType": "bar_stacked" + } + }, + "title": "lnsXYvis", + "visualizationType": "lnsXY" + }, + "coreMigrationVersion": "8.0.0", + "id": "76fc4200-cf44-11e9-b933-fd84270f3ac2", + "migrationVersion": { + "lens": "7.14.0" + }, + "references": [ + { + "id": "ftr-remote:logstash-*", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern" + }, + { + "id": "ftr-remote:logstash-*", + "name": "indexpattern-datasource-layer-4ba1a1be-6e67-434b-b3a0-f30db8ea5395", + "type": "index-pattern" + } + ], + "type": "lens", + "updated_at": "2019-10-16T00:28:08.979Z", + "version": "WzIyLDJd" +} + +{ + "attributes": { + "state": { + "datasourceStates": { + "indexpattern": { + "layers": { + "4ba1a1be-6e67-434b-b3a0-f30db8ea5395": { + "columnOrder": [ + "bafe3009-1776-4227-a0fe-b0d6ccbb4961", + "c1ebe4c9-f283-486c-ae95-6b3e99e83bd8", + "3dc0bd55-2087-4e60-aea2-f9910714f7db" + ], + "columns": { + "3dc0bd55-2087-4e60-aea2-f9910714f7db": { + "dataType": "number", + "isBucketed": false, + "label": "Average of bytes", + "operationType": "average", + "scale": "ratio", + "sourceField": "bytes" + }, + "5bd1c078-e1dd-465b-8d25-7a6404befa88": { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": { + "interval": "auto" + }, + "scale": "interval", + "sourceField": "@timestamp" + }, + "65340cf3-8402-4494-96f2-293701c59571": { + "dataType": "number", + "isBucketed": true, + "label": "Top values of bytes", + "operationType": "terms", + "params": { + "orderBy": { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "type": "column" + }, + "orderDirection": "desc", + "size": 3 + }, + "scale": "ordinal", + "sourceField": "bytes" + }, + "87554e1d-3dbf-4c1c-a358-4c9d40424cfa": { + "dataType": "string", + "isBucketed": true, + "label": "Top values of type", + "operationType": "terms", + "params": { + "orderBy": { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "type": "column" + }, + "orderDirection": "desc", + "size": 3 + }, + "scale": "ordinal", + "sourceField": "type" + }, + "bafe3009-1776-4227-a0fe-b0d6ccbb4961": { + "dataType": "string", + "isBucketed": true, + "label": "Top values of geo.dest", + "operationType": "terms", + "params": { + "orderBy": { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "type": "column" + }, + "orderDirection": "desc", + "size": 7 + }, + "scale": "ordinal", + "sourceField": "geo.dest" + }, + "c1ebe4c9-f283-486c-ae95-6b3e99e83bd8": { + "dataType": "string", + "isBucketed": true, + "label": "Top values of geo.src", + "operationType": "terms", + "params": { + "orderBy": { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "type": "column" + }, + "orderDirection": "desc", + "size": 3 + }, + "scale": "ordinal", + "sourceField": "geo.src" + } + } + } + } + } + }, + "filters": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "layers": [ + { + "categoryDisplay": "default", + "groups": [ + "bafe3009-1776-4227-a0fe-b0d6ccbb4961", + "c1ebe4c9-f283-486c-ae95-6b3e99e83bd8" + ], + "layerId": "4ba1a1be-6e67-434b-b3a0-f30db8ea5395", + "legendDisplay": "default", + "metric": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "nestedLegend": false, + "numberDisplay": "percent" + } + ], + "shape": "pie" + } + }, + "title": "lnsPieVis", + "visualizationType": "lnsPie" + }, + "coreMigrationVersion": "8.0.0", + "id": "9536bed0-d57e-11ea-b169-e3a222a76b9c", + "migrationVersion": { + "lens": "7.14.0" + }, + "references": [ + { + "id": "ftr-remote:logstash-*", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern" + }, + { + "id": "ftr-remote:logstash-*", + "name": "indexpattern-datasource-layer-4ba1a1be-6e67-434b-b3a0-f30db8ea5395", + "type": "index-pattern" + } + ], + "type": "lens", + "updated_at": "2020-08-03T11:43:43.421Z", + "version": "WzIxLDJd" +} + +{ + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "title": "A Pie", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"A Pie\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100},\"dimensions\":{\"metric\":{\"accessor\":0,\"format\":{\"id\":\"number\"},\"params\":{},\"aggType\":\"count\"}},\"palette\":{\"type\":\"palette\",\"name\":\"kibana_palette\"},\"distinctColors\":true},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"geo.src\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"}}]}" + }, + "coreMigrationVersion": "8.0.0", + "id": "i-exist", + "migrationVersion": { + "visualization": "7.14.0" + }, + "references": [ + { + "id": "ftr-remote:logstash-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "visualization", + "updated_at": "2019-01-22T19:32:31.206Z", + "version": "WzE2LDJd" +} + +{ + "attributes": { + "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "timeFieldName": "@timestamp", + "title": "ftr-remote:log*" + }, + "coreMigrationVersion": "8.0.0", + "id": "ftr-remote:log*", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2018-12-21T00:43:07.096Z", + "version": "WzE0LDJd" +} + +{ + "attributes": { + "description": "Ok responses for jpg files", + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "index": "b15b1d40-a8bb-11e9-98cf-2bb06ef63e0b", + "key": "extension.raw", + "negate": false, + "params": { + "query": "jpg" + }, + "type": "phrase", + "value": "jpg" + }, + "query": { + "match": { + "extension.raw": { + "query": "jpg", + "type": "phrase" + } + } + } + } + ], + "query": { + "language": "kuery", + "query": "response:200" + }, + "title": "OKJpgs" + }, + "coreMigrationVersion": "8.0.0", + "id": "okjpgs", + "references": [], + "type": "query", + "updated_at": "2019-07-17T17:54:26.378Z", + "version": "WzE4LDJd" +} \ No newline at end of file diff --git a/x-pack/test/functional/services/remote_es/remote_es.ts b/x-pack/test/functional/services/remote_es/remote_es.ts new file mode 100644 index 0000000000000..15f46131d6927 --- /dev/null +++ b/x-pack/test/functional/services/remote_es/remote_es.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Client } from '@elastic/elasticsearch'; + +import { systemIndicesSuperuser, createRemoteEsClientForFtrConfig } from '@kbn/test'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +/** + * Kibana-specific @elastic/elasticsearch client instance. + */ +export function RemoteEsProvider({ getService }: FtrProviderContext): Client { + const config = getService('config'); + + return createRemoteEsClientForFtrConfig(config, { + // Use system indices user so tests can write to system indices + authOverride: systemIndicesSuperuser, + }); +} diff --git a/x-pack/test/functional/services/remote_es/remote_es_archiver.ts b/x-pack/test/functional/services/remote_es/remote_es_archiver.ts new file mode 100644 index 0000000000000..82439258a0e43 --- /dev/null +++ b/x-pack/test/functional/services/remote_es/remote_es_archiver.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EsArchiver } from '@kbn/es-archiver'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function RemoteEsArchiverProvider({ getService }: FtrProviderContext): EsArchiver { + const remoteEs = getService('remoteEs' as 'es'); + const log = getService('log'); + const kibanaServer = getService('kibanaServer'); + + return new EsArchiver({ + client: remoteEs, + log, + kbnClient: kibanaServer, + }); +} From 8aaad5f34ea99fa57de747cd9f741722699b6e45 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Fri, 1 Apr 2022 09:46:47 +0100 Subject: [PATCH 09/65] [ML] Fix new AD job from saved search with no query filter (#129022) --- .../jobs/new_job/utils/new_job_utils.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts b/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts index dc1b4c03a03fb..be4d8f16658e1 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts @@ -66,6 +66,20 @@ export function createSearchItems( } const filterQuery = buildQueryFromFilters(filters, indexPattern); + if (combinedQuery.bool === undefined) { + combinedQuery.bool = {}; + // toElasticsearchQuery may add a single multi_match item to the + // root of its returned query, rather than putting it inside + // a bool.should + // in this case, move it to a bool.should + if (combinedQuery.multi_match !== undefined) { + combinedQuery.bool.should = { + multi_match: combinedQuery.multi_match, + }; + delete combinedQuery.multi_match; + } + } + if (Array.isArray(combinedQuery.bool.filter) === false) { combinedQuery.bool.filter = combinedQuery.bool.filter === undefined ? [] : [combinedQuery.bool.filter]; From 984ad343c7cc462a8fb0050de599e1395b1285f6 Mon Sep 17 00:00:00 2001 From: Muhammad Ibragimov <53621505+mibragimov@users.noreply.github.com> Date: Fri, 1 Apr 2022 01:48:32 -0700 Subject: [PATCH 10/65] [Console] Fix requests with date-math format (#128727) * Fix requests with date-math format * Add tests Co-authored-by: Muhammad Ibragimov Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../console/server/lib/proxy_request.test.ts | 72 +++++++++++-------- .../console/server/lib/proxy_request.ts | 11 +-- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/src/plugins/console/server/lib/proxy_request.test.ts b/src/plugins/console/server/lib/proxy_request.test.ts index 3740d7e16a3c9..2bb5e481fbb26 100644 --- a/src/plugins/console/server/lib/proxy_request.test.ts +++ b/src/plugins/console/server/lib/proxy_request.test.ts @@ -9,7 +9,7 @@ import http, { ClientRequest } from 'http'; import * as sinon from 'sinon'; import { proxyRequest } from './proxy_request'; -import { URL, URLSearchParams } from 'url'; +import { URL } from 'url'; import { fail } from 'assert'; describe(`Console's send request`, () => { @@ -102,38 +102,52 @@ describe(`Console's send request`, () => { }); }); - it('should decode percent-encoded uri and encode it correctly', async () => { - fakeRequest = { - abort: sinon.stub(), - on() {}, - once(event: string, fn: (v: string) => void) { - if (event === 'response') { - return fn('done'); - } - }, - } as any; + describe('with percent-encoded uri pathname', () => { + beforeEach(() => { + fakeRequest = { + abort: sinon.stub(), + on() {}, + once(event: string, fn: (v: string) => void) { + if (event === 'response') { + return fn('done'); + } + }, + } as any; + }); - const uri = new URL( - `http://noone.nowhere.none/%{[@metadata][beat]}-%{[@metadata][version]}-2020.08.23` - ); + it('should decode percent-encoded uri pathname and encode it correctly', async () => { + const uri = new URL( + `http://noone.nowhere.none/%{[@metadata][beat]}-%{[@metadata][version]}-2020.08.23` + ); + const result = await proxyRequest({ + agent: null as any, + headers: {}, + method: 'get', + payload: null as any, + timeout: 30000, + uri, + }); - const result = await proxyRequest({ - agent: null as any, - headers: {}, - method: 'get', - payload: null as any, - timeout: 30000, - uri, + expect(result).toEqual('done'); + const [httpRequestOptions] = stub.firstCall.args; + expect((httpRequestOptions as any).path).toEqual( + '/%25%7B%5B%40metadata%5D%5Bbeat%5D%7D-%25%7B%5B%40metadata%5D%5Bversion%5D%7D-2020.08.23' + ); }); - expect(result).toEqual('done'); + it('should issue request with date-math format', async () => { + const result = await proxyRequest({ + agent: null as any, + headers: {}, + method: 'get', + payload: null as any, + timeout: 30000, + uri: new URL(`http://noone.nowhere.none/%3Cmy-index-%7Bnow%2Fd%7D%3E`), + }); - const decoded = new URLSearchParams(`path=${uri.pathname}`).get('path'); - const encoded = decoded - ?.split('/') - .map((str) => encodeURIComponent(str)) - .join('/'); - const [httpRequestOptions] = stub.firstCall.args; - expect((httpRequestOptions as any).path).toEqual(encoded); + expect(result).toEqual('done'); + const [httpRequestOptions] = stub.firstCall.args; + expect((httpRequestOptions as any).path).toEqual('/%3Cmy-index-%7Bnow%2Fd%7D%3E'); + }); }); }); diff --git a/src/plugins/console/server/lib/proxy_request.ts b/src/plugins/console/server/lib/proxy_request.ts index a1b34e08b916e..c4fbfd315da4e 100644 --- a/src/plugins/console/server/lib/proxy_request.ts +++ b/src/plugins/console/server/lib/proxy_request.ts @@ -12,6 +12,7 @@ import net from 'net'; import stream from 'stream'; import Boom from '@hapi/boom'; import { URL, URLSearchParams } from 'url'; +import { trimStart } from 'lodash'; interface Args { method: 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head'; @@ -38,10 +39,12 @@ const sanitizeHostname = (hostName: string): string => const encodePathname = (pathname: string) => { const decodedPath = new URLSearchParams(`path=${pathname}`).get('path') ?? ''; - return decodedPath - .split('/') - .map((str) => encodeURIComponent(str)) - .join('/'); + // Skip if it is valid + if (pathname === decodedPath) { + return pathname; + } + + return `/${encodeURIComponent(trimStart(decodedPath, '/'))}`; }; // We use a modified version of Hapi's Wreck because Hapi, Axios, and Superagent don't support GET requests From 57b2a541908f967464caacc4f493819c0caa6124 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Fri, 1 Apr 2022 10:51:08 +0200 Subject: [PATCH 11/65] [ML] Functional tests - stabilize and re-enable transform tests (#129081) This PR fixes and re-enables the transform functional tests. --- .../test/functional/apps/transform/cloning.ts | 3 +-- .../apps/transform/creation_index_pattern.ts | 10 +++++++-- .../test/functional/apps/transform/index.ts | 4 ++-- .../services/transform/security_common.ts | 2 +- .../functional/services/transform/wizard.ts | 21 +++++++++++++++++++ 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/x-pack/test/functional/apps/transform/cloning.ts b/x-pack/test/functional/apps/transform/cloning.ts index 9d3ce49803d28..3cbb0892bd4ec 100644 --- a/x-pack/test/functional/apps/transform/cloning.ts +++ b/x-pack/test/functional/apps/transform/cloning.ts @@ -85,8 +85,7 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const transform = getService('transform'); - // FAILING: https://github.com/elastic/kibana/issues/128967 - describe.skip('cloning', function () { + describe('cloning', function () { const transformConfigWithPivot = getTransformConfig(); const transformConfigWithRuntimeMapping = getTransformConfigWithRuntimeMappings(); const transformConfigWithLatest = getLatestTransformConfig('cloning'); diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation_index_pattern.ts index dc8190c877d61..bc680cd2c0ff9 100644 --- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation_index_pattern.ts @@ -21,8 +21,7 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const transform = getService('transform'); - // Failing ES Promotion: https://github.com/elastic/kibana/issues/126812 - describe.skip('creation_index_pattern', function () { + describe('creation_index_pattern', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/ecommerce'); await transform.testResources.createIndexPatternIfNeeded('ft_ecommerce', 'order_date'); @@ -417,6 +416,7 @@ export default function ({ getService }: FtrProviderContext) { get destinationIndex(): string { return `user-${this.transformId}`; }, + destinationDataViewTimeField: 'order_date', discoverAdjustSuperDatePicker: true, expected: { latestPreview: { @@ -592,6 +592,12 @@ export default function ({ getService }: FtrProviderContext) { await transform.wizard.assertCreateDataViewSwitchExists(); await transform.wizard.assertCreateDataViewSwitchCheckState(true); + if (testData.destinationDataViewTimeField) { + await transform.testExecution.logTestStep('sets the data view time field'); + await transform.wizard.assertDataViewTimeFieldInputExists(); + await transform.wizard.setDataViewTimeField(testData.destinationDataViewTimeField); + } + await transform.testExecution.logTestStep('displays the continuous mode switch'); await transform.wizard.assertContinuousModeSwitchExists(); await transform.wizard.assertContinuousModeSwitchCheckState(false); diff --git a/x-pack/test/functional/apps/transform/index.ts b/x-pack/test/functional/apps/transform/index.ts index 5f5f35ce1c2f8..4966e018a4b03 100644 --- a/x-pack/test/functional/apps/transform/index.ts +++ b/x-pack/test/functional/apps/transform/index.ts @@ -15,8 +15,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const transform = getService('transform'); - // FAILING TEST: https://github.com/elastic/kibana/issues/109687 - describe.skip('transform', function () { + describe('transform', function () { this.tags(['ciGroup21', 'transform']); before(async () => { @@ -66,6 +65,7 @@ export interface BaseTransformTestData { transformDescription: string; expected: any; destinationIndex: string; + destinationDataViewTimeField?: string; discoverAdjustSuperDatePicker: boolean; } diff --git a/x-pack/test/functional/services/transform/security_common.ts b/x-pack/test/functional/services/transform/security_common.ts index dfe6d9e0917ef..36670a65211b3 100644 --- a/x-pack/test/functional/services/transform/security_common.ts +++ b/x-pack/test/functional/services/transform/security_common.ts @@ -31,7 +31,7 @@ export function TransformSecurityCommonProvider({ getService }: FtrProviderConte { name: 'transform_dest', elasticsearch: { - indices: [{ names: ['user-*'], privileges: ['read', 'index', 'manage'] }], + indices: [{ names: ['user-*'], privileges: ['read', 'index', 'manage', 'delete'] }], }, kibana: [], }, diff --git a/x-pack/test/functional/services/transform/wizard.ts b/x-pack/test/functional/services/transform/wizard.ts index 2b95570a9fb1a..3c75db1c4c366 100644 --- a/x-pack/test/functional/services/transform/wizard.ts +++ b/x-pack/test/functional/services/transform/wizard.ts @@ -684,6 +684,27 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi ); }, + async assertDataViewTimeFieldInputExists() { + await testSubjects.existOrFail(`transformDataViewTimeFieldSelect`); + }, + + async assertDataViewTimeFieldValue(expectedValue: string) { + const actualValue = await testSubjects.getAttribute( + `transformDataViewTimeFieldSelect`, + 'value' + ); + expect(actualValue).to.eql( + expectedValue, + `Data view time field should be ${expectedValue}, got ${actualValue}` + ); + }, + + async setDataViewTimeField(fieldName: string) { + const selectControl = await testSubjects.find('transformDataViewTimeFieldSelect'); + await selectControl.type(fieldName); + await this.assertDataViewTimeFieldValue(fieldName); + }, + async assertContinuousModeSwitchExists() { await testSubjects.existOrFail(`transformContinuousModeSwitch`, { allowHidden: true }); }, From 5305597d94f07c08d7fe0d2504ee934ed6e85cf9 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Fri, 1 Apr 2022 11:01:07 +0200 Subject: [PATCH 12/65] [Lens] add experimental badge for annotations (#129012) * [Lens] add experimental badge for annotations * disabled state * Update x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx Co-authored-by: Michael Marcialis * Update x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx Co-authored-by: Michael Marcialis * Update x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss Co-authored-by: Michael Marcialis Co-authored-by: Michael Marcialis --- .../assets/lens_app_graphic_dark_2x.png | Bin 82733 -> 0 bytes .../assets/lens_app_graphic_light_2x.png | Bin 94444 -> 0 bytes .../editor_frame/config_panel/add_layer.tsx | 36 +++++++++++++++++- .../config_panel/dimension_container.scss | 15 +++++++- 4 files changed, 48 insertions(+), 3 deletions(-) delete mode 100644 x-pack/plugins/lens/public/assets/lens_app_graphic_dark_2x.png delete mode 100644 x-pack/plugins/lens/public/assets/lens_app_graphic_light_2x.png diff --git a/x-pack/plugins/lens/public/assets/lens_app_graphic_dark_2x.png b/x-pack/plugins/lens/public/assets/lens_app_graphic_dark_2x.png deleted file mode 100644 index 2c2c71b82180a7574427c284cfffa91771a2296a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82733 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

D 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

s4&bcDbHgys`Q8|Ssnb#9qa?* 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 + + {label} + + + + + {i18n.translate('xpack.lens.configPanel.experimentalLabel', { + defaultMessage: 'Technical preview', + })} + + + + ) : ( + {label} + ), + className: 'lnsLayerAddButton', + width: 300, icon: icon && , ['data-test-subj']: `lnsLayerAddButton-${type}`, onClick: () => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss index 48bb8bc1d70d5..b8ae7fa515190 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss @@ -45,4 +45,17 @@ .lnsBody--overflowHidden { overflow: hidden; -} \ No newline at end of file +} + +.lnsLayerAddButton:hover { + text-decoration: none; + + .lnsLayerAddButton__label { + text-decoration: underline; + } + + .lnsLayerAddButton__techBadge, + .lnsLayerAddButton__techBadge * { + cursor: pointer; + } +} From f9d83f9b8b3d432725c09969a91064f15796f37a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ester=20Mart=C3=AD=20Vilaseca?= Date: Fri, 1 Apr 2022 11:02:30 +0200 Subject: [PATCH 13/65] [Unified Observability] Fix shadow for overview panels (#128878) * replace shadow with border for overview panels * remove not needed prop --- .../observability/public/components/app/section/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/section/index.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx index 582952195bf98..c0f765083d8e5 100644 --- a/x-pack/plugins/observability/public/components/app/section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/index.tsx @@ -42,7 +42,7 @@ export function SectionContainer({ }: Props) { const { http } = useKibana().services; return ( - + <> - {hasError ? : <>{children}} + {hasError ? : <>{children}} From 0427952e7683fce71a3aa5cc5bc41132ac203ccd Mon Sep 17 00:00:00 2001 From: Dmitry Tomashevich <39378793+Dmitriynj@users.noreply.github.com> Date: Fri, 1 Apr 2022 14:57:57 +0500 Subject: [PATCH 14/65] [Discover] Extend Elasticsearch query rule with search source based data fetching (#124534) * [Discover] introduce .index-threshold rule * [Discover] change filters in alert expression * [Discover] fix cursor issue * [Discover] add loading * [Discover] separate validation params * [Discover] add view alert route * [Discover] enable "view in app" for alert created from discover * [Discover] fix filter popover * [Discover] fix linting, unit tests * [Discover] fix remaining tests * [Discover] add unit tests, add link back to stack management for es query * Update src/plugins/discover/public/application/view_alert/view_alert_route.tsx * [Discover] add tool tip for data view without time field * [Discover] add info alert about possible document difference that triggered alert and displayed documents * [Discover] update unit test * [Discover] fix unit tests * Update x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type/alert_type.ts Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type/alert_type.ts Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type/alert_type.ts Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * [Discover] fix unit tests * [Discover] fix security solution alerts * [Discover] fix eslint errors * [Discover] fix unit tests * Update x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type/alert_type.ts Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type/alert_type.ts Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * [Discover] apply suggestions * [Discover] fix tests * Update x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type/alert_type.ts * [Discover] remove close button in filters * Improve code structure * Fix missing name in fetchEsQuery * Fix messages * Fix messages, again * Refactor * Refactor, add tests + a bit more of documentation * Move size field, change text * Implement readonly callout * change icon in callout * add padding to popover * Hide query and filter UI if there are no values to display * [Discover] add unit test, improve comparator types * [Discover] fix linting and unit test * [Discover] add es query alert integration tests * [Discover] fix linting * [Discover] uncomment one expect * [Discover] fix latesTimestamp for searchSource type, unify test logic * Update x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * [Discover] apply suggestions * [Discover] make searchType optional, adjust tests * [Discover] remove updated translations * [Discover] apply suggestions * [Discover] fix unit test * [Discover] close popover on alert rule creation * [Discover] apply suggestions * [Discover] add first functional test * [Discover] implement tests * Move functionals x-pack since ssl is needed * Fix potential flakiness in functional test * [Discover] remove timeout waiter * Fix functional test - adding permissions to fix the functional * [Discover] add logger * [Discover] add more log points * [Discover] wait for indices creation finished * Try to fix the functional flakiness - by creating data views in a serial way - lets see if that work Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> Co-authored-by: Matthias Wilhelm Co-authored-by: andreadelrio --- src/plugins/data/public/index.ts | 7 +- .../ui/filter_bar/_global_filter_item.scss | 5 + .../data/public/ui/filter_bar/filter_bar.tsx | 2 +- .../filter_editor/lib/filter_label.tsx | 10 +- .../data/public/ui/filter_bar/filter_item.tsx | 67 +- .../ui/filter_bar/filter_view/index.tsx | 60 +- src/plugins/discover/kibana.json | 2 +- .../public/application/discover_router.tsx | 4 + ...top_nav_links.ts => get_top_nav_links.tsx} | 21 + .../top_nav/open_alerts_popover.tsx | 185 +++ .../public/application/view_alert/index.ts | 9 + .../view_alert/view_alert_route.tsx | 134 +++ .../view_alert/view_alert_utils.tsx | 116 ++ src/plugins/discover/public/build_services.ts | 15 +- src/plugins/discover/public/plugin.tsx | 11 +- src/plugins/discover/tsconfig.json | 3 +- test/common/services/index_patterns.ts | 7 +- x-pack/plugins/alerting/kibana.json | 1 + x-pack/plugins/alerting/server/mocks.ts | 7 + x-pack/plugins/alerting/server/plugin.test.ts | 4 + x-pack/plugins/alerting/server/plugin.ts | 3 + .../server/task_runner/task_runner.test.ts | 3 + .../server/task_runner/task_runner.ts | 1 + .../task_runner/task_runner_cancel.test.ts | 3 + .../task_runner/task_runner_factory.test.ts | 3 + .../server/task_runner/task_runner_factory.ts | 2 + x-pack/plugins/alerting/server/types.ts | 2 + .../__snapshots__/oss_features.test.ts.snap | 40 +- .../plugins/features/server/oss_features.ts | 17 + .../utils/create_lifecycle_rule_type.test.ts | 2 + .../server/utils/rule_executor_test_utils.ts | 7 + .../routes/rules/preview_rules_route.ts | 8 +- .../security_solution/server/routes/index.ts | 3 +- .../stack_alerts/common/comparator_types.ts | 15 + .../public/alert_types/es_query/constants.ts | 21 + .../es_query_expression.test.tsx} | 53 +- .../es_query_expression.tsx} | 115 +- .../es_query/expression/expression.tsx | 70 ++ .../alert_types/es_query/expression/index.ts | 11 + .../expression/read_only_filter_items.tsx | 61 + .../expression/search_source_expression.scss | 9 + .../search_source_expression.test.tsx | 116 ++ .../expression/search_source_expression.tsx | 220 ++++ .../public/alert_types/es_query/index.ts | 26 +- .../public/alert_types/es_query/types.ts | 25 +- .../public/alert_types/es_query/util.ts | 14 + .../alert_types/es_query/validation.test.ts | 43 +- .../public/alert_types/es_query/validation.ts | 105 +- .../stack_alerts/public/alert_types/index.ts | 5 +- x-pack/plugins/stack_alerts/public/plugin.tsx | 5 +- .../es_query/action_context.test.ts | 13 +- .../alert_types/es_query/action_context.ts | 11 +- .../alert_types/es_query/alert_type.test.ts | 1072 ++++++++--------- .../server/alert_types/es_query/alert_type.ts | 251 +--- .../es_query/alert_type_params.test.ts | 3 +- .../alert_types/es_query/alert_type_params.ts | 50 +- .../server/alert_types/es_query/constants.ts | 10 + .../alert_types/es_query/executor.test.ts | 80 ++ .../server/alert_types/es_query/executor.ts | 193 +++ .../server/alert_types/es_query/index.ts | 7 +- .../es_query/lib/fetch_es_query.ts | 88 ++ .../lib/fetch_search_source_query.test.ts | 164 +++ .../es_query/lib/fetch_search_source_query.ts | 89 ++ .../es_query/lib/get_search_params.ts | 56 + .../server/alert_types/es_query/types.ts | 28 + .../stack_alerts/server/alert_types/index.ts | 3 +- .../index_threshold/alert_type.test.ts | 11 +- .../index_threshold/alert_type_params.test.ts | 3 +- .../index_threshold/alert_type_params.ts | 11 +- .../server/alert_types/lib/comparator.ts | 42 + .../alert_types/lib/comparator_types.ts | 55 - .../server/alert_types/lib/index.ts | 2 +- x-pack/plugins/stack_alerts/server/feature.ts | 2 +- x-pack/plugins/stack_alerts/server/plugin.ts | 1 + x-pack/plugins/stack_alerts/server/types.ts | 1 + .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../common/lib/es_test_index_tool.ts | 3 +- .../builtin_alert_types/es_query/alert.ts | 534 +++++--- .../spaces_only/tests/alerting/index.ts | 4 +- .../apps/discover/index.ts | 14 + .../apps/discover/search_source_alert.ts | 340 ++++++ x-pack/test/functional_with_es_ssl/config.ts | 25 + 84 files changed, 3560 insertions(+), 1287 deletions(-) rename src/plugins/discover/public/application/main/components/top_nav/{get_top_nav_links.ts => get_top_nav_links.tsx} (89%) create mode 100644 src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx create mode 100644 src/plugins/discover/public/application/view_alert/index.ts create mode 100644 src/plugins/discover/public/application/view_alert/view_alert_route.tsx create mode 100644 src/plugins/discover/public/application/view_alert/view_alert_utils.tsx create mode 100644 x-pack/plugins/stack_alerts/common/comparator_types.ts create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/es_query/constants.ts rename x-pack/plugins/stack_alerts/public/alert_types/es_query/{expression.test.tsx => expression/es_query_expression.test.tsx} (83%) rename x-pack/plugins/stack_alerts/public/alert_types/es_query/{expression.tsx => expression/es_query_expression.tsx} (81%) create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/index.ts create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.scss create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.test.tsx create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/es_query/util.ts create mode 100644 x-pack/plugins/stack_alerts/server/alert_types/es_query/constants.ts create mode 100644 x-pack/plugins/stack_alerts/server/alert_types/es_query/executor.test.ts create mode 100644 x-pack/plugins/stack_alerts/server/alert_types/es_query/executor.ts create mode 100644 x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_es_query.ts create mode 100644 x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.test.ts create mode 100644 x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.ts create mode 100644 x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/get_search_params.ts create mode 100644 x-pack/plugins/stack_alerts/server/alert_types/es_query/types.ts create mode 100644 x-pack/plugins/stack_alerts/server/alert_types/lib/comparator.ts delete mode 100644 x-pack/plugins/stack_alerts/server/alert_types/lib/comparator_types.ts create mode 100644 x-pack/test/functional_with_es_ssl/apps/discover/index.ts create mode 100644 x-pack/test/functional_with_es_ssl/apps/discover/search_source_alert.ts diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 02480aded9655..77f17d3a63eee 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -20,7 +20,12 @@ export * from './deprecated'; export { getEsQueryConfig, FilterStateStore } from '../common'; export { FilterLabel, FilterItem } from './ui'; -export { getDisplayValueFromFilter, generateFilters, extractTimeRange } from './query'; +export { + getDisplayValueFromFilter, + generateFilters, + extractTimeRange, + getIndexPatternFromFilter, +} from './query'; /** * Exporters (CSV) diff --git a/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss b/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss index 4873989cde638..1c9cea7291770 100644 --- a/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss +++ b/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss @@ -88,3 +88,8 @@ .globalFilterItem__popoverAnchor { display: block; } + +.globalFilterItem__readonlyPanel { + min-width: auto; + padding: $euiSizeM; +} diff --git a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx index 9bc64eb1f6919..00557dfab0e98 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx @@ -30,7 +30,7 @@ import { IDataPluginServices, IIndexPattern } from '../..'; import { UI_SETTINGS } from '../../../common'; -interface Props { +export interface Props { filters: Filter[]; onFiltersUpdated?: (filters: Filter[]) => void; className: string; diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_label.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_label.tsx index 1a272a5d79f37..d0924258831cb 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_label.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_label.tsx @@ -17,11 +17,17 @@ export interface FilterLabelProps { filter: Filter; valueLabel?: string; filterLabelStatus?: FilterLabelStatus; + hideAlias?: boolean; } // Needed for React.lazy // eslint-disable-next-line import/no-default-export -export default function FilterLabel({ filter, valueLabel, filterLabelStatus }: FilterLabelProps) { +export default function FilterLabel({ + filter, + valueLabel, + filterLabelStatus, + hideAlias, +}: FilterLabelProps) { const prefixText = filter.meta.negate ? ` ${i18n.translate('data.filter.filterBar.negatedFilterPrefix', { defaultMessage: 'NOT ', @@ -38,7 +44,7 @@ export default function FilterLabel({ filter, valueLabel, filterLabelStatus }: F return {text}; }; - if (filter.meta.alias !== null) { + if (!hideAlias && filter.meta.alias !== null) { return ( {prefix} diff --git a/src/plugins/data/public/ui/filter_bar/filter_item.tsx b/src/plugins/data/public/ui/filter_bar/filter_item.tsx index 9e535513aa014..5f57072425844 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_item.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_item.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { EuiContextMenu, EuiPopover } from '@elastic/eui'; +import { EuiContextMenu, EuiPopover, EuiPopoverProps } from '@elastic/eui'; import { InjectedIntl } from '@kbn/i18n-react'; import { Filter, @@ -16,7 +16,7 @@ import { toggleFilterDisabled, } from '@kbn/es-query'; import classNames from 'classnames'; -import React, { MouseEvent, useState, useEffect } from 'react'; +import React, { MouseEvent, useState, useEffect, HTMLAttributes } from 'react'; import { IUiSettingsClient } from 'src/core/public'; import { FilterEditor } from './filter_editor'; import { FilterView } from './filter_view'; @@ -37,8 +37,11 @@ export interface FilterItemProps { uiSettings: IUiSettingsClient; hiddenPanelOptions?: PanelOptions[]; timeRangeForSuggestionsOverride?: boolean; + readonly?: boolean; } +type FilterPopoverProps = HTMLAttributes & EuiPopoverProps; + interface LabelOptions { title: string; status: FilterLabelStatus; @@ -349,32 +352,44 @@ export function FilterItem(props: FilterItemProps) { return null; } - const badge = ( - props.onRemove()} - onClick={handleBadgeClick} - data-test-subj={getDataTestSubj(valueLabelConfig)} - /> - ); + const filterViewProps = { + filter, + valueLabel: valueLabelConfig.title, + filterLabelStatus: valueLabelConfig.status, + errorMessage: valueLabelConfig.message, + className: getClasses(!!filter.meta.negate, valueLabelConfig), + iconOnClick: props.onRemove, + onClick: handleBadgeClick, + 'data-test-subj': getDataTestSubj(valueLabelConfig), + readonly: props.readonly, + }; + + const popoverProps: FilterPopoverProps = { + id: `popoverFor_filter${id}`, + className: `globalFilterItem__popover`, + anchorClassName: `globalFilterItem__popoverAnchor`, + isOpen: isPopoverOpen, + closePopover: () => { + setIsPopoverOpen(false); + }, + button: , + panelPaddingSize: 'none', + }; + + if (props.readonly) { + return ( + + + + ); + } return ( - { - setIsPopoverOpen(false); - }} - button={badge} - anchorPosition="downLeft" - panelPaddingSize="none" - > + ); diff --git a/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx b/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx index d551af87c7279..e5345462b7df2 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { EuiBadge, useInnerText } from '@elastic/eui'; +import { EuiBadge, EuiBadgeProps, useInnerText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { FC } from 'react'; import { Filter, isFilterPinned } from '@kbn/es-query'; @@ -18,6 +18,8 @@ interface Props { valueLabel: string; filterLabelStatus: FilterLabelStatus; errorMessage?: string; + readonly?: boolean; + hideAlias?: boolean; [propName: string]: any; } @@ -28,6 +30,8 @@ export const FilterView: FC = ({ valueLabel, errorMessage, filterLabelStatus, + readonly, + hideAlias, ...rest }: Props) => { const [ref, innerText] = useInnerText(); @@ -50,33 +54,45 @@ export const FilterView: FC = ({ })} ${title}`; } + const badgeProps: EuiBadgeProps = readonly + ? { + title, + color: 'hollow', + onClick, + onClickAriaLabel: i18n.translate('data.filter.filterBar.filterItemReadOnlyBadgeAriaLabel', { + defaultMessage: 'Filter entry', + }), + iconOnClick, + } + : { + title, + color: 'hollow', + iconType: 'cross', + iconSide: 'right', + closeButtonProps: { + // Removing tab focus on close button because the same option can be obtained through the context menu + // Also, we may want to add a `DEL` keyboard press functionality + tabIndex: -1, + }, + iconOnClick, + iconOnClickAriaLabel: i18n.translate('data.filter.filterBar.filterItemBadgeIconAriaLabel', { + defaultMessage: 'Delete {filter}', + values: { filter: innerText }, + }), + onClick, + onClickAriaLabel: i18n.translate('data.filter.filterBar.filterItemBadgeAriaLabel', { + defaultMessage: 'Filter actions', + }), + }; + return ( - + diff --git a/src/plugins/discover/kibana.json b/src/plugins/discover/kibana.json index 015cb6ddaf285..2b09f2bef4896 100644 --- a/src/plugins/discover/kibana.json +++ b/src/plugins/discover/kibana.json @@ -16,7 +16,7 @@ "dataViewFieldEditor", "dataViewEditor" ], - "optionalPlugins": ["home", "share", "usageCollection", "spaces"], + "optionalPlugins": ["home", "share", "usageCollection", "spaces", "triggersActionsUi"], "requiredBundles": ["kibanaUtils", "home", "kibanaReact", "dataViews"], "extraPublicDirs": ["common"], "owner": { diff --git a/src/plugins/discover/public/application/discover_router.tsx b/src/plugins/discover/public/application/discover_router.tsx index 16ff443d15d24..0270af2383488 100644 --- a/src/plugins/discover/public/application/discover_router.tsx +++ b/src/plugins/discover/public/application/discover_router.tsx @@ -16,6 +16,7 @@ import { SingleDocRoute } from './doc'; import { DiscoverMainRoute } from './main'; import { NotFoundRoute } from './not_found'; import { DiscoverServices } from '../build_services'; +import { ViewAlertRoute } from './view_alert'; export const discoverRouter = (services: DiscoverServices, history: History) => ( @@ -36,6 +37,9 @@ export const discoverRouter = (services: DiscoverServices, history: History) => + + + diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.ts b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx similarity index 89% rename from src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.ts rename to src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx index a5627cc1d19d9..abf9a790a2417 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.ts +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx @@ -18,6 +18,7 @@ import { onSaveSearch } from './on_save_search'; import { GetStateReturn } from '../../services/discover_state'; import { openOptionsPopover } from './open_options_popover'; import type { TopNavMenuData } from '../../../../../../navigation/public'; +import { openAlertsPopover } from './open_alerts_popover'; /** * Helper function to build the top nav links @@ -59,6 +60,25 @@ export const getTopNavLinks = ({ testId: 'discoverOptionsButton', }; + const alerts = { + id: 'alerts', + label: i18n.translate('discover.localMenu.localMenu.alertsTitle', { + defaultMessage: 'Alerts', + }), + description: i18n.translate('discover.localMenu.alertsDescription', { + defaultMessage: 'Alerts', + }), + run: (anchorElement: HTMLElement) => { + openAlertsPopover({ + I18nContext: services.core.i18n.Context, + anchorElement, + searchSource: savedSearch.searchSource, + services, + }); + }, + testId: 'discoverAlertsButton', + }; + const newSearch = { id: 'new', label: i18n.translate('discover.localMenu.localMenu.newSearchTitle', { @@ -162,6 +182,7 @@ export const getTopNavLinks = ({ ...(services.capabilities.advancedSettings.save ? [options] : []), newSearch, openSearch, + ...(services.triggersActionsUi ? [alerts] : []), shareSearch, inspectSearch, ...(services.capabilities.discover.save ? [saveSearch] : []), diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx new file mode 100644 index 0000000000000..21d560ccb539d --- /dev/null +++ b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx @@ -0,0 +1,185 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback, useState, useMemo } from 'react'; +import ReactDOM from 'react-dom'; +import { I18nStart } from 'kibana/public'; +import { EuiWrappingPopover, EuiLink, EuiContextMenu, EuiToolTip } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { ISearchSource } from '../../../../../../data/common'; +import { KibanaContextProvider } from '../../../../../../kibana_react/public'; +import { DiscoverServices } from '../../../../build_services'; +import { updateSearchSource } from '../../utils/update_search_source'; +import { useDiscoverServices } from '../../../../utils/use_discover_services'; + +const container = document.createElement('div'); +let isOpen = false; + +const ALERT_TYPE_ID = '.es-query'; + +interface AlertsPopoverProps { + onClose: () => void; + anchorElement: HTMLElement; + searchSource: ISearchSource; +} + +export function AlertsPopover({ searchSource, anchorElement, onClose }: AlertsPopoverProps) { + const dataView = searchSource.getField('index')!; + const services = useDiscoverServices(); + const { triggersActionsUi } = services; + const [alertFlyoutVisible, setAlertFlyoutVisibility] = useState(false); + + /** + * Provides the default parameters used to initialize the new rule + */ + const getParams = useCallback(() => { + const nextSearchSource = searchSource.createCopy(); + updateSearchSource(nextSearchSource, true, { + indexPattern: searchSource.getField('index')!, + services, + sort: [], + useNewFieldsApi: true, + }); + + return { + searchType: 'searchSource', + searchConfiguration: nextSearchSource.getSerializedFields(), + }; + }, [searchSource, services]); + + const SearchThresholdAlertFlyout = useMemo(() => { + if (!alertFlyoutVisible) { + return; + } + return triggersActionsUi?.getAddAlertFlyout({ + consumer: 'discover', + onClose, + canChangeTrigger: false, + ruleTypeId: ALERT_TYPE_ID, + initialValues: { + params: getParams(), + }, + }); + }, [getParams, onClose, triggersActionsUi, alertFlyoutVisible]); + + const hasTimeFieldName = dataView.timeFieldName; + let createSearchThresholdRuleLink = ( + setAlertFlyoutVisibility(true)} + disabled={!hasTimeFieldName} + > + + + ); + + if (!hasTimeFieldName) { + const toolTipContent = ( + + ); + createSearchThresholdRuleLink = ( + + {createSearchThresholdRuleLink} + + ); + } + + const panels = [ + { + id: 'mainPanel', + name: 'Alerting', + items: [ + { + name: ( + <> + {SearchThresholdAlertFlyout} + {createSearchThresholdRuleLink} + + ), + icon: 'bell', + disabled: !hasTimeFieldName, + }, + { + name: ( + + + + ), + icon: 'tableOfContents', + }, + ], + }, + ]; + + return ( + <> + {SearchThresholdAlertFlyout} + + + + + ); +} + +function closeAlertsPopover() { + ReactDOM.unmountComponentAtNode(container); + document.body.removeChild(container); + isOpen = false; +} + +export function openAlertsPopover({ + I18nContext, + anchorElement, + searchSource, + services, +}: { + I18nContext: I18nStart['Context']; + anchorElement: HTMLElement; + searchSource: ISearchSource; + services: DiscoverServices; +}) { + if (isOpen) { + closeAlertsPopover(); + return; + } + + isOpen = true; + document.body.appendChild(container); + + const element = ( + + + + + + ); + ReactDOM.render(element, container); +} diff --git a/src/plugins/discover/public/application/view_alert/index.ts b/src/plugins/discover/public/application/view_alert/index.ts new file mode 100644 index 0000000000000..9b3e4f5d3bf7e --- /dev/null +++ b/src/plugins/discover/public/application/view_alert/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { ViewAlertRoute } from './view_alert_route'; diff --git a/src/plugins/discover/public/application/view_alert/view_alert_route.tsx b/src/plugins/discover/public/application/view_alert/view_alert_route.tsx new file mode 100644 index 0000000000000..82481660d339c --- /dev/null +++ b/src/plugins/discover/public/application/view_alert/view_alert_route.tsx @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useEffect, useMemo } from 'react'; +import { useHistory, useLocation, useParams } from 'react-router-dom'; +import { sha256 } from 'js-sha256'; +import type { Alert } from '../../../../../../x-pack/plugins/alerting/common'; +import { getTime, IndexPattern } from '../../../../data/common'; +import type { Filter } from '../../../../data/public'; +import { DiscoverAppLocatorParams } from '../../locator'; +import { useDiscoverServices } from '../../utils/use_discover_services'; +import { getAlertUtils, QueryParams, SearchThresholdAlertParams } from './view_alert_utils'; + +type NonNullableEntry = { [K in keyof T]: NonNullable }; + +const getCurrentChecksum = (params: SearchThresholdAlertParams) => + sha256.create().update(JSON.stringify(params)).hex(); + +const isActualAlert = (queryParams: QueryParams): queryParams is NonNullableEntry => { + return Boolean(queryParams.from && queryParams.to && queryParams.checksum); +}; + +const buildTimeRangeFilter = ( + dataView: IndexPattern, + fetchedAlert: Alert, + timeFieldName: string +) => { + const filter = getTime(dataView, { + from: `now-${fetchedAlert.params.timeWindowSize}${fetchedAlert.params.timeWindowUnit}`, + to: 'now', + }); + return { + from: filter?.query.range[timeFieldName].gte, + to: filter?.query.range[timeFieldName].lte, + }; +}; + +const DISCOVER_MAIN_ROUTE = '/'; + +export function ViewAlertRoute() { + const { core, data, locator, toastNotifications } = useDiscoverServices(); + const { id } = useParams<{ id: string }>(); + const history = useHistory(); + const { search } = useLocation(); + + const query = useMemo(() => new URLSearchParams(search), [search]); + + const queryParams: QueryParams = useMemo( + () => ({ + from: query.get('from'), + to: query.get('to'), + checksum: query.get('checksum'), + }), + [query] + ); + + const openActualAlert = useMemo(() => isActualAlert(queryParams), [queryParams]); + + useEffect(() => { + const { + fetchAlert, + fetchSearchSource, + displayRuleChangedWarn, + displayPossibleDocsDiffInfoAlert, + showDataViewFetchError, + } = getAlertUtils(toastNotifications, core, data); + + const navigateToResults = async () => { + const fetchedAlert = await fetchAlert(id); + if (!fetchedAlert) { + history.push(DISCOVER_MAIN_ROUTE); + return; + } + + const calculatedChecksum = getCurrentChecksum(fetchedAlert.params); + if (openActualAlert && calculatedChecksum !== queryParams.checksum) { + displayRuleChangedWarn(); + } else if (openActualAlert && calculatedChecksum === queryParams.checksum) { + displayPossibleDocsDiffInfoAlert(); + } + + const fetchedSearchSource = await fetchSearchSource(fetchedAlert); + if (!fetchedSearchSource) { + history.push(DISCOVER_MAIN_ROUTE); + return; + } + + const dataView = fetchedSearchSource.getField('index'); + const timeFieldName = dataView?.timeFieldName; + if (!dataView || !timeFieldName) { + showDataViewFetchError(fetchedAlert.id); + history.push(DISCOVER_MAIN_ROUTE); + return; + } + + const timeRange = openActualAlert + ? { from: queryParams.from, to: queryParams.to } + : buildTimeRangeFilter(dataView, fetchedAlert, timeFieldName); + const state: DiscoverAppLocatorParams = { + query: fetchedSearchSource.getField('query') || data.query.queryString.getDefaultQuery(), + indexPatternId: dataView.id, + timeRange, + }; + + const filters = fetchedSearchSource.getField('filter'); + if (filters) { + state.filters = filters as Filter[]; + } + + await locator.navigate(state); + }; + + navigateToResults(); + }, [ + toastNotifications, + data.query.queryString, + data.search.searchSource, + core.http, + locator, + id, + queryParams, + history, + openActualAlert, + core, + data, + ]); + + return null; +} diff --git a/src/plugins/discover/public/application/view_alert/view_alert_utils.tsx b/src/plugins/discover/public/application/view_alert/view_alert_utils.tsx new file mode 100644 index 0000000000000..b61f0c9a8720c --- /dev/null +++ b/src/plugins/discover/public/application/view_alert/view_alert_utils.tsx @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { CoreStart, ToastsStart } from 'kibana/public'; +import type { Alert } from '../../../../../../x-pack/plugins/alerting/common'; +import type { AlertTypeParams } from '../../../../../../x-pack/plugins/alerting/common'; +import { SerializedSearchSourceFields } from '../../../../data/common'; +import type { DataPublicPluginStart } from '../../../../data/public'; +import { MarkdownSimple, toMountPoint } from '../../../../kibana_react/public'; + +export interface SearchThresholdAlertParams extends AlertTypeParams { + searchConfiguration: SerializedSearchSourceFields; +} + +export interface QueryParams { + from: string | null; + to: string | null; + checksum: string | null; +} + +const LEGACY_BASE_ALERT_API_PATH = '/api/alerts'; + +export const getAlertUtils = ( + toastNotifications: ToastsStart, + core: CoreStart, + data: DataPublicPluginStart +) => { + const showDataViewFetchError = (alertId: string) => { + const errorTitle = i18n.translate('discover.viewAlert.dataViewErrorTitle', { + defaultMessage: 'Error fetching data view', + }); + toastNotifications.addDanger({ + title: errorTitle, + text: toMountPoint( + + {new Error(`Data view failure of the alert rule with id ${alertId}.`).message} + + ), + }); + }; + + const displayRuleChangedWarn = () => { + const warnTitle = i18n.translate('discover.viewAlert.alertRuleChangedWarnTitle', { + defaultMessage: 'Alert rule has changed', + }); + const warnDescription = i18n.translate('discover.viewAlert.alertRuleChangedWarnDescription', { + defaultMessage: `The displayed documents might not match the documents that triggered the alert + because the rule configuration changed.`, + }); + + toastNotifications.addWarning({ + title: warnTitle, + text: toMountPoint({warnDescription}), + }); + }; + + const displayPossibleDocsDiffInfoAlert = () => { + const infoTitle = i18n.translate('discover.viewAlert.documentsMayVaryInfoTitle', { + defaultMessage: 'Displayed documents may vary', + }); + const infoDescription = i18n.translate('discover.viewAlert.documentsMayVaryInfoDescription', { + defaultMessage: `The displayed documents might differ from the documents that triggered the alert. + Some documents might have been added or deleted.`, + }); + + toastNotifications.addInfo({ + title: infoTitle, + text: toMountPoint({infoDescription}), + }); + }; + + const fetchAlert = async (id: string) => { + try { + return await core.http.get>( + `${LEGACY_BASE_ALERT_API_PATH}/alert/${id}` + ); + } catch (error) { + const errorTitle = i18n.translate('discover.viewAlert.alertRuleFetchErrorTitle', { + defaultMessage: 'Error fetching alert rule', + }); + toastNotifications.addDanger({ + title: errorTitle, + text: toMountPoint({error.message}), + }); + } + }; + + const fetchSearchSource = async (fetchedAlert: Alert) => { + try { + return await data.search.searchSource.create(fetchedAlert.params.searchConfiguration); + } catch (error) { + const errorTitle = i18n.translate('discover.viewAlert.searchSourceErrorTitle', { + defaultMessage: 'Error fetching search source', + }); + toastNotifications.addDanger({ + title: errorTitle, + text: toMountPoint({error.message}), + }); + } + }; + + return { + displayRuleChangedWarn, + displayPossibleDocsDiffInfoAlert, + showDataViewFetchError, + fetchAlert, + fetchSearchSource, + }; +}; diff --git a/src/plugins/discover/public/build_services.ts b/src/plugins/discover/public/build_services.ts index f3c697d400a93..100235cb95a0d 100644 --- a/src/plugins/discover/public/build_services.ts +++ b/src/plugins/discover/public/build_services.ts @@ -18,6 +18,8 @@ import { IUiSettingsClient, PluginInitializerContext, HttpStart, + NotificationsStart, + ApplicationStart, } from 'kibana/public'; import { FilterManager, @@ -38,15 +40,18 @@ import { NavigationPublicPluginStart } from '../../navigation/public'; import { IndexPatternFieldEditorStart } from '../../data_view_field_editor/public'; import { FieldFormatsStart } from '../../field_formats/public'; import { EmbeddableStart } from '../../embeddable/public'; +import { DiscoverAppLocator } from './locator'; import type { SpacesApi } from '../../../../x-pack/plugins/spaces/public'; import { DataViewEditorStart } from '../../../plugins/data_view_editor/public'; +import type { TriggersAndActionsUIPublicPluginStart } from '../../../../x-pack/plugins/triggers_actions_ui/public'; export interface HistoryLocationState { referrer: string; } export interface DiscoverServices { + application: ApplicationStart; addBasePath: (path: string) => string; capabilities: Capabilities; chrome: ChromeStart; @@ -66,6 +71,7 @@ export interface DiscoverServices { urlForwarding: UrlForwardingStart; timefilter: TimefilterContract; toastNotifications: ToastsStart; + notifications: NotificationsStart; uiSettings: IUiSettingsClient; trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void; dataViewFieldEditor: IndexPatternFieldEditorStart; @@ -73,17 +79,21 @@ export interface DiscoverServices { http: HttpStart; storage: Storage; spaces?: SpacesApi; + triggersActionsUi: TriggersAndActionsUIPublicPluginStart; + locator: DiscoverAppLocator; } export const buildServices = memoize(function ( core: CoreStart, plugins: DiscoverStartPlugins, - context: PluginInitializerContext + context: PluginInitializerContext, + locator: DiscoverAppLocator ): DiscoverServices { const { usageCollection } = plugins; const storage = new Storage(localStorage); return { + application: core.application, addBasePath: core.http.basePath.prepend, capabilities: core.application.capabilities, chrome: core.chrome, @@ -105,6 +115,7 @@ export const buildServices = memoize(function ( urlForwarding: plugins.urlForwarding, timefilter: plugins.data.query.timefilter.timefilter, toastNotifications: core.notifications.toasts, + notifications: core.notifications, uiSettings: core.uiSettings, storage, trackUiMetric: usageCollection?.reportUiCounter.bind(usageCollection, 'discover'), @@ -112,5 +123,7 @@ export const buildServices = memoize(function ( http: core.http, spaces: plugins.spaces, dataViewEditor: plugins.dataViewEditor, + triggersActionsUi: plugins.triggersActionsUi, + locator, }; }); diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index 09042fda9a38a..0f7be875a4f21 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -53,6 +53,7 @@ import { injectTruncateStyles } from './utils/truncate_styles'; import { DOC_TABLE_LEGACY, TRUNCATE_MAX_HEIGHT } from '../common'; import { DataViewEditorStart } from '../../../plugins/data_view_editor/public'; import { useDiscoverServices } from './utils/use_discover_services'; +import type { TriggersAndActionsUIPublicPluginStart } from '../../../../x-pack/plugins/triggers_actions_ui/public'; import { initializeKbnUrlTracking } from './utils/initialize_kbn_url_tracking'; const DocViewerLegacyTable = React.lazy( @@ -170,6 +171,7 @@ export interface DiscoverStartPlugins { usageCollection?: UsageCollectionSetup; dataViewFieldEditor: IndexPatternFieldEditorStart; spaces?: SpacesPluginStart; + triggersActionsUi: TriggersAndActionsUIPublicPluginStart; } /** @@ -274,7 +276,12 @@ export class DiscoverPlugin window.dispatchEvent(new HashChangeEvent('hashchange')); }); - const services = buildServices(coreStart, discoverStartPlugins, this.initializerContext); + const services = buildServices( + coreStart, + discoverStartPlugins, + this.initializerContext, + this.locator! + ); // make sure the index pattern list is up to date await discoverStartPlugins.data.indexPatterns.clearCache(); @@ -364,7 +371,7 @@ export class DiscoverPlugin const getDiscoverServices = async () => { const [coreStart, discoverStartPlugins] = await core.getStartServices(); - return buildServices(coreStart, discoverStartPlugins, this.initializerContext); + return buildServices(coreStart, discoverStartPlugins, this.initializerContext, this.locator!); }; const factory = new SearchEmbeddableFactory(getStartServices, getDiscoverServices); diff --git a/src/plugins/discover/tsconfig.json b/src/plugins/discover/tsconfig.json index 6dad573a272fb..817e73f16617e 100644 --- a/src/plugins/discover/tsconfig.json +++ b/src/plugins/discover/tsconfig.json @@ -26,6 +26,7 @@ { "path": "../field_formats/tsconfig.json" }, { "path": "../data_views/tsconfig.json" }, { "path": "../../../x-pack/plugins/spaces/tsconfig.json" }, - { "path": "../data_view_editor/tsconfig.json" } + { "path": "../data_view_editor/tsconfig.json" }, + { "path": "../../../x-pack/plugins/triggers_actions_ui/tsconfig.json" } ] } diff --git a/test/common/services/index_patterns.ts b/test/common/services/index_patterns.ts index 549137c79e9a2..1e7e998ae24d9 100644 --- a/test/common/services/index_patterns.ts +++ b/test/common/services/index_patterns.ts @@ -16,13 +16,14 @@ export class IndexPatternsService extends FtrService { * Create a new index pattern */ async create( - indexPattern: { title: string }, - { override = false }: { override: boolean } = { override: false } + indexPattern: { title: string; timeFieldName?: string }, + { override = false }: { override: boolean }, + spaceId = '' ): Promise { const response = await this.kibanaServer.request<{ index_pattern: DataViewSpec; }>({ - path: '/api/index_patterns/index_pattern', + path: `${spaceId}/api/index_patterns/index_pattern`, method: 'POST', body: { override, diff --git a/x-pack/plugins/alerting/kibana.json b/x-pack/plugins/alerting/kibana.json index 6bfc420a89e52..fc45f22d9c9a6 100644 --- a/x-pack/plugins/alerting/kibana.json +++ b/x-pack/plugins/alerting/kibana.json @@ -11,6 +11,7 @@ "configPath": ["xpack", "alerting"], "requiredPlugins": [ "actions", + "data", "encryptedSavedObjects", "eventLog", "features", diff --git a/x-pack/plugins/alerting/server/mocks.ts b/x-pack/plugins/alerting/server/mocks.ts index a94b30b59104c..c952e9182190c 100644 --- a/x-pack/plugins/alerting/server/mocks.ts +++ b/x-pack/plugins/alerting/server/mocks.ts @@ -12,7 +12,9 @@ import { elasticsearchServiceMock, savedObjectsClientMock, uiSettingsServiceMock, + httpServerMock, } from '../../../../src/core/server/mocks'; +import { dataPluginMock } from '../../../../src/plugins/data/server/mocks'; import { AlertInstanceContext, AlertInstanceState } from './types'; export { rulesClientMock }; @@ -111,6 +113,11 @@ const createAlertServicesMock = < shouldWriteAlerts: () => true, shouldStopExecution: () => true, search: createAbortableSearchServiceMock(), + searchSourceClient: Promise.resolve( + dataPluginMock + .createStartContract() + .search.searchSource.asScoped(httpServerMock.createKibanaRequest()) + ), }; }; export type AlertServicesMock = ReturnType; diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index 450c177d72473..8eb73aa25954b 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -19,6 +19,7 @@ import { AlertingConfig } from './config'; import { RuleType } from './types'; import { eventLogMock } from '../../event_log/server/mocks'; import { actionsMock } from '../../actions/server/mocks'; +import { dataPluginMock } from '../../../../src/plugins/data/server/mocks'; import { monitoringCollectionMock } from '../../monitoring_collection/server/mocks'; const generateAlertingConfig = (): AlertingConfig => ({ @@ -276,6 +277,7 @@ describe('Alerting Plugin', () => { licensing: licensingMock.createStart(), eventLog: eventLogMock.createStart(), taskManager: taskManagerMock.createStart(), + data: dataPluginMock.createStartContract(), }); expect(encryptedSavedObjectsSetup.canEncrypt).toEqual(false); @@ -313,6 +315,7 @@ describe('Alerting Plugin', () => { licensing: licensingMock.createStart(), eventLog: eventLogMock.createStart(), taskManager: taskManagerMock.createStart(), + data: dataPluginMock.createStartContract(), }); const fakeRequest = { @@ -361,6 +364,7 @@ describe('Alerting Plugin', () => { licensing: licensingMock.createStart(), eventLog: eventLogMock.createStart(), taskManager: taskManagerMock.createStart(), + data: dataPluginMock.createStartContract(), }); const fakeRequest = { diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 8fa394445fe50..47e2450b7a85c 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -63,6 +63,7 @@ import { getHealth } from './health/get_health'; import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory'; import { AlertingAuthorization } from './authorization'; import { getSecurityHealth, SecurityHealth } from './lib/get_security_health'; +import { PluginStart as DataPluginStart } from '../../../../src/plugins/data/server'; import { MonitoringCollectionSetup } from '../../monitoring_collection/server'; import { registerNodeCollector, registerClusterCollector, InMemoryMetrics } from './monitoring'; import { getExecutionConfigForRuleType } from './lib/get_rules_config'; @@ -139,6 +140,7 @@ export interface AlertingPluginsStart { licensing: LicensingPluginStart; spaces?: SpacesPluginStart; security?: SecurityPluginStart; + data: DataPluginStart; } export class AlertingPlugin { @@ -407,6 +409,7 @@ export class AlertingPlugin { taskRunnerFactory.initialize({ logger, + data: plugins.data, savedObjects: core.savedObjects, uiSettings: core.uiSettings, elasticsearch: core.elasticsearch, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index 3eed02bb10e31..c261b4ddbba25 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -70,6 +70,7 @@ import { import { EVENT_LOG_ACTIONS } from '../plugin'; import { IN_MEMORY_METRICS } from '../monitoring'; import { translations } from '../constants/translations'; +import { dataPluginMock } from '../../../../../src/plugins/data/server/mocks'; jest.mock('uuid', () => ({ v4: () => '5f6aa57d-3e22-484e-bae8-cbed868f4d28', @@ -101,6 +102,7 @@ describe('Task Runner', () => { const ruleTypeRegistry = ruleTypeRegistryMock.create(); const savedObjectsService = savedObjectsServiceMock.createInternalStartContract(); const elasticsearchService = elasticsearchServiceMock.createInternalStart(); + const dataPlugin = dataPluginMock.createStartContract(); const uiSettingsService = uiSettingsServiceMock.createStartContract(); const inMemoryMetrics = inMemoryMetricsMock.create(); @@ -113,6 +115,7 @@ describe('Task Runner', () => { type EnqueueFunction = (options: ExecuteOptions) => Promise; const taskRunnerFactoryInitializerParams: TaskRunnerFactoryInitializerParamsType = { + data: dataPlugin, savedObjects: savedObjectsService, uiSettings: uiSettingsService, elasticsearch: elasticsearchService, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index d3be5e3e6623d..0e69131711067 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -391,6 +391,7 @@ export class TaskRunner< savedObjectsClient, uiSettingsClient: this.context.uiSettings.asScopedToClient(savedObjectsClient), scopedClusterClient: wrappedScopedClusterClient.client(), + searchSourceClient: this.context.data.search.searchSource.asScoped(fakeRequest), alertFactory: createAlertFactory< InstanceState, InstanceContext, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts index 39d2aa8418394..68c005cc4b765 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts @@ -35,6 +35,7 @@ import { IEventLogger } from '../../../event_log/server'; import { Alert, RecoveredActionGroup } from '../../common'; import { UntypedNormalizedRuleType } from '../rule_type_registry'; import { ruleTypeRegistryMock } from '../rule_type_registry.mock'; +import { dataPluginMock } from '../../../../../src/plugins/data/server/mocks'; import { inMemoryMetricsMock } from '../monitoring/in_memory_metrics.mock'; jest.mock('uuid', () => ({ @@ -104,6 +105,7 @@ describe('Task Runner Cancel', () => { const savedObjectsService = savedObjectsServiceMock.createInternalStartContract(); const elasticsearchService = elasticsearchServiceMock.createInternalStart(); const uiSettingsService = uiSettingsServiceMock.createStartContract(); + const dataPlugin = dataPluginMock.createStartContract(); const inMemoryMetrics = inMemoryMetricsMock.create(); type TaskRunnerFactoryInitializerParamsType = jest.Mocked & { @@ -113,6 +115,7 @@ describe('Task Runner Cancel', () => { }; const taskRunnerFactoryInitializerParams: TaskRunnerFactoryInitializerParamsType = { + data: dataPlugin, savedObjects: savedObjectsService, uiSettings: uiSettingsService, elasticsearch: elasticsearchService, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts index 123f5d46e62ad..8cda4f9567d3f 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts @@ -24,6 +24,7 @@ import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; import { UntypedNormalizedRuleType } from '../rule_type_registry'; import { ruleTypeRegistryMock } from '../rule_type_registry.mock'; import { executionContextServiceMock } from '../../../../../src/core/server/mocks'; +import { dataPluginMock } from '../../../../../src/plugins/data/server/mocks'; import { inMemoryMetricsMock } from '../monitoring/in_memory_metrics.mock'; const inMemoryMetrics = inMemoryMetricsMock.create(); @@ -33,6 +34,7 @@ const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); const savedObjectsService = savedObjectsServiceMock.createInternalStartContract(); const uiSettingsService = uiSettingsServiceMock.createStartContract(); const elasticsearchService = elasticsearchServiceMock.createInternalStart(); +const dataPlugin = dataPluginMock.createStartContract(); const ruleType: UntypedNormalizedRuleType = { id: 'test', name: 'My test alert', @@ -80,6 +82,7 @@ describe('Task Runner Factory', () => { const rulesClient = rulesClientMock.create(); const taskRunnerFactoryInitializerParams: jest.Mocked = { + data: dataPlugin, savedObjects: savedObjectsService, uiSettings: uiSettingsService, elasticsearch: elasticsearchService, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts index f6f80e66ce9c3..0ceced10e799b 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts @@ -32,10 +32,12 @@ import { TaskRunner } from './task_runner'; import { IEventLogger } from '../../../event_log/server'; import { RulesClient } from '../rules_client'; import { NormalizedRuleType } from '../rule_type_registry'; +import { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/server'; import { InMemoryMetrics } from '../monitoring'; export interface TaskRunnerContext { logger: Logger; + data: DataPluginStart; savedObjects: SavedObjectsServiceStart; uiSettings: UiSettingsServiceStart; elasticsearch: ElasticsearchServiceStart; diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index 37df778b6185a..10d191d9b43e4 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -42,6 +42,7 @@ import { AlertExecutionStatusWarningReasons, } from '../common'; import { LicenseType } from '../../licensing/server'; +import { ISearchStartSearchSource } from '../../../../src/plugins/data/common'; import { RuleTypeConfig } from './config'; export type WithoutQueryAndParams = Pick>; export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined; @@ -73,6 +74,7 @@ export interface AlertServices< InstanceContext extends AlertInstanceContext = AlertInstanceContext, ActionGroupIds extends string = never > { + searchSourceClient: Promise; savedObjectsClient: SavedObjectsClientContract; uiSettingsClient: IUiSettingsClient; scopedClusterClient: IScopedClusterClient; diff --git a/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap b/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap index 85eb1f5b71e71..92e9394d6cddb 100644 --- a/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap +++ b/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap @@ -660,11 +660,15 @@ Array [ "privilege": Object { "alerting": Object { "alert": Object { - "all": Array [], + "all": Array [ + ".es-query", + ], "read": Array [], }, "rule": Object { - "all": Array [], + "all": Array [ + ".es-query", + ], "read": Array [], }, }, @@ -711,6 +715,18 @@ Array [ }, Object { "privilege": Object { + "alerting": Object { + "alert": Object { + "all": Array [ + ".es-query", + ], + }, + "rule": Object { + "all": Array [ + ".es-query", + ], + }, + }, "app": Array [ "discover", "kibana", @@ -1140,11 +1156,15 @@ Array [ "privilege": Object { "alerting": Object { "alert": Object { - "all": Array [], + "all": Array [ + ".es-query", + ], "read": Array [], }, "rule": Object { - "all": Array [], + "all": Array [ + ".es-query", + ], "read": Array [], }, }, @@ -1191,6 +1211,18 @@ Array [ }, Object { "privilege": Object { + "alerting": Object { + "alert": Object { + "all": Array [ + ".es-query", + ], + }, + "rule": Object { + "all": Array [ + ".es-query", + ], + }, + }, "app": Array [ "discover", "kibana", diff --git a/x-pack/plugins/features/server/oss_features.ts b/x-pack/plugins/features/server/oss_features.ts index 0c240d541f80e..4b9bd9cf15e39 100644 --- a/x-pack/plugins/features/server/oss_features.ts +++ b/x-pack/plugins/features/server/oss_features.ts @@ -32,6 +32,7 @@ export const buildOSSFeatures = ({ category: DEFAULT_APP_CATEGORIES.kibana, app: ['discover', 'kibana'], catalogue: ['discover'], + alerting: ['.es-query'], privileges: { all: { app: ['discover', 'kibana'], @@ -42,6 +43,14 @@ export const buildOSSFeatures = ({ read: ['index-pattern'], }, ui: ['show', 'save', 'saveQuery'], + alerting: { + rule: { + all: ['.es-query'], + }, + alert: { + all: ['.es-query'], + }, + }, }, read: { app: ['discover', 'kibana'], @@ -51,6 +60,14 @@ export const buildOSSFeatures = ({ read: ['index-pattern', 'search', 'query'], }, ui: ['show'], + alerting: { + rule: { + all: ['.es-query'], + }, + alert: { + all: ['.es-query'], + }, + }, }, }, subFeatures: [ diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts index dbd9116498700..79ab866c62802 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts @@ -18,6 +18,7 @@ import { castArray, omit } from 'lodash'; import { RuleDataClient } from '../rule_data_client'; import { createRuleDataClientMock } from '../rule_data_client/rule_data_client.mock'; import { createLifecycleRuleTypeFactory } from './create_lifecycle_rule_type_factory'; +import { ISearchStartSearchSource } from '../../../../../src/plugins/data/common'; type RuleTestHelpers = ReturnType; @@ -117,6 +118,7 @@ function createRule(shouldWriteAlerts: boolean = true) { shouldWriteAlerts: () => shouldWriteAlerts, shouldStopExecution: () => false, search: {} as any, + searchSourceClient: Promise.resolve({} as ISearchStartSearchSource), }, spaceId: 'spaceId', state, diff --git a/x-pack/plugins/rule_registry/server/utils/rule_executor_test_utils.ts b/x-pack/plugins/rule_registry/server/utils/rule_executor_test_utils.ts index 53b688a87845d..d5ec6e236e567 100644 --- a/x-pack/plugins/rule_registry/server/utils/rule_executor_test_utils.ts +++ b/x-pack/plugins/rule_registry/server/utils/rule_executor_test_utils.ts @@ -7,6 +7,7 @@ import { elasticsearchServiceMock, savedObjectsClientMock, + httpServerMock, uiSettingsServiceMock, } from '../../../../../src/core/server/mocks'; import { @@ -17,6 +18,7 @@ import { AlertTypeState, } from '../../../alerting/server'; import { alertsMock } from '../../../alerting/server/mocks'; +import { dataPluginMock } from '../../../../../src/plugins/data/server/mocks'; export const createDefaultAlertExecutorOptions = < Params extends AlertTypeParams = never, @@ -74,6 +76,11 @@ export const createDefaultAlertExecutorOptions = < scopedClusterClient: elasticsearchServiceMock.createScopedClusterClient(), shouldWriteAlerts: () => shouldWriteAlerts, shouldStopExecution: () => false, + searchSourceClient: Promise.resolve( + dataPluginMock + .createStartContract() + .search.searchSource.asScoped(httpServerMock.createKibanaRequest()) + ), }, state, updatedBy: null, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts index 3deb87e864f25..9b8477bd247b5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts @@ -7,7 +7,9 @@ import moment from 'moment'; import uuid from 'uuid'; import { transformError } from '@kbn/securitysolution-es-utils'; +import type { StartServicesAccessor } from 'kibana/server'; import { IRuleDataClient } from '../../../../../../rule_registry/server'; +import type { StartPlugins } from '../../../../plugin'; import { buildSiemResponse } from '../utils'; import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters'; import { RuleParams } from '../../schemas/rule_schemas'; @@ -59,7 +61,8 @@ export const previewRulesRoute = async ( security: SetupPlugins['security'], ruleOptions: CreateRuleOptions, securityRuleTypeOptions: CreateSecurityRuleTypeWrapperProps, - previewRuleDataClient: IRuleDataClient + previewRuleDataClient: IRuleDataClient, + getStartServices: StartServicesAccessor ) => { router.post( { @@ -78,6 +81,8 @@ export const previewRulesRoute = async ( return siemResponse.error({ statusCode: 400, body: validationErrors }); } try { + const [, { data }] = await getStartServices(); + const searchSourceClient = data.search.searchSource.asScoped(request); const savedObjectsClient = context.core.savedObjects.client; const siemClient = context.securitySolution.getAppClient(); @@ -203,6 +208,7 @@ export const previewRulesRoute = async ( abortController, scopedClusterClient: context.core.elasticsearch.client, }), + searchSourceClient, uiSettingsClient: context.core.uiSettings.client, }, spaceId, diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index 935948d6d5938..2efb132c96ff6 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -103,7 +103,8 @@ export const initRoutes = ( security, ruleOptions, securityRuleTypeOptions, - previewRuleDataClient + previewRuleDataClient, + getStartServices ); // Once we no longer have the legacy notifications system/"side car actions" this should be removed. diff --git a/x-pack/plugins/stack_alerts/common/comparator_types.ts b/x-pack/plugins/stack_alerts/common/comparator_types.ts new file mode 100644 index 0000000000000..9e35d50f9158c --- /dev/null +++ b/x-pack/plugins/stack_alerts/common/comparator_types.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export enum Comparator { + GT = '>', + LT = '<', + GT_OR_EQ = '>=', + LT_OR_EQ = '<=', + BETWEEN = 'between', + NOT_BETWEEN = 'notBetween', +} diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/constants.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/constants.ts new file mode 100644 index 0000000000000..bd2de94ce1e96 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/constants.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { COMPARATORS } from '../../../../triggers_actions_ui/public'; + +export const DEFAULT_VALUES = { + THRESHOLD_COMPARATOR: COMPARATORS.GREATER_THAN, + QUERY: `{ + "query":{ + "match_all" : {} + } + }`, + SIZE: 100, + TIME_WINDOW_SIZE: 5, + TIME_WINDOW_UNIT: 'm', + THRESHOLD: [1000], +}; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/es_query_expression.test.tsx similarity index 83% rename from x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.test.tsx rename to x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/es_query_expression.test.tsx index 7ecdcd6dbce38..3cddd1ef112f0 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.test.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/es_query_expression.test.tsx @@ -10,7 +10,6 @@ import 'brace'; import { of } from 'rxjs'; import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; -import EsQueryAlertTypeExpression from './expression'; import { dataPluginMock } from 'src/plugins/data/public/mocks'; import { chartPluginMock } from 'src/plugins/charts/public/mocks'; import { @@ -18,11 +17,12 @@ import { IKibanaSearchResponse, ISearchStart, } from 'src/plugins/data/public'; -import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; -import { EsQueryAlertParams } from './types'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { EsQueryAlertParams, SearchType } from '../types'; +import { EsQueryExpression } from './es_query_expression'; -jest.mock('../../../../../../src/plugins/kibana_react/public'); -jest.mock('../../../../../../src/plugins/es_ui_shared/public', () => ({ +jest.mock('../../../../../../../src/plugins/kibana_react/public'); +jest.mock('../../../../../../../src/plugins/es_ui_shared/public', () => ({ XJson: { useXJsonMode: jest.fn().mockReturnValue({ convertToJson: jest.fn(), @@ -42,8 +42,8 @@ jest.mock('../../../../../../src/plugins/es_ui_shared/public', () => ({ /> ), })); -jest.mock('../../../../triggers_actions_ui/public', () => { - const original = jest.requireActual('../../../../triggers_actions_ui/public'); +jest.mock('../../../../../triggers_actions_ui/public', () => { + const original = jest.requireActual('../../../../../triggers_actions_ui/public'); return { ...original, getIndexPatterns: () => { @@ -100,6 +100,17 @@ const createDataPluginMock = () => { const dataMock = createDataPluginMock(); const chartsStartMock = chartPluginMock.createStartContract(); +const defaultEsQueryExpressionParams: EsQueryAlertParams = { + size: 100, + thresholdComparator: '>', + threshold: [0], + timeWindowSize: 15, + timeWindowUnit: 's', + index: ['test-index'], + timeField: '@timestamp', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, +}; + describe('EsQueryAlertTypeExpression', () => { beforeAll(() => { (useKibana as jest.Mock).mockReturnValue({ @@ -117,20 +128,7 @@ describe('EsQueryAlertTypeExpression', () => { }); }); - function getAlertParams(overrides = {}) { - return { - index: ['test-index'], - timeField: '@timestamp', - esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, - size: 100, - thresholdComparator: '>', - threshold: [0], - timeWindowSize: 15, - timeWindowUnit: 's', - ...overrides, - }; - } - async function setup(alertParams: EsQueryAlertParams) { + async function setup(alertParams: EsQueryAlertParams) { const errors = { index: [], esQuery: [], @@ -140,7 +138,7 @@ describe('EsQueryAlertTypeExpression', () => { }; const wrapper = mountWithIntl( - { } test('should render EsQueryAlertTypeExpression with expected components', async () => { - const wrapper = await setup(getAlertParams()); + const wrapper = await setup(defaultEsQueryExpressionParams); expect(wrapper.find('[data-test-subj="indexSelectPopover"]').exists()).toBeTruthy(); expect(wrapper.find('[data-test-subj="sizeValueExpression"]').exists()).toBeTruthy(); expect(wrapper.find('[data-test-subj="queryJsonEditor"]').exists()).toBeTruthy(); @@ -181,7 +179,10 @@ describe('EsQueryAlertTypeExpression', () => { }); test('should render Test Query button disabled if alert params are invalid', async () => { - const wrapper = await setup(getAlertParams({ timeField: null })); + const wrapper = await setup({ + ...defaultEsQueryExpressionParams, + timeField: null, + } as unknown as EsQueryAlertParams); const testQueryButton = wrapper.find('EuiButtonEmpty[data-test-subj="testQuery"]'); expect(testQueryButton.exists()).toBeTruthy(); expect(testQueryButton.prop('disabled')).toBe(true); @@ -196,7 +197,7 @@ describe('EsQueryAlertTypeExpression', () => { }, }); dataMock.search.search.mockImplementation(() => searchResponseMock$); - const wrapper = await setup(getAlertParams()); + const wrapper = await setup(defaultEsQueryExpressionParams); const testQueryButton = wrapper.find('EuiButtonEmpty[data-test-subj="testQuery"]'); testQueryButton.simulate('click'); @@ -217,7 +218,7 @@ describe('EsQueryAlertTypeExpression', () => { dataMock.search.search.mockImplementation(() => { throw new Error('What is this query'); }); - const wrapper = await setup(getAlertParams()); + const wrapper = await setup(defaultEsQueryExpressionParams); const testQueryButton = wrapper.find('EuiButtonEmpty[data-test-subj="testQuery"]'); testQueryButton.simulate('click'); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/es_query_expression.tsx similarity index 81% rename from x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.tsx rename to x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/es_query_expression.tsx index 402d6fca7a1a9..45d7791055f87 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/es_query_expression.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, Fragment, useEffect } from 'react'; +import React, { useState, Fragment, useEffect, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -18,7 +18,6 @@ import { EuiButtonEmpty, EuiSpacer, EuiFormRow, - EuiCallOut, EuiText, EuiTitle, EuiLink, @@ -27,49 +26,26 @@ import { import { DocLinksStart, HttpSetup } from 'kibana/public'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { XJson, EuiCodeEditor } from '../../../../../../src/plugins/es_ui_shared/public'; -import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { XJson, EuiCodeEditor } from '../../../../../../../src/plugins/es_ui_shared/public'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { getFields, - COMPARATORS, - ThresholdExpression, - ForLastExpression, ValueExpression, RuleTypeParamsExpressionProps, -} from '../../../../triggers_actions_ui/public'; -import { validateExpression } from './validation'; -import { parseDuration } from '../../../../alerting/common'; -import { buildSortedEventsQuery } from '../../../common/build_sorted_events_query'; -import { EsQueryAlertParams } from './types'; -import { IndexSelectPopover } from '../components/index_select_popover'; + ForLastExpression, + ThresholdExpression, +} from '../../../../../triggers_actions_ui/public'; +import { validateExpression } from '../validation'; +import { parseDuration } from '../../../../../alerting/common'; +import { buildSortedEventsQuery } from '../../../../common/build_sorted_events_query'; +import { EsQueryAlertParams, SearchType } from '../types'; +import { IndexSelectPopover } from '../../components/index_select_popover'; +import { DEFAULT_VALUES } from '../constants'; function totalHitsToNumber(total: estypes.SearchHitsMetadata['total']): number { return typeof total === 'number' ? total : total?.value ?? 0; } -const DEFAULT_VALUES = { - THRESHOLD_COMPARATOR: COMPARATORS.GREATER_THAN, - QUERY: `{ - "query":{ - "match_all" : {} - } -}`, - SIZE: 100, - TIME_WINDOW_SIZE: 5, - TIME_WINDOW_UNIT: 'm', - THRESHOLD: [1000], -}; - -const expressionFieldsWithValidation = [ - 'index', - 'esQuery', - 'size', - 'timeField', - 'threshold0', - 'threshold1', - 'timeWindowSize', -]; - const { useXJsonMode } = XJson; const xJsonMode = new XJsonMode(); @@ -78,9 +54,13 @@ interface KibanaDeps { docLinks: DocLinksStart; } -export const EsQueryAlertTypeExpression: React.FunctionComponent< - RuleTypeParamsExpressionProps -> = ({ ruleParams, setRuleParams, setRuleProperty, errors, data }) => { +export const EsQueryExpression = ({ + ruleParams, + setRuleParams, + setRuleProperty, + errors, + data, +}: RuleTypeParamsExpressionProps>) => { const { index, timeField, @@ -92,16 +72,29 @@ export const EsQueryAlertTypeExpression: React.FunctionComponent< timeWindowUnit, } = ruleParams; - const getDefaultParams = () => ({ + const [currentAlertParams, setCurrentAlertParams] = useState< + EsQueryAlertParams + >({ ...ruleParams, - esQuery: esQuery ?? DEFAULT_VALUES.QUERY, - size: size ?? DEFAULT_VALUES.SIZE, timeWindowSize: timeWindowSize ?? DEFAULT_VALUES.TIME_WINDOW_SIZE, timeWindowUnit: timeWindowUnit ?? DEFAULT_VALUES.TIME_WINDOW_UNIT, threshold: threshold ?? DEFAULT_VALUES.THRESHOLD, thresholdComparator: thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR, + size: size ?? DEFAULT_VALUES.SIZE, + esQuery: esQuery ?? DEFAULT_VALUES.QUERY, }); + const setParam = useCallback( + (paramField: string, paramValue: unknown) => { + setCurrentAlertParams((currentParams) => ({ + ...currentParams, + [paramField]: paramValue, + })); + setRuleParams(paramField, paramValue); + }, + [setRuleParams] + ); + const { http, docLinks } = useKibana().services; const [esFields, setEsFields] = useState< @@ -114,29 +107,11 @@ export const EsQueryAlertTypeExpression: React.FunctionComponent< }> >([]); const { convertToJson, setXJson, xJson } = useXJsonMode(DEFAULT_VALUES.QUERY); - const [currentAlertParams, setCurrentAlertParams] = useState( - getDefaultParams() - ); const [testQueryResult, setTestQueryResult] = useState(null); const [testQueryError, setTestQueryError] = useState(null); - const hasExpressionErrors = !!Object.keys(errors).find( - (errorKey) => - expressionFieldsWithValidation.includes(errorKey) && - errors[errorKey].length >= 1 && - ruleParams[errorKey as keyof EsQueryAlertParams] !== undefined - ); - - const expressionErrorMessage = i18n.translate( - 'xpack.stackAlerts.esQuery.ui.alertParams.fixErrorInExpressionBelowValidationMessage', - { - defaultMessage: 'Expression contains errors.', - } - ); - const setDefaultExpressionValues = async () => { - setRuleProperty('params', getDefaultParams()); - + setRuleProperty('params', currentAlertParams); setXJson(esQuery ?? DEFAULT_VALUES.QUERY); if (index && index.length > 0) { @@ -144,14 +119,6 @@ export const EsQueryAlertTypeExpression: React.FunctionComponent< } }; - const setParam = (paramField: string, paramValue: unknown) => { - setCurrentAlertParams({ - ...currentAlertParams, - [paramField]: paramValue, - }); - setRuleParams(paramField, paramValue); - }; - useEffect(() => { setDefaultExpressionValues(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -216,13 +183,6 @@ export const EsQueryAlertTypeExpression: React.FunctionComponent< return ( - {hasExpressionErrors ? ( - - - - - - ) : null}

); }; - -// eslint-disable-next-line import/no-default-export -export { EsQueryAlertTypeExpression as default }; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx new file mode 100644 index 0000000000000..2c825cdc3c286 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +import 'brace/theme/github'; + +import { EuiSpacer, EuiCallOut } from '@elastic/eui'; +import { RuleTypeParamsExpressionProps } from '../../../../../triggers_actions_ui/public'; +import { EsQueryAlertParams } from '../types'; +import { SearchSourceExpression } from './search_source_expression'; +import { EsQueryExpression } from './es_query_expression'; +import { isSearchSourceAlert } from '../util'; + +const expressionFieldsWithValidation = [ + 'index', + 'size', + 'timeField', + 'threshold0', + 'threshold1', + 'timeWindowSize', + 'searchType', + 'esQuery', + 'searchConfiguration', +]; + +export const EsQueryAlertTypeExpression: React.FunctionComponent< + RuleTypeParamsExpressionProps +> = (props) => { + const { ruleParams, errors } = props; + const isSearchSource = isSearchSourceAlert(ruleParams); + + const hasExpressionErrors = !!Object.keys(errors).find((errorKey) => { + return ( + expressionFieldsWithValidation.includes(errorKey) && + errors[errorKey].length >= 1 && + ruleParams[errorKey as keyof EsQueryAlertParams] !== undefined + ); + }); + + const expressionErrorMessage = i18n.translate( + 'xpack.stackAlerts.esQuery.ui.alertParams.fixErrorInExpressionBelowValidationMessage', + { + defaultMessage: 'Expression contains errors.', + } + ); + + return ( + <> + {hasExpressionErrors && ( + <> + + + + + )} + + {isSearchSource ? ( + + ) : ( + + )} + + ); +}; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/index.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/index.ts new file mode 100644 index 0000000000000..ee3eb83299748 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EsQueryAlertTypeExpression } from './expression'; + +// eslint-disable-next-line import/no-default-export +export default EsQueryAlertTypeExpression; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx new file mode 100644 index 0000000000000..b4b0c6ae9ac5d --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { injectI18n } from '@kbn/i18n-react'; + +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { + FilterItem, + getDisplayValueFromFilter, +} from '../../../../../../../src/plugins/data/public'; +import { Filter, IIndexPattern } from '../../../../../../../src/plugins/data/common'; + +const FilterItemComponent = injectI18n(FilterItem); + +interface ReadOnlyFilterItemsProps { + filters: Filter[]; + indexPatterns: IIndexPattern[]; +} + +const noOp = () => {}; + +export const ReadOnlyFilterItems = ({ filters, indexPatterns }: ReadOnlyFilterItemsProps) => { + const { uiSettings } = useKibana().services; + + const filterList = filters.map((filter, index) => { + const filterValue = getDisplayValueFromFilter(filter, indexPatterns); + return ( + + + + ); + }); + + return ( + + {filterList} + + ); +}; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.scss b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.scss new file mode 100644 index 0000000000000..418449eb792c1 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.scss @@ -0,0 +1,9 @@ +.searchSourceAlertFilters { + .euiExpression__value { + width: 80%; + } +} + +.dscExpressionParam.euiExpression { + margin-left: 0; +} diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.test.tsx new file mode 100644 index 0000000000000..4b18a5a5f96a2 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.test.tsx @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; +import React from 'react'; +import { dataPluginMock } from 'src/plugins/data/public/mocks'; +import { DataPublicPluginStart, ISearchStart } from 'src/plugins/data/public'; +import { EsQueryAlertParams, SearchType } from '../types'; +import { SearchSourceExpression } from './search_source_expression'; +import { chartPluginMock } from 'src/plugins/charts/public/mocks'; +import { act } from 'react-dom/test-utils'; +import { EuiCallOut, EuiLoadingSpinner } from '@elastic/eui'; +import { ReactWrapper } from 'enzyme'; + +const dataMock = dataPluginMock.createStartContract() as DataPublicPluginStart & { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + search: ISearchStart & { searchSource: { create: jest.MockedFunction } }; +}; +const chartsStartMock = chartPluginMock.createStartContract(); + +const defaultSearchSourceExpressionParams: EsQueryAlertParams = { + size: 100, + thresholdComparator: '>', + threshold: [0], + timeWindowSize: 15, + timeWindowUnit: 's', + index: ['test-index'], + timeField: '@timestamp', + searchType: SearchType.searchSource, + searchConfiguration: {}, +}; + +const searchSourceMock = { + getField: (name: string) => { + if (name === 'filter') { + return []; + } + return ''; + }, +}; + +const setup = async (alertParams: EsQueryAlertParams) => { + const errors = { + size: [], + timeField: [], + timeWindowSize: [], + searchConfiguration: [], + }; + + const wrapper = mountWithIntl( + {}} + setRuleProperty={() => {}} + errors={errors} + data={dataMock} + defaultActionGroupId="" + actionGroups={[]} + charts={chartsStartMock} + /> + ); + + return wrapper; +}; + +const rerender = async (wrapper: ReactWrapper) => { + const update = async () => + await act(async () => { + await nextTick(); + wrapper.update(); + }); + await update(); +}; + +describe('SearchSourceAlertTypeExpression', () => { + test('should render loading prompt', async () => { + dataMock.search.searchSource.create.mockImplementation(() => + Promise.resolve(() => searchSourceMock) + ); + + const wrapper = await setup(defaultSearchSourceExpressionParams); + + expect(wrapper.find(EuiLoadingSpinner).exists()).toBeTruthy(); + }); + + test('should render error prompt', async () => { + dataMock.search.searchSource.create.mockImplementation(() => + Promise.reject(() => 'test error') + ); + + const wrapper = await setup(defaultSearchSourceExpressionParams); + await rerender(wrapper); + + expect(wrapper.find(EuiCallOut).exists()).toBeTruthy(); + }); + + test('should render SearchSourceAlertTypeExpression with expected components', async () => { + dataMock.search.searchSource.create.mockImplementation(() => + Promise.resolve(() => searchSourceMock) + ); + + const wrapper = await setup(defaultSearchSourceExpressionParams); + await rerender(wrapper); + + expect(wrapper.find('[data-test-subj="sizeValueExpression"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="thresholdExpression"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="forLastExpression"]').exists()).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx new file mode 100644 index 0000000000000..2e99fa8e1356b --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx @@ -0,0 +1,220 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { Fragment, useCallback, useEffect, useState } from 'react'; +import './search_source_expression.scss'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiSpacer, + EuiTitle, + EuiExpression, + EuiLoadingSpinner, + EuiEmptyPrompt, + EuiCallOut, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Filter, ISearchSource } from '../../../../../../../src/plugins/data/common'; +import { EsQueryAlertParams, SearchType } from '../types'; +import { + ForLastExpression, + RuleTypeParamsExpressionProps, + ThresholdExpression, + ValueExpression, +} from '../../../../../triggers_actions_ui/public'; +import { DEFAULT_VALUES } from '../constants'; +import { ReadOnlyFilterItems } from './read_only_filter_items'; + +export const SearchSourceExpression = ({ + ruleParams, + setRuleParams, + setRuleProperty, + data, + errors, +}: RuleTypeParamsExpressionProps>) => { + const { + searchConfiguration, + thresholdComparator, + threshold, + timeWindowSize, + timeWindowUnit, + size, + } = ruleParams; + const [usedSearchSource, setUsedSearchSource] = useState(); + const [paramsError, setParamsError] = useState(); + + const [currentAlertParams, setCurrentAlertParams] = useState< + EsQueryAlertParams + >({ + searchConfiguration, + searchType: SearchType.searchSource, + timeWindowSize: timeWindowSize ?? DEFAULT_VALUES.TIME_WINDOW_SIZE, + timeWindowUnit: timeWindowUnit ?? DEFAULT_VALUES.TIME_WINDOW_UNIT, + threshold: threshold ?? DEFAULT_VALUES.THRESHOLD, + thresholdComparator: thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR, + size: size ?? DEFAULT_VALUES.SIZE, + }); + + const setParam = useCallback( + (paramField: string, paramValue: unknown) => { + setCurrentAlertParams((currentParams) => ({ + ...currentParams, + [paramField]: paramValue, + })); + setRuleParams(paramField, paramValue); + }, + [setRuleParams] + ); + + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(() => setRuleProperty('params', currentAlertParams), []); + + useEffect(() => { + async function initSearchSource() { + try { + const loadedSearchSource = await data.search.searchSource.create(searchConfiguration); + setUsedSearchSource(loadedSearchSource); + } catch (error) { + setParamsError(error); + } + } + if (searchConfiguration) { + initSearchSource(); + } + }, [data.search.searchSource, searchConfiguration]); + + if (paramsError) { + return ( + <> + +

{paramsError.message}

+
+ + + ); + } + + if (!usedSearchSource) { + return } />; + } + + const dataView = usedSearchSource.getField('index')!; + const query = usedSearchSource.getField('query')!; + const filters = (usedSearchSource.getField('filter') as Filter[]).filter( + ({ meta }) => !meta.disabled + ); + const dataViews = [dataView]; + return ( + + +
+ +
+
+ + + } + iconType="iInCircle" + /> + + + {query.query !== '' && ( + + )} + {filters.length > 0 && ( + } + display="columns" + /> + )} + + + +
+ +
+
+ + + setParam('threshold', selectedThresholds) + } + onChangeSelectedThresholdComparator={(selectedThresholdComparator) => + setParam('thresholdComparator', selectedThresholdComparator) + } + /> + + setParam('timeWindowSize', selectedWindowSize) + } + onChangeWindowUnit={(selectedWindowUnit: string) => + setParam('timeWindowUnit', selectedWindowUnit) + } + /> + + +
+ +
+
+ + { + setParam('size', updatedValue); + }} + /> + +
+ ); +}; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/index.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/index.ts index cf54c5934c026..218cbff3bb1ec 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/index.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/index.ts @@ -8,12 +8,19 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; import { validateExpression } from './validation'; -import { EsQueryAlertParams } from './types'; +import { EsQueryAlertParams, SearchType } from './types'; import { RuleTypeModel } from '../../../../triggers_actions_ui/public'; +import { PluginSetupContract as AlertingSetup } from '../../../../alerting/public'; +import { SanitizedAlert } from '../../../../alerting/common'; + +const PLUGIN_ID = 'discover'; +const ES_QUERY_ALERT_TYPE = '.es-query'; + +export function getAlertType(alerting: AlertingSetup): RuleTypeModel { + registerNavigation(alerting); -export function getAlertType(): RuleTypeModel { return { - id: '.es-query', + id: ES_QUERY_ALERT_TYPE, description: i18n.translate('xpack.stackAlerts.esQuery.ui.alertType.descriptionText', { defaultMessage: 'Alert when matches are found during the latest query run.', }), @@ -28,9 +35,20 @@ export function getAlertType(): RuleTypeModel { - Value: \\{\\{context.value\\}\\} - Conditions Met: \\{\\{context.conditions\\}\\} over \\{\\{params.timeWindowSize\\}\\}\\{\\{params.timeWindowUnit\\}\\} -- Timestamp: \\{\\{context.date\\}\\}`, +- Timestamp: \\{\\{context.date\\}\\} +- Link: \\{\\{context.link\\}\\}`, } ), requiresAppContext: false, }; } + +function registerNavigation(alerting: AlertingSetup) { + alerting.registerNavigation( + PLUGIN_ID, + ES_QUERY_ALERT_TYPE, + (alert: SanitizedAlert>) => { + return `#/viewAlert/${alert.id}`; + } + ); +} diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts index 826d9b25a5394..b60183d7ae2fb 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts @@ -6,6 +6,7 @@ */ import { AlertTypeParams } from '../../../../alerting/common'; +import { SerializedSearchSourceFields } from '../../../../../../src/plugins/data/common'; export interface Comparator { text: string; @@ -13,13 +14,29 @@ export interface Comparator { requiredValues: number; } -export interface EsQueryAlertParams extends AlertTypeParams { - index: string[]; - timeField?: string; - esQuery: string; +export enum SearchType { + esQuery = 'esQuery', + searchSource = 'searchSource', +} + +export interface CommonAlertParams extends AlertTypeParams { size: number; thresholdComparator?: string; threshold: number[]; timeWindowSize: number; timeWindowUnit: string; } + +export type EsQueryAlertParams = T extends SearchType.searchSource + ? CommonAlertParams & OnlySearchSourceAlertParams + : CommonAlertParams & OnlyEsQueryAlertParams; + +export interface OnlyEsQueryAlertParams { + esQuery: string; + index: string[]; + timeField: string; +} +export interface OnlySearchSourceAlertParams { + searchType: 'searchSource'; + searchConfiguration: SerializedSearchSourceFields; +} diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/util.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/util.ts new file mode 100644 index 0000000000000..5b70da7cb3e80 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/util.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EsQueryAlertParams, SearchType } from './types'; + +export const isSearchSourceAlert = ( + ruleParams: EsQueryAlertParams +): ruleParams is EsQueryAlertParams => { + return ruleParams.searchType === 'searchSource'; +}; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.test.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.test.ts index 52278b4576557..90b7f96b781b9 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.test.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.test.ts @@ -5,64 +5,82 @@ * 2.0. */ -import { EsQueryAlertParams } from './types'; +import { EsQueryAlertParams, SearchType } from './types'; import { validateExpression } from './validation'; describe('expression params validation', () => { test('if index property is invalid should return proper error message', () => { - const initialParams: EsQueryAlertParams = { + const initialParams: EsQueryAlertParams = { index: [], esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, size: 100, timeWindowSize: 1, timeWindowUnit: 's', threshold: [0], + timeField: '', }; expect(validateExpression(initialParams).errors.index.length).toBeGreaterThan(0); expect(validateExpression(initialParams).errors.index[0]).toBe('Index is required.'); }); test('if timeField property is not defined should return proper error message', () => { - const initialParams: EsQueryAlertParams = { + const initialParams: EsQueryAlertParams = { index: ['test'], esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, size: 100, timeWindowSize: 1, timeWindowUnit: 's', threshold: [0], + timeField: '', }; expect(validateExpression(initialParams).errors.timeField.length).toBeGreaterThan(0); expect(validateExpression(initialParams).errors.timeField[0]).toBe('Time field is required.'); }); test('if esQuery property is invalid JSON should return proper error message', () => { - const initialParams: EsQueryAlertParams = { + const initialParams: EsQueryAlertParams = { index: ['test'], esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n`, size: 100, timeWindowSize: 1, timeWindowUnit: 's', threshold: [0], + timeField: '', }; expect(validateExpression(initialParams).errors.esQuery.length).toBeGreaterThan(0); expect(validateExpression(initialParams).errors.esQuery[0]).toBe('Query must be valid JSON.'); }); test('if esQuery property is invalid should return proper error message', () => { - const initialParams: EsQueryAlertParams = { + const initialParams: EsQueryAlertParams = { index: ['test'], esQuery: `{\n \"aggs\":{\n \"match_all\" : {}\n }\n}`, size: 100, timeWindowSize: 1, timeWindowUnit: 's', threshold: [0], + timeField: '', }; expect(validateExpression(initialParams).errors.esQuery.length).toBeGreaterThan(0); expect(validateExpression(initialParams).errors.esQuery[0]).toBe(`Query field is required.`); }); + test('if searchConfiguration property is not set should return proper error message', () => { + const initialParams = { + size: 100, + timeWindowSize: 1, + timeWindowUnit: 's', + threshold: [0], + searchType: SearchType.searchSource, + } as EsQueryAlertParams; + expect(validateExpression(initialParams).errors.searchConfiguration.length).toBeGreaterThan(0); + expect(validateExpression(initialParams).errors.searchConfiguration[0]).toBe( + `Search source configuration is required.` + ); + }); + test('if threshold0 property is not set should return proper error message', () => { - const initialParams: EsQueryAlertParams = { + const initialParams: EsQueryAlertParams = { index: ['test'], esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, size: 100, @@ -70,13 +88,14 @@ describe('expression params validation', () => { timeWindowSize: 1, timeWindowUnit: 's', thresholdComparator: '<', + timeField: '', }; expect(validateExpression(initialParams).errors.threshold0.length).toBeGreaterThan(0); expect(validateExpression(initialParams).errors.threshold0[0]).toBe('Threshold 0 is required.'); }); test('if threshold1 property is needed by thresholdComparator but not set should return proper error message', () => { - const initialParams: EsQueryAlertParams = { + const initialParams: EsQueryAlertParams = { index: ['test'], esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, size: 100, @@ -84,13 +103,14 @@ describe('expression params validation', () => { timeWindowSize: 1, timeWindowUnit: 's', thresholdComparator: 'between', + timeField: '', }; expect(validateExpression(initialParams).errors.threshold1.length).toBeGreaterThan(0); expect(validateExpression(initialParams).errors.threshold1[0]).toBe('Threshold 1 is required.'); }); test('if threshold0 property greater than threshold1 property should return proper error message', () => { - const initialParams: EsQueryAlertParams = { + const initialParams: EsQueryAlertParams = { index: ['test'], esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, size: 100, @@ -98,6 +118,7 @@ describe('expression params validation', () => { timeWindowSize: 1, timeWindowUnit: 's', thresholdComparator: 'between', + timeField: '', }; expect(validateExpression(initialParams).errors.threshold1.length).toBeGreaterThan(0); expect(validateExpression(initialParams).errors.threshold1[0]).toBe( @@ -106,13 +127,14 @@ describe('expression params validation', () => { }); test('if size property is < 0 should return proper error message', () => { - const initialParams: EsQueryAlertParams = { + const initialParams: EsQueryAlertParams = { index: ['test'], esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n`, size: -1, timeWindowSize: 1, timeWindowUnit: 's', threshold: [0], + timeField: '', }; expect(validateExpression(initialParams).errors.size.length).toBeGreaterThan(0); expect(validateExpression(initialParams).errors.size[0]).toBe( @@ -121,13 +143,14 @@ describe('expression params validation', () => { }); test('if size property is > 10000 should return proper error message', () => { - const initialParams: EsQueryAlertParams = { + const initialParams: EsQueryAlertParams = { index: ['test'], esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n`, size: 25000, timeWindowSize: 1, timeWindowUnit: 's', threshold: [0], + timeField: '', }; expect(validateExpression(initialParams).errors.size.length).toBeGreaterThan(0); expect(validateExpression(initialParams).errors.size[0]).toBe( diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts index d4ab8801fcdde..4766ce68dba4a 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts @@ -8,10 +8,10 @@ import { i18n } from '@kbn/i18n'; import { EsQueryAlertParams } from './types'; import { ValidationResult, builtInComparators } from '../../../../triggers_actions_ui/public'; +import { isSearchSourceAlert } from './util'; export const validateExpression = (alertParams: EsQueryAlertParams): ValidationResult => { - const { index, timeField, esQuery, size, threshold, timeWindowSize, thresholdComparator } = - alertParams; + const { size, threshold, timeWindowSize, thresholdComparator } = alertParams; const validationResult = { errors: {} }; const errors = { index: new Array(), @@ -22,46 +22,9 @@ export const validateExpression = (alertParams: EsQueryAlertParams): ValidationR threshold1: new Array(), thresholdComparator: new Array(), timeWindowSize: new Array(), + searchConfiguration: new Array(), }; validationResult.errors = errors; - if (!index || index.length === 0) { - errors.index.push( - i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredIndexText', { - defaultMessage: 'Index is required.', - }) - ); - } - if (!timeField) { - errors.timeField.push( - i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredTimeFieldText', { - defaultMessage: 'Time field is required.', - }) - ); - } - if (!esQuery) { - errors.esQuery.push( - i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredQueryText', { - defaultMessage: 'Elasticsearch query is required.', - }) - ); - } else { - try { - const parsedQuery = JSON.parse(esQuery); - if (!parsedQuery.query) { - errors.esQuery.push( - i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredEsQueryText', { - defaultMessage: `Query field is required.`, - }) - ); - } - } catch (err) { - errors.esQuery.push( - i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.jsonQueryText', { - defaultMessage: 'Query must be valid JSON.', - }) - ); - } - } if (!threshold || threshold.length === 0 || threshold[0] === undefined) { errors.threshold0.push( i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredThreshold0Text', { @@ -96,6 +59,7 @@ export const validateExpression = (alertParams: EsQueryAlertParams): ValidationR }) ); } + if (!size) { errors.size.push( i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredSizeText', { @@ -111,5 +75,66 @@ export const validateExpression = (alertParams: EsQueryAlertParams): ValidationR }) ); } + + /** + * Skip esQuery and index params check if it is search source alert, + * since it should contain searchConfiguration instead of esQuery and index. + */ + const isSearchSource = isSearchSourceAlert(alertParams); + if (isSearchSource) { + if (!alertParams.searchConfiguration) { + errors.searchConfiguration.push( + i18n.translate( + 'xpack.stackAlerts.esQuery.ui.validation.error.requiredSearchConfiguration', + { + defaultMessage: 'Search source configuration is required.', + } + ) + ); + } + return validationResult; + } + + if (!alertParams.index || alertParams.index.length === 0) { + errors.index.push( + i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredIndexText', { + defaultMessage: 'Index is required.', + }) + ); + } + + if (!alertParams.timeField) { + errors.timeField.push( + i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredTimeFieldText', { + defaultMessage: 'Time field is required.', + }) + ); + } + + if (!alertParams.esQuery) { + errors.esQuery.push( + i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredQueryText', { + defaultMessage: 'Elasticsearch query is required.', + }) + ); + } else { + try { + const parsedQuery = JSON.parse(alertParams.esQuery); + if (!parsedQuery.query) { + errors.esQuery.push( + i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredEsQueryText', { + defaultMessage: `Query field is required.`, + }) + ); + } + } catch (err) { + errors.esQuery.push( + i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.jsonQueryText', { + defaultMessage: 'Query must be valid JSON.', + }) + ); + } + } + return validationResult; }; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/index.ts b/x-pack/plugins/stack_alerts/public/alert_types/index.ts index d2f0f1860417b..c0a0f7b6548cf 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/index.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/index.ts @@ -10,15 +10,18 @@ import { getAlertType as getThresholdAlertType } from './threshold'; import { getAlertType as getEsQueryAlertType } from './es_query'; import { Config } from '../../common'; import { TriggersAndActionsUIPublicPluginSetup } from '../../../triggers_actions_ui/public'; +import { PluginSetupContract as AlertingSetup } from '../../../alerting/public'; export function registerAlertTypes({ ruleTypeRegistry, config, + alerting, }: { ruleTypeRegistry: TriggersAndActionsUIPublicPluginSetup['ruleTypeRegistry']; config: Config; + alerting: AlertingSetup; }) { ruleTypeRegistry.register(getGeoContainmentAlertType()); ruleTypeRegistry.register(getThresholdAlertType()); - ruleTypeRegistry.register(getEsQueryAlertType()); + ruleTypeRegistry.register(getEsQueryAlertType(alerting)); } diff --git a/x-pack/plugins/stack_alerts/public/plugin.tsx b/x-pack/plugins/stack_alerts/public/plugin.tsx index f636139571ca8..8d004523558fb 100644 --- a/x-pack/plugins/stack_alerts/public/plugin.tsx +++ b/x-pack/plugins/stack_alerts/public/plugin.tsx @@ -9,12 +9,14 @@ import { CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; import { TriggersAndActionsUIPublicPluginSetup } from '../../../plugins/triggers_actions_ui/public'; import { registerAlertTypes } from './alert_types'; import { Config } from '../common'; +import { PluginSetupContract as AlertingSetup } from '../../alerting/public'; export type Setup = void; export type Start = void; export interface StackAlertsPublicSetupDeps { triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; + alerting: AlertingSetup; } export class StackAlertsPublicPlugin implements Plugin { @@ -24,10 +26,11 @@ export class StackAlertsPublicPlugin implements Plugin(), + alerting, }); } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.test.ts index 9d4edd83a3913..468729fb2120d 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.test.ts @@ -7,6 +7,7 @@ import { EsQueryAlertActionContext, addMessages } from './action_context'; import { EsQueryAlertParamsSchema } from './alert_type_params'; +import { OnlyEsQueryAlertParams } from './types'; describe('ActionContext', () => { it('generates expected properties', async () => { @@ -19,12 +20,13 @@ describe('ActionContext', () => { timeWindowUnit: 'm', thresholdComparator: '>', threshold: [4], - }); + }) as OnlyEsQueryAlertParams; const base: EsQueryAlertActionContext = { date: '2020-01-01T00:00:00.000Z', value: 42, conditions: 'count greater than 4', hits: [], + link: 'link-mock', }; const context = addMessages({ name: '[alert-name]' }, base, params); expect(context.title).toMatchInlineSnapshot(`"alert '[alert-name]' matched query"`); @@ -33,7 +35,8 @@ describe('ActionContext', () => { - Value: 42 - Conditions Met: count greater than 4 over 5m -- Timestamp: 2020-01-01T00:00:00.000Z` +- Timestamp: 2020-01-01T00:00:00.000Z +- Link: link-mock` ); }); @@ -47,12 +50,13 @@ describe('ActionContext', () => { timeWindowUnit: 'm', thresholdComparator: 'between', threshold: [4, 5], - }); + }) as OnlyEsQueryAlertParams; const base: EsQueryAlertActionContext = { date: '2020-01-01T00:00:00.000Z', value: 4, conditions: 'count between 4 and 5', hits: [], + link: 'link-mock', }; const context = addMessages({ name: '[alert-name]' }, base, params); expect(context.title).toMatchInlineSnapshot(`"alert '[alert-name]' matched query"`); @@ -61,7 +65,8 @@ describe('ActionContext', () => { - Value: 4 - Conditions Met: count between 4 and 5 over 5m -- Timestamp: 2020-01-01T00:00:00.000Z` +- Timestamp: 2020-01-01T00:00:00.000Z +- Link: link-mock` ); }); }); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.ts index f4886e3c055a2..e340ba6505506 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { AlertExecutorOptions, AlertInstanceContext } from '../../../../alerting/server'; -import { EsQueryAlertParams } from './alert_type_params'; +import { OnlyEsQueryAlertParams, OnlySearchSourceAlertParams } from './types'; // alert type context provided to actions @@ -30,12 +30,15 @@ export interface EsQueryAlertActionContext extends AlertInstanceContext { conditions: string; // query matches hits: estypes.SearchHit[]; + // a link to see records that triggered the alert for Discover alert + // a link which navigates to stack management in case of Elastic query alert + link: string; } export function addMessages( alertInfo: AlertInfo, baseContext: EsQueryAlertActionContext, - params: EsQueryAlertParams + params: OnlyEsQueryAlertParams | OnlySearchSourceAlertParams ): ActionContext { const title = i18n.translate('xpack.stackAlerts.esQuery.alertTypeContextSubjectTitle', { defaultMessage: `alert '{name}' matched query`, @@ -50,13 +53,15 @@ export function addMessages( - Value: {value} - Conditions Met: {conditions} over {window} -- Timestamp: {date}`, +- Timestamp: {date} +- Link: {link}`, values: { name: alertInfo.name, value: baseContext.value, conditions: baseContext.conditions, window, date: baseContext.date, + link: baseContext.link, }, }); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts index e117f1db008f0..18f182d32deb2 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts @@ -14,18 +14,23 @@ import { AlertInstanceMock, } from '../../../../alerting/server/mocks'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; -import { getAlertType, ConditionMetAlertInstanceId, ActionGroupId } from './alert_type'; +import { getAlertType } from './alert_type'; import { EsQueryAlertParams, EsQueryAlertState } from './alert_type_params'; import { ActionContext } from './action_context'; import { ESSearchResponse, ESSearchRequest } from '../../../../../../src/core/types/elasticsearch'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from '../../../../../../src/core/server/elasticsearch/client/mocks'; +import { coreMock } from '../../../../../../src/core/server/mocks'; +import { ActionGroupId, ConditionMetAlertInstanceId } from './constants'; +import { OnlyEsQueryAlertParams, OnlySearchSourceAlertParams } from './types'; +import { searchSourceInstanceMock } from 'src/plugins/data/common/search/search_source/mocks'; +import { Comparator } from '../../../common/comparator_types'; -describe('alertType', () => { - const logger = loggingSystemMock.create().get(); - - const alertType = getAlertType(logger); +const logger = loggingSystemMock.create().get(); +const coreSetup = coreMock.createSetup(); +const alertType = getAlertType(logger, coreSetup); +describe('alertType', () => { it('alert type creation structure is the expected value', async () => { expect(alertType.id).toBe('.es-query'); expect(alertType.name).toBe('Elasticsearch query'); @@ -58,642 +63,514 @@ describe('alertType', () => { "description": "A string that describes the threshold condition.", "name": "conditions", }, - ], - "params": Array [ Object { - "description": "The index the query was run against.", - "name": "index", - }, - Object { - "description": "The string representation of the Elasticsearch query.", - "name": "esQuery", + "description": "Navigate to Discover and show the records that triggered + the alert when the rule is created in Discover. Otherwise, navigate to the status page for the rule.", + "name": "link", }, + ], + "params": Array [ Object { "description": "The number of hits to retrieve for each query.", "name": "size", }, Object { - "description": "An array of values to use as the threshold; 'between' and 'notBetween' require two values, the others require one.", + "description": "An array of values to use as the threshold. 'between' and 'notBetween' require two values.", "name": "threshold", }, Object { - "description": "A function to determine if the threshold has been met.", + "description": "A function to determine if the threshold was met.", "name": "thresholdComparator", }, + Object { + "description": "Serialized search source fields used to fetch the documents from Elasticsearch.", + "name": "searchConfiguration", + }, + Object { + "description": "The string representation of the Elasticsearch query.", + "name": "esQuery", + }, + Object { + "description": "The index the query was run against.", + "name": "index", + }, ], } `); }); - it('validator succeeds with valid params', async () => { - const params: Partial> = { - index: ['index-name'], - timeField: 'time-field', - esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, - size: 100, - timeWindowSize: 5, - timeWindowUnit: 'm', - thresholdComparator: '<', - threshold: [0], - }; + describe('elasticsearch query', () => { + it('validator succeeds with valid es query params', async () => { + const params: Partial> = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: Comparator.LT, + threshold: [0], + }; + + expect(alertType.validate?.params?.validate(params)).toBeTruthy(); + }); - expect(alertType.validate?.params?.validate(params)).toBeTruthy(); - }); + it('validator fails with invalid es query params - threshold', async () => { + const paramsSchema = alertType.validate?.params; + if (!paramsSchema) throw new Error('params validator not set'); + + const params: Partial> = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: Comparator.BETWEEN, + threshold: [0], + }; + + expect(() => paramsSchema.validate(params)).toThrowErrorMatchingInlineSnapshot( + `"[threshold]: must have two elements for the \\"between\\" comparator"` + ); + }); - it('validator fails with invalid params - threshold', async () => { - const paramsSchema = alertType.validate?.params; - if (!paramsSchema) throw new Error('params validator not set'); + it('alert executor handles no documents returned by ES', async () => { + const params: OnlyEsQueryAlertParams = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: Comparator.BETWEEN, + threshold: [0], + }; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + + const searchResult: ESSearchResponse = generateResults([]); + alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise(searchResult) + ); + + const result = await invokeExecutor({ params, alertServices }); + + expect(alertServices.alertFactory.create).not.toHaveBeenCalled(); + + expect(result).toMatchInlineSnapshot(` + Object { + "latestTimestamp": undefined, + } + `); + }); - const params: Partial> = { - index: ['index-name'], - timeField: 'time-field', - esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, - size: 100, - timeWindowSize: 5, - timeWindowUnit: 'm', - thresholdComparator: 'between', - threshold: [0], - }; + it('alert executor returns the latestTimestamp of the newest detected document', async () => { + const params: OnlyEsQueryAlertParams = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: Comparator.GT, + threshold: [0], + }; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + + const newestDocumentTimestamp = Date.now(); + + const searchResult: ESSearchResponse = generateResults([ + { + 'time-field': newestDocumentTimestamp, + }, + { + 'time-field': newestDocumentTimestamp - 1000, + }, + { + 'time-field': newestDocumentTimestamp - 2000, + }, + ]); + alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise(searchResult) + ); - expect(() => paramsSchema.validate(params)).toThrowErrorMatchingInlineSnapshot( - `"[threshold]: must have two elements for the \\"between\\" comparator"` - ); - }); + const result = await invokeExecutor({ params, alertServices }); - it('alert executor handles no documentes returned by ES', async () => { - const params: EsQueryAlertParams = { - index: ['index-name'], - timeField: 'time-field', - esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, - size: 100, - timeWindowSize: 5, - timeWindowUnit: 'm', - thresholdComparator: 'between', - threshold: [0], - }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); - - const searchResult: ESSearchResponse = generateResults([]); - alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise(searchResult) - ); - - const result = await alertType.executor({ - alertId: uuid.v4(), - executionId: uuid.v4(), - startedAt: new Date(), - previousStartedAt: new Date(), - services: alertServices as unknown as AlertServices< - EsQueryAlertState, - ActionContext, - typeof ActionGroupId - >, - params, - state: { + expect(alertServices.alertFactory.create).toHaveBeenCalledWith(ConditionMetAlertInstanceId); + const instance: AlertInstanceMock = alertServices.alertFactory.create.mock.results[0].value; + expect(instance.replaceState).toHaveBeenCalledWith({ latestTimestamp: undefined, - }, - spaceId: uuid.v4(), - name: uuid.v4(), - tags: [], - createdBy: null, - updatedBy: null, - rule: { - name: uuid.v4(), - tags: [], - consumer: '', - producer: '', - ruleTypeId: '', - ruleTypeName: '', - enabled: true, - schedule: { - interval: '1h', - }, - actions: [], - createdBy: null, - updatedBy: null, - createdAt: new Date(), - updatedAt: new Date(), - throttle: null, - notifyWhen: null, - }, - }); + dateStart: expect.any(String), + dateEnd: expect.any(String), + }); - expect(alertServices.alertFactory.create).not.toHaveBeenCalled(); + expect(result).toMatchObject({ + latestTimestamp: new Date(newestDocumentTimestamp).toISOString(), + }); + }); - expect(result).toMatchInlineSnapshot(` - Object { - "latestTimestamp": undefined, - } - `); - }); + it('alert executor correctly handles numeric time fields that were stored by legacy rules prior to v7.12.1', async () => { + const params: OnlyEsQueryAlertParams = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: Comparator.GT, + threshold: [0], + }; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + + const previousTimestamp = Date.now(); + const newestDocumentTimestamp = previousTimestamp + 1000; + + alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise( + generateResults([ + { + 'time-field': newestDocumentTimestamp, + }, + ]) + ) + ); + + const result = await invokeExecutor({ + params, + alertServices, + state: { + // @ts-expect-error previousTimestamp is numeric, but should be string (this was a bug prior to v7.12.1) + latestTimestamp: previousTimestamp, + }, + }); + + const instance: AlertInstanceMock = alertServices.alertFactory.create.mock.results[0].value; + expect(instance.replaceState).toHaveBeenCalledWith({ + // ensure the invalid "latestTimestamp" in the state is stored as an ISO string going forward + latestTimestamp: new Date(previousTimestamp).toISOString(), + dateStart: expect.any(String), + dateEnd: expect.any(String), + }); + + expect(result).toMatchObject({ + latestTimestamp: new Date(newestDocumentTimestamp).toISOString(), + }); + }); - it('alert executor returns the latestTimestamp of the newest detected document', async () => { - const params: EsQueryAlertParams = { - index: ['index-name'], - timeField: 'time-field', - esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, - size: 100, - timeWindowSize: 5, - timeWindowUnit: 'm', - thresholdComparator: '>', - threshold: [0], - }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + it('alert executor ignores previous invalid latestTimestamp values stored by legacy rules prior to v7.12.1', async () => { + const params: OnlyEsQueryAlertParams = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: Comparator.GT, + threshold: [0], + }; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + + const oldestDocumentTimestamp = Date.now(); + + alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise( + generateResults([ + { + 'time-field': oldestDocumentTimestamp, + }, + { + 'time-field': oldestDocumentTimestamp - 1000, + }, + ]) + ) + ); - const newestDocumentTimestamp = Date.now(); + const result = await invokeExecutor({ params, alertServices }); - const searchResult: ESSearchResponse = generateResults([ - { - 'time-field': newestDocumentTimestamp, - }, - { - 'time-field': newestDocumentTimestamp - 1000, - }, - { - 'time-field': newestDocumentTimestamp - 2000, - }, - ]); - alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise(searchResult) - ); - - const result = await alertType.executor({ - alertId: uuid.v4(), - executionId: uuid.v4(), - startedAt: new Date(), - previousStartedAt: new Date(), - services: alertServices as unknown as AlertServices< - EsQueryAlertState, - ActionContext, - typeof ActionGroupId - >, - params, - state: { + const instance: AlertInstanceMock = alertServices.alertFactory.create.mock.results[0].value; + expect(instance.replaceState).toHaveBeenCalledWith({ latestTimestamp: undefined, - }, - spaceId: uuid.v4(), - name: uuid.v4(), - tags: [], - createdBy: null, - updatedBy: null, - rule: { - name: uuid.v4(), - tags: [], - consumer: '', - producer: '', - ruleTypeId: '', - ruleTypeName: '', - enabled: true, - schedule: { - interval: '1h', - }, - actions: [], - createdBy: null, - updatedBy: null, - createdAt: new Date(), - updatedAt: new Date(), - throttle: null, - notifyWhen: null, - }, - }); - - expect(alertServices.alertFactory.create).toHaveBeenCalledWith(ConditionMetAlertInstanceId); - const instance: AlertInstanceMock = alertServices.alertFactory.create.mock.results[0].value; - expect(instance.replaceState).toHaveBeenCalledWith({ - latestTimestamp: undefined, - dateStart: expect.any(String), - dateEnd: expect.any(String), - }); + dateStart: expect.any(String), + dateEnd: expect.any(String), + }); - expect(result).toMatchObject({ - latestTimestamp: new Date(newestDocumentTimestamp).toISOString(), + expect(result).toMatchObject({ + latestTimestamp: new Date(oldestDocumentTimestamp).toISOString(), + }); }); - }); - it('alert executor correctly handles numeric time fields that were stored by legacy rules prior to v7.12.1', async () => { - const params: EsQueryAlertParams = { - index: ['index-name'], - timeField: 'time-field', - esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, - size: 100, - timeWindowSize: 5, - timeWindowUnit: 'm', - thresholdComparator: '>', - threshold: [0], - }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); - - const previousTimestamp = Date.now(); - const newestDocumentTimestamp = previousTimestamp + 1000; + it('alert executor carries over the queried latestTimestamp in the alert state', async () => { + const params: OnlyEsQueryAlertParams = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: Comparator.GT, + threshold: [0], + }; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + + const oldestDocumentTimestamp = Date.now(); + + alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise( + generateResults([ + { + 'time-field': oldestDocumentTimestamp, + }, + ]) + ) + ); - alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise( - generateResults([ - { - 'time-field': newestDocumentTimestamp, - }, - ]) - ) - ); - - const executorOptions = { - alertId: uuid.v4(), - startedAt: new Date(), - previousStartedAt: new Date(), - services: alertServices as unknown as AlertServices< - EsQueryAlertState, - ActionContext, - typeof ActionGroupId - >, - params, - spaceId: uuid.v4(), - name: uuid.v4(), - tags: [], - createdBy: null, - updatedBy: null, - createdAt: new Date(), - updatedAt: new Date(), - consumer: '', - throttle: null, - notifyWhen: null, - schedule: { - interval: '1h', - }, - }; - const result = await alertType.executor({ - ...executorOptions, - state: { - // @ts-expect-error previousTimestamp is numeric, but should be string (this was a bug prior to v7.12.1) - latestTimestamp: previousTimestamp, - }, - }); + const result = await invokeExecutor({ params, alertServices }); - const instance: AlertInstanceMock = alertServices.alertFactory.create.mock.results[0].value; - expect(instance.replaceState).toHaveBeenCalledWith({ - // ensure the invalid "latestTimestamp" in the state is stored as an ISO string going forward - latestTimestamp: new Date(previousTimestamp).toISOString(), - dateStart: expect.any(String), - dateEnd: expect.any(String), + const instance: AlertInstanceMock = alertServices.alertFactory.create.mock.results[0].value; + expect(instance.replaceState).toHaveBeenCalledWith({ + latestTimestamp: undefined, + dateStart: expect.any(String), + dateEnd: expect.any(String), + }); + + expect(result).toMatchObject({ + latestTimestamp: new Date(oldestDocumentTimestamp).toISOString(), + }); + + const newestDocumentTimestamp = oldestDocumentTimestamp + 5000; + alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise( + generateResults([ + { + 'time-field': newestDocumentTimestamp, + }, + { + 'time-field': newestDocumentTimestamp - 1000, + }, + ]) + ) + ); + + const secondResult = await invokeExecutor({ + params, + alertServices, + state: result as EsQueryAlertState, + }); + + const existingInstance: AlertInstanceMock = + alertServices.alertFactory.create.mock.results[1].value; + expect(existingInstance.replaceState).toHaveBeenCalledWith({ + latestTimestamp: new Date(oldestDocumentTimestamp).toISOString(), + dateStart: expect.any(String), + dateEnd: expect.any(String), + }); + + expect(secondResult).toMatchObject({ + latestTimestamp: new Date(newestDocumentTimestamp).toISOString(), + }); }); - expect(result).toMatchObject({ - latestTimestamp: new Date(newestDocumentTimestamp).toISOString(), - }); - }); + it('alert executor ignores tie breaker sort values', async () => { + const params: OnlyEsQueryAlertParams = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: Comparator.GT, + threshold: [0], + }; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + + const oldestDocumentTimestamp = Date.now(); + + alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise( + generateResults( + [ + { + 'time-field': oldestDocumentTimestamp, + }, + { + 'time-field': oldestDocumentTimestamp - 1000, + }, + ], + true + ) + ) + ); - it('alert executor ignores previous invalid latestTimestamp values stored by legacy rules prior to v7.12.1', async () => { - const params: EsQueryAlertParams = { - index: ['index-name'], - timeField: 'time-field', - esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, - size: 100, - timeWindowSize: 5, - timeWindowUnit: 'm', - thresholdComparator: '>', - threshold: [0], - }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + const result = await invokeExecutor({ params, alertServices }); - const oldestDocumentTimestamp = Date.now(); + const instance: AlertInstanceMock = alertServices.alertFactory.create.mock.results[0].value; + expect(instance.replaceState).toHaveBeenCalledWith({ + latestTimestamp: undefined, + dateStart: expect.any(String), + dateEnd: expect.any(String), + }); - alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise( - generateResults([ - { - 'time-field': oldestDocumentTimestamp, - }, - { - 'time-field': oldestDocumentTimestamp - 1000, - }, - ]) - ) - ); - - const result = await alertType.executor({ - alertId: uuid.v4(), - executionId: uuid.v4(), - startedAt: new Date(), - previousStartedAt: new Date(), - services: alertServices as unknown as AlertServices< - EsQueryAlertState, - ActionContext, - typeof ActionGroupId - >, - params, - state: { - // inaalid legacy `latestTimestamp` - latestTimestamp: 'FaslK3QBySSL_rrj9zM5', - }, - spaceId: uuid.v4(), - name: uuid.v4(), - tags: [], - createdBy: null, - updatedBy: null, - rule: { - name: uuid.v4(), - tags: [], - consumer: '', - producer: '', - ruleTypeId: '', - ruleTypeName: '', - enabled: true, - schedule: { - interval: '1h', - }, - actions: [], - createdBy: null, - updatedBy: null, - createdAt: new Date(), - updatedAt: new Date(), - throttle: null, - notifyWhen: null, - }, + expect(result).toMatchObject({ + latestTimestamp: new Date(oldestDocumentTimestamp).toISOString(), + }); }); - const instance: AlertInstanceMock = alertServices.alertFactory.create.mock.results[0].value; - expect(instance.replaceState).toHaveBeenCalledWith({ - latestTimestamp: undefined, - dateStart: expect.any(String), - dateEnd: expect.any(String), - }); + it('alert executor ignores results with no sort values', async () => { + const params: OnlyEsQueryAlertParams = { + index: ['index-name'], + timeField: 'time-field', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: Comparator.GT, + threshold: [0], + }; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + + const oldestDocumentTimestamp = Date.now(); + + alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise( + generateResults( + [ + { + 'time-field': oldestDocumentTimestamp, + }, + { + 'time-field': oldestDocumentTimestamp - 1000, + }, + ], + true, + true + ) + ) + ); + + const result = await invokeExecutor({ params, alertServices }); + + const instance: AlertInstanceMock = alertServices.alertFactory.create.mock.results[0].value; + expect(instance.replaceState).toHaveBeenCalledWith({ + latestTimestamp: undefined, + dateStart: expect.any(String), + dateEnd: expect.any(String), + }); - expect(result).toMatchObject({ - latestTimestamp: new Date(oldestDocumentTimestamp).toISOString(), + expect(result).toMatchObject({ + latestTimestamp: new Date(oldestDocumentTimestamp - 1000).toISOString(), + }); }); }); - it('alert executor carries over the queried latestTimestamp in the alert state', async () => { - const params: EsQueryAlertParams = { - index: ['index-name'], - timeField: 'time-field', - esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + describe('search source query', () => { + const dataViewMock = { + id: 'test-id', + title: 'test-title', + timeFieldName: 'time-field', + fields: [ + { + name: 'message', + type: 'string', + displayName: 'message', + scripted: false, + filterable: false, + aggregatable: false, + }, + { + name: 'timestamp', + type: 'date', + displayName: 'timestamp', + scripted: false, + filterable: false, + aggregatable: false, + }, + ], + }; + const defaultParams: OnlySearchSourceAlertParams = { size: 100, timeWindowSize: 5, timeWindowUnit: 'm', - thresholdComparator: '>', + thresholdComparator: Comparator.LT, threshold: [0], + searchConfiguration: {}, + searchType: 'searchSource', }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); - - const oldestDocumentTimestamp = Date.now(); - - alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise( - generateResults([ - { - 'time-field': oldestDocumentTimestamp, - }, - ]) - ) - ); - - const executorOptions = { - alertId: uuid.v4(), - executionId: uuid.v4(), - startedAt: new Date(), - previousStartedAt: new Date(), - services: alertServices as unknown as AlertServices< - EsQueryAlertState, - ActionContext, - typeof ActionGroupId - >, - params, - state: { - latestTimestamp: undefined, - }, - spaceId: uuid.v4(), - name: uuid.v4(), - tags: [], - createdBy: null, - updatedBy: null, - rule: { - name: uuid.v4(), - tags: [], - consumer: '', - producer: '', - ruleTypeId: '', - ruleTypeName: '', - enabled: true, - schedule: { - interval: '1h', - }, - actions: [], - createdBy: null, - updatedBy: null, - createdAt: new Date(), - updatedAt: new Date(), - throttle: null, - notifyWhen: null, - }, - }; - const result = await alertType.executor(executorOptions); - const instance: AlertInstanceMock = alertServices.alertFactory.create.mock.results[0].value; - expect(instance.replaceState).toHaveBeenCalledWith({ - latestTimestamp: undefined, - dateStart: expect.any(String), - dateEnd: expect.any(String), + afterAll(() => { + jest.resetAllMocks(); }); - expect(result).toMatchObject({ - latestTimestamp: new Date(oldestDocumentTimestamp).toISOString(), + it('validator succeeds with valid search source params', async () => { + expect(alertType.validate?.params?.validate(defaultParams)).toBeTruthy(); }); - const newestDocumentTimestamp = oldestDocumentTimestamp + 5000; - alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise( - generateResults([ - { - 'time-field': newestDocumentTimestamp, - }, - { - 'time-field': newestDocumentTimestamp - 1000, - }, - ]) - ) - ); - - const secondResult = await alertType.executor({ - ...executorOptions, - state: result as EsQueryAlertState, - }); - const existingInstance: AlertInstanceMock = - alertServices.alertFactory.create.mock.results[1].value; - expect(existingInstance.replaceState).toHaveBeenCalledWith({ - latestTimestamp: new Date(oldestDocumentTimestamp).toISOString(), - dateStart: expect.any(String), - dateEnd: expect.any(String), + it('validator fails with invalid search source params - esQuery provided', async () => { + const paramsSchema = alertType.validate?.params!; + const params: Partial> = { + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: Comparator.LT, + threshold: [0], + esQuery: '', + searchType: 'searchSource', + }; + + expect(() => paramsSchema.validate(params)).toThrowErrorMatchingInlineSnapshot( + `"[esQuery]: a value wasn't expected to be present"` + ); }); - expect(secondResult).toMatchObject({ - latestTimestamp: new Date(newestDocumentTimestamp).toISOString(), - }); - }); + it('alert executor handles no documents returned by ES', async () => { + const params = defaultParams; + const searchResult: ESSearchResponse = generateResults([]); + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); - it('alert executor ignores tie breaker sort values', async () => { - const params: EsQueryAlertParams = { - index: ['index-name'], - timeField: 'time-field', - esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, - size: 100, - timeWindowSize: 5, - timeWindowUnit: 'm', - thresholdComparator: '>', - threshold: [0], - }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + (searchSourceInstanceMock.getField as jest.Mock).mockImplementationOnce((name: string) => { + if (name === 'index') { + return dataViewMock; + } + }); + (searchSourceInstanceMock.fetch as jest.Mock).mockResolvedValueOnce(searchResult); - const oldestDocumentTimestamp = Date.now(); + await invokeExecutor({ params, alertServices }); - alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise( - generateResults( - [ - { - 'time-field': oldestDocumentTimestamp, - }, - { - 'time-field': oldestDocumentTimestamp - 1000, - }, - ], - true - ) - ) - ); - - const result = await alertType.executor({ - alertId: uuid.v4(), - executionId: uuid.v4(), - startedAt: new Date(), - previousStartedAt: new Date(), - services: alertServices as unknown as AlertServices< - EsQueryAlertState, - ActionContext, - typeof ActionGroupId - >, - params, - state: { - latestTimestamp: undefined, - }, - spaceId: uuid.v4(), - name: uuid.v4(), - tags: [], - createdBy: null, - updatedBy: null, - rule: { - name: uuid.v4(), - tags: [], - consumer: '', - producer: '', - ruleTypeId: '', - ruleTypeName: '', - enabled: true, - schedule: { - interval: '1h', - }, - actions: [], - createdBy: null, - updatedBy: null, - createdAt: new Date(), - updatedAt: new Date(), - throttle: null, - notifyWhen: null, - }, + expect(alertServices.alertFactory.create).not.toHaveBeenCalled(); }); - const instance: AlertInstanceMock = alertServices.alertFactory.create.mock.results[0].value; - expect(instance.replaceState).toHaveBeenCalledWith({ - latestTimestamp: undefined, - dateStart: expect.any(String), - dateEnd: expect.any(String), - }); + it('alert executor throws an error when index does not have time field', async () => { + const params = defaultParams; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); - expect(result).toMatchObject({ - latestTimestamp: new Date(oldestDocumentTimestamp).toISOString(), + (searchSourceInstanceMock.getField as jest.Mock).mockImplementationOnce((name: string) => { + if (name === 'index') { + return { dataViewMock, timeFieldName: undefined }; + } + }); + + await expect(invokeExecutor({ params, alertServices })).rejects.toThrow( + 'Invalid data view without timeFieldName.' + ); }); - }); - it('alert executor ignores results with no sort values', async () => { - const params: EsQueryAlertParams = { - index: ['index-name'], - timeField: 'time-field', - esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, - size: 100, - timeWindowSize: 5, - timeWindowUnit: 'm', - thresholdComparator: '>', - threshold: [0], - }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + it('alert executor schedule actions when condition met', async () => { + const params = { ...defaultParams, thresholdComparator: Comparator.GT_OR_EQ, threshold: [3] }; + const alertServices: AlertServicesMock = alertsMock.createAlertServices(); - const oldestDocumentTimestamp = Date.now(); + (searchSourceInstanceMock.getField as jest.Mock).mockImplementationOnce((name: string) => { + if (name === 'index') { + return dataViewMock; + } + }); - alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise( - generateResults( - [ - { - 'time-field': oldestDocumentTimestamp, - }, - { - 'time-field': oldestDocumentTimestamp - 1000, - }, - ], - true, - true - ) - ) - ); - - const result = await alertType.executor({ - alertId: uuid.v4(), - executionId: uuid.v4(), - startedAt: new Date(), - previousStartedAt: new Date(), - services: alertServices as unknown as AlertServices< - EsQueryAlertState, - ActionContext, - typeof ActionGroupId - >, - params, - state: { - latestTimestamp: undefined, - }, - spaceId: uuid.v4(), - name: uuid.v4(), - tags: [], - createdBy: null, - updatedBy: null, - rule: { - name: uuid.v4(), - tags: [], - consumer: '', - producer: '', - ruleTypeId: '', - ruleTypeName: '', - enabled: true, - schedule: { - interval: '1h', - }, - actions: [], - createdBy: null, - updatedBy: null, - createdAt: new Date(), - updatedAt: new Date(), - throttle: null, - notifyWhen: null, - }, - }); + (searchSourceInstanceMock.fetch as jest.Mock).mockResolvedValueOnce({ + hits: { total: 3, hits: [{}, {}, {}] }, + }); - const instance: AlertInstanceMock = alertServices.alertFactory.create.mock.results[0].value; - expect(instance.replaceState).toHaveBeenCalledWith({ - latestTimestamp: undefined, - dateStart: expect.any(String), - dateEnd: expect.any(String), - }); + await invokeExecutor({ params, alertServices }); - expect(result).toMatchObject({ - latestTimestamp: new Date(oldestDocumentTimestamp - 1000).toISOString(), + const instance: AlertInstanceMock = alertServices.alertFactory.create.mock.results[0].value; + expect(instance.scheduleActions).toHaveBeenCalled(); }); }); }); @@ -736,3 +613,54 @@ function generateResults( }, }; } + +async function invokeExecutor({ + params, + alertServices, + state, +}: { + params: OnlySearchSourceAlertParams | OnlyEsQueryAlertParams; + alertServices: AlertServicesMock; + state?: EsQueryAlertState; +}) { + return await alertType.executor({ + alertId: uuid.v4(), + executionId: uuid.v4(), + startedAt: new Date(), + previousStartedAt: new Date(), + services: alertServices as unknown as AlertServices< + EsQueryAlertState, + ActionContext, + typeof ActionGroupId + >, + params: params as EsQueryAlertParams, + state: { + latestTimestamp: undefined, + ...state, + }, + spaceId: uuid.v4(), + name: uuid.v4(), + tags: [], + createdBy: null, + updatedBy: null, + rule: { + name: uuid.v4(), + tags: [], + consumer: '', + producer: '', + ruleTypeId: '', + ruleTypeName: '', + enabled: true, + schedule: { + interval: '1h', + }, + actions: [], + createdBy: null, + updatedBy: null, + createdAt: new Date(), + updatedAt: new Date(), + throttle: null, + notifyWhen: null, + }, + }); +} diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts index d0a23dd403419..86732a6803ee7 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts @@ -6,26 +6,23 @@ */ import { i18n } from '@kbn/i18n'; -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { Logger } from 'src/core/server'; -import { RuleType, AlertExecutorOptions } from '../../types'; -import { ActionContext, EsQueryAlertActionContext, addMessages } from './action_context'; +import { CoreSetup, Logger } from 'kibana/server'; +import { RuleType } from '../../types'; +import { ActionContext } from './action_context'; import { EsQueryAlertParams, EsQueryAlertParamsSchema, EsQueryAlertState, } from './alert_type_params'; import { STACK_ALERTS_FEATURE_ID } from '../../../common'; -import { ComparatorFns, getHumanReadableComparator } from '../lib'; -import { parseDuration } from '../../../../alerting/server'; -import { buildSortedEventsQuery } from '../../../common/build_sorted_events_query'; - -export const ES_QUERY_ID = '.es-query'; - -export const ActionGroupId = 'query matched'; -export const ConditionMetAlertInstanceId = 'query matched'; - -export function getAlertType(logger: Logger): RuleType< +import { ExecutorOptions } from './types'; +import { ActionGroupId, ES_QUERY_ID } from './constants'; +import { executor } from './executor'; + +export function getAlertType( + logger: Logger, + core: CoreSetup +): RuleType< EsQueryAlertParams, never, // Only use if defining useSavedObjectReferences hook EsQueryAlertState, @@ -101,14 +98,14 @@ export function getAlertType(logger: Logger): RuleType< 'xpack.stackAlerts.esQuery.actionVariableContextThresholdLabel', { defaultMessage: - "An array of values to use as the threshold; 'between' and 'notBetween' require two values, the others require one.", + "An array of values to use as the threshold. 'between' and 'notBetween' require two values.", } ); const actionVariableContextThresholdComparatorLabel = i18n.translate( 'xpack.stackAlerts.esQuery.actionVariableContextThresholdComparatorLabel', { - defaultMessage: 'A function to determine if the threshold has been met.', + defaultMessage: 'A function to determine if the threshold was met.', } ); @@ -119,6 +116,22 @@ export function getAlertType(logger: Logger): RuleType< } ); + const actionVariableSearchConfigurationLabel = i18n.translate( + 'xpack.stackAlerts.esQuery.actionVariableContextSearchConfigurationLabel', + { + defaultMessage: + 'Serialized search source fields used to fetch the documents from Elasticsearch.', + } + ); + + const actionVariableContextLinkLabel = i18n.translate( + 'xpack.stackAlerts.esQuery.actionVariableContextLinkLabel', + { + defaultMessage: `Navigate to Discover and show the records that triggered + the alert when the rule is created in Discover. Otherwise, navigate to the status page for the rule.`, + } + ); + return { id: ES_QUERY_ID, name: alertTypeName, @@ -135,214 +148,22 @@ export function getAlertType(logger: Logger): RuleType< { name: 'value', description: actionVariableContextValueLabel }, { name: 'hits', description: actionVariableContextHitsLabel }, { name: 'conditions', description: actionVariableContextConditionsLabel }, + { name: 'link', description: actionVariableContextLinkLabel }, ], params: [ - { name: 'index', description: actionVariableContextIndexLabel }, - { name: 'esQuery', description: actionVariableContextQueryLabel }, { name: 'size', description: actionVariableContextSizeLabel }, { name: 'threshold', description: actionVariableContextThresholdLabel }, { name: 'thresholdComparator', description: actionVariableContextThresholdComparatorLabel }, + { name: 'searchConfiguration', description: actionVariableSearchConfigurationLabel }, + { name: 'esQuery', description: actionVariableContextQueryLabel }, + { name: 'index', description: actionVariableContextIndexLabel }, ], }, minimumLicenseRequired: 'basic', isExportable: true, - executor, + executor: async (options: ExecutorOptions) => { + return await executor(logger, core, options); + }, producer: STACK_ALERTS_FEATURE_ID, }; - - async function executor( - options: AlertExecutorOptions< - EsQueryAlertParams, - EsQueryAlertState, - {}, - ActionContext, - typeof ActionGroupId - > - ) { - const { alertId, name, services, params, state } = options; - const { alertFactory, scopedClusterClient } = services; - const previousTimestamp = state.latestTimestamp; - - const esClient = scopedClusterClient.asCurrentUser; - const { parsedQuery, dateStart, dateEnd } = getSearchParams(params); - - const compareFn = ComparatorFns.get(params.thresholdComparator); - if (compareFn == null) { - throw new Error(getInvalidComparatorError(params.thresholdComparator)); - } - - // During each alert execution, we run the configured query, get a hit count - // (hits.total) and retrieve up to params.size hits. We - // evaluate the threshold condition using the value of hits.total. If the threshold - // condition is met, the hits are counted toward the query match and we update - // the alert state with the timestamp of the latest hit. In the next execution - // of the alert, the latestTimestamp will be used to gate the query in order to - // avoid counting a document multiple times. - - let timestamp: string | undefined = tryToParseAsDate(previousTimestamp); - const filter = timestamp - ? { - bool: { - filter: [ - parsedQuery.query, - { - bool: { - must_not: [ - { - bool: { - filter: [ - { - range: { - [params.timeField]: { - lte: timestamp, - format: 'strict_date_optional_time', - }, - }, - }, - ], - }, - }, - ], - }, - }, - ], - }, - } - : parsedQuery.query; - - const query = buildSortedEventsQuery({ - index: params.index, - from: dateStart, - to: dateEnd, - filter, - size: params.size, - sortOrder: 'desc', - searchAfterSortId: undefined, - timeField: params.timeField, - track_total_hits: true, - }); - - logger.debug(`alert ${ES_QUERY_ID}:${alertId} "${name}" query - ${JSON.stringify(query)}`); - - const { body: searchResult } = await esClient.search(query, { meta: true }); - - logger.debug( - `alert ${ES_QUERY_ID}:${alertId} "${name}" result - ${JSON.stringify(searchResult)}` - ); - - const numMatches = (searchResult.hits.total as estypes.SearchTotalHits).value; - - // apply the alert condition - const conditionMet = compareFn(numMatches, params.threshold); - - if (conditionMet) { - const humanFn = i18n.translate( - 'xpack.stackAlerts.esQuery.alertTypeContextConditionsDescription', - { - defaultMessage: `Number of matching documents is {thresholdComparator} {threshold}`, - values: { - thresholdComparator: getHumanReadableComparator(params.thresholdComparator), - threshold: params.threshold.join(' and '), - }, - } - ); - - const baseContext: EsQueryAlertActionContext = { - date: new Date().toISOString(), - value: numMatches, - conditions: humanFn, - hits: searchResult.hits.hits, - }; - - const actionContext = addMessages(options, baseContext, params); - const alertInstance = alertFactory.create(ConditionMetAlertInstanceId); - alertInstance - // store the params we would need to recreate the query that led to this alert instance - .replaceState({ latestTimestamp: timestamp, dateStart, dateEnd }) - .scheduleActions(ActionGroupId, actionContext); - - // update the timestamp based on the current search results - const firstValidTimefieldSort = getValidTimefieldSort( - searchResult.hits.hits.find((hit) => getValidTimefieldSort(hit.sort))?.sort - ); - if (firstValidTimefieldSort) { - timestamp = firstValidTimefieldSort; - } - } - - return { - latestTimestamp: timestamp, - }; - } -} - -function getValidTimefieldSort(sortValues: Array = []): undefined | string { - for (const sortValue of sortValues) { - const sortDate = tryToParseAsDate(sortValue); - if (sortDate) { - return sortDate; - } - } -} -function tryToParseAsDate(sortValue?: string | number | null): undefined | string { - const sortDate = typeof sortValue === 'string' ? Date.parse(sortValue) : sortValue; - if (sortDate && !isNaN(sortDate)) { - return new Date(sortDate).toISOString(); - } -} - -function getInvalidComparatorError(comparator: string) { - return i18n.translate('xpack.stackAlerts.esQuery.invalidComparatorErrorMessage', { - defaultMessage: 'invalid thresholdComparator specified: {comparator}', - values: { - comparator, - }, - }); -} - -function getInvalidWindowSizeError(windowValue: string) { - return i18n.translate('xpack.stackAlerts.esQuery.invalidWindowSizeErrorMessage', { - defaultMessage: 'invalid format for windowSize: "{windowValue}"', - values: { - windowValue, - }, - }); -} - -function getInvalidQueryError(query: string) { - return i18n.translate('xpack.stackAlerts.esQuery.invalidQueryErrorMessage', { - defaultMessage: 'invalid query specified: "{query}" - query must be JSON', - values: { - query, - }, - }); -} - -function getSearchParams(queryParams: EsQueryAlertParams) { - const date = Date.now(); - const { esQuery, timeWindowSize, timeWindowUnit } = queryParams; - - let parsedQuery; - try { - parsedQuery = JSON.parse(esQuery); - } catch (err) { - throw new Error(getInvalidQueryError(esQuery)); - } - - if (parsedQuery && !parsedQuery.query) { - throw new Error(getInvalidQueryError(esQuery)); - } - - const window = `${timeWindowSize}${timeWindowUnit}`; - let timeWindow: number; - try { - timeWindow = parseDuration(window); - } catch (err) { - throw new Error(getInvalidWindowSizeError(window)); - } - - const dateStart = new Date(date - timeWindow).toISOString(); - const dateEnd = new Date(date).toISOString(); - - return { parsedQuery, dateStart, dateEnd }; } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.test.ts index ab3ca6a2d4c31..62833725bba1c 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.test.ts @@ -7,6 +7,7 @@ import { TypeOf } from '@kbn/config-schema'; import type { Writable } from '@kbn/utility-types'; +import { Comparator } from '../../../common/comparator_types'; import { EsQueryAlertParamsSchema, EsQueryAlertParams, @@ -20,7 +21,7 @@ const DefaultParams: Writable> = { size: 100, timeWindowSize: 5, timeWindowUnit: 'm', - thresholdComparator: '>', + thresholdComparator: Comparator.GT, threshold: [0], }; diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.ts index de9c583ef885e..79618976d3aa0 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.ts @@ -6,10 +6,11 @@ */ import { i18n } from '@kbn/i18n'; -import { schema, TypeOf } from '@kbn/config-schema'; -import { ComparatorFnNames } from '../lib'; +import { schema, Type, TypeOf } from '@kbn/config-schema'; import { validateTimeWindowUnits } from '../../../../triggers_actions_ui/server'; import { AlertTypeState } from '../../../../alerting/server'; +import { Comparator } from '../../../common/comparator_types'; +import { ComparatorFnNames } from '../lib'; export const ES_QUERY_MAX_HITS_PER_EXECUTION = 10000; @@ -19,15 +20,39 @@ export interface EsQueryAlertState extends AlertTypeState { latestTimestamp: string | undefined; } -export const EsQueryAlertParamsSchemaProperties = { - index: schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), - timeField: schema.string({ minLength: 1 }), - esQuery: schema.string({ minLength: 1 }), +const EsQueryAlertParamsSchemaProperties = { size: schema.number({ min: 0, max: ES_QUERY_MAX_HITS_PER_EXECUTION }), timeWindowSize: schema.number({ min: 1 }), timeWindowUnit: schema.string({ validate: validateTimeWindowUnits }), threshold: schema.arrayOf(schema.number(), { minSize: 1, maxSize: 2 }), - thresholdComparator: schema.string({ validate: validateComparator }), + thresholdComparator: schema.string({ validate: validateComparator }) as Type, + searchType: schema.nullable(schema.literal('searchSource')), + // searchSource alert param only + searchConfiguration: schema.conditional( + schema.siblingRef('searchType'), + schema.literal('searchSource'), + schema.object({}, { unknowns: 'allow' }), + schema.never() + ), + // esQuery alert params only + esQuery: schema.conditional( + schema.siblingRef('searchType'), + schema.literal('searchSource'), + schema.never(), + schema.string({ minLength: 1 }) + ), + index: schema.conditional( + schema.siblingRef('searchType'), + schema.literal('searchSource'), + schema.never(), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }) + ), + timeField: schema.conditional( + schema.siblingRef('searchType'), + schema.literal('searchSource'), + schema.never(), + schema.string({ minLength: 1 }) + ), }; export const EsQueryAlertParamsSchema = schema.object(EsQueryAlertParamsSchemaProperties, { @@ -38,8 +63,7 @@ const betweenComparators = new Set(['between', 'notBetween']); // using direct type not allowed, circular reference, so body is typed to any function validateParams(anyParams: unknown): string | undefined { - const { esQuery, thresholdComparator, threshold }: EsQueryAlertParams = - anyParams as EsQueryAlertParams; + const { esQuery, thresholdComparator, threshold, searchType } = anyParams as EsQueryAlertParams; if (betweenComparators.has(thresholdComparator) && threshold.length === 1) { return i18n.translate('xpack.stackAlerts.esQuery.invalidThreshold2ErrorMessage', { @@ -51,6 +75,10 @@ function validateParams(anyParams: unknown): string | undefined { }); } + if (searchType === 'searchSource') { + return; + } + try { const parsedQuery = JSON.parse(esQuery); @@ -66,8 +94,8 @@ function validateParams(anyParams: unknown): string | undefined { } } -export function validateComparator(comparator: string): string | undefined { - if (ComparatorFnNames.has(comparator)) return; +function validateComparator(comparator: string): string | undefined { + if (ComparatorFnNames.has(comparator as Comparator)) return; return i18n.translate('xpack.stackAlerts.esQuery.invalidComparatorErrorMessage', { defaultMessage: 'invalid thresholdComparator specified: {comparator}', diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/constants.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/constants.ts new file mode 100644 index 0000000000000..700cba4680bff --- /dev/null +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/constants.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const ES_QUERY_ID = '.es-query'; +export const ActionGroupId = 'query matched'; +export const ConditionMetAlertInstanceId = 'query matched'; diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/executor.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/executor.test.ts new file mode 100644 index 0000000000000..670f76f5e19de --- /dev/null +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/executor.test.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getSearchParams, getValidTimefieldSort, tryToParseAsDate } from './executor'; +import { OnlyEsQueryAlertParams } from './types'; + +describe('es_query executor', () => { + const defaultProps = { + size: 3, + timeWindowSize: 5, + timeWindowUnit: 'm', + threshold: [], + thresholdComparator: '>=', + esQuery: '{ "query": "test-query" }', + index: ['test-index'], + timeField: '', + }; + describe('tryToParseAsDate', () => { + it.each<[string | number]>([['2019-01-01T00:00:00.000Z'], [1546300800000]])( + 'should parse as date correctly', + (value) => { + expect(tryToParseAsDate(value)).toBe('2019-01-01T00:00:00.000Z'); + } + ); + it.each<[string | null | undefined]>([[null], ['invalid date'], [undefined]])( + 'should not parse as date', + (value) => { + expect(tryToParseAsDate(value)).toBe(undefined); + } + ); + }); + + describe('getValidTimefieldSort', () => { + it('should return valid time field', () => { + const result = getValidTimefieldSort([ + null, + 'invalid date', + '2018-12-31T19:00:00.000Z', + 1546282800000, + ]); + expect(result).toEqual('2018-12-31T19:00:00.000Z'); + }); + }); + + describe('getSearchParams', () => { + it('should return search params correctly', () => { + const result = getSearchParams(defaultProps as OnlyEsQueryAlertParams); + expect(result.parsedQuery.query).toBe('test-query'); + }); + + it('should throw invalid query error', () => { + expect(() => + getSearchParams({ ...defaultProps, esQuery: '' } as OnlyEsQueryAlertParams) + ).toThrow('invalid query specified: "" - query must be JSON'); + }); + + it('should throw invalid query error due to missing query property', () => { + expect(() => + getSearchParams({ + ...defaultProps, + esQuery: '{ "someProperty": "test-query" }', + } as OnlyEsQueryAlertParams) + ).toThrow('invalid query specified: "{ "someProperty": "test-query" }" - query must be JSON'); + }); + + it('should throw invalid window size error', () => { + expect(() => + getSearchParams({ + ...defaultProps, + timeWindowSize: 5, + timeWindowUnit: 'r', + } as OnlyEsQueryAlertParams) + ).toThrow('invalid format for windowSize: "5r"'); + }); + }); +}); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/executor.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/executor.ts new file mode 100644 index 0000000000000..175695aca5f4d --- /dev/null +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/executor.ts @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { sha256 } from 'js-sha256'; +import { i18n } from '@kbn/i18n'; +import { CoreSetup, Logger } from 'kibana/server'; +import { addMessages, EsQueryAlertActionContext } from './action_context'; +import { ComparatorFns, getHumanReadableComparator } from '../lib'; +import { parseDuration } from '../../../../alerting/server'; +import { ExecutorOptions, OnlyEsQueryAlertParams, OnlySearchSourceAlertParams } from './types'; +import { ActionGroupId, ConditionMetAlertInstanceId } from './constants'; +import { fetchEsQuery } from './lib/fetch_es_query'; +import { EsQueryAlertParams } from './alert_type_params'; +import { fetchSearchSourceQuery } from './lib/fetch_search_source_query'; +import { Comparator } from '../../../common/comparator_types'; + +export async function executor( + logger: Logger, + core: CoreSetup, + options: ExecutorOptions +) { + const esQueryAlert = isEsQueryAlert(options); + const { alertId, name, services, params, state } = options; + const { alertFactory, scopedClusterClient, searchSourceClient } = services; + const currentTimestamp = new Date().toISOString(); + const publicBaseUrl = core.http.basePath.publicBaseUrl ?? ''; + + const compareFn = ComparatorFns.get(params.thresholdComparator); + if (compareFn == null) { + throw new Error(getInvalidComparatorError(params.thresholdComparator)); + } + let latestTimestamp: string | undefined = tryToParseAsDate(state.latestTimestamp); + + // During each alert execution, we run the configured query, get a hit count + // (hits.total) and retrieve up to params.size hits. We + // evaluate the threshold condition using the value of hits.total. If the threshold + // condition is met, the hits are counted toward the query match and we update + // the alert state with the timestamp of the latest hit. In the next execution + // of the alert, the latestTimestamp will be used to gate the query in order to + // avoid counting a document multiple times. + + const { numMatches, searchResult, dateStart, dateEnd } = esQueryAlert + ? await fetchEsQuery(alertId, name, params as OnlyEsQueryAlertParams, latestTimestamp, { + scopedClusterClient, + logger, + }) + : await fetchSearchSourceQuery( + alertId, + params as OnlySearchSourceAlertParams, + latestTimestamp, + { + searchSourceClient, + logger, + } + ); + + // apply the alert condition + const conditionMet = compareFn(numMatches, params.threshold); + + if (conditionMet) { + const base = publicBaseUrl; + const link = esQueryAlert + ? `${base}/app/management/insightsAndAlerting/triggersActions/rule/${alertId}` + : `${base}/app/discover#/viewAlert/${alertId}?from=${dateStart}&to=${dateEnd}&checksum=${getChecksum( + params + )}`; + + const conditions = getContextConditionsDescription( + params.thresholdComparator, + params.threshold + ); + const baseContext: EsQueryAlertActionContext = { + title: name, + date: currentTimestamp, + value: numMatches, + conditions, + hits: searchResult.hits.hits, + link, + }; + + const actionContext = addMessages(options, baseContext, params); + const alertInstance = alertFactory.create(ConditionMetAlertInstanceId); + alertInstance + // store the params we would need to recreate the query that led to this alert instance + .replaceState({ latestTimestamp, dateStart, dateEnd }) + .scheduleActions(ActionGroupId, actionContext); + + // update the timestamp based on the current search results + const firstValidTimefieldSort = getValidTimefieldSort( + searchResult.hits.hits.find((hit) => getValidTimefieldSort(hit.sort))?.sort + ); + if (firstValidTimefieldSort) { + latestTimestamp = firstValidTimefieldSort; + } + } + + return { latestTimestamp }; +} + +function getInvalidWindowSizeError(windowValue: string) { + return i18n.translate('xpack.stackAlerts.esQuery.invalidWindowSizeErrorMessage', { + defaultMessage: 'invalid format for windowSize: "{windowValue}"', + values: { + windowValue, + }, + }); +} + +function getInvalidQueryError(query: string) { + return i18n.translate('xpack.stackAlerts.esQuery.invalidQueryErrorMessage', { + defaultMessage: 'invalid query specified: "{query}" - query must be JSON', + values: { + query, + }, + }); +} + +export function getSearchParams(queryParams: OnlyEsQueryAlertParams) { + const date = Date.now(); + const { esQuery, timeWindowSize, timeWindowUnit } = queryParams; + + let parsedQuery; + try { + parsedQuery = JSON.parse(esQuery); + } catch (err) { + throw new Error(getInvalidQueryError(esQuery)); + } + + if (parsedQuery && !parsedQuery.query) { + throw new Error(getInvalidQueryError(esQuery)); + } + + const window = `${timeWindowSize}${timeWindowUnit}`; + let timeWindow: number; + try { + timeWindow = parseDuration(window); + } catch (err) { + throw new Error(getInvalidWindowSizeError(window)); + } + + const dateStart = new Date(date - timeWindow).toISOString(); + const dateEnd = new Date(date).toISOString(); + + return { parsedQuery, dateStart, dateEnd }; +} + +export function getValidTimefieldSort( + sortValues: Array = [] +): undefined | string { + for (const sortValue of sortValues) { + const sortDate = tryToParseAsDate(sortValue); + if (sortDate) { + return sortDate; + } + } +} + +export function tryToParseAsDate(sortValue?: string | number | null): undefined | string { + const sortDate = typeof sortValue === 'string' ? Date.parse(sortValue) : sortValue; + if (sortDate && !isNaN(sortDate)) { + return new Date(sortDate).toISOString(); + } +} + +export function isEsQueryAlert(options: ExecutorOptions) { + return options.params.searchType !== 'searchSource'; +} + +export function getChecksum(params: EsQueryAlertParams) { + return sha256.create().update(JSON.stringify(params)); +} + +export function getInvalidComparatorError(comparator: string) { + return i18n.translate('xpack.stackAlerts.esQuery.invalidComparatorErrorMessage', { + defaultMessage: 'invalid thresholdComparator specified: {comparator}', + values: { + comparator, + }, + }); +} + +export function getContextConditionsDescription(comparator: Comparator, threshold: number[]) { + return i18n.translate('xpack.stackAlerts.esQuery.alertTypeContextConditionsDescription', { + defaultMessage: 'Number of matching documents is {thresholdComparator} {threshold}', + values: { + thresholdComparator: getHumanReadableComparator(comparator), + threshold: threshold.join(' and '), + }, + }); +} diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/index.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/index.ts index ffe0388e64216..99f9eb1795688 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/index.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/index.ts @@ -5,16 +5,17 @@ * 2.0. */ -import { Logger } from 'src/core/server'; +import { CoreSetup, Logger } from 'src/core/server'; import { AlertingSetup } from '../../types'; import { getAlertType } from './alert_type'; interface RegisterParams { logger: Logger; alerting: AlertingSetup; + core: CoreSetup; } export function register(params: RegisterParams) { - const { logger, alerting } = params; - alerting.registerType(getAlertType(logger)); + const { logger, alerting, core } = params; + alerting.registerType(getAlertType(logger, core)); } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_es_query.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_es_query.ts new file mode 100644 index 0000000000000..519c392753c58 --- /dev/null +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_es_query.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { IScopedClusterClient, Logger } from 'kibana/server'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { OnlyEsQueryAlertParams } from '../types'; +import { buildSortedEventsQuery } from '../../../../common/build_sorted_events_query'; +import { ES_QUERY_ID } from '../constants'; +import { getSearchParams } from './get_search_params'; + +/** + * Fetching matching documents for a given alert from elasticsearch by a given index and query + */ +export async function fetchEsQuery( + alertId: string, + name: string, + params: OnlyEsQueryAlertParams, + timestamp: string | undefined, + services: { + scopedClusterClient: IScopedClusterClient; + logger: Logger; + } +) { + const { scopedClusterClient, logger } = services; + const esClient = scopedClusterClient.asCurrentUser; + const { parsedQuery, dateStart, dateEnd } = getSearchParams(params); + + const filter = timestamp + ? { + bool: { + filter: [ + parsedQuery.query, + { + bool: { + must_not: [ + { + bool: { + filter: [ + { + range: { + [params.timeField]: { + lte: timestamp, + format: 'strict_date_optional_time', + }, + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + } + : parsedQuery.query; + + const query = buildSortedEventsQuery({ + index: params.index, + from: dateStart, + to: dateEnd, + filter, + size: params.size, + sortOrder: 'desc', + searchAfterSortId: undefined, + timeField: params.timeField, + track_total_hits: true, + }); + + logger.debug( + `es query alert ${ES_QUERY_ID}:${alertId} "${name}" query - ${JSON.stringify(query)}` + ); + + const { body: searchResult } = await esClient.search(query, { meta: true }); + + logger.debug( + ` es query alert ${ES_QUERY_ID}:${alertId} "${name}" result - ${JSON.stringify(searchResult)}` + ); + return { + numMatches: (searchResult.hits.total as estypes.SearchTotalHits).value, + searchResult, + dateStart, + dateEnd, + }; +} diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.test.ts new file mode 100644 index 0000000000000..1e6aac5dcb035 --- /dev/null +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.test.ts @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { OnlySearchSourceAlertParams } from '../types'; +import { createSearchSourceMock } from 'src/plugins/data/common/search/search_source/mocks'; +import { updateSearchSource } from './fetch_search_source_query'; +import { stubbedSavedObjectIndexPattern } from '../../../../../../../src/plugins/data_views/common/data_view.stub'; +import { DataView } from '../../../../../../../src/plugins/data_views/common'; +import { fieldFormatsMock } from '../../../../../../../src/plugins/field_formats/common/mocks'; +import { Comparator } from '../../../../common/comparator_types'; + +const createDataView = () => { + const id = 'test-id'; + const { + type, + version, + attributes: { timeFieldName, fields, title }, + } = stubbedSavedObjectIndexPattern(id); + + return new DataView({ + spec: { id, type, version, timeFieldName, fields: JSON.parse(fields), title }, + fieldFormats: fieldFormatsMock, + shortDotsEnable: false, + metaFields: ['_id', '_type', '_score'], + }); +}; + +const defaultParams: OnlySearchSourceAlertParams = { + size: 100, + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: Comparator.LT, + threshold: [0], + searchConfiguration: {}, + searchType: 'searchSource', +}; + +describe('fetchSearchSourceQuery', () => { + describe('updateSearchSource', () => { + const dataViewMock = createDataView(); + afterAll(() => { + jest.resetAllMocks(); + }); + + const fakeNow = new Date('2020-02-09T23:15:41.941Z'); + + beforeAll(() => { + jest.resetAllMocks(); + global.Date.now = jest.fn(() => fakeNow.getTime()); + }); + + it('without latest timestamp', async () => { + const params = { ...defaultParams, thresholdComparator: Comparator.GT_OR_EQ, threshold: [3] }; + + const searchSourceInstance = createSearchSourceMock({ index: dataViewMock }); + + const { searchSource, dateStart, dateEnd } = updateSearchSource( + searchSourceInstance, + params, + undefined + ); + const searchRequest = searchSource.getSearchRequestBody(); + expect(searchRequest.query).toMatchInlineSnapshot(` + Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "time": Object { + "format": "strict_date_optional_time", + "gte": "2020-02-09T23:10:41.941Z", + "lte": "2020-02-09T23:15:41.941Z", + }, + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + } + `); + expect(dateStart).toMatch('2020-02-09T23:10:41.941Z'); + expect(dateEnd).toMatch('2020-02-09T23:15:41.941Z'); + }); + + it('with latest timestamp in between the given time range ', async () => { + const params = { ...defaultParams, thresholdComparator: Comparator.GT_OR_EQ, threshold: [3] }; + + const searchSourceInstance = createSearchSourceMock({ index: dataViewMock }); + + const { searchSource } = updateSearchSource( + searchSourceInstance, + params, + '2020-02-09T23:12:41.941Z' + ); + const searchRequest = searchSource.getSearchRequestBody(); + expect(searchRequest.query).toMatchInlineSnapshot(` + Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "time": Object { + "format": "strict_date_optional_time", + "gte": "2020-02-09T23:10:41.941Z", + "lte": "2020-02-09T23:15:41.941Z", + }, + }, + }, + Object { + "range": Object { + "time": Object { + "gt": "2020-02-09T23:12:41.941Z", + }, + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + } + `); + }); + + it('with latest timestamp in before the given time range ', async () => { + const params = { ...defaultParams, thresholdComparator: Comparator.GT_OR_EQ, threshold: [3] }; + + const searchSourceInstance = createSearchSourceMock({ index: dataViewMock }); + + const { searchSource } = updateSearchSource( + searchSourceInstance, + params, + '2020-01-09T22:12:41.941Z' + ); + const searchRequest = searchSource.getSearchRequestBody(); + expect(searchRequest.query).toMatchInlineSnapshot(` + Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "time": Object { + "format": "strict_date_optional_time", + "gte": "2020-02-09T23:10:41.941Z", + "lte": "2020-02-09T23:15:41.941Z", + }, + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + } + `); + }); + }); +}); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.ts new file mode 100644 index 0000000000000..490c6f81ec485 --- /dev/null +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { buildRangeFilter, Filter } from '@kbn/es-query'; +import { Logger } from 'kibana/server'; +import { OnlySearchSourceAlertParams } from '../types'; +import { + getTime, + ISearchSource, + ISearchStartSearchSource, + SortDirection, +} from '../../../../../../../src/plugins/data/common'; + +export async function fetchSearchSourceQuery( + alertId: string, + params: OnlySearchSourceAlertParams, + latestTimestamp: string | undefined, + services: { + logger: Logger; + searchSourceClient: Promise; + } +) { + const { logger, searchSourceClient } = services; + const client = await searchSourceClient; + const initialSearchSource = await client.create(params.searchConfiguration); + + const { searchSource, dateStart, dateEnd } = updateSearchSource( + initialSearchSource, + params, + latestTimestamp + ); + + logger.debug( + `search source query alert (${alertId}) query: ${JSON.stringify( + searchSource.getSearchRequestBody() + )}` + ); + + const searchResult = await searchSource.fetch(); + + return { + numMatches: Number(searchResult.hits.total), + searchResult, + dateStart, + dateEnd, + }; +} + +export function updateSearchSource( + searchSource: ISearchSource, + params: OnlySearchSourceAlertParams, + latestTimestamp: string | undefined +) { + const index = searchSource.getField('index'); + + const timeFieldName = index?.timeFieldName; + if (!timeFieldName) { + throw new Error('Invalid data view without timeFieldName.'); + } + + searchSource.setField('size', params.size); + + const timerangeFilter = getTime(index, { + from: `now-${params.timeWindowSize}${params.timeWindowUnit}`, + to: 'now', + }); + const dateStart = timerangeFilter?.query.range[timeFieldName].gte; + const dateEnd = timerangeFilter?.query.range[timeFieldName].lte; + const filters = [timerangeFilter]; + + if (latestTimestamp && latestTimestamp > dateStart) { + // add additional filter for documents with a timestamp greater then + // the timestamp of the previous run, so that those documents are not counted twice + const field = index.fields.find((f) => f.name === timeFieldName); + const addTimeRangeField = buildRangeFilter(field!, { gt: latestTimestamp }, index); + filters.push(addTimeRangeField); + } + const searchSourceChild = searchSource.createChild(); + searchSourceChild.setField('filter', filters as Filter[]); + searchSourceChild.setField('sort', [{ [timeFieldName]: SortDirection.desc }]); + return { + searchSource: searchSourceChild, + dateStart, + dateEnd, + }; +} diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/get_search_params.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/get_search_params.ts new file mode 100644 index 0000000000000..9a4d83630f21e --- /dev/null +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/get_search_params.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; +import { OnlyEsQueryAlertParams } from '../types'; +import { parseDuration } from '../../../../../alerting/common'; + +export function getSearchParams(queryParams: OnlyEsQueryAlertParams) { + const date = Date.now(); + const { esQuery, timeWindowSize, timeWindowUnit } = queryParams; + + let parsedQuery; + try { + parsedQuery = JSON.parse(esQuery); + } catch (err) { + throw new Error(getInvalidQueryError(esQuery)); + } + + if (parsedQuery && !parsedQuery.query) { + throw new Error(getInvalidQueryError(esQuery)); + } + + const window = `${timeWindowSize}${timeWindowUnit}`; + let timeWindow: number; + try { + timeWindow = parseDuration(window); + } catch (err) { + throw new Error(getInvalidWindowSizeError(window)); + } + + const dateStart = new Date(date - timeWindow).toISOString(); + const dateEnd = new Date(date).toISOString(); + + return { parsedQuery, dateStart, dateEnd }; +} + +function getInvalidWindowSizeError(windowValue: string) { + return i18n.translate('xpack.stackAlerts.esQuery.invalidWindowSizeErrorMessage', { + defaultMessage: 'invalid format for windowSize: "{windowValue}"', + values: { + windowValue, + }, + }); +} + +function getInvalidQueryError(query: string) { + return i18n.translate('xpack.stackAlerts.esQuery.invalidQueryErrorMessage', { + defaultMessage: 'invalid query specified: "{query}" - query must be JSON', + values: { + query, + }, + }); +} diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/types.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/types.ts new file mode 100644 index 0000000000000..3bcfbc1aef8f9 --- /dev/null +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/types.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AlertExecutorOptions, AlertTypeParams } from '../../types'; +import { ActionContext } from './action_context'; +import { EsQueryAlertParams, EsQueryAlertState } from './alert_type_params'; +import { ActionGroupId } from './constants'; + +export type OnlyEsQueryAlertParams = Omit; + +export type OnlySearchSourceAlertParams = Omit< + EsQueryAlertParams, + 'esQuery' | 'index' | 'timeField' +> & { + searchType: 'searchSource'; +}; + +export type ExecutorOptions

= AlertExecutorOptions< + P, + EsQueryAlertState, + {}, + ActionContext, + typeof ActionGroupId +>; diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index.ts b/x-pack/plugins/stack_alerts/server/alert_types/index.ts index 219ccad2ece63..8ad10298f351b 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Logger } from 'src/core/server'; +import { CoreSetup, Logger } from 'src/core/server'; import { AlertingSetup, StackAlertsStartDeps } from '../types'; import { register as registerIndexThreshold } from './index_threshold'; import { register as registerGeoContainment } from './geo_containment'; @@ -14,6 +14,7 @@ interface RegisterAlertTypesParams { logger: Logger; data: Promise; alerting: AlertingSetup; + core: CoreSetup; } export function registerBuiltInAlertTypes(params: RegisterAlertTypesParams) { diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts index e55ce6e3a3aba..060730fb668e3 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts @@ -13,6 +13,7 @@ import { getAlertType, ActionGroupId } from './alert_type'; import { ActionContext } from './action_context'; import { Params } from './alert_type_params'; import { AlertServicesMock, alertsMock } from '../../../../alerting/server/mocks'; +import { Comparator } from '../../../common/comparator_types'; describe('alertType', () => { const logger = loggingSystemMock.create().get(); @@ -118,7 +119,7 @@ describe('alertType', () => { groupBy: 'all', timeWindowSize: 5, timeWindowUnit: 'm', - thresholdComparator: '<', + thresholdComparator: Comparator.LT, threshold: [0], }; @@ -136,7 +137,7 @@ describe('alertType', () => { groupBy: 'all', timeWindowSize: 5, timeWindowUnit: 'm', - thresholdComparator: '>', + thresholdComparator: Comparator.GT, threshold: [0], }; @@ -163,7 +164,7 @@ describe('alertType', () => { groupBy: 'all', timeWindowSize: 5, timeWindowUnit: 'm', - thresholdComparator: '<', + thresholdComparator: Comparator.LT, threshold: [1], }; @@ -225,7 +226,7 @@ describe('alertType', () => { groupBy: 'all', timeWindowSize: 5, timeWindowUnit: 'm', - thresholdComparator: '<', + thresholdComparator: Comparator.LT, threshold: [1], }; @@ -291,7 +292,7 @@ describe('alertType', () => { groupBy: 'all', timeWindowSize: 5, timeWindowUnit: 'm', - thresholdComparator: '<', + thresholdComparator: Comparator.LT, threshold: [1], }; diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.test.ts index 65980601b67a8..a6533c494250f 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.test.ts @@ -9,6 +9,7 @@ import { ParamsSchema, Params } from './alert_type_params'; import { ObjectType, TypeOf } from '@kbn/config-schema'; import type { Writable } from '@kbn/utility-types'; import { CoreQueryParams, MAX_GROUPS } from '../../../../triggers_actions_ui/server'; +import { Comparator } from '../../../common/comparator_types'; const DefaultParams: Writable> = { index: 'index-name', @@ -17,7 +18,7 @@ const DefaultParams: Writable> = { groupBy: 'all', timeWindowSize: 5, timeWindowUnit: 'm', - thresholdComparator: '>', + thresholdComparator: Comparator.GT, threshold: [0], }; diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.ts index d32e7890b17c6..821ddce6fcfb1 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.ts @@ -6,12 +6,13 @@ */ import { i18n } from '@kbn/i18n'; -import { schema, TypeOf } from '@kbn/config-schema'; -import { ComparatorFnNames } from '../lib'; +import { schema, Type, TypeOf } from '@kbn/config-schema'; import { CoreQueryParamsSchemaProperties, validateCoreQueryBody, } from '../../../../triggers_actions_ui/server'; +import { ComparatorFnNames } from '../lib'; +import { Comparator } from '../../../common/comparator_types'; // alert type parameters @@ -21,7 +22,7 @@ export const ParamsSchema = schema.object( { ...CoreQueryParamsSchemaProperties, // the comparison function to use to determine if the threshold as been met - thresholdComparator: schema.string({ validate: validateComparator }), + thresholdComparator: schema.string({ validate: validateComparator }) as Type, // the values to use as the threshold; `between` and `notBetween` require // two values, the others require one. threshold: schema.arrayOf(schema.number(), { minSize: 1, maxSize: 2 }), @@ -52,8 +53,8 @@ function validateParams(anyParams: unknown): string | undefined { } } -export function validateComparator(comparator: string): string | undefined { - if (ComparatorFnNames.has(comparator)) return; +function validateComparator(comparator: string): string | undefined { + if (ComparatorFnNames.has(comparator as Comparator)) return; return i18n.translate('xpack.stackAlerts.indexThreshold.invalidComparatorErrorMessage', { defaultMessage: 'invalid thresholdComparator specified: {comparator}', diff --git a/x-pack/plugins/stack_alerts/server/alert_types/lib/comparator.ts b/x-pack/plugins/stack_alerts/server/alert_types/lib/comparator.ts new file mode 100644 index 0000000000000..ac817256798a4 --- /dev/null +++ b/x-pack/plugins/stack_alerts/server/alert_types/lib/comparator.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Comparator } from '../../../common/comparator_types'; + +export type ComparatorFn = (value: number, threshold: number[]) => boolean; + +const humanReadableComparators = new Map([ + [Comparator.LT, 'less than'], + [Comparator.LT_OR_EQ, 'less than or equal to'], + [Comparator.GT_OR_EQ, 'greater than or equal to'], + [Comparator.GT, 'greater than'], + [Comparator.BETWEEN, 'between'], + [Comparator.NOT_BETWEEN, 'not between'], +]); + +export const ComparatorFns = new Map([ + [Comparator.LT, (value: number, threshold: number[]) => value < threshold[0]], + [Comparator.LT_OR_EQ, (value: number, threshold: number[]) => value <= threshold[0]], + [Comparator.GT_OR_EQ, (value: number, threshold: number[]) => value >= threshold[0]], + [Comparator.GT, (value: number, threshold: number[]) => value > threshold[0]], + [ + Comparator.BETWEEN, + (value: number, threshold: number[]) => value >= threshold[0] && value <= threshold[1], + ], + [ + Comparator.NOT_BETWEEN, + (value: number, threshold: number[]) => value < threshold[0] || value > threshold[1], + ], +]); + +export const ComparatorFnNames = new Set(ComparatorFns.keys()); + +export function getHumanReadableComparator(comparator: Comparator) { + return humanReadableComparators.has(comparator) + ? humanReadableComparators.get(comparator) + : comparator; +} diff --git a/x-pack/plugins/stack_alerts/server/alert_types/lib/comparator_types.ts b/x-pack/plugins/stack_alerts/server/alert_types/lib/comparator_types.ts deleted file mode 100644 index b364a31b10151..0000000000000 --- a/x-pack/plugins/stack_alerts/server/alert_types/lib/comparator_types.ts +++ /dev/null @@ -1,55 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -enum Comparator { - GT = '>', - LT = '<', - GT_OR_EQ = '>=', - LT_OR_EQ = '<=', - BETWEEN = 'between', - NOT_BETWEEN = 'notBetween', -} - -const humanReadableComparators = new Map([ - [Comparator.LT, 'less than'], - [Comparator.LT_OR_EQ, 'less than or equal to'], - [Comparator.GT_OR_EQ, 'greater than or equal to'], - [Comparator.GT, 'greater than'], - [Comparator.BETWEEN, 'between'], - [Comparator.NOT_BETWEEN, 'not between'], -]); - -export const ComparatorFns = getComparatorFns(); -export const ComparatorFnNames = new Set(ComparatorFns.keys()); - -type ComparatorFn = (value: number, threshold: number[]) => boolean; - -function getComparatorFns(): Map { - const fns: Record = { - [Comparator.LT]: (value: number, threshold: number[]) => value < threshold[0], - [Comparator.LT_OR_EQ]: (value: number, threshold: number[]) => value <= threshold[0], - [Comparator.GT_OR_EQ]: (value: number, threshold: number[]) => value >= threshold[0], - [Comparator.GT]: (value: number, threshold: number[]) => value > threshold[0], - [Comparator.BETWEEN]: (value: number, threshold: number[]) => - value >= threshold[0] && value <= threshold[1], - [Comparator.NOT_BETWEEN]: (value: number, threshold: number[]) => - value < threshold[0] || value > threshold[1], - }; - - const result = new Map(); - for (const key of Object.keys(fns)) { - result.set(key, fns[key]); - } - - return result; -} - -export function getHumanReadableComparator(comparator: string) { - return humanReadableComparators.has(comparator) - ? humanReadableComparators.get(comparator) - : comparator; -} diff --git a/x-pack/plugins/stack_alerts/server/alert_types/lib/index.ts b/x-pack/plugins/stack_alerts/server/alert_types/lib/index.ts index 09219aad6fe5e..7d2469defc91a 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/lib/index.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/lib/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { ComparatorFns, ComparatorFnNames, getHumanReadableComparator } from './comparator_types'; +export { ComparatorFns, ComparatorFnNames, getHumanReadableComparator } from './comparator'; diff --git a/x-pack/plugins/stack_alerts/server/feature.ts b/x-pack/plugins/stack_alerts/server/feature.ts index 39ea41374df7b..166f2103526ef 100644 --- a/x-pack/plugins/stack_alerts/server/feature.ts +++ b/x-pack/plugins/stack_alerts/server/feature.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { KibanaFeatureConfig } from '../../../plugins/features/common'; import { ID as IndexThreshold } from './alert_types/index_threshold/alert_type'; import { GEO_CONTAINMENT_ID as GeoContainment } from './alert_types/geo_containment/alert_type'; -import { ES_QUERY_ID as ElasticsearchQuery } from './alert_types/es_query/alert_type'; +import { ES_QUERY_ID as ElasticsearchQuery } from './alert_types/es_query/constants'; import { STACK_ALERTS_FEATURE_ID } from '../common'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; import { TRANSFORM_RULE_TYPE } from '../../transform/common'; diff --git a/x-pack/plugins/stack_alerts/server/plugin.ts b/x-pack/plugins/stack_alerts/server/plugin.ts index 1a671466b69b3..ac8f60de88b6f 100644 --- a/x-pack/plugins/stack_alerts/server/plugin.ts +++ b/x-pack/plugins/stack_alerts/server/plugin.ts @@ -29,6 +29,7 @@ export class AlertingBuiltinsPlugin .getStartServices() .then(async ([, { triggersActionsUi }]) => triggersActionsUi.data), alerting, + core, }); } diff --git a/x-pack/plugins/stack_alerts/server/types.ts b/x-pack/plugins/stack_alerts/server/types.ts index 6422389fefbe3..719373d21f18a 100644 --- a/x-pack/plugins/stack_alerts/server/types.ts +++ b/x-pack/plugins/stack_alerts/server/types.ts @@ -13,6 +13,7 @@ export type { RuleType, RuleParamsAndRefs, AlertExecutorOptions, + AlertTypeParams, } from '../../alerting/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 2701e63045eb0..c17975560873e 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -23329,7 +23329,6 @@ "xpack.stackAlerts.esQuery.actionVariableContextTitleLabel": "Titre pour l'alerte.", "xpack.stackAlerts.esQuery.actionVariableContextValueLabel": "Valeur ayant rempli la condition de seuil.", "xpack.stackAlerts.esQuery.alertTypeContextConditionsDescription": "Le nombre de documents correspondants est {thresholdComparator} {threshold}", - "xpack.stackAlerts.esQuery.alertTypeContextMessageDescription": "l'alerte \"{name}\" est active :\n\n- Valeur : {value}\n- Conditions remplies : {conditions} sur {window}\n- Horodatage : {date}", "xpack.stackAlerts.esQuery.alertTypeContextSubjectTitle": "l'alerte \"{name}\" correspond à la recherche", "xpack.stackAlerts.esQuery.alertTypeTitle": "Recherche Elasticsearch", "xpack.stackAlerts.esQuery.invalidComparatorErrorMessage": "thresholdComparator spécifié non valide : {comparator}", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 1f0117fd7096c..0bdb7f8b02255 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -26653,7 +26653,6 @@ "xpack.stackAlerts.esQuery.actionVariableContextTitleLabel": "アラートのタイトル。", "xpack.stackAlerts.esQuery.actionVariableContextValueLabel": "しきい値条件を満たした値。", "xpack.stackAlerts.esQuery.alertTypeContextConditionsDescription": "一致するドキュメント数は{thresholdComparator} {threshold}です", - "xpack.stackAlerts.esQuery.alertTypeContextMessageDescription": "アラート'{name}'は有効です。\n\n- 値:{value}\n- 条件が満たされました:{window} の {conditions}\n- タイムスタンプ:{date}", "xpack.stackAlerts.esQuery.alertTypeContextSubjectTitle": "アラート'{name}'はクエリと一致しました", "xpack.stackAlerts.esQuery.alertTypeTitle": "Elasticsearch クエリ", "xpack.stackAlerts.esQuery.invalidComparatorErrorMessage": "無効な thresholdComparator が指定されました:{comparator}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2e661138cbcac..39fd63bf68d80 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -26682,7 +26682,6 @@ "xpack.stackAlerts.esQuery.actionVariableContextTitleLabel": "告警的标题。", "xpack.stackAlerts.esQuery.actionVariableContextValueLabel": "满足阈值条件的值。", "xpack.stackAlerts.esQuery.alertTypeContextConditionsDescription": "匹配文档的数目{thresholdComparator} {threshold}", - "xpack.stackAlerts.esQuery.alertTypeContextMessageDescription": "告警“{name}”处于活动状态:\n\n- 值:{value}\n- 满足的条件:{conditions} 超过 {window}\n- 时间戳:{date}", "xpack.stackAlerts.esQuery.alertTypeContextSubjectTitle": "告警“{name}”已匹配查询", "xpack.stackAlerts.esQuery.alertTypeTitle": "Elasticsearch 查询", "xpack.stackAlerts.esQuery.invalidComparatorErrorMessage": "指定的 thresholdComparator 无效:{comparator}", diff --git a/x-pack/test/alerting_api_integration/common/lib/es_test_index_tool.ts b/x-pack/test/alerting_api_integration/common/lib/es_test_index_tool.ts index c880ce945042f..524709e6c02a7 100644 --- a/x-pack/test/alerting_api_integration/common/lib/es_test_index_tool.ts +++ b/x-pack/test/alerting_api_integration/common/lib/es_test_index_tool.ts @@ -107,7 +107,8 @@ export class ESTestIndexTool { return await this.retry.try(async () => { const searchResult = await this.search(source, reference); // @ts-expect-error doesn't handle total: number - if (searchResult.body.hits.total.value < numDocs) { + const value = searchResult.body.hits.total.value?.value || searchResult.body.hits.total.value; + if (value < numDocs) { // @ts-expect-error doesn't handle total: number throw new Error(`Expected ${numDocs} but received ${searchResult.body.hits.total.value}.`); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/alert.ts index 2a2fdbcf9e4d2..e62ad1db4a652 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/alert.ts @@ -32,6 +32,7 @@ const ES_GROUPS_TO_WRITE = 3; export default function alertTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const retry = getService('retry'); + const indexPatterns = getService('indexPatterns'); const es = getService('es'); const esTestIndexTool = new ESTestIndexTool(es, retry); const esTestIndexToolOutput = new ESTestIndexTool(es, retry, ES_TEST_OUTPUT_INDEX_NAME); @@ -61,180 +62,357 @@ export default function alertTests({ getService }: FtrProviderContext) { await esTestIndexToolOutput.destroy(); }); - it('runs correctly: threshold on hit count < >', async () => { - // write documents from now to the future end date in groups - createEsDocumentsInGroups(ES_GROUPS_TO_WRITE); - - await createAlert({ - name: 'never fire', - esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, - size: 100, - thresholdComparator: '<', - threshold: [0], - }); - - await createAlert({ - name: 'always fire', - esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, - size: 100, - thresholdComparator: '>', - threshold: [-1], - }); - - const docs = await waitForDocs(2); - for (let i = 0; i < docs.length; i++) { - const doc = docs[i]; - const { previousTimestamp, hits } = doc._source; - const { name, title, message } = doc._source.params; - - expect(name).to.be('always fire'); - expect(title).to.be(`alert 'always fire' matched query`); - const messagePattern = - /alert 'always fire' is active:\n\n- Value: \d+\n- Conditions Met: Number of matching documents is greater than -1 over 15s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; - expect(message).to.match(messagePattern); - expect(hits).not.to.be.empty(); - - // during the first execution, the latestTimestamp value should be empty - // since this alert always fires, the latestTimestamp value should be updated each execution - if (!i) { - expect(previousTimestamp).to.be.empty(); - } else { - expect(previousTimestamp).not.to.be.empty(); + [ + [ + 'esQuery', + async () => { + await createAlert({ + name: 'never fire', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + thresholdComparator: '<', + threshold: [0], + }); + await createAlert({ + name: 'always fire', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + thresholdComparator: '>', + threshold: [-1], + }); + }, + ] as const, + [ + 'searchSource', + async () => { + const esTestDataView = await indexPatterns.create( + { title: ES_TEST_INDEX_NAME, timeFieldName: 'date' }, + { override: true }, + getUrlPrefix(Spaces.space1.id) + ); + await createAlert({ + name: 'never fire', + size: 100, + thresholdComparator: '<', + threshold: [0], + searchType: 'searchSource', + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: esTestDataView.id, + filter: [], + }, + }); + await createAlert({ + name: 'always fire', + size: 100, + thresholdComparator: '>', + threshold: [-1], + searchType: 'searchSource', + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: esTestDataView.id, + filter: [], + }, + }); + }, + ] as const, + ].forEach(([searchType, initData]) => + it(`runs correctly: threshold on hit count < > for ${searchType} search type`, async () => { + // write documents from now to the future end date in groups + createEsDocumentsInGroups(ES_GROUPS_TO_WRITE); + await initData(); + + const docs = await waitForDocs(2); + for (let i = 0; i < docs.length; i++) { + const doc = docs[i]; + const { previousTimestamp, hits } = doc._source; + const { name, title, message } = doc._source.params; + + expect(name).to.be('always fire'); + expect(title).to.be(`alert 'always fire' matched query`); + const messagePattern = + /alert 'always fire' is active:\n\n- Value: \d+\n- Conditions Met: Number of matching documents is greater than -1 over 15s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); + expect(hits).not.to.be.empty(); + + // during the first execution, the latestTimestamp value should be empty + // since this alert always fires, the latestTimestamp value should be updated each execution + if (!i) { + expect(previousTimestamp).to.be.empty(); + } else { + expect(previousTimestamp).not.to.be.empty(); + } } - } - }); - - it('runs correctly: use epoch millis - threshold on hit count < >', async () => { - // write documents from now to the future end date in groups - createEsDocumentsInGroups(ES_GROUPS_TO_WRITE); - - await createAlert({ - name: 'never fire', - esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, - size: 100, - thresholdComparator: '<', - threshold: [0], - timeField: 'date_epoch_millis', - }); - - await createAlert({ - name: 'always fire', - esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, - size: 100, - thresholdComparator: '>', - threshold: [-1], - timeField: 'date_epoch_millis', - }); - - const docs = await waitForDocs(2); - for (let i = 0; i < docs.length; i++) { - const doc = docs[i]; - const { previousTimestamp, hits } = doc._source; - const { name, title, message } = doc._source.params; - - expect(name).to.be('always fire'); - expect(title).to.be(`alert 'always fire' matched query`); - const messagePattern = - /alert 'always fire' is active:\n\n- Value: \d+\n- Conditions Met: Number of matching documents is greater than -1 over 15s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; - expect(message).to.match(messagePattern); - expect(hits).not.to.be.empty(); - - // during the first execution, the latestTimestamp value should be empty - // since this alert always fires, the latestTimestamp value should be updated each execution - if (!i) { - expect(previousTimestamp).to.be.empty(); - } else { - expect(previousTimestamp).not.to.be.empty(); + }) + ); + + [ + [ + 'esQuery', + async () => { + await createAlert({ + name: 'never fire', + size: 100, + thresholdComparator: '<', + threshold: [0], + timeField: 'date_epoch_millis', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + }); + await createAlert({ + name: 'always fire', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + thresholdComparator: '>', + threshold: [-1], + timeField: 'date_epoch_millis', + }); + }, + ] as const, + [ + 'searchSource', + async () => { + const esTestDataView = await indexPatterns.create( + { title: ES_TEST_INDEX_NAME, timeFieldName: 'date_epoch_millis' }, + { override: true }, + getUrlPrefix(Spaces.space1.id) + ); + await createAlert({ + name: 'never fire', + size: 100, + thresholdComparator: '<', + threshold: [0], + searchType: 'searchSource', + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: esTestDataView.id, + filter: [], + }, + }); + await createAlert({ + name: 'always fire', + size: 100, + thresholdComparator: '>', + threshold: [-1], + searchType: 'searchSource', + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: esTestDataView.id, + filter: [], + }, + }); + }, + ] as const, + ].forEach(([searchType, initData]) => + it(`runs correctly: use epoch millis - threshold on hit count < > for ${searchType} search type`, async () => { + // write documents from now to the future end date in groups + createEsDocumentsInGroups(ES_GROUPS_TO_WRITE); + await initData(); + + const docs = await waitForDocs(2); + for (let i = 0; i < docs.length; i++) { + const doc = docs[i]; + const { previousTimestamp, hits } = doc._source; + const { name, title, message } = doc._source.params; + + expect(name).to.be('always fire'); + expect(title).to.be(`alert 'always fire' matched query`); + const messagePattern = + /alert 'always fire' is active:\n\n- Value: \d+\n- Conditions Met: Number of matching documents is greater than -1 over 15s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); + expect(hits).not.to.be.empty(); + + // during the first execution, the latestTimestamp value should be empty + // since this alert always fires, the latestTimestamp value should be updated each execution + if (!i) { + expect(previousTimestamp).to.be.empty(); + } else { + expect(previousTimestamp).not.to.be.empty(); + } } - } - }); - - it('runs correctly with query: threshold on hit count < >', async () => { - // write documents from now to the future end date in groups - createEsDocumentsInGroups(ES_GROUPS_TO_WRITE); - - const rangeQuery = (rangeThreshold: number) => { - return { - query: { - bool: { - filter: [ - { - range: { - testedValue: { - gte: rangeThreshold, + }) + ); + + [ + [ + 'esQuery', + async () => { + const rangeQuery = (rangeThreshold: number) => { + return { + query: { + bool: { + filter: [ + { + range: { + testedValue: { + gte: rangeThreshold, + }, + }, }, - }, + ], }, - ], + }, + }; + }; + await createAlert({ + name: 'never fire', + esQuery: JSON.stringify(rangeQuery(ES_GROUPS_TO_WRITE * ALERT_INTERVALS_TO_WRITE + 1)), + size: 100, + thresholdComparator: '<', + threshold: [-1], + }); + await createAlert({ + name: 'fires once', + esQuery: JSON.stringify( + rangeQuery(Math.floor((ES_GROUPS_TO_WRITE * ALERT_INTERVALS_TO_WRITE) / 2)) + ), + size: 100, + thresholdComparator: '>=', + threshold: [0], + }); + }, + ] as const, + [ + 'searchSource', + async () => { + const esTestDataView = await indexPatterns.create( + { title: ES_TEST_INDEX_NAME, timeFieldName: 'date' }, + { override: true }, + getUrlPrefix(Spaces.space1.id) + ); + await createAlert({ + name: 'never fire', + size: 100, + thresholdComparator: '<', + threshold: [-1], + searchType: 'searchSource', + searchConfiguration: { + query: { + query: `testedValue > ${ES_GROUPS_TO_WRITE * ALERT_INTERVALS_TO_WRITE + 1}`, + language: 'kuery', + }, + index: esTestDataView.id, + filter: [], }, - }, - }; - }; - - await createAlert({ - name: 'never fire', - esQuery: JSON.stringify(rangeQuery(ES_GROUPS_TO_WRITE * ALERT_INTERVALS_TO_WRITE + 1)), - size: 100, - thresholdComparator: '<', - threshold: [-1], - }); - - await createAlert({ - name: 'fires once', - esQuery: JSON.stringify( - rangeQuery(Math.floor((ES_GROUPS_TO_WRITE * ALERT_INTERVALS_TO_WRITE) / 2)) - ), - size: 100, - thresholdComparator: '>=', - threshold: [0], - }); - - const docs = await waitForDocs(1); - for (const doc of docs) { - const { previousTimestamp, hits } = doc._source; - const { name, title, message } = doc._source.params; - - expect(name).to.be('fires once'); - expect(title).to.be(`alert 'fires once' matched query`); - const messagePattern = - /alert 'fires once' is active:\n\n- Value: \d+\n- Conditions Met: Number of matching documents is greater than or equal to 0 over 15s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; - expect(message).to.match(messagePattern); - expect(hits).not.to.be.empty(); - expect(previousTimestamp).to.be.empty(); - } - }); - - it('runs correctly: no matches', async () => { - await createAlert({ - name: 'always fire', - esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, - size: 100, - thresholdComparator: '<', - threshold: [1], - }); - - const docs = await waitForDocs(1); - for (let i = 0; i < docs.length; i++) { - const doc = docs[i]; - const { previousTimestamp, hits } = doc._source; - const { name, title, message } = doc._source.params; - - expect(name).to.be('always fire'); - expect(title).to.be(`alert 'always fire' matched query`); - const messagePattern = - /alert 'always fire' is active:\n\n- Value: 0+\n- Conditions Met: Number of matching documents is less than 1 over 15s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; - expect(message).to.match(messagePattern); - expect(hits).to.be.empty(); - - // during the first execution, the latestTimestamp value should be empty - // since this alert always fires, the latestTimestamp value should be updated each execution - if (!i) { + }); + await createAlert({ + name: 'fires once', + size: 100, + thresholdComparator: '>=', + threshold: [0], + searchType: 'searchSource', + searchConfiguration: { + query: { + query: `testedValue > ${Math.floor( + (ES_GROUPS_TO_WRITE * ALERT_INTERVALS_TO_WRITE) / 2 + )}`, + language: 'kuery', + }, + index: esTestDataView.id, + filter: [], + }, + }); + }, + ] as const, + ].forEach(([searchType, initData]) => + it(`runs correctly with query: threshold on hit count < > for ${searchType}`, async () => { + // write documents from now to the future end date in groups + createEsDocumentsInGroups(ES_GROUPS_TO_WRITE); + await initData(); + + const docs = await waitForDocs(1); + for (const doc of docs) { + const { previousTimestamp, hits } = doc._source; + const { name, title, message } = doc._source.params; + + expect(name).to.be('fires once'); + expect(title).to.be(`alert 'fires once' matched query`); + const messagePattern = + /alert 'fires once' is active:\n\n- Value: \d+\n- Conditions Met: Number of matching documents is greater than or equal to 0 over 15s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); + expect(hits).not.to.be.empty(); expect(previousTimestamp).to.be.empty(); - } else { - expect(previousTimestamp).not.to.be.empty(); } - } - }); + }) + ); + + [ + [ + 'esQuery', + async () => { + await createAlert({ + name: 'always fire', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + thresholdComparator: '<', + threshold: [1], + }); + }, + ] as const, + [ + 'searchSource', + async () => { + const esTestDataView = await indexPatterns.create( + { title: ES_TEST_INDEX_NAME, timeFieldName: 'date' }, + { override: true }, + getUrlPrefix(Spaces.space1.id) + ); + + await createAlert({ + name: 'always fire', + size: 100, + thresholdComparator: '<', + threshold: [1], + searchType: 'searchSource', + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: esTestDataView.id, + filter: [], + }, + }); + }, + ] as const, + ].forEach(([searchType, initData]) => + it(`runs correctly: no matches for ${searchType} search type`, async () => { + await initData(); + + const docs = await waitForDocs(1); + for (let i = 0; i < docs.length; i++) { + const doc = docs[i]; + const { previousTimestamp, hits } = doc._source; + const { name, title, message } = doc._source.params; + + expect(name).to.be('always fire'); + expect(title).to.be(`alert 'always fire' matched query`); + const messagePattern = + /alert 'always fire' is active:\n\n- Value: 0+\n- Conditions Met: Number of matching documents is less than 1 over 15s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); + expect(hits).to.be.empty(); + + // during the first execution, the latestTimestamp value should be empty + // since this alert always fires, the latestTimestamp value should be updated each execution + if (!i) { + expect(previousTimestamp).to.be.empty(); + } else { + expect(previousTimestamp).not.to.be.empty(); + } + } + }) + ); async function createEsDocumentsInGroups(groups: number) { await createEsDocuments( @@ -257,12 +435,14 @@ export default function alertTests({ getService }: FtrProviderContext) { interface CreateAlertParams { name: string; - timeField?: string; - esQuery: string; size: number; thresholdComparator: string; threshold: number[]; timeWindowSize?: number; + esQuery?: string; + timeField?: string; + searchConfiguration?: unknown; + searchType?: 'searchSource'; } async function createAlert(params: CreateAlertParams): Promise { @@ -288,6 +468,17 @@ export default function alertTests({ getService }: FtrProviderContext) { }, }; + const alertParams = + params.searchType === 'searchSource' + ? { + searchConfiguration: params.searchConfiguration, + } + : { + index: [ES_TEST_INDEX_NAME], + timeField: params.timeField || 'date', + esQuery: params.esQuery, + }; + const { body: createdAlert } = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') @@ -300,14 +491,13 @@ export default function alertTests({ getService }: FtrProviderContext) { actions: [action], notify_when: 'onActiveAlert', params: { - index: [ES_TEST_INDEX_NAME], - timeField: params.timeField || 'date', - esQuery: params.esQuery, size: params.size, timeWindowSize: params.timeWindowSize || ALERT_INTERVAL_SECONDS * 5, timeWindowUnit: 's', thresholdComparator: params.thresholdComparator, threshold: params.threshold, + searchType: params.searchType, + ...alertParams, }, }) .expect(200); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts index 3007e37395156..bdc5a6c5ef646 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts @@ -11,8 +11,8 @@ import { buildUp, tearDown } from '..'; // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile, getService }: FtrProviderContext) { describe('Alerting', () => { - before(async () => buildUp(getService)); - after(async () => tearDown(getService)); + before(async () => await buildUp(getService)); + after(async () => await tearDown(getService)); loadTestFile(require.resolve('./aggregate')); loadTestFile(require.resolve('./create')); diff --git a/x-pack/test/functional_with_es_ssl/apps/discover/index.ts b/x-pack/test/functional_with_es_ssl/apps/discover/index.ts new file mode 100644 index 0000000000000..708da2f02da74 --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/apps/discover/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default ({ loadTestFile, getService }: FtrProviderContext) => { + describe('Discover alerting', function () { + this.tags('ciGroup6'); + loadTestFile(require.resolve('./search_source_alert')); + }); +}; diff --git a/x-pack/test/functional_with_es_ssl/apps/discover/search_source_alert.ts b/x-pack/test/functional_with_es_ssl/apps/discover/search_source_alert.ts new file mode 100644 index 0000000000000..bae045fc93838 --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/apps/discover/search_source_alert.ts @@ -0,0 +1,340 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { asyncForEach } from '@kbn/std'; +import { last } from 'lodash'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const log = getService('log'); + const es = getService('es'); + const monacoEditor = getService('monacoEditor'); + const PageObjects = getPageObjects([ + 'settings', + 'common', + 'header', + 'discover', + 'timePicker', + 'dashboard', + ]); + const deployment = getService('deployment'); + const dataGrid = getService('dataGrid'); + const browser = getService('browser'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const supertest = getService('supertest'); + const queryBar = getService('queryBar'); + const security = getService('security'); + + const SOURCE_DATA_INDEX = 'search-source-alert'; + const OUTPUT_DATA_INDEX = 'search-source-alert-output'; + const ACTION_TYPE_ID = '.index'; + const RULE_NAME = 'test-search-source-alert'; + let sourceDataViewId: string; + let outputDataViewId: string; + let connectorId: string; + + const createSourceIndex = () => + es.index({ + index: SOURCE_DATA_INDEX, + body: { + settings: { number_of_shards: 1 }, + mappings: { + properties: { + '@timestamp': { type: 'date' }, + message: { type: 'text' }, + }, + }, + }, + }); + + const generateNewDocs = async (docsNumber: number) => { + const mockMessages = new Array(docsNumber).map((current) => `msg-${current}`); + const dateNow = new Date().toISOString(); + for (const message of mockMessages) { + await es.transport.request({ + path: `/${SOURCE_DATA_INDEX}/_doc`, + method: 'POST', + body: { + '@timestamp': dateNow, + message, + }, + }); + } + }; + + const createOutputDataIndex = () => + es.index({ + index: OUTPUT_DATA_INDEX, + body: { + settings: { + number_of_shards: 1, + }, + mappings: { + properties: { + rule_id: { type: 'text' }, + rule_name: { type: 'text' }, + alert_id: { type: 'text' }, + context_message: { type: 'text' }, + }, + }, + }, + }); + + const deleteAlerts = (alertIds: string[]) => + asyncForEach(alertIds, async (alertId: string) => { + await supertest + .delete(`/api/alerting/rule/${alertId}`) + .set('kbn-xsrf', 'foo') + .expect(204, ''); + }); + + const getAlertsByName = async (name: string) => { + const { + body: { data: alerts }, + } = await supertest + .get(`/api/alerting/rules/_find?search=${name}&search_fields=name`) + .expect(200); + + return alerts; + }; + + const createDataView = async (dataView: string) => { + log.debug(`create data view ${dataView}`); + return await supertest + .post(`/api/data_views/data_view`) + .set('kbn-xsrf', 'foo') + .send({ data_view: { title: dataView, timeFieldName: '@timestamp' } }) + .expect(200); + }; + + const createConnector = async (): Promise => { + const { body: createdAction } = await supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'search-source-alert-test-connector', + connector_type_id: ACTION_TYPE_ID, + config: { index: OUTPUT_DATA_INDEX }, + secrets: {}, + }) + .expect(200); + + return createdAction.id; + }; + + const deleteConnector = (id: string) => + supertest.delete(`/api/actions/connector/${id}`).set('kbn-xsrf', 'foo').expect(204, ''); + + const deleteDataViews = (dataViews: string[]) => + asyncForEach( + dataViews, + async (dataView: string) => + await supertest + .delete(`/api/data_views/data_view/${dataView}`) + .set('kbn-xsrf', 'foo') + .expect(200) + ); + + const defineSearchSourceAlert = async (alertName: string) => { + await testSubjects.click('discoverAlertsButton'); + await testSubjects.click('discoverCreateAlertButton'); + + await testSubjects.setValue('ruleNameInput', alertName); + await testSubjects.click('thresholdPopover'); + await testSubjects.setValue('alertThresholdInput', '3'); + await testSubjects.click('.index-ActionTypeSelectOption'); + + await monacoEditor.setCodeEditorValue(`{ + "rule_id": "{{ruleId}}", + "rule_name": "{{ruleName}}", + "alert_id": "{{alertId}}", + "context_message": "{{context.message}}" + }`); + await testSubjects.click('saveRuleButton'); + }; + + const getLastToast = async () => { + const toastList = await testSubjects.find('globalToastList'); + const titles = await toastList.findAllByCssSelector('.euiToastHeader'); + const lastTitleElement = last(titles)!; + const title = await lastTitleElement.getVisibleText(); + const messages = await toastList.findAllByCssSelector('.euiToastBody'); + const lastMessageElement = last(messages)!; + const message = await lastMessageElement.getVisibleText(); + return { message, title }; + }; + + const openOutputIndex = async () => { + await PageObjects.common.navigateToApp('discover'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.selectIndexPattern(OUTPUT_DATA_INDEX); + + const [{ id: alertId }] = await getAlertsByName(RULE_NAME); + await queryBar.setQuery(`alert_id:${alertId}`); + await retry.waitFor('document explorer contains alert', async () => { + await queryBar.submitQuery(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + return (await dataGrid.getDocCount()) > 0; + }); + }; + + const getResultsLink = async () => { + // getting the link + await dataGrid.clickRowToggle(); + await testSubjects.click('collapseBtn'); + const contextMessageElement = await testSubjects.find('tableDocViewRow-context_message-value'); + const contextMessage = await contextMessageElement.getVisibleText(); + const [, link] = contextMessage.split(`Link\: `); + + return link; + }; + + const navigateToDiscover = async (link: string) => { + // following ling provided by alert to see documents triggered the alert + const baseUrl = deployment.getHostPort(); + await browser.navigateTo(baseUrl + link); + await PageObjects.discover.waitUntilSearchingHasFinished(); + + await retry.waitFor('navigate to discover', async () => { + const currentUrl = await browser.getCurrentUrl(); + return currentUrl.includes(sourceDataViewId); + }); + }; + + const navigateToResults = async () => { + const link = await getResultsLink(); + await navigateToDiscover(link); + }; + + const openAlertRule = async () => { + await PageObjects.common.navigateToApp('management'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await testSubjects.click('triggersActions'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + const rulesList = await testSubjects.find('rulesList'); + const alertRule = await rulesList.findByCssSelector('[title="test-search-source-alert"]'); + await alertRule.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }; + + describe('Search source Alert', () => { + before(async () => { + await security.testUser.setRoles(['discover_alert']); + + log.debug('create source index'); + await createSourceIndex(); + + log.debug('generate documents'); + await generateNewDocs(5); + + log.debug('create output index'); + await createOutputDataIndex(); + + log.debug('create data views'); + const sourceDataViewResponse = await createDataView(SOURCE_DATA_INDEX); + const outputDataViewResponse = await createDataView(OUTPUT_DATA_INDEX); + + log.debug('create connector'); + connectorId = await createConnector(); + + sourceDataViewId = sourceDataViewResponse.body.data_view.id; + outputDataViewId = outputDataViewResponse.body.data_view.id; + }); + + after(async () => { + // delete only remaining output index + await es.transport.request({ + path: `/${OUTPUT_DATA_INDEX}`, + method: 'DELETE', + }); + await deleteDataViews([sourceDataViewId, outputDataViewId]); + await deleteConnector(connectorId); + const alertsToDelete = await getAlertsByName(RULE_NAME); + await deleteAlerts(alertsToDelete.map((alertItem: { id: string }) => alertItem.id)); + await security.testUser.restoreDefaults(); + }); + + it('should navigate to discover via view in app link', async () => { + await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.discover.selectIndexPattern(SOURCE_DATA_INDEX); + await PageObjects.timePicker.setCommonlyUsedTime('Last_15 minutes'); + + // create an alert + await defineSearchSourceAlert(RULE_NAME); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await openAlertRule(); + + await testSubjects.click('ruleDetails-viewInApp'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await retry.waitFor('navigate to discover', async () => { + const currentUrl = await browser.getCurrentUrl(); + return currentUrl.includes(sourceDataViewId); + }); + + expect(await dataGrid.getDocCount()).to.be(5); + }); + + it('should open documents triggered the alert', async () => { + await openOutputIndex(); + await navigateToResults(); + + const { message, title } = await getLastToast(); + expect(await dataGrid.getDocCount()).to.be(5); + expect(title).to.be.equal('Displayed documents may vary'); + expect(message).to.be.equal( + 'The displayed documents might differ from the documents that triggered the alert. Some documents might have been added or deleted.' + ); + }); + + it('should display warning about updated alert rule', async () => { + await openAlertRule(); + + // change rule configuration + await testSubjects.click('openEditRuleFlyoutButton'); + await testSubjects.click('thresholdPopover'); + await testSubjects.setValue('alertThresholdInput', '1'); + await testSubjects.click('saveEditedRuleButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await openOutputIndex(); + await navigateToResults(); + + const { message, title } = await getLastToast(); + expect(await dataGrid.getDocCount()).to.be(5); + expect(title).to.be.equal('Alert rule has changed'); + expect(message).to.be.equal( + 'The displayed documents might not match the documents that triggered the alert because the rule configuration changed.' + ); + }); + + it('should display not found index error', async () => { + await openOutputIndex(); + const link = await getResultsLink(); + await navigateToDiscover(link); + + await es.transport.request({ + path: `/${SOURCE_DATA_INDEX}`, + method: 'DELETE', + }); + await browser.refresh(); + + await navigateToDiscover(link); + + const { title } = await getLastToast(); + expect(title).to.be.equal( + 'No matching indices found: No indices match "search-source-alert"' + ); + }); + }); +} diff --git a/x-pack/test/functional_with_es_ssl/config.ts b/x-pack/test/functional_with_es_ssl/config.ts index cee793c8c8bb9..1c10548b90699 100644 --- a/x-pack/test/functional_with_es_ssl/config.ts +++ b/x-pack/test/functional_with_es_ssl/config.ts @@ -48,6 +48,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { pageObjects, // list paths to the files that contain your plugins tests testFiles: [ + resolve(__dirname, './apps/discover'), resolve(__dirname, './apps/triggers_actions_ui'), resolve(__dirname, './apps/uptime'), resolve(__dirname, './apps/ml'), @@ -111,6 +112,30 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { }, ], }, + discover_alert: { + kibana: [ + { + feature: { + actions: ['all'], + stackAlerts: ['all'], + discover: ['all'], + advancedSettings: ['all'], + }, + spaces: ['*'], + }, + ], + elasticsearch: { + cluster: [], + indices: [ + { + names: ['search-source-alert', 'search-source-alert-output'], + privileges: ['read', 'view_index_metadata', 'manage', 'create_index', 'index'], + field_security: { grant: ['*'], except: [] }, + }, + ], + run_as: [], + }, + }, }, defaultRoles: ['superuser'], }, From 4a76b114315c89bc481056fe12902c014898dc73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Fri, 1 Apr 2022 06:19:59 -0400 Subject: [PATCH 15/65] [APM] Transaction Duration Anomaly rule fires alerts for other detector types (#127973) * adding detector filter * removing console * addressing pr comments * pr changes * fixing * reverting * fixing alerts rules and adding synthtrace sample * renaming file * using unit to check dates * removing console * removing synthtrace scenario * api test * creating api test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../index.tsx | 2 +- x-pack/plugins/apm/scripts/test/api.js | 1 + .../alerts/register_anomaly_alert_type.ts | 60 ++++++---- .../tests/alerts/anomaly_alert.spec.ts | 106 ++++++++++++++++++ .../tests/alerts/wait_for_rule_status.ts | 56 +++++++++ 5 files changed, 202 insertions(+), 23 deletions(-) create mode 100644 x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts create mode 100644 x-pack/test/apm_api_integration/tests/alerts/wait_for_rule_status.ts diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx index 628ba40f20efd..392adb9c589a4 100644 --- a/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx @@ -60,7 +60,7 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) { ...ruleParams, }, { - windowSize: 15, + windowSize: 30, windowUnit: 'm', anomalySeverityType: ANOMALY_SEVERITY.CRITICAL, environment: ENVIRONMENT_ALL.value, diff --git a/x-pack/plugins/apm/scripts/test/api.js b/x-pack/plugins/apm/scripts/test/api.js index 5769224f90ac2..01e0198360bc3 100644 --- a/x-pack/plugins/apm/scripts/test/api.js +++ b/x-pack/plugins/apm/scripts/test/api.js @@ -57,6 +57,7 @@ const { argv } = yargs(process.argv.slice(2)) const { trial, server, runner, grep, inspect } = argv; const license = trial ? 'trial' : 'basic'; + console.log(`License: ${license}`); let ftrScript = 'functional_tests'; diff --git a/x-pack/plugins/apm/server/routes/alerts/register_anomaly_alert_type.ts b/x-pack/plugins/apm/server/routes/alerts/register_anomaly_alert_type.ts index 04d1fb775cea0..5affecb3541cc 100644 --- a/x-pack/plugins/apm/server/routes/alerts/register_anomaly_alert_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/register_anomaly_alert_type.ts @@ -4,44 +4,48 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { schema } from '@kbn/config-schema'; -import { compact } from 'lodash'; -import { ESSearchResponse } from 'src/core/types/elasticsearch'; +import datemath from '@elastic/datemath'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { schema } from '@kbn/config-schema'; import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, - ALERT_SEVERITY, ALERT_REASON, + ALERT_SEVERITY, } from '@kbn/rule-data-utils'; -import { createLifecycleRuleTypeFactory } from '../../../../rule_registry/server'; -import { ProcessorEvent } from '../../../common/processor_event'; -import { getSeverity } from '../../../common/anomaly_detection'; -import { - PROCESSOR_EVENT, - SERVICE_NAME, - TRANSACTION_TYPE, - SERVICE_ENVIRONMENT, -} from '../../../common/elasticsearch_fieldnames'; -import { getAlertUrlTransaction } from '../../../common/utils/formatters'; -import { asMutableArray } from '../../../common/utils/as_mutable_array'; -import { ANOMALY_SEVERITY } from '../../../common/ml_constants'; +import { compact } from 'lodash'; +import { ESSearchResponse } from 'src/core/types/elasticsearch'; import { KibanaRequest } from '../../../../../../src/core/server'; +import { termQuery } from '../../../../observability/server'; +import { createLifecycleRuleTypeFactory } from '../../../../rule_registry/server'; import { AlertType, ALERT_TYPES_CONFIG, ANOMALY_ALERT_SEVERITY_TYPES, formatAnomalyReason, } from '../../../common/alert_types'; -import { getMLJobs } from '../service_map/get_service_anomalies'; -import { apmActionVariables } from './action_variables'; -import { RegisterRuleDependencies } from './register_apm_alerts'; +import { getSeverity } from '../../../common/anomaly_detection'; +import { + ApmMlDetectorType, + getApmMlDetectorIndex, +} from '../../../common/anomaly_detection/apm_ml_detectors'; +import { + PROCESSOR_EVENT, + SERVICE_ENVIRONMENT, + SERVICE_NAME, + TRANSACTION_TYPE, +} from '../../../common/elasticsearch_fieldnames'; import { getEnvironmentEsField, getEnvironmentLabel, } from '../../../common/environment_filter_values'; -import { termQuery } from '../../../../observability/server'; +import { ANOMALY_SEVERITY } from '../../../common/ml_constants'; +import { ProcessorEvent } from '../../../common/processor_event'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; +import { getAlertUrlTransaction } from '../../../common/utils/formatters'; +import { getMLJobs } from '../service_map/get_service_anomalies'; +import { apmActionVariables } from './action_variables'; +import { RegisterRuleDependencies } from './register_apm_alerts'; const paramsSchema = schema.object({ serviceName: schema.maybe(schema.string()), @@ -130,6 +134,14 @@ export function registerAnomalyAlertType({ return {}; } + // start time must be at least 30, does like this to support rules created before this change where default was 15 + const startTime = Math.min( + datemath.parse('now-30m')!.valueOf(), + datemath + .parse(`now-${ruleParams.windowSize}${ruleParams.windowUnit}`) + ?.valueOf() || 0 + ); + const jobIds = mlJobs.map((job) => job.jobId); const anomalySearchParams = { body: { @@ -143,13 +155,17 @@ export function registerAnomalyAlertType({ { range: { timestamp: { - gte: `now-${ruleParams.windowSize}${ruleParams.windowUnit}`, + gte: startTime, format: 'epoch_millis', }, }, }, ...termQuery('partition_field_value', ruleParams.serviceName), ...termQuery('by_field_value', ruleParams.transactionType), + ...termQuery( + 'detector_index', + getApmMlDetectorIndex(ApmMlDetectorType.txLatency) + ), ] as QueryDslQueryContainer[], }, }, diff --git a/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts new file mode 100644 index 0000000000000..bd4be58e2bb1c --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { apm, timerange } from '@elastic/apm-synthtrace'; +import datemath from '@elastic/datemath'; +import expect from '@kbn/expect'; +import { range } from 'lodash'; +import { AlertType } from '../../../../plugins/apm/common/alert_types'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { createAndRunApmMlJob } from '../../common/utils/create_and_run_apm_ml_job'; +import { waitForRuleStatus } from './wait_for_rule_status'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const synthtraceEsClient = getService('synthtraceEsClient'); + const ml = getService('ml'); + const supertest = getService('supertest'); + const log = getService('log'); + + registry.when( + 'fetching service anomalies with a trial license', + { config: 'trial', archives: ['apm_mappings_only_8.0.0'] }, + () => { + const spikeStart = datemath.parse('now-2h')!.valueOf(); + const spikeEnd = datemath.parse('now')!.valueOf(); + + const start = datemath.parse('now-2w')!.valueOf(); + const end = datemath.parse('now')!.valueOf(); + + const NORMAL_DURATION = 100; + const NORMAL_RATE = 1; + + let ruleId: string | undefined; + + before(async () => { + const serviceA = apm.service('service-a', 'production', 'java').instance('a'); + + const events = timerange(new Date(start).getTime(), new Date(end).getTime()) + .interval('1m') + .rate(1) + .spans((timestamp) => { + const isInSpike = timestamp >= spikeStart && timestamp < spikeEnd; + const count = isInSpike ? 4 : NORMAL_RATE; + const duration = isInSpike ? 1000 : NORMAL_DURATION; + const outcome = isInSpike ? 'failure' : 'success'; + + return [ + ...range(0, count).flatMap((_) => + serviceA + .transaction('tx', 'request') + .timestamp(timestamp) + .duration(duration) + .outcome(outcome) + .serialize() + ), + ]; + }); + + await synthtraceEsClient.index(events); + await createAndRunApmMlJob({ environment: 'production', ml }); + const { body: createdRule } = await supertest + .post(`/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + environment: 'production', + serviceName: 'service-a', + transactionType: 'request', + windowSize: 30, + windowUnit: 'm', + anomalySeverityType: 'warning', + }, + consumer: 'apm', + schedule: { + interval: '1m', + }, + tags: ['apm', 'service.name:service-a'], + name: 'Latency anomaly | service-a', + rule_type_id: AlertType.Anomaly, + notify_when: 'onActiveAlert', + actions: [], + }); + ruleId = createdRule.id; + }); + + after(async () => { + await synthtraceEsClient.clean(); + await ml.cleanMlIndices(); + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + }); + + it('checks if alert is active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + log, + }); + expect(executionStatus.status).to.be('active'); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/alerts/wait_for_rule_status.ts b/x-pack/test/apm_api_integration/tests/alerts/wait_for_rule_status.ts new file mode 100644 index 0000000000000..b31f8b70eab10 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/alerts/wait_for_rule_status.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ToolingLog } from '@kbn/dev-utils'; +import expect from '@kbn/expect'; +import type SuperTest from 'supertest'; + +const WAIT_FOR_STATUS_INCREMENT = 500; + +export async function waitForRuleStatus({ + id, + expectedStatus, + waitMillis = 10000, + supertest, + log, +}: { + expectedStatus: string; + supertest: SuperTest.SuperTest; + log: ToolingLog; + waitMillis?: number; + id?: string; +}): Promise> { + if (waitMillis < 0 || !id) { + expect().fail(`waiting for alert ${id} status ${expectedStatus} timed out`); + } + + const response = await supertest.get(`/api/alerting/rule/${id}`); + expect(response.status).to.eql(200); + + const { execution_status: executionStatus } = response.body || {}; + const { status } = executionStatus || {}; + + const message = `waitForStatus(${expectedStatus}): got ${JSON.stringify(executionStatus)}`; + + if (status === expectedStatus) { + return executionStatus; + } + + log.debug(`${message}, retrying`); + + await delay(WAIT_FOR_STATUS_INCREMENT); + return await waitForRuleStatus({ + id, + expectedStatus, + waitMillis: waitMillis - WAIT_FOR_STATUS_INCREMENT, + supertest, + log, + }); +} + +async function delay(millis: number): Promise { + await new Promise((resolve) => setTimeout(resolve, millis)); +} From c8b327c52ca1bccbdb24691f02f8d425f3ac2180 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Fri, 1 Apr 2022 12:24:55 +0200 Subject: [PATCH 16/65] [Workplace Search] Private sources can now make an OAuth prepare call correctly (#129169) --- .../components/add_source/add_source_logic.test.ts | 2 +- .../components/add_source/add_source_logic.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts index 6b335b1f7ffe4..56468ae26dea3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts @@ -720,7 +720,7 @@ describe('AddSourceLogic', () => { expect(http.get).toHaveBeenCalledWith( '/internal/workplace_search/account/sources/github/prepare', - { query: { index_permissions: false } } + { query: {} } ); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts index c621e0ee16bd5..d6797556b857a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts @@ -375,12 +375,17 @@ export const AddSourceLogic = kea(route, { From fe645786709bfcce4551d668a1098e932c2a000e Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Fri, 1 Apr 2022 05:49:40 -0600 Subject: [PATCH 17/65] [Metrics UI][Rules] Standardize NOW as startedAt from executor options (#128020) * [Metrics UI][Rules] Standardize NOW as startedAt from executor options * Removing moment --- .../evaluate_condition.ts | 13 +++++-------- .../inventory_metric_threshold_executor.ts | 6 +++--- .../lib/calculate_from_based_on_metric.ts | 11 +++++------ .../metric_threshold/metric_threshold_executor.ts | 10 +++++----- .../apis/metrics_ui/inventory_threshold_alert.ts | 6 +++--- 5 files changed, 21 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts index b59235d3ea95a..7aaee25304a08 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts @@ -7,14 +7,13 @@ import { ElasticsearchClient } from 'kibana/server'; import { mapValues } from 'lodash'; -import moment from 'moment'; import { Logger } from '@kbn/logging'; import { InventoryMetricConditions } from '../../../../common/alerting/metrics'; import { InfraTimerangeInput } from '../../../../common/http_api'; import { InventoryItemType } from '../../../../common/inventory_models/types'; import { LogQueryFields } from '../../metrics/types'; import { InfraSource } from '../../sources'; -import { calcualteFromBasedOnMetric } from './lib/calculate_from_based_on_metric'; +import { calculateFromBasedOnMetric } from './lib/calculate_from_based_on_metric'; import { getData } from './lib/get_data'; type ConditionResult = InventoryMetricConditions & { @@ -34,7 +33,7 @@ export const evaluateCondition = async ({ compositeSize, filterQuery, lookbackSize, - startTime, + executionTimestamp, logger, }: { condition: InventoryMetricConditions; @@ -45,16 +44,14 @@ export const evaluateCondition = async ({ compositeSize: number; filterQuery?: string; lookbackSize?: number; - startTime?: number; + executionTimestamp: Date; logger: Logger; }): Promise> => { const { metric, customMetric } = condition; - const to = startTime ? moment(startTime) : moment(); - const timerange = { - to: to.valueOf(), - from: calcualteFromBasedOnMetric(to, condition, nodeType, metric, customMetric), + to: executionTimestamp.valueOf(), + from: calculateFromBasedOnMetric(executionTimestamp, condition, nodeType, metric, customMetric), interval: `${condition.timeSize}${condition.timeUnit}`, forceInterval: true, } as InfraTimerangeInput; diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index f962d73edebc4..f12cd565a6b97 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -8,7 +8,6 @@ import { i18n } from '@kbn/i18n'; import { ALERT_REASON, ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils'; import { first, get } from 'lodash'; -import moment from 'moment'; import { ActionGroup, ActionGroupIdsOf, @@ -98,8 +97,8 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = group: '*', alertState: stateToAlertMessage[AlertStates.ERROR], reason, + timestamp: startedAt.toISOString(), viewInAppUrl, - timestamp: moment().toISOString(), value: null, metric: mapToConditionsLookup(criteria, (c) => c.metric), }); @@ -128,6 +127,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = esClient: services.scopedClusterClient.asCurrentUser, compositeSize, filterQuery, + executionTimestamp: startedAt, logger, }) ) @@ -218,8 +218,8 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = group, alertState: stateToAlertMessage[nextState], reason, + timestamp: startedAt.toISOString(), viewInAppUrl, - timestamp: moment().toISOString(), value: mapToConditionsLookup(results, (result) => formatMetric(result[group].metric, result[group].currentValue) ), diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/lib/calculate_from_based_on_metric.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/lib/calculate_from_based_on_metric.ts index 5adaa44130929..9b1399fccf705 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/lib/calculate_from_based_on_metric.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/lib/calculate_from_based_on_metric.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Moment } from 'moment'; +import moment from 'moment'; import { InventoryMetricConditions } from '../../../../../common/alerting/metrics'; import { SnapshotCustomMetricInput } from '../../../../../common/http_api'; import { findInventoryModel } from '../../../../../common/inventory_models'; @@ -15,8 +15,8 @@ import { } from '../../../../../common/inventory_models/types'; import { isRate } from './is_rate'; -export const calcualteFromBasedOnMetric = ( - to: Moment, +export const calculateFromBasedOnMetric = ( + to: Date, condition: InventoryMetricConditions, nodeType: InventoryItemType, metric: SnapshotMetricType, @@ -25,11 +25,10 @@ export const calcualteFromBasedOnMetric = ( const inventoryModel = findInventoryModel(nodeType); const metricAgg = inventoryModel.metrics.snapshot[metric]; if (isRate(metricAgg, customMetric)) { - return to - .clone() + return moment(to) .subtract(condition.timeSize * 2, condition.timeUnit) .valueOf(); } else { - return to.clone().subtract(condition.timeSize, condition.timeUnit).valueOf(); + return moment(to).subtract(condition.timeSize, condition.timeUnit).valueOf(); } }; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index d8792e7cb9d34..1f0e1c3d192c2 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -8,7 +8,6 @@ import { i18n } from '@kbn/i18n'; import { ALERT_REASON } from '@kbn/rule-data-utils'; import { first, isEqual, last } from 'lodash'; -import moment from 'moment'; import { ActionGroupIdsOf, AlertInstanceContext as AlertContext, @@ -67,7 +66,7 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => MetricThresholdAlertContext, MetricThresholdAllowedActionGroups >(async function (options) { - const { services, params, state } = options; + const { services, params, state, startedAt } = options; const { criteria } = params; if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); const { alertWithLifecycle, savedObjectsClient } = services; @@ -94,7 +93,7 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => const { fromKueryExpression } = await import('@kbn/es-query'); fromKueryExpression(params.filterQueryText); } catch (e) { - const timestamp = moment().toISOString(); + const timestamp = startedAt.toISOString(); const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able const reason = buildInvalidQueryAlertReason(params.filterQueryText); const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason); @@ -138,7 +137,8 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => params as EvaluatedRuleParams, config, prevGroups, - compositeSize + compositeSize, + { end: startedAt.valueOf() } ); // Because each alert result has the same group definitions, just grab the groups from the first one. @@ -225,7 +225,7 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => if (reason) { const firstResult = first(alertResults); - const timestamp = (firstResult && firstResult[group].timestamp) ?? moment().toISOString(); + const timestamp = (firstResult && firstResult[group].timestamp) ?? startedAt.toISOString(); const actionGroupId = nextState === AlertStates.OK ? RecoveredActionGroup.id diff --git a/x-pack/test/api_integration/apis/metrics_ui/inventory_threshold_alert.ts b/x-pack/test/api_integration/apis/metrics_ui/inventory_threshold_alert.ts index 456d69d90ad45..75869e37199c5 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/inventory_threshold_alert.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/inventory_threshold_alert.ts @@ -93,7 +93,7 @@ export default function ({ getService }: FtrProviderContext) { source, logQueryFields: void 0, compositeSize: 10000, - startTime: DATES['8.0.0'].hosts_only.max, + executionTimestamp: new Date(DATES['8.0.0'].hosts_only.max), logger, }; @@ -451,7 +451,7 @@ export default function ({ getService }: FtrProviderContext) { it('should work FOR LAST 1 minute', async () => { const results = await evaluateCondition({ ...baseOptions, - startTime: DATES['8.0.0'].pods_only.max, + executionTimestamp: new Date(DATES['8.0.0'].pods_only.max), nodeType: 'pod' as InventoryItemType, condition: { ...baseCondition, @@ -492,7 +492,7 @@ export default function ({ getService }: FtrProviderContext) { it('should work FOR LAST 5 minute', async () => { const results = await evaluateCondition({ ...baseOptions, - startTime: DATES['8.0.0'].pods_only.max, + executionTimestamp: new Date(DATES['8.0.0'].pods_only.max), logQueryFields: { indexPattern: 'metricbeat-*' }, nodeType: 'pod', condition: { From b70b69f3ea7e3626834b010ea93ffaa088b11fbe Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Fri, 1 Apr 2022 13:50:17 +0200 Subject: [PATCH 18/65] Update dependency chromedriver to v100 (#129176) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 76a84675c2eb2..bb5a1ac61bb5d 100644 --- a/package.json +++ b/package.json @@ -753,7 +753,7 @@ "callsites": "^3.1.0", "chai": "3.5.0", "chance": "1.0.18", - "chromedriver": "^99.0.0", + "chromedriver": "^100.0.0", "clean-webpack-plugin": "^3.0.0", "cmd-shim": "^2.1.0", "compression-webpack-plugin": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index cb4bbea65b4a2..f90363032c43b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9974,10 +9974,10 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" -chromedriver@^99.0.0: - version "99.0.0" - resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-99.0.0.tgz#fbfcc7e74991dd50962e7dd456d78eaf49f56774" - integrity sha512-pyB+5LuyZdb7EBPL3i5D5yucZUD+SlkdiUtmpjaEnLd9zAXp+SvD/hP5xF4l/ZmWvUo/1ZLxAI1YBdhazGTpgA== +chromedriver@^100.0.0: + version "100.0.0" + resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-100.0.0.tgz#1b4bf5c89cea12c79f53bc94d8f5bb5aa79ed7be" + integrity sha512-oLfB0IgFEGY9qYpFQO/BNSXbPw7bgfJUN5VX8Okps9W2qNT4IqKh5hDwKWtpUIQNI6K3ToWe2/J5NdpurTY02g== dependencies: "@testim/chrome-version" "^1.1.2" axios "^0.24.0" From b5f66ca57c5edebe0192aad12a47b0af2c1e395d Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Fri, 1 Apr 2022 07:21:44 -0500 Subject: [PATCH 19/65] remove unused deprecated var (#129155) --- src/plugins/data/common/index.ts | 1 - src/plugins/data_views/common/constants.ts | 6 ------ src/plugins/data_views/common/index.ts | 1 - 3 files changed, 8 deletions(-) diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index a97b8025426f2..93aeb918bc53a 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -139,7 +139,6 @@ export { DEFAULT_ASSETS_TO_IGNORE, META_FIELDS, DATA_VIEW_SAVED_OBJECT_TYPE, - INDEX_PATTERN_SAVED_OBJECT_TYPE, isFilterable, fieldList, DataViewField, diff --git a/src/plugins/data_views/common/constants.ts b/src/plugins/data_views/common/constants.ts index 42f869908ec25..d6a9def882a1b 100644 --- a/src/plugins/data_views/common/constants.ts +++ b/src/plugins/data_views/common/constants.ts @@ -37,10 +37,4 @@ export const META_FIELDS = 'metaFields'; /** @public **/ export const DATA_VIEW_SAVED_OBJECT_TYPE = 'index-pattern'; -/** - * @deprecated Use DATA_VIEW_SAVED_OBJECT_TYPE. All index pattern interfaces were renamed. - */ - -export const INDEX_PATTERN_SAVED_OBJECT_TYPE = DATA_VIEW_SAVED_OBJECT_TYPE; - export const PLUGIN_NAME = 'DataViews'; diff --git a/src/plugins/data_views/common/index.ts b/src/plugins/data_views/common/index.ts index 954d3ed7e3590..13842b62a9d53 100644 --- a/src/plugins/data_views/common/index.ts +++ b/src/plugins/data_views/common/index.ts @@ -11,7 +11,6 @@ export { DEFAULT_ASSETS_TO_IGNORE, META_FIELDS, DATA_VIEW_SAVED_OBJECT_TYPE, - INDEX_PATTERN_SAVED_OBJECT_TYPE, } from './constants'; export type { IFieldType, IIndexPatternFieldList } from './fields'; export { From d70849d85225cabead1e19fd0f47779546935be1 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Fri, 1 Apr 2022 14:52:43 +0200 Subject: [PATCH 20/65] fixed windows command line separator (#129189) --- .../fleet_server_on_prem_instructions.tsx | 11 +---- .../components/install_command_utils.test.ts | 41 +++---------------- .../components/install_command_utils.ts | 31 +++++++------- 3 files changed, 22 insertions(+), 61 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx index 44c1ebd5f2c36..0c24579acf9f0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx @@ -235,7 +235,6 @@ export const useFleetServerInstructions = (policyId?: string) => { } return getInstallCommandForPlatform( - platform, esHost, serviceToken, policyId, @@ -243,15 +242,7 @@ export const useFleetServerInstructions = (policyId?: string) => { deploymentMode === 'production', sslCATrustedFingerprint ); - }, [ - serviceToken, - esHost, - platform, - policyId, - fleetServerHost, - deploymentMode, - sslCATrustedFingerprint, - ]); + }, [serviceToken, esHost, policyId, fleetServerHost, deploymentMode, sslCATrustedFingerprint]); const getServiceToken = useCallback(async () => { setIsLoadingServiceToken(true); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts index a78bbaa4db3de..dd8913c49907f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts @@ -10,11 +10,7 @@ import { getInstallCommandForPlatform } from './install_command_utils'; describe('getInstallCommandForPlatform', () => { describe('without policy id', () => { it('should return the correct command if the the policyId is not set for linux', () => { - const res = getInstallCommandForPlatform( - 'linux', - 'http://elasticsearch:9200', - 'service-token-1' - ); + const res = getInstallCommandForPlatform('http://elasticsearch:9200', 'service-token-1'); expect(res.linux).toMatchInlineSnapshot(` "sudo ./elastic-agent install \\\\ @@ -25,11 +21,7 @@ describe('getInstallCommandForPlatform', () => { }); it('should return the correct command if the the policyId is not set for mac', () => { - const res = getInstallCommandForPlatform( - 'mac', - 'http://elasticsearch:9200', - 'service-token-1' - ); + const res = getInstallCommandForPlatform('http://elasticsearch:9200', 'service-token-1'); expect(res.mac).toMatchInlineSnapshot(` "sudo ./elastic-agent install \\\\ @@ -40,11 +32,7 @@ describe('getInstallCommandForPlatform', () => { }); it('should return the correct command if the the policyId is not set for windows', () => { - const res = getInstallCommandForPlatform( - 'windows', - 'http://elasticsearch:9200', - 'service-token-1' - ); + const res = getInstallCommandForPlatform('http://elasticsearch:9200', 'service-token-1'); expect(res.windows).toMatchInlineSnapshot(` ".\\\\elastic-agent.exe install \` @@ -55,11 +43,7 @@ describe('getInstallCommandForPlatform', () => { }); it('should return the correct command if the the policyId is not set for rpm', () => { - const res = getInstallCommandForPlatform( - 'rpm', - 'http://elasticsearch:9200', - 'service-token-1' - ); + const res = getInstallCommandForPlatform('http://elasticsearch:9200', 'service-token-1'); expect(res.rpm).toMatchInlineSnapshot(` "sudo elastic-agent enroll \\\\ @@ -70,11 +54,7 @@ describe('getInstallCommandForPlatform', () => { }); it('should return the correct command if the the policyId is not set for deb', () => { - const res = getInstallCommandForPlatform( - 'deb', - 'http://elasticsearch:9200', - 'service-token-1' - ); + const res = getInstallCommandForPlatform('http://elasticsearch:9200', 'service-token-1'); expect(res.deb).toMatchInlineSnapshot(` "sudo elastic-agent enroll \\\\ @@ -86,7 +66,6 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command sslCATrustedFingerprint option is passed', () => { const res = getInstallCommandForPlatform( - 'linux', 'http://elasticsearch:9200', 'service-token-1', undefined, @@ -108,7 +87,6 @@ describe('getInstallCommandForPlatform', () => { describe('with policy id', () => { it('should return the correct command if the the policyId is set for linux', () => { const res = getInstallCommandForPlatform( - 'linux', 'http://elasticsearch:9200', 'service-token-1', 'policy-1' @@ -125,7 +103,6 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for mac', () => { const res = getInstallCommandForPlatform( - 'mac', 'http://elasticsearch:9200', 'service-token-1', 'policy-1' @@ -142,7 +119,6 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for windows', () => { const res = getInstallCommandForPlatform( - 'windows', 'http://elasticsearch:9200', 'service-token-1', 'policy-1' @@ -159,7 +135,6 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for rpm', () => { const res = getInstallCommandForPlatform( - 'rpm', 'http://elasticsearch:9200', 'service-token-1', 'policy-1' @@ -176,7 +151,6 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for deb', () => { const res = getInstallCommandForPlatform( - 'deb', 'http://elasticsearch:9200', 'service-token-1', 'policy-1' @@ -195,7 +169,6 @@ describe('getInstallCommandForPlatform', () => { describe('with policy id and fleet server host and production deployment', () => { it('should return the correct command if the the policyId is set for linux', () => { const res = getInstallCommandForPlatform( - 'linux', 'http://elasticsearch:9200', 'service-token-1', 'policy-1', @@ -217,7 +190,6 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for mac', () => { const res = getInstallCommandForPlatform( - 'mac', 'http://elasticsearch:9200', 'service-token-1', 'policy-1', @@ -239,7 +211,6 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for windows', () => { const res = getInstallCommandForPlatform( - 'windows', 'http://elasticsearch:9200', 'service-token-1', 'policy-1', @@ -261,7 +232,6 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for rpm', () => { const res = getInstallCommandForPlatform( - 'rpm', 'http://elasticsearch:9200', 'service-token-1', 'policy-1', @@ -283,7 +253,6 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for deb', () => { const res = getInstallCommandForPlatform( - 'deb', 'http://elasticsearch:9200', 'service-token-1', 'policy-1', diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts index fd73975e4e2e6..f5413f44e81af 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts @@ -12,7 +12,6 @@ export type CommandsByPlatform = { }; export function getInstallCommandForPlatform( - platform: PLATFORM_TYPE, esHost: string, serviceToken: string, policyId?: string, @@ -20,8 +19,7 @@ export function getInstallCommandForPlatform( isProductionDeployment?: boolean, sslCATrustedFingerprint?: string ): CommandsByPlatform { - const commandArguments = []; - const newLineSeparator = platform === 'windows' ? '`\n' : '\\\n'; + const commandArguments: Array<[string, string] | [string]> = []; if (isProductionDeployment && fleetServerHost) { commandArguments.push(['url', fleetServerHost]); @@ -48,19 +46,22 @@ export function getInstallCommandForPlatform( commandArguments.push(['fleet-server-insecure-http']); } - const commandArgumentsStr = commandArguments.reduce((acc, [key, val]) => { - if (acc === '' && key === 'url') { - return `--${key}=${val}`; - } - const valOrEmpty = val ? `=${val}` : ''; - return (acc += ` ${newLineSeparator} --${key}${valOrEmpty}`); - }, ''); + const commandArgumentsStr = (platform?: string) => { + const newLineSeparator = platform === 'windows' ? '`\n' : '\\\n'; + return commandArguments.reduce((acc, [key, val]) => { + if (acc === '' && key === 'url') { + return `--${key}=${val}`; + } + const valOrEmpty = val ? `=${val}` : ''; + return (acc += ` ${newLineSeparator} --${key}${valOrEmpty}`); + }, ''); + }; return { - linux: `sudo ./elastic-agent install ${commandArgumentsStr}`, - mac: `sudo ./elastic-agent install ${commandArgumentsStr}`, - windows: `.\\elastic-agent.exe install ${commandArgumentsStr}`, - deb: `sudo elastic-agent enroll ${commandArgumentsStr}`, - rpm: `sudo elastic-agent enroll ${commandArgumentsStr}`, + linux: `sudo ./elastic-agent install ${commandArgumentsStr()}`, + mac: `sudo ./elastic-agent install ${commandArgumentsStr()}`, + windows: `.\\elastic-agent.exe install ${commandArgumentsStr('windows')}`, + deb: `sudo elastic-agent enroll ${commandArgumentsStr()}`, + rpm: `sudo elastic-agent enroll ${commandArgumentsStr()}`, }; } From a45c0aa5eb5b2ff7eeeac3709e0b5b0d839b10ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Fri, 1 Apr 2022 14:56:18 +0200 Subject: [PATCH 21/65] Fix flaky test #128561 (#129102) --- .../cluster_routing_allocation_disabled.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/server/saved_objects/migrations/integration_tests/cluster_routing_allocation_disabled.test.ts b/src/core/server/saved_objects/migrations/integration_tests/cluster_routing_allocation_disabled.test.ts index 0f4522b156fe7..ea70478d6ce7b 100644 --- a/src/core/server/saved_objects/migrations/integration_tests/cluster_routing_allocation_disabled.test.ts +++ b/src/core/server/saved_objects/migrations/integration_tests/cluster_routing_allocation_disabled.test.ts @@ -113,8 +113,8 @@ describe('unsupported_cluster_routing_allocation', () => { await root.preboot(); await root.setup(); - await expect(root.start()).rejects.toMatchInlineSnapshot( - `[Error: Unable to complete saved object migrations for the [.kibana] index: The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue. To proceed, please remove the cluster routing allocation settings with PUT /_cluster/settings {"transient": {"cluster.routing.allocation.enable": null}, "persistent": {"cluster.routing.allocation.enable": null}}]` + await expect(root.start()).rejects.toThrowError( + /Unable to complete saved object migrations for the \[\.kibana.*\] index: The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue\. To proceed, please remove the cluster routing allocation settings with PUT \/_cluster\/settings {"transient": {"cluster\.routing\.allocation\.enable": null}, "persistent": {"cluster\.routing\.allocation\.enable": null}}/ ); await retryAsync( @@ -126,8 +126,8 @@ describe('unsupported_cluster_routing_allocation', () => { .map((str) => JSON5.parse(str)) as LogRecord[]; expect( records.find((rec) => - rec.message.startsWith( - `Unable to complete saved object migrations for the [.kibana] index: The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue.` + /^Unable to complete saved object migrations for the \[\.kibana.*\] index: The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue\./.test( + rec.message ) ) ).toBeDefined(); @@ -148,8 +148,8 @@ describe('unsupported_cluster_routing_allocation', () => { await root.preboot(); await root.setup(); - await expect(root.start()).rejects.toMatchInlineSnapshot( - `[Error: Unable to complete saved object migrations for the [.kibana] index: The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue. To proceed, please remove the cluster routing allocation settings with PUT /_cluster/settings {"transient": {"cluster.routing.allocation.enable": null}, "persistent": {"cluster.routing.allocation.enable": null}}]` + await expect(root.start()).rejects.toThrowError( + /Unable to complete saved object migrations for the \[\.kibana.*\] index: The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue\. To proceed, please remove the cluster routing allocation settings with PUT \/_cluster\/settings {"transient": {"cluster\.routing\.allocation\.enable": null}, "persistent": {"cluster\.routing\.allocation\.enable": null}}/ ); }); }); From 6e01ddbae832017494eef061090c9abf8c74efbd Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Fri, 1 Apr 2022 15:03:11 +0200 Subject: [PATCH 22/65] [Fleet] added missing change to show Monitor logs/metrics as Enabled/Disabled (#129165) --- .../components/agent_details/agent_details_overview.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx index 2f6e501ed9bc1..44f545aa56b43 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx @@ -146,12 +146,12 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ agentPolicy?.monitoring_enabled?.includes('logs') ? ( ) : ( ) ) : null, @@ -164,12 +164,12 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ agentPolicy?.monitoring_enabled?.includes('metrics') ? ( ) : ( ) ) : null, From a39b116a337fcf3f63049d3291b11b1d06509e3b Mon Sep 17 00:00:00 2001 From: Andrew Tate Date: Fri, 1 Apr 2022 08:10:49 -0500 Subject: [PATCH 23/65] [Lens] settings menu unit tests (#128984) --- .../public/app_plugin/settings_menu.test.tsx | 86 +++++++++++++++++++ .../lens/public/app_plugin/settings_menu.tsx | 20 +++-- x-pack/plugins/lens/public/mocks/index.ts | 1 + .../plugins/lens/public/mocks/store_mocks.tsx | 36 ++++++-- 4 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugins/lens/public/app_plugin/settings_menu.test.tsx diff --git a/x-pack/plugins/lens/public/app_plugin/settings_menu.test.tsx b/x-pack/plugins/lens/public/app_plugin/settings_menu.test.tsx new file mode 100644 index 0000000000000..626a4bdb57c1b --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/settings_menu.test.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiSwitch, EuiSwitchEvent, EuiWrappingPopover } from '@elastic/eui'; +import { getMountWithProviderParams } from '../mocks'; +import { SettingsMenu } from './settings_menu'; +import { selectAutoApplyEnabled } from '../state_management'; +import { mount, ReactWrapper } from 'enzyme'; +import { EnhancedStore } from '@reduxjs/toolkit'; +import { I18nProvider } from '@kbn/i18n-react'; + +class Harness { + private _instance: ReactWrapper; + + constructor(instance: ReactWrapper) { + this._instance = instance; + } + + triggerClose() { + this._instance.find(EuiWrappingPopover).props().closePopover(); + } + + private get autoApplySwitch() { + return this._instance.find(EuiSwitch); + } + + public toggleAutoApply() { + this.autoApplySwitch.props().onChange({} as EuiSwitchEvent); + this._instance.update(); + } + + public get autoApplyEnabled() { + return this.autoApplySwitch.props().checked; + } +} + +describe('settings menu', () => { + const anchorButton = document.createElement('button'); + let onCloseMock: jest.Mock; + let instance: ReactWrapper; + let harness: Harness; + let lensStore: EnhancedStore; + + beforeEach(() => { + onCloseMock = jest.fn(); + + // not using mountWithProvider since it wraps the mount call in ReactTestUtils.act + // which causes the EuiPopover to close + const { mountArgs, lensStore: _lensStore } = getMountWithProviderParams( + + + + ); + + lensStore = _lensStore; + instance = mount(mountArgs.component, mountArgs.options); + harness = new Harness(instance); + }); + + it('should call onClose when popover closes', async () => { + harness.triggerClose(); + expect(onCloseMock).toHaveBeenCalledTimes(1); + }); + + it('should toggle auto-apply', async () => { + const enabledInState = () => selectAutoApplyEnabled(lensStore.getState()); + + expect(harness.autoApplyEnabled).toBeTruthy(); + expect(enabledInState()).toBeTruthy(); + + harness.toggleAutoApply(); + + expect(harness.autoApplyEnabled).toBeFalsy(); + expect(enabledInState()).toBeFalsy(); + + harness.toggleAutoApply(); + + expect(harness.autoApplyEnabled).toBeTruthy(); + expect(enabledInState()).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/lens/public/app_plugin/settings_menu.tsx b/x-pack/plugins/lens/public/app_plugin/settings_menu.tsx index 37f4feb5f5520..1adebcf123df9 100644 --- a/x-pack/plugins/lens/public/app_plugin/settings_menu.tsx +++ b/x-pack/plugins/lens/public/app_plugin/settings_menu.tsx @@ -29,13 +29,16 @@ import { writeToStorage } from '../settings_storage'; import { AUTO_APPLY_DISABLED_STORAGE_KEY } from '../editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper'; const container = document.createElement('div'); -let isOpen = false; +let isMenuOpen = false; -function SettingsMenu({ +// exported for testing purposes only +export function SettingsMenu({ anchorElement, + isOpen, onClose, }: { anchorElement: HTMLElement; + isOpen: boolean; onClose: () => void; }) { const autoApplyEnabled = useLensSelector(selectAutoApplyEnabled); @@ -79,27 +82,32 @@ function SettingsMenu({ function closeSettingsMenu() { ReactDOM.unmountComponentAtNode(container); document.body.removeChild(container); - isOpen = false; + isMenuOpen = false; } +/** + * Toggles the settings menu + * + * Note: the code inside this function is covered only at the functional test level + */ export function toggleSettingsMenuOpen(props: { lensStore: Store; anchorElement: HTMLElement; theme$: Observable; }) { - if (isOpen) { + if (isMenuOpen) { closeSettingsMenu(); return; } - isOpen = true; + isMenuOpen = true; document.body.appendChild(container); const element = ( - + diff --git a/x-pack/plugins/lens/public/mocks/index.ts b/x-pack/plugins/lens/public/mocks/index.ts index e9f03cde2642e..4c97fea960eac 100644 --- a/x-pack/plugins/lens/public/mocks/index.ts +++ b/x-pack/plugins/lens/public/mocks/index.ts @@ -23,6 +23,7 @@ export { defaultState, makeLensStore, mountWithProvider, + getMountWithProviderParams, } from './store_mocks'; export { lensPluginMock } from './lens_plugin_mock'; diff --git a/x-pack/plugins/lens/public/mocks/store_mocks.tsx b/x-pack/plugins/lens/public/mocks/store_mocks.tsx index 3365657fea34e..a4067bc955368 100644 --- a/x-pack/plugins/lens/public/mocks/store_mocks.tsx +++ b/x-pack/plugins/lens/public/mocks/store_mocks.tsx @@ -105,6 +105,26 @@ export const mountWithProvider = async ( }>; attachTo?: HTMLElement; } +) => { + const { mountArgs, lensStore, deps } = getMountWithProviderParams(component, store, options); + + let instance: ReactWrapper = {} as ReactWrapper; + + await act(async () => { + instance = mount(mountArgs.component, mountArgs.options); + }); + return { instance, lensStore, deps }; +}; + +export const getMountWithProviderParams = ( + component: React.ReactElement, + store?: MountStoreProps, + options?: { + wrappingComponent?: React.FC<{ + children: React.ReactNode; + }>; + attachTo?: HTMLElement; + } ) => { const { store: lensStore, deps } = makeLensStore(store || {}); @@ -114,7 +134,7 @@ export const mountWithProvider = async ( let restOptions: { attachTo?: HTMLElement | undefined; - }; + } = {}; if (options) { const { wrappingComponent: _wrappingComponent, ...rest } = options; restOptions = rest; @@ -128,13 +148,13 @@ export const mountWithProvider = async ( } } - let instance: ReactWrapper = {} as ReactWrapper; - - await act(async () => { - instance = mount(component, { + const mountArgs = { + component, + options: { wrappingComponent, ...restOptions, - } as unknown as ReactWrapper); - }); - return { instance, lensStore, deps }; + } as unknown as ReactWrapper, + }; + + return { mountArgs, lensStore, deps }; }; From 3b8b8f1adb691ea392971834cff263ed9a7e3d48 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Fri, 1 Apr 2022 15:18:29 +0200 Subject: [PATCH 24/65] [Workplace Search] Private sources with multiple options now route correctly (#129192) --- .../components/add_source/add_source.test.tsx | 13 ++++++ .../components/add_source/add_source.tsx | 3 +- .../components/add_source/add_source_logic.ts | 1 + .../configured_sources_list.test.tsx | 46 ++++++++++++++++++- .../add_source/configured_sources_list.tsx | 4 +- 5 files changed, 64 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx index 48e57d92272ba..da42844de56dc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx @@ -87,6 +87,19 @@ describe('AddSourceList', () => { ); }); + it('renders default state correctly when there are not multiple connector options, and the connector has been configured', () => { + const sourceData = { + ...staticSourceData[0], + externalConnectorAvailable: false, + configured: true, + }; + shallow(); + expect(initializeAddSource).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ connect: true }) + ); + }); + it('renders default state correctly when there are multiple connector options', () => { const wrapper = shallow( = (props) => { useEffect(() => { // We can land on this page from a choice page for multiple types of connectors // If that's the case we want to skip the intro and configuration, if the external & internal connector have already been configured - const goToConnect = externalConnectorAvailable && externalConfigured && configured; + // Otherwise, we skip to connect if the source is already configured + const goToConnect = externalConnectorAvailable ? externalConfigured && configured : configured; initializeAddSource(goToConnect ? { ...props, connect: true } : props); return resetSourceState; }, [configured]); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts index d6797556b857a..631789e591635 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts @@ -232,6 +232,7 @@ export const AddSourceLogic = kea false, resetSourceState: () => false, setPreContentSourceConfigData: () => false, + getSourceConfigData: () => true, }, ], buttonLoading: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.test.tsx index eafd5e705b0de..f0e81748a6561 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.test.tsx @@ -50,7 +50,51 @@ describe('ConfiguredSourcesList', () => { }} /> ); - expect(wrapper.find(EuiButtonEmptyTo)).toHaveLength(1); + const button = wrapper.find(EuiButtonEmptyTo); + expect(button).toHaveLength(1); + expect(button.prop('to')).toEqual('/sources/add/external/connect'); + }); + + it('connect button for an unconnected source with multiple connector options routes to choice page', () => { + const wrapper = shallow( + + ); + const button = wrapper.find(EuiButtonEmptyTo); + expect(button).toHaveLength(1); + expect(button.prop('to')).toEqual('/sources/add/share_point/'); + }); + + it('connect button for a source with multiple connector options routes to connect page for private sources', () => { + const wrapper = shallow( + + ); + const button = wrapper.find(EuiButtonEmptyTo); + expect(button).toHaveLength(1); + expect(button.prop('to')).toEqual('/p/sources/add/share_point/connect'); }); it('handles empty state', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx index 99fb7de7ddbae..6e4e4dcec2ba0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx @@ -132,7 +132,9 @@ export const ConfiguredSourcesList: React.FC = ({ {!connected From dc43c01fa1e741667b7cf3f1f240a425a0a6d365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= Date: Fri, 1 Apr 2022 15:19:53 +0200 Subject: [PATCH 25/65] [Security Solution][Endpoint] Fix UI breaks for large values in artifact description (#129163) * Adds css classes on EuiText component to break long words like urls or long names * Updates snapshot due css changes --- .../components/text_value_display.tsx | 2 +- .../__snapshots__/index.test.tsx.snap | 480 +++++++++--------- 2 files changed, 241 insertions(+), 241 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/text_value_display.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/text_value_display.tsx index dedb2c0ada87e..327eb963bbb7d 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/text_value_display.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/text_value_display.tsx @@ -43,7 +43,7 @@ export const TextValueDisplay = memo( }, [bold, children]); return ( - + {withTooltip && 'string' === typeof children && children.length > 0 && diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap index 9ec4b93c7c8af..665122574de70 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap @@ -513,7 +513,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-label" >

Last updated
@@ -523,7 +523,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -559,7 +559,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -569,7 +569,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -678,7 +678,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -743,7 +743,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -773,7 +773,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -785,7 +785,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` class="euiSpacer euiSpacer--l" />
Trusted App 0 @@ -895,7 +895,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -905,7 +905,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -941,7 +941,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -951,7 +951,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -1060,7 +1060,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -1125,7 +1125,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -1155,7 +1155,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -1167,7 +1167,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` class="euiSpacer euiSpacer--l" />
Trusted App 1 @@ -1277,7 +1277,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -1287,7 +1287,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -1323,7 +1323,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -1333,7 +1333,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -1442,7 +1442,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -1507,7 +1507,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -1537,7 +1537,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -1549,7 +1549,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` class="euiSpacer euiSpacer--l" />
Trusted App 2 @@ -1659,7 +1659,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -1669,7 +1669,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -1705,7 +1705,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -1715,7 +1715,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -1824,7 +1824,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -1889,7 +1889,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -1919,7 +1919,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -1931,7 +1931,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` class="euiSpacer euiSpacer--l" />
Trusted App 3 @@ -2041,7 +2041,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -2051,7 +2051,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -2087,7 +2087,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -2097,7 +2097,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -2206,7 +2206,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -2271,7 +2271,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -2301,7 +2301,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -2313,7 +2313,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` class="euiSpacer euiSpacer--l" />
Trusted App 4 @@ -2423,7 +2423,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -2433,7 +2433,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -2469,7 +2469,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -2479,7 +2479,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -2588,7 +2588,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -2653,7 +2653,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -2683,7 +2683,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -2695,7 +2695,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` class="euiSpacer euiSpacer--l" />
Trusted App 5 @@ -2805,7 +2805,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -2815,7 +2815,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -2851,7 +2851,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -2861,7 +2861,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -2970,7 +2970,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -3035,7 +3035,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -3065,7 +3065,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -3077,7 +3077,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` class="euiSpacer euiSpacer--l" />
Trusted App 6 @@ -3187,7 +3187,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -3197,7 +3197,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -3233,7 +3233,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -3243,7 +3243,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -3352,7 +3352,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -3417,7 +3417,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -3447,7 +3447,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -3459,7 +3459,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` class="euiSpacer euiSpacer--l" />
Trusted App 7 @@ -3569,7 +3569,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -3579,7 +3579,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -3615,7 +3615,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -3625,7 +3625,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -3734,7 +3734,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -3799,7 +3799,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -3829,7 +3829,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -3841,7 +3841,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` class="euiSpacer euiSpacer--l" />
Trusted App 8 @@ -3951,7 +3951,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -3961,7 +3961,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -3997,7 +3997,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -4007,7 +4007,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -4116,7 +4116,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -4181,7 +4181,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -4211,7 +4211,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -4223,7 +4223,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` class="euiSpacer euiSpacer--l" />
Trusted App 9 @@ -4665,7 +4665,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -4675,7 +4675,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -4711,7 +4711,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -4721,7 +4721,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -4830,7 +4830,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -4895,7 +4895,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -4925,7 +4925,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -4937,7 +4937,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time class="euiSpacer euiSpacer--l" />
Trusted App 0 @@ -5047,7 +5047,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -5057,7 +5057,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -5093,7 +5093,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -5103,7 +5103,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -5212,7 +5212,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -5277,7 +5277,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -5307,7 +5307,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -5319,7 +5319,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time class="euiSpacer euiSpacer--l" />
Trusted App 1 @@ -5429,7 +5429,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -5439,7 +5439,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -5475,7 +5475,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -5485,7 +5485,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -5594,7 +5594,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -5659,7 +5659,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -5689,7 +5689,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -5701,7 +5701,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time class="euiSpacer euiSpacer--l" />
Trusted App 2 @@ -5811,7 +5811,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -5821,7 +5821,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -5857,7 +5857,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -5867,7 +5867,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -5976,7 +5976,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -6041,7 +6041,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -6071,7 +6071,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -6083,7 +6083,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time class="euiSpacer euiSpacer--l" />
Trusted App 3 @@ -6193,7 +6193,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -6203,7 +6203,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -6239,7 +6239,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -6249,7 +6249,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -6358,7 +6358,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -6423,7 +6423,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -6453,7 +6453,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -6465,7 +6465,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time class="euiSpacer euiSpacer--l" />
Trusted App 4 @@ -6575,7 +6575,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -6585,7 +6585,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -6621,7 +6621,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -6631,7 +6631,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -6740,7 +6740,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -6805,7 +6805,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -6835,7 +6835,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -6847,7 +6847,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time class="euiSpacer euiSpacer--l" />
Trusted App 5 @@ -6957,7 +6957,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -6967,7 +6967,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -7003,7 +7003,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -7013,7 +7013,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -7122,7 +7122,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -7187,7 +7187,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -7217,7 +7217,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -7229,7 +7229,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time class="euiSpacer euiSpacer--l" />
Trusted App 6 @@ -7339,7 +7339,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -7349,7 +7349,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -7385,7 +7385,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -7395,7 +7395,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -7504,7 +7504,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -7569,7 +7569,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -7599,7 +7599,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -7611,7 +7611,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time class="euiSpacer euiSpacer--l" />
Trusted App 7 @@ -7721,7 +7721,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -7731,7 +7731,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -7767,7 +7767,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -7777,7 +7777,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -7886,7 +7886,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -7951,7 +7951,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -7981,7 +7981,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -7993,7 +7993,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time class="euiSpacer euiSpacer--l" />
Trusted App 8 @@ -8103,7 +8103,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -8113,7 +8113,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -8149,7 +8149,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -8159,7 +8159,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -8268,7 +8268,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -8333,7 +8333,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -8363,7 +8363,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -8375,7 +8375,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time class="euiSpacer euiSpacer--l" />
Trusted App 9 @@ -8774,7 +8774,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -8784,7 +8784,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -8820,7 +8820,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -8830,7 +8830,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -8939,7 +8939,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -9004,7 +9004,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -9034,7 +9034,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -9046,7 +9046,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not class="euiSpacer euiSpacer--l" />
Trusted App 0 @@ -9156,7 +9156,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -9166,7 +9166,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -9202,7 +9202,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -9212,7 +9212,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -9321,7 +9321,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -9386,7 +9386,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -9416,7 +9416,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -9428,7 +9428,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not class="euiSpacer euiSpacer--l" />
Trusted App 1 @@ -9538,7 +9538,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -9548,7 +9548,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -9584,7 +9584,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -9594,7 +9594,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -9703,7 +9703,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -9768,7 +9768,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -9798,7 +9798,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -9810,7 +9810,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not class="euiSpacer euiSpacer--l" />
Trusted App 2 @@ -9920,7 +9920,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -9930,7 +9930,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -9966,7 +9966,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -9976,7 +9976,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -10085,7 +10085,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -10150,7 +10150,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -10180,7 +10180,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -10192,7 +10192,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not class="euiSpacer euiSpacer--l" />
Trusted App 3 @@ -10302,7 +10302,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -10312,7 +10312,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -10348,7 +10348,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -10358,7 +10358,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -10467,7 +10467,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -10532,7 +10532,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -10562,7 +10562,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -10574,7 +10574,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not class="euiSpacer euiSpacer--l" />
Trusted App 4 @@ -10684,7 +10684,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -10694,7 +10694,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -10730,7 +10730,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -10740,7 +10740,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -10849,7 +10849,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -10914,7 +10914,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -10944,7 +10944,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -10956,7 +10956,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not class="euiSpacer euiSpacer--l" />
Trusted App 5 @@ -11066,7 +11066,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -11076,7 +11076,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -11112,7 +11112,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -11122,7 +11122,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -11231,7 +11231,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -11296,7 +11296,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -11326,7 +11326,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -11338,7 +11338,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not class="euiSpacer euiSpacer--l" />
Trusted App 6 @@ -11448,7 +11448,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -11458,7 +11458,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -11494,7 +11494,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -11504,7 +11504,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -11613,7 +11613,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -11678,7 +11678,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -11708,7 +11708,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -11720,7 +11720,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not class="euiSpacer euiSpacer--l" />
Trusted App 7 @@ -11830,7 +11830,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -11840,7 +11840,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -11876,7 +11876,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -11886,7 +11886,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -11995,7 +11995,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -12060,7 +12060,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -12090,7 +12090,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -12102,7 +12102,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not class="euiSpacer euiSpacer--l" />
Trusted App 8 @@ -12212,7 +12212,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-label" >
Last updated
@@ -12222,7 +12222,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-updated-value" >
1 minute ago @@ -12258,7 +12258,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-label" >
Created
@@ -12268,7 +12268,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-header-created-value" >
1 minute ago @@ -12377,7 +12377,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-createdBy-value" >
someone
@@ -12442,7 +12442,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-touchedBy-updatedBy-value" >
someone
@@ -12472,7 +12472,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not data-test-subj="trustedAppCard-subHeader-effectScope-value" >
Applied globally
@@ -12484,7 +12484,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not class="euiSpacer euiSpacer--l" />
Trusted App 9 From 93d227a644ad845cd99fd75443625d8129faf93b Mon Sep 17 00:00:00 2001 From: Faisal Kanout Date: Fri, 1 Apr 2022 16:28:29 +0300 Subject: [PATCH 26/65] [Actionable Observability] Bug: Rules summary on the Alerts view is not showing the count of rules (#129052) * [Actionable Observability] Bug: Rules summary on the Alerts view is not showing the count of rules --- .../public/application/lib/rule_api/aggregate.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.ts index b2556900e763a..d67c4f80b142d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.ts @@ -14,12 +14,14 @@ const rewriteBodyRes: RewriteRequestCase = ({ rule_execution_status: ruleExecutionStatus, rule_enabled_status: ruleEnabledStatus, rule_muted_status: ruleMutedStatus, + rule_snoozed_status: ruleSnoozedStatus, ...rest }: any) => ({ ...rest, ruleExecutionStatus, ruleEnabledStatus, ruleMutedStatus, + ruleSnoozedStatus, }); export async function loadRuleAggregations({ From 862dac8e24adbcb731c64538ed799bb2ee04bd4b Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Fri, 1 Apr 2022 15:59:14 +0200 Subject: [PATCH 27/65] [Reporting] Reinstate ability to return partial CSVs (#128732) * reinstate ability to return partial CSVs by not throwing an Unknown error * added tests and better logging of errors for CSVs * remove unused imports * Remove check for `maxSizeReached` in error handler Co-authored-by: Michael Dokolin Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Michael Dokolin --- .../generate_csv/generate_csv.test.ts | 105 ++++++++++++++---- .../generate_csv/generate_csv.ts | 23 ++-- .../generate_csv/i18n_texts.ts | 10 ++ 3 files changed, 100 insertions(+), 38 deletions(-) diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts index 37ec0d400f473..5c52a6b74e67d 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts @@ -28,7 +28,6 @@ import { UI_SETTINGS_CSV_SEPARATOR, UI_SETTINGS_DATEFORMAT_TZ, } from '../../../../common/constants'; -import { UnknownError } from '../../../../common/errors'; import { createMockConfig, createMockConfigSchema } from '../../../test_helpers'; import { JobParamsCSV } from '../types'; import { CsvGenerator } from './generate_csv'; @@ -805,6 +804,87 @@ it('can override ignoring frozen indices', async () => { ); }); +it('will return partial data if the scroll or search fails', async () => { + mockDataClient.search = jest.fn().mockImplementation(() => { + throw new esErrors.ResponseError({ + statusCode: 500, + meta: {} as any, + body: 'my error', + warnings: [], + }); + }); + const generateCsv = new CsvGenerator( + createMockJob({ columns: ['date', 'ip', 'message'] }), + mockConfig, + { + es: mockEsClient, + data: mockDataClient, + uiSettings: uiSettingsClient, + }, + { + searchSourceStart: mockSearchSourceService, + fieldFormatsRegistry: mockFieldFormatsRegistry, + }, + new CancellationToken(), + logger, + stream + ); + await expect(generateCsv.generateData()).resolves.toMatchInlineSnapshot(` + Object { + "content_type": "text/csv", + "csv_contains_formulas": false, + "error_code": undefined, + "max_size_reached": false, + "metrics": Object { + "csv": Object { + "rows": 0, + }, + }, + "warnings": Array [ + "Received a 500 response from Elasticsearch: my error", + ], + } + `); +}); + +it('handles unknown errors', async () => { + mockDataClient.search = jest.fn().mockImplementation(() => { + throw new Error('An unknown error'); + }); + const generateCsv = new CsvGenerator( + createMockJob({ columns: ['date', 'ip', 'message'] }), + mockConfig, + { + es: mockEsClient, + data: mockDataClient, + uiSettings: uiSettingsClient, + }, + { + searchSourceStart: mockSearchSourceService, + fieldFormatsRegistry: mockFieldFormatsRegistry, + }, + new CancellationToken(), + logger, + stream + ); + await expect(generateCsv.generateData()).resolves.toMatchInlineSnapshot(` + Object { + "content_type": "text/csv", + "csv_contains_formulas": false, + "error_code": undefined, + "max_size_reached": false, + "metrics": Object { + "csv": Object { + "rows": 0, + }, + }, + "warnings": Array [ + "Encountered an unknown error: An unknown error", + ], + } + `); +}); + describe('error codes', () => { it('returns the expected error code when authentication expires', async () => { mockDataClient.search = jest.fn().mockImplementation(() => @@ -854,27 +934,4 @@ describe('error codes', () => { ] `); }); - - it('throws for unknown errors', async () => { - mockDataClient.search = jest.fn().mockImplementation(() => { - throw new esErrors.ResponseError({ statusCode: 500, meta: {} as any, warnings: [] }); - }); - const generateCsv = new CsvGenerator( - createMockJob({ columns: ['date', 'ip', 'message'] }), - mockConfig, - { - es: mockEsClient, - data: mockDataClient, - uiSettings: uiSettingsClient, - }, - { - searchSourceStart: mockSearchSourceService, - fieldFormatsRegistry: mockFieldFormatsRegistry, - }, - new CancellationToken(), - logger, - stream - ); - await expect(generateCsv.generateData()).rejects.toBeInstanceOf(UnknownError); - }); }); diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts index c913706f58562..dc2067490521c 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts @@ -27,14 +27,9 @@ import type { FieldFormatConfig, IFieldFormatsRegistry, } from '../../../../../../../src/plugins/field_formats/common'; -import { KbnServerError } from '../../../../../../../src/plugins/kibana_utils/server'; import type { CancellationToken } from '../../../../common/cancellation_token'; import { CONTENT_TYPE_CSV } from '../../../../common/constants'; -import { - AuthenticationExpiredError, - ReportingError, - UnknownError, -} from '../../../../common/errors'; +import { AuthenticationExpiredError, ReportingError } from '../../../../common/errors'; import { byteSizeValueToNumber } from '../../../../common/schema_utils'; import type { TaskRunResult } from '../../../lib/tasks'; import type { JobParamsCSV } from '../types'; @@ -368,15 +363,15 @@ export class CsvGenerator { } } catch (err) { this.logger.error(err); - if (err instanceof KbnServerError && err.errBody) { - throw JSON.stringify(err.errBody.error); - } - - if (err instanceof esErrors.ResponseError && [401, 403].includes(err.statusCode ?? 0)) { - reportingError = new AuthenticationExpiredError(); - warnings.push(i18nTexts.authenticationError.partialResultsMessage); + if (err instanceof esErrors.ResponseError) { + if ([401, 403].includes(err.statusCode ?? 0)) { + reportingError = new AuthenticationExpiredError(); + warnings.push(i18nTexts.authenticationError.partialResultsMessage); + } else { + warnings.push(i18nTexts.esErrorMessage(err.statusCode ?? 0, String(err.body))); + } } else { - throw new UnknownError(err.message); + warnings.push(i18nTexts.unknownError(err?.message)); } } finally { // clear scrollID diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/i18n_texts.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/i18n_texts.ts index 72f6d96092e26..5fb39a3dc7ec5 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/i18n_texts.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/i18n_texts.ts @@ -23,4 +23,14 @@ export const i18nTexts = { } ), }, + esErrorMessage: (statusCode: number, message: string) => + i18n.translate('xpack.reporting.exportTypes.csv.generateCsv.esErrorMessage', { + defaultMessage: 'Received a {statusCode} response from Elasticsearch: {message}', + values: { statusCode, message }, + }), + unknownError: (message: string = 'unknown') => + i18n.translate('xpack.reporting.exportTypes.csv.generateCsv.unknownErrorMessage', { + defaultMessage: 'Encountered an unknown error: {message}', + values: { message }, + }), }; From a7b239f8d955ed21f9923a9ab63389e9e6e4be36 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Fri, 1 Apr 2022 15:00:03 +0100 Subject: [PATCH 28/65] [ML] Only suppoer ner pytorch model testing in UI (#129103) --- .../ml/common/constants/trained_models.ts | 11 ++++++++- .../test_models/selected_model.tsx | 10 ++++++-- .../models_management/test_models/utils.ts | 24 ++++++++++++++----- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/ml/common/constants/trained_models.ts b/x-pack/plugins/ml/common/constants/trained_models.ts index e7508af45f5b6..79fe65936b231 100644 --- a/x-pack/plugins/ml/common/constants/trained_models.ts +++ b/x-pack/plugins/ml/common/constants/trained_models.ts @@ -18,5 +18,14 @@ export const TRAINED_MODEL_TYPE = { TREE_ENSEMBLE: 'tree_ensemble', LANG_IDENT: 'lang_ident', } as const; - export type TrainedModelType = typeof TRAINED_MODEL_TYPE[keyof typeof TRAINED_MODEL_TYPE]; + +export const SUPPORTED_PYTORCH_TASKS = { + NER: 'ner', + // ZERO_SHOT_CLASSIFICATION: 'zero_shot_classification', + // CLASSIFICATION_LABELS: 'classification_labels', + // TEXT_CLASSIFICATION: 'text_classification', + // TEXT_EMBEDDING: 'text_embedding', +} as const; +export type SupportedPytorchTasksType = + typeof SUPPORTED_PYTORCH_TASKS[keyof typeof SUPPORTED_PYTORCH_TASKS]; diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/selected_model.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/selected_model.tsx index cab0826d5584a..29dbb855b0084 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/selected_model.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/selected_model.tsx @@ -13,7 +13,10 @@ import type { FormattedNerResp } from './models/ner'; import { LangIdentOutput, LangIdentInference } from './models/lang_ident'; import type { FormattedLangIdentResp } from './models/lang_ident'; -import { TRAINED_MODEL_TYPE } from '../../../../../common/constants/trained_models'; +import { + TRAINED_MODEL_TYPE, + SUPPORTED_PYTORCH_TASKS, +} from '../../../../../common/constants/trained_models'; import { useMlApiContext } from '../../../contexts/kibana'; import { InferenceInputForm } from './models/inference_input_form'; @@ -28,7 +31,10 @@ export const SelectedModel: FC = ({ model }) => { return null; } - if (model.model_type === TRAINED_MODEL_TYPE.PYTORCH) { + if ( + model.model_type === TRAINED_MODEL_TYPE.PYTORCH && + Object.keys(model.inference_config)[0] === SUPPORTED_PYTORCH_TASKS.NER + ) { const inferrer = new NerInference(trainedModels, model); return ( Date: Fri, 1 Apr 2022 07:58:00 -0700 Subject: [PATCH 29/65] Adds navigation flags to reload a page unconditionally (#128671) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- ...ana-plugin-core-public.applicationstart.md | 2 +- ...e-public.applicationstart.navigatetourl.md | 3 +- .../core/public/kibana-plugin-core-public.md | 1 + ...plugin-core-public.navigatetoappoptions.md | 1 + ...ublic.navigatetoappoptions.skipappleave.md | 13 ++++ ...blic.navigatetourloptions.forceredirect.md | 13 ++++ ...plugin-core-public.navigatetourloptions.md | 21 ++++++ ...ublic.navigatetourloptions.skipappleave.md | 13 ++++ .../application/application_service.test.ts | 68 ++++++++++++++++++- .../application/application_service.tsx | 30 +++++--- src/core/public/application/index.ts | 1 + .../application_service.test.tsx | 55 ++++++++++++++- src/core/public/application/types.ts | 25 ++++++- src/core/public/index.ts | 1 + src/core/public/public.api.md | 9 ++- .../shared/kibana/kibana_logic.ts | 5 +- 16 files changed, 243 insertions(+), 18 deletions(-) create mode 100644 docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.skipappleave.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.navigatetourloptions.forceredirect.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.navigatetourloptions.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.navigatetourloptions.skipappleave.md diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationstart.md b/docs/development/core/public/kibana-plugin-core-public.applicationstart.md index cadf0f91b01d6..eeb8ff3753f13 100644 --- a/docs/development/core/public/kibana-plugin-core-public.applicationstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.applicationstart.md @@ -25,5 +25,5 @@ export interface ApplicationStart | --- | --- | | [getUrlForApp(appId, options)](./kibana-plugin-core-public.applicationstart.geturlforapp.md) | Returns the absolute path (or URL) to a given app, including the global base path.By default, it returns the absolute path of the application (e.g /basePath/app/my-app). Use the absolute option to generate an absolute url instead (e.g http://host:port/basePath/app/my-app)Note that when generating absolute urls, the origin (protocol, host and port) are determined from the browser's current location. | | [navigateToApp(appId, options)](./kibana-plugin-core-public.applicationstart.navigatetoapp.md) | Navigate to a given app | -| [navigateToUrl(url)](./kibana-plugin-core-public.applicationstart.navigatetourl.md) | Navigate to given URL in a SPA friendly way when possible (when the URL will redirect to a valid application within the current basePath).The method resolves pathnames the same way browsers do when resolving a <a href> value. The provided url can be: - an absolute URL - an absolute path - a path relative to the current URL (window.location.href)If all these criteria are true for the given URL: - (only for absolute URLs) The origin of the URL matches the origin of the browser's current location - The resolved pathname of the provided URL/path starts with the current basePath (eg. /mybasepath/s/my-space) - The pathname segment after the basePath matches any known application route (eg. /app// or any application's appRoute configuration)Then a SPA navigation will be performed using navigateToApp using the corresponding application and path. Otherwise, fallback to a full page reload to navigate to the url using window.location.assign | +| [navigateToUrl(url, options)](./kibana-plugin-core-public.applicationstart.navigatetourl.md) | Navigate to given URL in a SPA friendly way when possible (when the URL will redirect to a valid application within the current basePath).The method resolves pathnames the same way browsers do when resolving a <a href> value. The provided url can be: - an absolute URL - an absolute path - a path relative to the current URL (window.location.href)If all these criteria are true for the given URL: - (only for absolute URLs) The origin of the URL matches the origin of the browser's current location - The resolved pathname of the provided URL/path starts with the current basePath (eg. /mybasepath/s/my-space) - The pathname segment after the basePath matches any known application route (eg. /app// or any application's appRoute configuration)Then a SPA navigation will be performed using navigateToApp using the corresponding application and path. Otherwise, fallback to a full page reload to navigate to the url using window.location.assign | diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationstart.navigatetourl.md b/docs/development/core/public/kibana-plugin-core-public.applicationstart.navigatetourl.md index 9e6644e2b1ca7..b7fbb12f12e29 100644 --- a/docs/development/core/public/kibana-plugin-core-public.applicationstart.navigatetourl.md +++ b/docs/development/core/public/kibana-plugin-core-public.applicationstart.navigatetourl.md @@ -15,7 +15,7 @@ Then a SPA navigation will be performed using `navigateToApp` using the correspo Signature: ```typescript -navigateToUrl(url: string): Promise; +navigateToUrl(url: string, options?: NavigateToUrlOptions): Promise; ``` ## Parameters @@ -23,6 +23,7 @@ navigateToUrl(url: string): Promise; | Parameter | Type | Description | | --- | --- | --- | | url | string | an absolute URL, an absolute path or a relative path, to navigate to. | +| options | NavigateToUrlOptions | | Returns: diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index 2e51a036dfe9f..241cd378ebcda 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -85,6 +85,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IHttpResponseInterceptorOverrides](./kibana-plugin-core-public.ihttpresponseinterceptoroverrides.md) | Properties that can be returned by HttpInterceptor.request to override the response. | | [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) | | [NavigateToAppOptions](./kibana-plugin-core-public.navigatetoappoptions.md) | Options for the [navigateToApp API](./kibana-plugin-core-public.applicationstart.navigatetoapp.md) | +| [NavigateToUrlOptions](./kibana-plugin-core-public.navigatetourloptions.md) | Options for the [navigateToUrl API](./kibana-plugin-core-public.applicationstart.navigatetourl.md) | | [NotificationsSetup](./kibana-plugin-core-public.notificationssetup.md) | | | [NotificationsStart](./kibana-plugin-core-public.notificationsstart.md) | | | [OverlayBannersStart](./kibana-plugin-core-public.overlaybannersstart.md) | | diff --git a/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.md b/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.md index c8ec5bdaf8c0d..337e9db1f80d2 100644 --- a/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.md +++ b/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.md @@ -20,5 +20,6 @@ export interface NavigateToAppOptions | [openInNewTab?](./kibana-plugin-core-public.navigatetoappoptions.openinnewtab.md) | boolean | (Optional) if true, will open the app in new tab, will share session information via window.open if base | | [path?](./kibana-plugin-core-public.navigatetoappoptions.path.md) | string | (Optional) optional path inside application to deep link to. If undefined, will use [the app's default path](./kibana-plugin-core-public.app.defaultpath.md) as default. | | [replace?](./kibana-plugin-core-public.navigatetoappoptions.replace.md) | boolean | (Optional) if true, will not create a new history entry when navigating (using replace instead of push) | +| [skipAppLeave?](./kibana-plugin-core-public.navigatetoappoptions.skipappleave.md) | boolean | (Optional) if true, will bypass the default onAppLeave behavior | | [state?](./kibana-plugin-core-public.navigatetoappoptions.state.md) | unknown | (Optional) optional state to forward to the application | diff --git a/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.skipappleave.md b/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.skipappleave.md new file mode 100644 index 0000000000000..553d557a92daa --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.skipappleave.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [NavigateToAppOptions](./kibana-plugin-core-public.navigatetoappoptions.md) > [skipAppLeave](./kibana-plugin-core-public.navigatetoappoptions.skipappleave.md) + +## NavigateToAppOptions.skipAppLeave property + +if true, will bypass the default onAppLeave behavior + +Signature: + +```typescript +skipAppLeave?: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.navigatetourloptions.forceredirect.md b/docs/development/core/public/kibana-plugin-core-public.navigatetourloptions.forceredirect.md new file mode 100644 index 0000000000000..1603524322dd7 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.navigatetourloptions.forceredirect.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [NavigateToUrlOptions](./kibana-plugin-core-public.navigatetourloptions.md) > [forceRedirect](./kibana-plugin-core-public.navigatetourloptions.forceredirect.md) + +## NavigateToUrlOptions.forceRedirect property + +if true, will redirect directly to the url + +Signature: + +```typescript +forceRedirect?: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.navigatetourloptions.md b/docs/development/core/public/kibana-plugin-core-public.navigatetourloptions.md new file mode 100644 index 0000000000000..ccf09e21189ef --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.navigatetourloptions.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [NavigateToUrlOptions](./kibana-plugin-core-public.navigatetourloptions.md) + +## NavigateToUrlOptions interface + +Options for the [navigateToUrl API](./kibana-plugin-core-public.applicationstart.navigatetourl.md) + +Signature: + +```typescript +export interface NavigateToUrlOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [forceRedirect?](./kibana-plugin-core-public.navigatetourloptions.forceredirect.md) | boolean | (Optional) if true, will redirect directly to the url | +| [skipAppLeave?](./kibana-plugin-core-public.navigatetourloptions.skipappleave.md) | boolean | (Optional) if true, will bypass the default onAppLeave behavior | + diff --git a/docs/development/core/public/kibana-plugin-core-public.navigatetourloptions.skipappleave.md b/docs/development/core/public/kibana-plugin-core-public.navigatetourloptions.skipappleave.md new file mode 100644 index 0000000000000..f3685c02ff40d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.navigatetourloptions.skipappleave.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [NavigateToUrlOptions](./kibana-plugin-core-public.navigatetourloptions.md) > [skipAppLeave](./kibana-plugin-core-public.navigatetourloptions.skipappleave.md) + +## NavigateToUrlOptions.skipAppLeave property + +if true, will bypass the default onAppLeave behavior + +Signature: + +```typescript +skipAppLeave?: boolean; +``` diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts index ccb0b220e0243..bb7378ff1f0f3 100644 --- a/src/core/public/application/application_service.test.ts +++ b/src/core/public/application/application_service.test.ts @@ -898,10 +898,9 @@ describe('#start()', () => { it('should call private function shouldNavigate with overlays and the nextAppId', async () => { service.setup(setupDeps); - const shouldNavigateSpy = jest.spyOn(service as any, 'shouldNavigate'); + const shouldNavigateSpy = jest.spyOn(service as any, 'shouldNavigate'); const { navigateToApp } = await service.start(startDeps); - await navigateToApp('myTestApp'); expect(shouldNavigateSpy).toHaveBeenCalledWith(startDeps.overlays, 'myTestApp'); @@ -909,6 +908,14 @@ describe('#start()', () => { expect(shouldNavigateSpy).toHaveBeenCalledWith(startDeps.overlays, 'myOtherApp'); }); + it('should call private function shouldNavigate with overlays, nextAppId and skipAppLeave', async () => { + service.setup(setupDeps); + const shouldNavigateSpy = jest.spyOn(service as any, 'shouldNavigate'); + const { navigateToApp } = await service.start(startDeps); + await navigateToApp('myTestApp', { skipAppLeave: true }); + expect(shouldNavigateSpy).not.toHaveBeenCalledWith(startDeps.overlays, 'myTestApp'); + }); + describe('when `replace` option is true', () => { it('use `history.replace` instead of `history.push`', async () => { service.setup(setupDeps); @@ -1117,6 +1124,63 @@ describe('#start()', () => { expect(MockHistory.push).toHaveBeenCalledWith('/app/foo/some-path', undefined); expect(setupDeps.redirectTo).not.toHaveBeenCalled(); }); + + describe('navigateToUrl with options', () => { + let addListenerSpy: jest.SpyInstance; + let removeListenerSpy: jest.SpyInstance; + beforeEach(() => { + addListenerSpy = jest.spyOn(window, 'addEventListener'); + removeListenerSpy = jest.spyOn(window, 'removeEventListener'); + }); + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('calls `navigateToApp` with `skipAppLeave` option', async () => { + parseAppUrlMock.mockReturnValue({ app: 'foo', path: '/some-path' }); + service.setup(setupDeps); + const { navigateToUrl } = await service.start(startDeps); + + await navigateToUrl('/an-app-path', { skipAppLeave: true }); + + expect(MockHistory.push).toHaveBeenCalledWith('/app/foo/some-path', undefined); + expect(setupDeps.redirectTo).not.toHaveBeenCalled(); + }); + + it('calls `redirectTo` when `forceRedirect` option is true', async () => { + parseAppUrlMock.mockReturnValue({ app: 'foo', path: '/some-path' }); + service.setup(setupDeps); + + const { navigateToUrl } = await service.start(startDeps); + + await navigateToUrl('/an-app-path', { forceRedirect: true }); + + expect(addListenerSpy).toHaveBeenCalledTimes(1); + expect(addListenerSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function)); + + expect(setupDeps.redirectTo).toHaveBeenCalledWith('/an-app-path'); + expect(MockHistory.push).not.toHaveBeenCalled(); + }); + + it('removes the beforeunload listener and calls `redirectTo` when `forceRedirect` and `skipAppLeave` option are both true', async () => { + parseAppUrlMock.mockReturnValue({ app: 'foo', path: '/some-path' }); + service.setup(setupDeps); + + const { navigateToUrl } = await service.start(startDeps); + + await navigateToUrl('/an-app-path', { skipAppLeave: true, forceRedirect: true }); + + expect(addListenerSpy).toHaveBeenCalledTimes(1); + expect(addListenerSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function)); + const handler = addListenerSpy.mock.calls[0][1]; + + expect(MockHistory.push).toHaveBeenCalledTimes(0); + expect(setupDeps.redirectTo).toHaveBeenCalledWith('/an-app-path'); + + expect(removeListenerSpy).toHaveBeenCalledTimes(1); + expect(removeListenerSpy).toHaveBeenCalledWith('beforeunload', handler); + }); + }); }); }); diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index 1cfae598f67c8..d49a33e3f1371 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -31,6 +31,7 @@ import { InternalApplicationStart, Mounter, NavigateToAppOptions, + NavigateToUrlOptions, } from './types'; import { getLeaveAction, isConfirmAction } from './application_leave'; import { getUserConfirmationHandler } from './navigation_confirm'; @@ -234,13 +235,19 @@ export class ApplicationService { const navigateToApp: InternalApplicationStart['navigateToApp'] = async ( appId, - { deepLinkId, path, state, replace = false, openInNewTab = false }: NavigateToAppOptions = {} + { + deepLinkId, + path, + state, + replace = false, + openInNewTab = false, + skipAppLeave = false, + }: NavigateToAppOptions = {} ) => { const currentAppId = this.currentAppId$.value; const navigatingToSameApp = currentAppId === appId; - const shouldNavigate = navigatingToSameApp - ? true - : await this.shouldNavigate(overlays, appId); + const shouldNavigate = + navigatingToSameApp || skipAppLeave ? true : await this.shouldNavigate(overlays, appId); const targetApp = applications$.value.get(appId); @@ -304,13 +311,20 @@ export class ApplicationService { return absolute ? relativeToAbsolute(relUrl) : relUrl; }, navigateToApp, - navigateToUrl: async (url) => { + navigateToUrl: async ( + url: string, + { skipAppLeave = false, forceRedirect = false }: NavigateToUrlOptions = {} + ) => { const appInfo = parseAppUrl(url, http.basePath, this.apps); - if (appInfo) { - return navigateToApp(appInfo.app, { path: appInfo.path }); - } else { + if ((forceRedirect || !appInfo) === true) { + if (skipAppLeave) { + window.removeEventListener('beforeunload', this.onBeforeUnload); + } return this.redirectTo!(url); } + if (appInfo) { + return navigateToApp(appInfo.app, { path: appInfo.path, skipAppLeave }); + } }, getComponent: () => { if (!this.history) { diff --git a/src/core/public/application/index.ts b/src/core/public/application/index.ts index 882555fcd60e0..55ac8f47becfa 100644 --- a/src/core/public/application/index.ts +++ b/src/core/public/application/index.ts @@ -28,6 +28,7 @@ export type { AppLeaveDefaultAction, AppLeaveConfirmAction, NavigateToAppOptions, + NavigateToUrlOptions, PublicAppInfo, PublicAppDeepLinkInfo, // Internal types diff --git a/src/core/public/application/integration_tests/application_service.test.tsx b/src/core/public/application/integration_tests/application_service.test.tsx index dda029c66f4c3..99e6d86b6a941 100644 --- a/src/core/public/application/integration_tests/application_service.test.tsx +++ b/src/core/public/application/integration_tests/application_service.test.tsx @@ -170,7 +170,28 @@ describe('ApplicationService', () => { '/app/app1/deep-link', ]); }); - //// + + it('handles `skipOnAppLeave` option', async () => { + const { register } = service.setup(setupDeps); + + register(Symbol(), { + id: 'app1', + title: 'App1', + mount: async ({}: AppMountParameters) => { + return () => undefined; + }, + }); + + const { navigateToApp } = await service.start(startDeps); + + await navigateToApp('app1', { path: '/foo' }); + await navigateToApp('app1', { path: '/bar', skipAppLeave: true }); + expect(history.entries.map((entry) => entry.pathname)).toEqual([ + '/', + '/app/app1/foo', + '/app/app1/bar', + ]); + }); }); }); @@ -249,6 +270,38 @@ describe('ApplicationService', () => { expect(history.entries[2].pathname).toEqual('/app/app2'); }); + it('does not trigger the action if `skipAppLeave` is true', async () => { + const { register } = service.setup(setupDeps); + + register(Symbol(), { + id: 'app1', + title: 'App1', + mount: ({ onAppLeave }: AppMountParameters) => { + onAppLeave((actions) => actions.confirm('confirmation-message', 'confirmation-title')); + return () => undefined; + }, + }); + register(Symbol(), { + id: 'app2', + title: 'App2', + mount: ({}: AppMountParameters) => { + return () => undefined; + }, + }); + + const { navigateToApp, getComponent } = await service.start(startDeps); + + update = createRenderer(getComponent()); + + await act(async () => { + await navigate('/app/app1'); + await navigateToApp('app2', { skipAppLeave: true }); + }); + expect(startDeps.overlays.openConfirm).toHaveBeenCalledTimes(0); + expect(history.entries.length).toEqual(3); + expect(history.entries[1].pathname).toEqual('/app/app1'); + }); + it('blocks navigation to the new app if action is confirm and user declined', async () => { startDeps.overlays.openConfirm.mockResolvedValue(false); diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 659145a9958f1..4e96e96505083 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -740,6 +740,26 @@ export interface NavigateToAppOptions { * if true, will open the app in new tab, will share session information via window.open if base */ openInNewTab?: boolean; + + /** + * if true, will bypass the default onAppLeave behavior + */ + skipAppLeave?: boolean; +} + +/** + * Options for the {@link ApplicationStart.navigateToUrl | navigateToUrl API} + * @public + */ +export interface NavigateToUrlOptions { + /** + * if true, will bypass the default onAppLeave behavior + */ + skipAppLeave?: boolean; + /** + * if true will force a full page reload/refresh/assign, overriding the outcome of other url checks against current the location (effectively using `window.location.assign` instead of `push`) + */ + forceRedirect?: boolean; } /** @public */ @@ -781,7 +801,7 @@ export interface ApplicationStart { * - The pathname segment after the basePath matches any known application route (eg. /app// or any application's `appRoute` configuration) * * Then a SPA navigation will be performed using `navigateToApp` using the corresponding application and path. - * Otherwise, fallback to a full page reload to navigate to the url using `window.location.assign` + * Otherwise, fallback to a full page reload to navigate to the url using `window.location.assign`. * * @example * ```ts @@ -802,8 +822,7 @@ export interface ApplicationStart { * * @param url - an absolute URL, an absolute path or a relative path, to navigate to. */ - navigateToUrl(url: string): Promise; - + navigateToUrl(url: string, options?: NavigateToUrlOptions): Promise; /** * Returns the absolute path (or URL) to a given app, including the global base path. * diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 3b3bccd7ec18b..d62df68cf827d 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -91,6 +91,7 @@ export type { PublicAppInfo, PublicAppDeepLinkInfo, NavigateToAppOptions, + NavigateToUrlOptions, } from './application'; export { SimpleSavedObject } from './saved_objects'; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 44224e6fcaea7..b60e26d282dc3 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -160,7 +160,7 @@ export interface ApplicationStart { deepLinkId?: string; }): string; navigateToApp(appId: string, options?: NavigateToAppOptions): Promise; - navigateToUrl(url: string): Promise; + navigateToUrl(url: string, options?: NavigateToUrlOptions): Promise; } // @public @@ -778,9 +778,16 @@ export interface NavigateToAppOptions { openInNewTab?: boolean; path?: string; replace?: boolean; + skipAppLeave?: boolean; state?: unknown; } +// @public +export interface NavigateToUrlOptions { + forceRedirect?: boolean; + skipAppLeave?: boolean; +} + // Warning: (ae-missing-release-tag) "NavType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts index 5a894c7b00748..704542bca918f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts @@ -18,11 +18,14 @@ import { SecurityPluginStart } from '../../../../../security/public'; import { HttpLogic } from '../http'; import { createHref, CreateHrefOptions } from '../react_router_helpers'; +type RequiredFieldsOnly = { + [K in keyof T as T[K] extends Required[K] ? K : never]: T[K]; +}; interface KibanaLogicProps { config: { host?: string }; // Kibana core history: History; - navigateToUrl: ApplicationStart['navigateToUrl']; + navigateToUrl: RequiredFieldsOnly; setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void; setChromeIsVisible(isVisible: boolean): void; setDocTitle(title: string): void; From 68630e4f77587091e31fc7ba4f053c38cf5a7df7 Mon Sep 17 00:00:00 2001 From: Constance Date: Fri, 1 Apr 2022 08:01:52 -0700 Subject: [PATCH 30/65] [i18n] Remove i18n token skipping from EUI pluralization defString functions (#129144) * Update tests to compare EUI pluralization function output against Kibana output + add comments/catches for possible future function cases that aren't pluralization * Fix mismatched EUI/Kibana default message - caught by new unit tests. hooray! --- src/core/public/i18n/i18n_eui_mapping.test.ts | 65 ++++++++++--------- src/core/public/i18n/i18n_eui_mapping.tsx | 2 +- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/core/public/i18n/i18n_eui_mapping.test.ts b/src/core/public/i18n/i18n_eui_mapping.test.ts index aa78345a86de1..a2d35b37ac569 100644 --- a/src/core/public/i18n/i18n_eui_mapping.test.ts +++ b/src/core/public/i18n/i18n_eui_mapping.test.ts @@ -17,6 +17,7 @@ import { getEuiContextMapping } from './i18n_eui_mapping'; const VALUES_REGEXP = /\{\w+\}/; describe('@elastic/eui i18n tokens', () => { + const i18nTranslateActual = jest.requireActual('@kbn/i18n').i18n.translate; const i18nTranslateMock = jest .fn() .mockImplementation((id, { defaultMessage }) => defaultMessage); @@ -74,34 +75,9 @@ describe('@elastic/eui i18n tokens', () => { }); test('defaultMessage is in sync with defString', () => { - // Certain complex tokens (e.g. ones that have a function as a defaultMessage) - // need custom i18n handling, and can't be checked for basic defString equality - const tokensToSkip = [ - 'euiColumnSorting.buttonActive', - 'euiSelectable.searchResults', - 'euiPrettyDuration.lastDurationSeconds', - 'euiPrettyDuration.nextDurationSeconds', - 'euiPrettyDuration.lastDurationMinutes', - 'euiPrettyDuration.nextDurationMinutes', - 'euiPrettyDuration.lastDurationHours', - 'euiPrettyDuration.nextDurationHours', - 'euiPrettyDuration.lastDurationDays', - 'euiPrettyDuration.nexttDurationDays', - 'euiPrettyDuration.lastDurationWeeks', - 'euiPrettyDuration.nextDurationWeeks', - 'euiPrettyDuration.lastDurationMonths', - 'euiPrettyDuration.nextDurationMonths', - 'euiPrettyDuration.lastDurationYears', - 'euiPrettyDuration.nextDurationYears', - 'euiPrettyInterval.seconds', - 'euiPrettyInterval.minutes', - 'euiPrettyInterval.hours', - 'euiPrettyInterval.days', - 'euiPrettyInterval.weeks', - 'euiPrettyInterval.months', - 'euiPrettyInterval.years', - ]; - if (tokensToSkip.includes(token)) return; + const isDefFunction = defString.includes('}) =>'); + const isPluralizationDefFunction = + defString.includes(' === 1 ?') || defString.includes(' > 1 ?'); // Clean up typical errors from the `@elastic/eui` extraction token tool const normalizedDefString = defString @@ -114,7 +90,38 @@ describe('@elastic/eui i18n tokens', () => { .replace(/\s{2,}/g, ' ') .trim(); - expect(i18nTranslateCall[1].defaultMessage).toBe(normalizedDefString); + if (!isDefFunction) { + expect(i18nTranslateCall[1].defaultMessage).toBe(normalizedDefString); + } else { + // Certain EUI defStrings are actually functions (that currently primarily handle + // pluralization). To check EUI's pluralization against Kibana's pluralization, we + // need to eval the defString and then actually i18n.translate & compare the 2 outputs + const defFunction = eval(defString); // eslint-disable-line no-eval + const defFunctionArg = normalizedDefString.split('({ ')[1].split('})')[0]; // TODO: All EUI pluralization fns currently only pass 1 arg. If this changes in the future and 2 args are passed, we'll need to do some extra splitting by ',' + + if (isPluralizationDefFunction) { + const singularValue = { [defFunctionArg]: 1 }; + expect( + i18nTranslateActual(token, { + defaultMessage: i18nTranslateCall[1].defaultMessage, + values: singularValue, + }) + ).toEqual(defFunction(singularValue)); + + const pluralValue = { [defFunctionArg]: 2 }; + expect( + i18nTranslateActual(token, { + defaultMessage: i18nTranslateCall[1].defaultMessage, + values: pluralValue, + }) + ).toEqual(defFunction(pluralValue)); + } else { + throw new Error( + `We currently only have logic written for EUI pluralization def functions. + This is a new type of def function that will need custom logic written for it.` + ); + } + } }); test('values should match', () => { diff --git a/src/core/public/i18n/i18n_eui_mapping.tsx b/src/core/public/i18n/i18n_eui_mapping.tsx index b58780db3087d..9969f4ee23f57 100644 --- a/src/core/public/i18n/i18n_eui_mapping.tsx +++ b/src/core/public/i18n/i18n_eui_mapping.tsx @@ -1268,7 +1268,7 @@ export const getEuiContextMapping = (): EuiTokensObject => { ), 'euiSelectable.searchResults': ({ resultsLength }: EuiValues) => i18n.translate('core.euiSelectable.searchResults', { - defaultMessage: '{resultsLength, plural, one {# result} other {# results}}', + defaultMessage: '{resultsLength, plural, one {# result} other {# results}} available', values: { resultsLength }, }), 'euiSelectable.placeholderName': i18n.translate('core.euiSelectable.placeholderName', { From b2be5f0e9f03e7de99b60f59760ffe741a7e730e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Fri, 1 Apr 2022 17:04:07 +0200 Subject: [PATCH 31/65] [Unified observability] Tweak overview page header (#128894) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../pages/overview/old_overview_page.tsx | 58 +++++++++++++------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx b/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx index 610c0f66a92e3..f396f594e0779 100644 --- a/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx +++ b/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx @@ -75,8 +75,7 @@ export function OverviewPage({ routeParams }: Props) { const { cases, docLinks, http } = useKibana().services; const { ObservabilityPageTemplate, config } = usePluginContext(); - const { relativeStart, relativeEnd, absoluteStart, absoluteEnd, refreshInterval, refreshPaused } = - useDatePickerContext(); + const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useDatePickerContext(); const { data: newsFeed } = useFetcher(() => getNewsFeed({ http }), [http]); @@ -135,22 +134,12 @@ export function OverviewPage({ routeParams }: Props) { pageHeader={ hasData ? { - pageTitle: overviewPageTitle, - rightSideItems: [ - - - , - , - ], + /> + ), } : undefined } @@ -246,6 +235,41 @@ export function OverviewPage({ routeParams }: Props) { ); } +interface PageHeaderProps { + handleGuidedSetupClick: () => void; + onTimeRangeRefresh: () => void; +} + +function PageHeader({ handleGuidedSetupClick, onTimeRangeRefresh }: PageHeaderProps) { + const { relativeStart, relativeEnd, refreshInterval, refreshPaused } = useDatePickerContext(); + return ( + + + +

{overviewPageTitle}

+
+
+ + + + + + + + +
+ ); +} + const overviewPageTitle = i18n.translate('xpack.observability.overview.pageTitle', { defaultMessage: 'Overview', }); From 75c5cf2093b8d25c77972d617eac8c03409ac6f4 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Fri, 1 Apr 2022 10:23:19 -0500 Subject: [PATCH 32/65] Revert "Revert "[ci/es_snapshots] Build cloud image (#127154)"" This reverts commit 742d09bbb6dd5d2223db96e64ab416b06d673982. --- .buildkite/scripts/steps/es_snapshots/build.sh | 18 +++++++++++++++++- .../steps/es_snapshots/create_manifest.js | 13 +++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/.buildkite/scripts/steps/es_snapshots/build.sh b/.buildkite/scripts/steps/es_snapshots/build.sh index c11f041836413..cdc1750e59bfc 100755 --- a/.buildkite/scripts/steps/es_snapshots/build.sh +++ b/.buildkite/scripts/steps/es_snapshots/build.sh @@ -69,6 +69,7 @@ echo "--- Build Elasticsearch" :distribution:archives:darwin-aarch64-tar:assemble \ :distribution:archives:darwin-tar:assemble \ :distribution:docker:docker-export:assemble \ + :distribution:docker:cloud-docker-export:assemble \ :distribution:archives:linux-aarch64-tar:assemble \ :distribution:archives:linux-tar:assemble \ :distribution:archives:windows-zip:assemble \ @@ -79,11 +80,26 @@ find distribution -type f \( -name 'elasticsearch-*-*-*-*.tar.gz' -o -name 'elas ls -alh "$destination" -echo "--- Create docker image archives" +echo "--- Create docker default image archives" docker images "docker.elastic.co/elasticsearch/elasticsearch" docker images "docker.elastic.co/elasticsearch/elasticsearch" --format "{{.Tag}}" | xargs -n1 echo 'docker save docker.elastic.co/elasticsearch/elasticsearch:${0} | gzip > ../es-build/elasticsearch-${0}-docker-image.tar.gz' docker images "docker.elastic.co/elasticsearch/elasticsearch" --format "{{.Tag}}" | xargs -n1 bash -c 'docker save docker.elastic.co/elasticsearch/elasticsearch:${0} | gzip > ../es-build/elasticsearch-${0}-docker-image.tar.gz' +echo "--- Create kibana-ci docker cloud image archives" +ES_CLOUD_ID=$(docker images "docker.elastic.co/elasticsearch-ci/elasticsearch-cloud" --format "{{.ID}}") +ES_CLOUD_VERSION=$(docker images "docker.elastic.co/elasticsearch-ci/elasticsearch-cloud" --format "{{.Tag}}") +KIBANA_ES_CLOUD_VERSION="$ES_CLOUD_VERSION-$ELASTICSEARCH_GIT_COMMIT" +KIBANA_ES_CLOUD_IMAGE="docker.elastic.co/kibana-ci/elasticsearch-cloud:$KIBANA_ES_CLOUD_VERSION" + +docker tag "$ES_CLOUD_ID" "$KIBANA_ES_CLOUD_IMAGE" + +echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co +trap 'docker logout docker.elastic.co' EXIT +docker image push "$KIBANA_ES_CLOUD_IMAGE" + +export ELASTICSEARCH_CLOUD_IMAGE="$KIBANA_ES_CLOUD_IMAGE" +export ELASTICSEARCH_CLOUD_IMAGE_CHECKSUM="$(docker images "$KIBANA_ES_CLOUD_IMAGE" --format "{{.Digest}}")" + echo "--- Create checksums for snapshot files" cd "$destination" find ./* -exec bash -c "shasum -a 512 {} > {}.sha512" \; diff --git a/.buildkite/scripts/steps/es_snapshots/create_manifest.js b/.buildkite/scripts/steps/es_snapshots/create_manifest.js index cb4ea29a9c534..9357cd72fff06 100644 --- a/.buildkite/scripts/steps/es_snapshots/create_manifest.js +++ b/.buildkite/scripts/steps/es_snapshots/create_manifest.js @@ -16,6 +16,8 @@ const { BASE_BUCKET_DAILY } = require('./bucket_config.js'); const destination = process.argv[2] || __dirname + '/test'; const ES_BRANCH = process.env.ELASTICSEARCH_BRANCH; + const ES_CLOUD_IMAGE = process.env.ELASTICSEARCH_CLOUD_IMAGE; + const ES_CLOUD_IMAGE_CHECKSUM = process.env.ELASTICSEARCH_CLOUD_IMAGE_CHECKSUM; const GIT_COMMIT = process.env.ELASTICSEARCH_GIT_COMMIT; const GIT_COMMIT_SHORT = process.env.ELASTICSEARCH_GIT_COMMIT_SHORT; @@ -59,6 +61,17 @@ const { BASE_BUCKET_DAILY } = require('./bucket_config.js'); }; }); + if (ES_CLOUD_IMAGE && ES_CLOUD_IMAGE_CHECKSUM) { + manifestEntries.push({ + checksum: ES_CLOUD_IMAGE_CHECKSUM, + url: ES_CLOUD_IMAGE, + version: VERSION, + platform: 'docker', + architecture: 'image', + license: 'default', + }); + } + const manifest = { id: SNAPSHOT_ID, bucket: `${BASE_BUCKET_DAILY}/${DESTINATION}`.toString(), From dd9aa8472451679627bdadd656aec569120baed3 Mon Sep 17 00:00:00 2001 From: Hannah Mudge Date: Fri, 1 Apr 2022 09:54:00 -0600 Subject: [PATCH 33/65] [Controls] Close controls flyouts on unmount, save, and view mode change (#128198) * Manage flyout via global ref in control group container * Add functional tests for settings flyout --- .../control_group/editor/create_control.tsx | 7 ++++- .../control_group/editor/edit_control.tsx | 8 ++++-- .../editor/edit_control_group.tsx | 7 ++++- .../embeddable/control_group_container.tsx | 12 ++++++++ .../application/top_nav/dashboard_top_nav.tsx | 15 +++++----- .../controls/control_group_settings.ts | 28 +++++++++++++++++++ 6 files changed, 66 insertions(+), 11 deletions(-) diff --git a/src/plugins/controls/public/control_group/editor/create_control.tsx b/src/plugins/controls/public/control_group/editor/create_control.tsx index 9f02ff3e970d0..7ae0416feac2e 100644 --- a/src/plugins/controls/public/control_group/editor/create_control.tsx +++ b/src/plugins/controls/public/control_group/editor/create_control.tsx @@ -16,6 +16,7 @@ import { ControlGroupStrings } from '../control_group_strings'; import { ControlWidth, ControlInput, IEditableControlFactory } from '../../types'; import { toMountPoint } from '../../../../kibana_react/public'; import { DEFAULT_CONTROL_WIDTH } from '../../../common/control_group/control_group_constants'; +import { setFlyoutRef } from '../embeddable/control_group_container'; export type CreateControlButtonTypes = 'toolbar' | 'callout'; export interface CreateControlButtonProps { @@ -99,9 +100,13 @@ export const CreateControlButton = ({ ), { outsideClickCloses: false, - onClose: (flyout) => onCancel(flyout), + onClose: (flyout) => { + onCancel(flyout); + setFlyoutRef(undefined); + }, } ); + setFlyoutRef(flyoutInstance); }); initialInputPromise.then( diff --git a/src/plugins/controls/public/control_group/editor/edit_control.tsx b/src/plugins/controls/public/control_group/editor/edit_control.tsx index 5b7177a64c633..7c114350f3679 100644 --- a/src/plugins/controls/public/control_group/editor/edit_control.tsx +++ b/src/plugins/controls/public/control_group/editor/edit_control.tsx @@ -20,7 +20,7 @@ import { IEditableControlFactory, ControlInput } from '../../types'; import { controlGroupReducers } from '../state/control_group_reducers'; import { EmbeddableFactoryNotFoundError } from '../../../../embeddable/public'; import { useReduxContainerContext } from '../../../../presentation_util/public'; -import { ControlGroupContainer } from '../embeddable/control_group_container'; +import { ControlGroupContainer, setFlyoutRef } from '../embeddable/control_group_container'; export const EditControlButton = ({ embeddableId }: { embeddableId: string }) => { // Controls Services Context @@ -127,9 +127,13 @@ export const EditControlButton = ({ embeddableId }: { embeddableId: string }) => ), { outsideClickCloses: false, - onClose: (flyout) => onCancel(flyout), + onClose: (flyout) => { + setFlyoutRef(undefined); + onCancel(flyout); + }, } ); + setFlyoutRef(flyoutInstance); }; return ( diff --git a/src/plugins/controls/public/control_group/editor/edit_control_group.tsx b/src/plugins/controls/public/control_group/editor/edit_control_group.tsx index dcf955666657f..8d9f81637ef65 100644 --- a/src/plugins/controls/public/control_group/editor/edit_control_group.tsx +++ b/src/plugins/controls/public/control_group/editor/edit_control_group.tsx @@ -15,6 +15,7 @@ import { ControlGroupEditor } from './control_group_editor'; import { OverlayRef } from '../../../../../core/public'; import { pluginServices } from '../../services'; import { ControlGroupContainer } from '..'; +import { setFlyoutRef } from '../embeddable/control_group_container'; export interface EditControlGroupButtonProps { controlGroupContainer: ControlGroupContainer; @@ -60,9 +61,13 @@ export const EditControlGroup = ({ ), { outsideClickCloses: false, - onClose: () => flyoutInstance.close(), + onClose: () => { + flyoutInstance.close(); + setFlyoutRef(undefined); + }, } ); + setFlyoutRef(flyoutInstance); }; const commonButtonProps = { diff --git a/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx b/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx index 7abcfbb5af6a3..5ee41946a14aa 100644 --- a/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx +++ b/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx @@ -46,11 +46,17 @@ import { Container, EmbeddableFactory } from '../../../../embeddable/public'; import { ControlEmbeddable, ControlInput, ControlOutput } from '../../types'; import { ControlGroupChainingSystems } from './control_group_chaining_system'; import { CreateControlButton, CreateControlButtonTypes } from '../editor/create_control'; +import { OverlayRef } from '../../../../../core/public'; const ControlGroupReduxWrapper = withSuspense< ReduxEmbeddableWrapperPropsWithChildren >(LazyReduxEmbeddableWrapper); +let flyoutRef: OverlayRef | undefined; +export const setFlyoutRef = (newRef: OverlayRef | undefined) => { + flyoutRef = newRef; +}; + export interface ChildEmbeddableOrderCache { IdsToOrder: { [key: string]: number }; idsInOrder: string[]; @@ -96,6 +102,11 @@ export class ControlGroupContainer extends Container< return this.lastUsedDataViewId ?? this.relevantDataViewId; }; + public closeAllFlyouts() { + flyoutRef?.close(); + flyoutRef = undefined; + } + /** * Returns a button that allows controls to be created externally using the embeddable * @param buttonType Controls the button styling @@ -367,6 +378,7 @@ export class ControlGroupContainer extends Container< public destroy() { super.destroy(); + this.closeAllFlyouts(); this.subscriptions.unsubscribe(); if (this.domNode) ReactDOM.unmountComponentAtNode(this.domNode); } diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx index e66525398b86b..23bc301f788c0 100644 --- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx @@ -210,16 +210,17 @@ export function DashboardTopNav({ [stateTransferService, data.search.session, trackUiMetric] ); - const clearAddPanel = useCallback(() => { + const closeAllFlyouts = useCallback(() => { + dashboardAppState.dashboardContainer.controlGroup?.closeAllFlyouts(); if (state.addPanelOverlay) { state.addPanelOverlay.close(); setState((s) => ({ ...s, addPanelOverlay: undefined })); } - }, [state.addPanelOverlay]); + }, [state.addPanelOverlay, dashboardAppState.dashboardContainer.controlGroup]); const onChangeViewMode = useCallback( (newMode: ViewMode) => { - clearAddPanel(); + closeAllFlyouts(); const willLoseChanges = newMode === ViewMode.VIEW && dashboardAppState.hasUnsavedChanges; if (!willLoseChanges) { @@ -231,7 +232,7 @@ export function DashboardTopNav({ dashboardAppState.resetToLastSavedState?.() ); }, - [clearAddPanel, core.overlays, dashboardAppState, dispatchDashboardStateChange] + [closeAllFlyouts, core.overlays, dashboardAppState, dispatchDashboardStateChange] ); const runSaveAs = useCallback(async () => { @@ -296,7 +297,7 @@ export function DashboardTopNav({ showCopyOnSave={lastDashboardId ? true : false} /> ); - clearAddPanel(); + closeAllFlyouts(); showSaveModal(dashboardSaveModal, core.i18n.Context); }, [ dispatchDashboardStateChange, @@ -305,7 +306,7 @@ export function DashboardTopNav({ dashboardAppState, core.i18n.Context, chrome.docTitle, - clearAddPanel, + closeAllFlyouts, kibanaVersion, timefilter, redirectTo, @@ -468,7 +469,7 @@ export function DashboardTopNav({ ]); UseUnmount(() => { - clearAddPanel(); + closeAllFlyouts(); setMounted(false); }); diff --git a/test/functional/apps/dashboard_elements/controls/control_group_settings.ts b/test/functional/apps/dashboard_elements/controls/control_group_settings.ts index ffda165443337..3ca09bba99cea 100644 --- a/test/functional/apps/dashboard_elements/controls/control_group_settings.ts +++ b/test/functional/apps/dashboard_elements/controls/control_group_settings.ts @@ -99,5 +99,33 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardControls.deleteAllControls(); }); }); + + describe('control group settings flyout closes', async () => { + it('on save', async () => { + await dashboardControls.openControlGroupSettingsFlyout(); + await dashboard.saveDashboard('Test Control Group Settings', { + saveAsNew: false, + exitFromEditMode: false, + }); + await testSubjects.missingOrFail('control-group-settings-flyout'); + }); + + it('on view mode change', async () => { + await dashboardControls.openControlGroupSettingsFlyout(); + await dashboard.clickCancelOutOfEditMode(); + await testSubjects.missingOrFail('control-group-settings-flyout'); + }); + + it('when navigating away from dashboard', async () => { + await dashboard.switchToEditMode(); + await dashboardControls.openControlGroupSettingsFlyout(); + await dashboard.gotoDashboardLandingPage(); + await testSubjects.missingOrFail('control-group-settings-flyout'); + }); + + after(async () => { + await dashboard.loadSavedDashboard('Test Control Group Settings'); + }); + }); }); } From 2c9d607c2fe658fed5680f7853b2f50b01f12e95 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 1 Apr 2022 18:00:20 +0200 Subject: [PATCH 34/65] [ML] Data Frame Analytics: Fix Outlier detection results exploration color legend display. (#129058) Fixes the display of the color legend for the outlier results table. The check to display the legend was based on just checking if the first row on display had feature influence information. However, depending on the jobs results, not every row might be populated with feature influence information. This PR fixes it by displaying the color legend in any case when feature influence calculation is enabled and calculating the necessary featureCount value by looking at all available rows instead of just the first row. Unit tests have been added for the getFeatureCount() function. --- .../components/data_grid/common.ts | 15 ++--- .../common/get_index_data.ts | 7 ++- .../outlier_exploration/common.test.ts | 61 +++++++++++++------ .../components/outlier_exploration/common.ts | 17 ++---- .../outlier_exploration.tsx | 8 +-- 5 files changed, 60 insertions(+), 48 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.ts index 31979000f4a60..1a35e8fa34308 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/common.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/common.ts @@ -7,7 +7,7 @@ import moment from 'moment-timezone'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { useEffect, useMemo } from 'react'; +import { useMemo } from 'react'; import { EuiDataGridCellValueElementProps, EuiDataGridStyle } from '@elastic/eui'; @@ -372,16 +372,9 @@ export const useRenderCellValue = ( const cellValue = getCellValue(columnId); - // React by default doesn't allow us to use a hook in a callback. - // However, this one will be passed on to EuiDataGrid and its docs - // recommend wrapping `setCellProps` in a `useEffect()` hook - // so we're ignoring the linting rule here. - // eslint-disable-next-line react-hooks/rules-of-hooks - useEffect(() => { - if (typeof cellPropsCallback === 'function') { - cellPropsCallback(columnId, cellValue, fullItem, setCellProps); - } - }, [columnId, cellValue]); + if (typeof cellPropsCallback === 'function') { + cellPropsCallback(columnId, cellValue, fullItem, setCellProps); + } if (typeof cellValue === 'object' && cellValue !== null) { return JSON.stringify(cellValue); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts index 0bf8ef0e22195..195a7d9b762cf 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts @@ -72,8 +72,11 @@ export const getIndexData = async ( ); setTableItems( resp.hits.hits.map((d) => - getProcessedFields(d.fields ?? {}, (key: string) => - key.startsWith(`${jobConfig.dest.results_field}.feature_importance`) + getProcessedFields( + d.fields ?? {}, + (key: string) => + key.startsWith(`${jobConfig.dest.results_field}.feature_importance`) || + key.startsWith(`${jobConfig.dest.results_field}.feature_influence`) ) ) ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.test.ts index fa54784ccf9f8..0dad16b989f9a 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.test.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.test.ts @@ -7,28 +7,51 @@ import { DataFrameAnalyticsConfig } from '../../../../common'; -import { getOutlierScoreFieldName } from './common'; +import { getFeatureCount, getOutlierScoreFieldName } from './common'; describe('Data Frame Analytics: common utils', () => { - test('getOutlierScoreFieldName()', () => { - const jobConfig: DataFrameAnalyticsConfig = { - id: 'the-id', - analysis: { outlier_detection: {} }, - dest: { - index: 'the-dest-index', - results_field: 'the-results-field', - }, - source: { - index: 'the-source-index', - }, - analyzed_fields: { includes: [], excludes: [] }, - model_memory_limit: '50mb', - create_time: 1234, - version: '1.0.0', - }; + describe('getOutlierScoreFieldName()', () => { + it('returns the outlier_score field name based on the job config.', () => { + const jobConfig: DataFrameAnalyticsConfig = { + id: 'the-id', + analysis: { outlier_detection: {} }, + dest: { + index: 'the-dest-index', + results_field: 'the-results-field', + }, + source: { + index: 'the-source-index', + }, + analyzed_fields: { includes: [], excludes: [] }, + model_memory_limit: '50mb', + create_time: 1234, + version: '1.0.0', + }; - const outlierScoreFieldName = getOutlierScoreFieldName(jobConfig); + const outlierScoreFieldName = getOutlierScoreFieldName(jobConfig); - expect(outlierScoreFieldName).toMatch('the-results-field.outlier_score'); + expect(outlierScoreFieldName).toMatch('the-results-field.outlier_score'); + }); + }); + + describe('getFeatureCount()', () => { + it('returns 0 features with no table items.', () => { + expect(getFeatureCount('ml')).toBe(0); + }); + it('returns 0 features with table items with no feature influencer info', () => { + expect(getFeatureCount('ml', [{}])).toBe(0); + }); + it('returns number of features with table items with feature influencer info', () => { + expect( + getFeatureCount('ml', [ + { + 'ml.feature_influence': [ + { feature_name: ['the-feature-name-1'], influence: [0.1] }, + { feature_name: ['the-feature-name-2'], influence: [0.2] }, + ], + }, + ]) + ).toBe(2); + }); }); }); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.ts index 3c3a0d8243ccc..35b0a0c2a0853 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.ts @@ -14,15 +14,10 @@ export const getOutlierScoreFieldName = (jobConfig: DataFrameAnalyticsConfig) => `${jobConfig.dest.results_field}.${OUTLIER_SCORE}`; export const getFeatureCount = (resultsField: string, tableItems: DataGridItem[] = []) => { - if (tableItems.length === 0) { - return 0; - } - - const fullItem = tableItems[0]; - - if (Array.isArray(fullItem[`${resultsField}.${FEATURE_INFLUENCE}`])) { - return fullItem[`${resultsField}.${FEATURE_INFLUENCE}`].length; - } - - return 0; + return tableItems.reduce((featureCount, fullItem) => { + if (Array.isArray(fullItem[`${resultsField}.${FEATURE_INFLUENCE}`])) { + return Math.max(featureCount, fullItem[`${resultsField}.${FEATURE_INFLUENCE}`].length); + } + return featureCount; + }, 0); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx index 58804de83acf9..8086ee4f573bc 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx @@ -66,17 +66,15 @@ export const OutlierExploration: FC = React.memo(({ jobId }) = const { columnsWithCharts, tableItems } = outlierData; - const featureCount = getFeatureCount(jobConfig?.dest?.results_field || '', tableItems); + const resultsField = jobConfig?.dest.results_field ?? ''; + const featureCount = getFeatureCount(resultsField, tableItems); const colorRange = useColorRange(COLOR_RANGE.BLUE, COLOR_RANGE_SCALE.INFLUENCER, featureCount); - // Show the color range only if feature influence is enabled and there's more than 0 features. + // Show the color range only if feature influence is enabled. const showColorRange = - featureCount > 0 && isOutlierAnalysis(jobConfig?.analysis) && jobConfig?.analysis.outlier_detection.compute_feature_influence === true; - const resultsField = jobConfig?.dest.results_field ?? ''; - // Identify if the results index has a legacy feature influence format. // If feature influence was enabled for the legacy job we'll show a callout // with some additional information for a workaround. From 02dca37fc666550c5a2f013d6ae5f441ca5dffa9 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 1 Apr 2022 18:48:23 +0200 Subject: [PATCH 35/65] [ML] Fix the nodes_overview endpoint (#129215) --- .../models_provider.test.ts | 28 +++++++++++++++++-- .../data_frame_analytics/models_provider.ts | 12 ++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.test.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.test.ts index 99919794001c9..bd1b64ba9f37d 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.test.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.test.ts @@ -24,12 +24,36 @@ describe('Model service', () => { getMemoryStats: jest.fn(() => { return Promise.resolve({ _nodes: { - total: 3, - successful: 3, + total: 4, + successful: 4, failed: 0, }, cluster_name: 'test_cluster', nodes: { + '3qIoLFnbSi-DwVr2333UCdw': { + name: 'node3', + transport_address: '10.10.10.2:9353', + // missing the ml role + roles: ['data', 'ingest', 'master', 'transform'], + attributes: {}, + jvm: { + heap_max_in_bytes: 1073741824, + java_inference_in_bytes: 0, + java_inference_max_in_bytes: 0, + }, + mem: { + adjusted_total_in_bytes: 15599742976, + total_in_bytes: 15599742976, + ml: { + data_frame_analytics_in_bytes: 0, + native_code_overhead_in_bytes: 0, + max_in_bytes: 1073741824, + anomaly_detectors_in_bytes: 0, + native_inference_in_bytes: 1555161790, + }, + }, + ephemeral_id: '3qIoLFnbSi-DwVrYioUCdw', + }, '3qIoLFnbSi-DwVrYioUCdw': { name: 'node3', transport_address: '10.10.10.2:9353', diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts index db0eee9ec757c..a3d1f98fbf999 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts @@ -111,13 +111,16 @@ export function modelsProvider(client: IScopedClusterClient, mlClient: MlClient) * Provides the ML nodes overview with allocated models. */ async getNodesOverview(): Promise { + // TODO set node_id to ml:true when elasticsearch client is updated. const response = (await mlClient.getMemoryStats()) as MemoryStatsResponse; const { trained_model_stats: trainedModelStats } = await mlClient.getTrainedModelsStats({ size: 10000, }); - const mlNodes = Object.entries(response.nodes); + const mlNodes = Object.entries(response.nodes).filter(([, node]) => + node.roles.includes('ml') + ); const nodeDeploymentStatsResponses: NodeDeploymentStatsResponse[] = mlNodes.map( ([nodeId, node]) => { @@ -204,7 +207,12 @@ export function modelsProvider(client: IScopedClusterClient, mlClient: MlClient) ); return { - _nodes: response._nodes, + // TODO preserve _nodes from the response when getMemoryStats method is updated to support ml:true filter + _nodes: { + ...response._nodes, + total: mlNodes.length, + successful: mlNodes.length, + }, nodes: nodeDeploymentStatsResponses, }; }, From 0c2ec496d0d1d0585ffe818bd0c692ddbbe2f2f4 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Fri, 1 Apr 2022 17:57:02 +0100 Subject: [PATCH 36/65] [Cases] Refactor: AllCasesList component in selection mode should only select (#128915) * Remove deprecated and unused attribute * Do not accept attachments as part of allcaseslist component props * Attach attachment in the hook * Add tests for useCasesAddToExistingCaseModal postComment hook * remove comment * Add tests for postcomment hook * Update tests for useCasesAddToExistingCaseModal hook * Add tests for toast messages in the postComment hook * Additional toast error validation Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../ui/get_all_cases_selector_modal.tsx | 10 - .../all_cases/all_cases_list.test.tsx | 42 +---- .../components/all_cases/all_cases_list.tsx | 47 +---- .../public/components/all_cases/columns.tsx | 23 +-- .../all_cases_selector_modal.test.tsx | 14 -- .../all_cases_selector_modal.tsx | 14 +- ..._cases_add_to_existing_case_modal.test.tsx | 175 +++++++++++++++--- .../use_cases_add_to_existing_case_modal.tsx | 72 ++++--- .../containers/use_post_comment.test.tsx | 38 +++- .../public/containers/use_post_comment.tsx | 6 +- 10 files changed, 249 insertions(+), 192 deletions(-) diff --git a/x-pack/plugins/cases/public/client/ui/get_all_cases_selector_modal.tsx b/x-pack/plugins/cases/public/client/ui/get_all_cases_selector_modal.tsx index 18821d24e3053..1666557ec2648 100644 --- a/x-pack/plugins/cases/public/client/ui/get_all_cases_selector_modal.tsx +++ b/x-pack/plugins/cases/public/client/ui/get_all_cases_selector_modal.tsx @@ -18,19 +18,15 @@ const AllCasesSelectorModalLazy: React.FC = lazy( export const getAllCasesSelectorModalLazy = ({ owner, userCanCrud, - alertData, hiddenStatuses, onRowClick, - updateCase, onClose, }: GetAllCasesSelectorModalProps) => ( }> @@ -42,20 +38,14 @@ export const getAllCasesSelectorModalLazy = ({ * cases provider. to be further refactored https://github.com/elastic/kibana/issues/123183 */ export const getAllCasesSelectorModalNoProviderLazy = ({ - alertData, - attachments, hiddenStatuses, onRowClick, - updateCase, onClose, }: AllCasesSelectorModalProps) => ( }> diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx index 88aad5fb64408..c8e656b8117eb 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx @@ -17,7 +17,7 @@ import { TestProviders } from '../../common/mock'; import { casesStatus, useGetCasesMockState, mockCase, connectorsMock } from '../../containers/mock'; import { StatusAll } from '../../../common/ui/types'; -import { CaseStatuses, CommentType } from '../../../common/api'; +import { CaseStatuses } from '../../../common/api'; import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { getEmptyTagValue } from '../empty_value'; import { useDeleteCases } from '../../containers/use_delete_cases'; @@ -515,46 +515,6 @@ describe('AllCasesListGeneric', () => { }); }); - it('should call postComment when a case is selected in isSelectorView=true and has attachments', async () => { - const postCommentMockedValue = { status: { isLoading: false }, postComment: jest.fn() }; - usePostCommentMock.mockReturnValueOnce(postCommentMockedValue); - const wrapper = mount( - - - - ); - wrapper.find('[data-test-subj="cases-table-row-select-1"]').first().simulate('click'); - await waitFor(() => { - expect(postCommentMockedValue.postComment).toHaveBeenCalledWith({ - caseId: '1', - data: { - alertId: 'alert-id-201', - index: 'index-id-1', - owner: 'test', - rule: { - id: 'rule-id-1', - name: 'Awesome myrule', - }, - type: 'alert', - }, - }); - }); - }); - it('should call onRowClick with no cases and isSelectorView=true', async () => { useGetCasesMock.mockReturnValue({ ...defaultGetCases, diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx index ffcb7a1abe416..5eac485e24c7b 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx @@ -16,14 +16,8 @@ import { FilterOptions, SortFieldCase, } from '../../../common/ui/types'; -import { - CaseStatuses, - CommentRequestAlertType, - caseStatuses, - CommentType, -} from '../../../common/api'; +import { CaseStatuses, caseStatuses } from '../../../common/api'; import { useGetCases } from '../../containers/use_get_cases'; -import { usePostComment } from '../../containers/use_post_comment'; import { useAvailableCasesOwners } from '../app/use_available_owners'; import { useCasesColumns } from './columns'; @@ -33,7 +27,6 @@ import { EuiBasicTableOnChange } from './types'; import { CasesTable } from './table'; import { useConnectors } from '../../containers/configure/use_connectors'; import { useCasesContext } from '../cases_context/use_cases_context'; -import { CaseAttachments } from '../../types'; const ProgressLoader = styled(EuiProgress)` ${({ $isShow }: { $isShow: boolean }) => @@ -52,28 +45,14 @@ const getSortField = (field: string): SortFieldCase => field === SortFieldCase.closedAt ? SortFieldCase.closedAt : SortFieldCase.createdAt; export interface AllCasesListProps { - /** - * @deprecated Use the attachments prop instead - */ - alertData?: Omit; hiddenStatuses?: CaseStatusWithAllStatus[]; isSelectorView?: boolean; onRowClick?: (theCase?: Case) => void; - updateCase?: (newCase: Case) => void; doRefresh?: () => void; - attachments?: CaseAttachments; } export const AllCasesList = React.memo( - ({ - alertData, - attachments, - hiddenStatuses = [], - isSelectorView = false, - onRowClick, - updateCase, - doRefresh, - }) => { + ({ hiddenStatuses = [], isSelectorView = false, onRowClick, doRefresh }) => { const { owner, userCanCrud } = useCasesContext(); const hasOwner = !!owner.length; const availableSolutions = useAvailableCasesOwners(); @@ -97,8 +76,6 @@ export const AllCasesList = React.memo( setSelectedCases, } = useGetCases({ initialFilterOptions }); - // Post Comment to Case - const { postComment, isLoading: isCommentUpdating } = usePostComment(); const { connectors } = useConnectors(); const sorting = useMemo( @@ -181,19 +158,6 @@ export const AllCasesList = React.memo( const showActions = userCanCrud && !isSelectorView; - // TODO remove the deprecated alertData field when cleaning up - // code https://github.com/elastic/kibana/issues/123183 - // This code is to support the deprecated alertData prop - const toAttach = useMemo((): CaseAttachments | undefined => { - if (attachments !== undefined || alertData !== undefined) { - const _toAttach = attachments ?? []; - if (alertData !== undefined) { - _toAttach.push({ ...alertData, type: CommentType.alert }); - } - return _toAttach; - } - }, [alertData, attachments]); - const columns = useCasesColumns({ dispatchUpdateCaseProperty, filterStatus: filterOptions.status, @@ -204,9 +168,6 @@ export const AllCasesList = React.memo( userCanCrud, connectors, onRowClick, - attachments: toAttach, - postComment, - updateCase, showSolutionColumn: !hasOwner && availableSolutions.length > 1, }); @@ -243,7 +204,7 @@ export const AllCasesList = React.memo( size="xs" color="accent" className="essentialAnimation" - $isShow={(isCasesLoading || isLoading || isCommentUpdating) && !isDataEmpty} + $isShow={(isCasesLoading || isLoading) && !isDataEmpty} /> ( goToCreateCase={onRowClick} handleIsLoading={handleIsLoading} isCasesLoading={isCasesLoading} - isCommentUpdating={isCommentUpdating} + isCommentUpdating={isCasesLoading} isDataEmpty={isDataEmpty} isSelectorView={isSelectorView} onChange={tableOnChangeCallback} diff --git a/x-pack/plugins/cases/public/components/all_cases/columns.tsx b/x-pack/plugins/cases/public/components/all_cases/columns.tsx index a05673d3e095a..543e6ef6f4871 100644 --- a/x-pack/plugins/cases/public/components/all_cases/columns.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/columns.tsx @@ -38,8 +38,6 @@ import { useApplicationCapabilities, useKibana } from '../../common/lib/kibana'; import { StatusContextMenu } from '../case_action_bar/status_context_menu'; import { TruncatedText } from '../truncated_text'; import { getConnectorIcon } from '../utils'; -import { PostComment } from '../../containers/use_post_comment'; -import { CaseAttachments } from '../../types'; import type { CasesOwners } from '../../client/helpers/can_use_cases'; import { useCasesFeatures } from '../cases_context/use_cases_features'; @@ -73,9 +71,6 @@ export interface GetCasesColumn { userCanCrud: boolean; connectors?: ActionConnector[]; onRowClick?: (theCase: Case) => void; - attachments?: CaseAttachments; - postComment?: (args: PostComment) => Promise; - updateCase?: (newCase: Case) => void; showSolutionColumn?: boolean; } @@ -89,9 +84,6 @@ export const useCasesColumns = ({ userCanCrud, connectors = [], onRowClick, - attachments, - postComment, - updateCase, showSolutionColumn, }: GetCasesColumn): CasesColumns[] => { // Delete case @@ -141,24 +133,11 @@ export const useCasesColumns = ({ const assignCaseAction = useCallback( async (theCase: Case) => { - // TODO currently the API only supports to add a comment at the time - // once the API is updated we should use bulk post comment #124814 - // this operation is intentionally made in sequence - if (attachments !== undefined && attachments.length > 0) { - for (const attachment of attachments) { - await postComment?.({ - caseId: theCase.id, - data: attachment, - }); - } - updateCase?.(theCase); - } - if (onRowClick) { onRowClick(theCase); } }, - [attachments, onRowClick, postComment, updateCase] + [onRowClick] ); useEffect(() => { diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx index ef01ead1cb07d..eba8888f3367a 100644 --- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx @@ -11,7 +11,6 @@ import { mount } from 'enzyme'; import { AllCasesSelectorModal } from '.'; import { TestProviders } from '../../../common/mock'; import { AllCasesList } from '../all_cases_list'; -import { SECURITY_SOLUTION_OWNER } from '../../../../common/constants'; jest.mock('../all_cases_list'); @@ -19,7 +18,6 @@ const onRowClick = jest.fn(); const defaultProps = { onRowClick, }; -const updateCase = jest.fn(); describe('AllCasesSelectorModal', () => { beforeEach(() => { @@ -50,17 +48,7 @@ describe('AllCasesSelectorModal', () => { it('pass the correct props to getAllCases method', () => { const fullProps = { ...defaultProps, - alertData: { - rule: { - id: 'rule-id', - name: 'rule', - }, - index: 'index-id', - alertId: 'alert-id', - owner: SECURITY_SOLUTION_OWNER, - }, hiddenStatuses: [], - updateCase, }; mount( @@ -72,10 +60,8 @@ describe('AllCasesSelectorModal', () => { // @ts-ignore idk what this mock style is but it works ¯\_(ツ)_/¯ expect(AllCasesList.type.mock.calls[0][0]).toEqual( expect.objectContaining({ - alertData: fullProps.alertData, hiddenStatuses: fullProps.hiddenStatuses, isSelectorView: true, - updateCase, }) ); }); diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx index ba553b28a34e0..581ecef47ad88 100644 --- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx @@ -16,20 +16,13 @@ import { } from '@elastic/eui'; import styled from 'styled-components'; import { Case, CaseStatusWithAllStatus } from '../../../../common/ui/types'; -import { CommentRequestAlertType } from '../../../../common/api'; import * as i18n from '../../../common/translations'; import { AllCasesList } from '../all_cases_list'; -import { CaseAttachments } from '../../../types'; + export interface AllCasesSelectorModalProps { - /** - * @deprecated Use the attachments prop instead - */ - alertData?: Omit; hiddenStatuses?: CaseStatusWithAllStatus[]; onRowClick?: (theCase?: Case) => void; - updateCase?: (newCase: Case) => void; onClose?: () => void; - attachments?: CaseAttachments; } const Modal = styled(EuiModal)` @@ -40,7 +33,7 @@ const Modal = styled(EuiModal)` `; export const AllCasesSelectorModal = React.memo( - ({ alertData, attachments, hiddenStatuses, onRowClick, updateCase, onClose }) => { + ({ hiddenStatuses, onRowClick, onClose }) => { const [isModalOpen, setIsModalOpen] = useState(true); const closeModal = useCallback(() => { if (onClose) { @@ -66,12 +59,9 @@ export const AllCasesSelectorModal = React.memo( diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx index b0e316e891744..25360800554b2 100644 --- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx @@ -5,42 +5,82 @@ * 2.0. */ -/* eslint-disable react/display-name */ - -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor } from '@testing-library/dom'; +import { act, renderHook } from '@testing-library/react-hooks'; +import userEvent from '@testing-library/user-event'; import React from 'react'; -import { CaseStatuses, StatusAll } from '../../../../common'; +import AllCasesSelectorModal from '.'; +import { Case, CaseStatuses, StatusAll } from '../../../../common'; +import { AppMockRenderer, createAppMockRenderer } from '../../../common/mock'; +import { useCasesToast } from '../../../common/use_cases_toast'; +import { alertComment } from '../../../containers/mock'; +import { usePostComment } from '../../../containers/use_post_comment'; +import { SupportedCaseAttachment } from '../../../types'; import { CasesContext } from '../../cases_context'; import { CasesContextStoreActionsList } from '../../cases_context/cases_context_reducer'; import { useCasesAddToExistingCaseModal } from './use_cases_add_to_existing_case_modal'; + jest.mock('../../../common/use_cases_toast'); +jest.mock('../../../containers/use_post_comment'); +// dummy mock, will call onRowclick when rendering +jest.mock('./all_cases_selector_modal', () => { + return { + AllCasesSelectorModal: jest.fn(), + }; +}); + +const useCasesToastMock = useCasesToast as jest.Mock; + +const AllCasesSelectorModalMock = AllCasesSelectorModal as unknown as jest.Mock; + +// test component to test the hook integration +const TestComponent: React.FC = () => { + const hook = useCasesAddToExistingCaseModal({ + attachments: [alertComment as SupportedCaseAttachment], + }); + + const onClick = () => { + hook.open(); + }; + + return
- } - body={ - <> -

- -

- - } - actions={[ - { - setIsEnabledUpdating(true); - setIsEnabled(true); - await enableRule(rule); - requestRefresh(); - setIsEnabledUpdating(false); - }} - > - Enable - , - ]} - /> - - - )} + diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_error_log.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_error_log.test.tsx new file mode 100644 index 0000000000000..e37f9abf67de3 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_error_log.test.tsx @@ -0,0 +1,312 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; +import { useKibana } from '../../../../common/lib/kibana'; + +import { EuiSuperDatePicker } from '@elastic/eui'; +import { Rule } from '../../../../types'; +import { RuleErrorLog } from './rule_error_log'; + +const useKibanaMock = useKibana as jest.Mocked; +jest.mock('../../../../common/lib/kibana'); + +const mockLogResponse: any = { + total: 8, + data: [], + totalErrors: 12, + errors: [ + { + id: '66b9c04a-d5d3-4ed4-aa7c-94ddaca3ac1d', + timestamp: '2022-03-31T18:03:33.133Z', + type: 'alerting', + message: + "rule execution failure: .es-query:d87fcbd0-b11b-11ec-88f6-293354dba871: 'Mine' - x_content_parse_exception: [parsing_exception] Reason: unknown query [match_allxxxx] did you mean [match_all]?", + }, + { + id: '14fcfe1c-5403-458f-8549-fa8ef59cdea3', + timestamp: '2022-03-31T18:02:30.119Z', + type: 'alerting', + message: + "rule execution failure: .es-query:d87fcbd0-b11b-11ec-88f6-293354dba871: 'Mine' - x_content_parse_exception: [parsing_exception] Reason: unknown query [match_allxxxx] did you mean [match_all]?", + }, + { + id: 'd53a401e-2a3a-4abe-8913-26e08a5039fd', + timestamp: '2022-03-31T18:01:27.112Z', + type: 'alerting', + message: + "rule execution failure: .es-query:d87fcbd0-b11b-11ec-88f6-293354dba871: 'Mine' - x_content_parse_exception: [parsing_exception] Reason: unknown query [match_allxxxx] did you mean [match_all]?", + }, + { + id: '9cfeae08-24b4-4d5c-b870-a303418f14d6', + timestamp: '2022-03-31T18:00:24.113Z', + type: 'alerting', + message: + "rule execution failure: .es-query:d87fcbd0-b11b-11ec-88f6-293354dba871: 'Mine' - x_content_parse_exception: [parsing_exception] Reason: unknown query [match_allxxxx] did you mean [match_all]?", + }, + { + id: '66b9c04a-d5d3-4ed4-aa7c-94ddaca3ac23', + timestamp: '2022-03-31T18:03:21.133Z', + type: 'alerting', + message: + "rule execution failure: .es-query:d87fcbd0-b11b-11ec-88f6-293354dba871: 'Mine' - x_content_parse_exception: [parsing_exception] Reason: unknown query [match_allxxxx] did you mean [match_all]?", + }, + { + id: '14fcfe1c-5403-458f-8549-fa8ef59cde18', + timestamp: '2022-03-31T18:02:18.119Z', + type: 'alerting', + message: + "rule execution failure: .es-query:d87fcbd0-b11b-11ec-88f6-293354dba871: 'Mine' - x_content_parse_exception: [parsing_exception] Reason: unknown query [match_allxxxx] did you mean [match_all]?", + }, + { + id: 'd53a401e-2a3a-4abe-8913-26e08a503915', + timestamp: '2022-03-31T18:01:15.112Z', + type: 'alerting', + message: + "rule execution failure: .es-query:d87fcbd0-b11b-11ec-88f6-293354dba871: 'Mine' - x_content_parse_exception: [parsing_exception] Reason: unknown query [match_allxxxx] did you mean [match_all]?", + }, + { + id: '9cfeae08-24b4-4d5c-b870-a303418f1412', + timestamp: '2022-03-31T18:00:12.113Z', + type: 'alerting', + message: + "rule execution failure: .es-query:d87fcbd0-b11b-11ec-88f6-293354dba871: 'Mine' - x_content_parse_exception: [parsing_exception] Reason: unknown query [match_allxxxx] did you mean [match_all]?", + }, + { + id: '66b9c04a-d5d3-4ed4-aa7c-94ddaca3ac09', + timestamp: '2022-03-31T18:03:09.133Z', + type: 'alerting', + message: + "rule execution failure: .es-query:d87fcbd0-b11b-11ec-88f6-293354dba871: 'Mine' - x_content_parse_exception: [parsing_exception] Reason: unknown query [match_allxxxx] did you mean [match_all]?", + }, + { + id: '14fcfe1c-5403-458f-8549-fa8ef59cde06', + timestamp: '2022-03-31T18:02:06.119Z', + type: 'alerting', + message: + "rule execution failure: .es-query:d87fcbd0-b11b-11ec-88f6-293354dba871: 'Mine' - x_content_parse_exception: [parsing_exception] Reason: unknown query [match_allxxxx] did you mean [match_all]?", + }, + { + id: 'd53a401e-2a3a-4abe-8913-26e08a503903', + timestamp: '2022-03-31T18:01:03.112Z', + type: 'alerting', + message: + "rule execution failure: .es-query:d87fcbd0-b11b-11ec-88f6-293354dba871: 'Mine' - x_content_parse_exception: [parsing_exception] Reason: unknown query [match_allxxxx] did you mean [match_all]?", + }, + { + id: '9cfeae08-24b4-4d5c-b870-a303418f1400', + timestamp: '2022-03-31T18:00:00.113Z', + type: 'alerting', + message: + "rule execution failure: .es-query:d87fcbd0-b11b-11ec-88f6-293354dba871: 'Mine' - x_content_parse_exception: [parsing_exception] Reason: unknown query [match_allxxxx] did you mean [match_all]?", + }, + ], +}; + +const mockRule: Rule = { + id: '56b61397-13d7-43d0-a583-0fa8c704a46f', + enabled: true, + name: 'rule-56b61397-13d7-43d0-a583-0fa8c704a46f', + tags: [], + ruleTypeId: '.noop', + consumer: 'consumer', + schedule: { interval: '1m' }, + actions: [], + params: {}, + createdBy: null, + updatedBy: null, + createdAt: new Date(), + updatedAt: new Date(), + apiKeyOwner: null, + throttle: null, + notifyWhen: null, + muteAll: false, + mutedInstanceIds: [], + executionStatus: { + status: 'unknown', + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + }, +}; + +const loadExecutionLogAggregationsMock = jest.fn(); + +describe('rule_error_log', () => { + beforeEach(() => { + jest.clearAllMocks(); + useKibanaMock().services.uiSettings.get = jest.fn().mockImplementation((value: string) => { + if (value === 'timepicker:quickRanges') { + return [ + { + from: 'now-15m', + to: 'now', + display: 'Last 15 minutes', + }, + ]; + } + }); + loadExecutionLogAggregationsMock.mockResolvedValue(mockLogResponse); + }); + + it('renders correctly', async () => { + const nowMock = jest.spyOn(Date, 'now').mockReturnValue(0); + const wrapper = mountWithIntl( + + ); + + // No data initially + expect(wrapper.find('.euiTableRow .euiTableCellContent__text').first().text()).toEqual( + 'No items found' + ); + + // Run the initial load fetch call + expect(loadExecutionLogAggregationsMock).toHaveBeenCalledTimes(1); + + expect(loadExecutionLogAggregationsMock).toHaveBeenCalledWith( + expect.objectContaining({ + dateEnd: '1969-12-31T19:00:00-05:00', + dateStart: '1969-12-30T19:00:00-05:00', + id: '56b61397-13d7-43d0-a583-0fa8c704a46f', + page: 0, + perPage: 1, + sort: { timestamp: { order: 'desc' } }, + }) + ); + + // Loading + expect(wrapper.find(EuiSuperDatePicker).props().isLoading).toBeTruthy(); + + expect(wrapper.find('[data-test-subj="tableHeaderCell_timestamp_0"]').exists()).toBeTruthy(); + + // Let the load resolve + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find(EuiSuperDatePicker).props().isLoading).toBeFalsy(); + expect(wrapper.find('.euiTableRow').length).toEqual(10); + + nowMock.mockRestore(); + }); + + it('can sort on timestamp columns', async () => { + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect( + wrapper.find('.euiTableRow').first().find('.euiTableCellContent').first().text() + ).toEqual('Mar 31, 2022 @ 14:03:33.133'); + + wrapper.find('button[data-test-subj="tableHeaderSortButton"]').first().simulate('click'); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect( + wrapper.find('.euiTableRow').first().find('.euiTableCellContent').first().text() + ).toEqual('Mar 31, 2022 @ 14:00:00.113'); + }); + + it('can paginate', async () => { + loadExecutionLogAggregationsMock.mockResolvedValue({ + ...mockLogResponse, + total: 100, + }); + + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find('.euiPagination').exists()).toBeTruthy(); + + // Paginate to the next page + wrapper.find('[data-test-subj="pagination-button-next"]').first().simulate('click'); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find('.euiTableRow').length).toEqual(2); + }); + + it('can filter by start and end date', async () => { + const nowMock = jest.spyOn(Date, 'now').mockReturnValue(0); + + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(loadExecutionLogAggregationsMock).toHaveBeenLastCalledWith( + expect.objectContaining({ + dateEnd: '1969-12-31T19:00:00-05:00', + dateStart: '1969-12-30T19:00:00-05:00', + id: '56b61397-13d7-43d0-a583-0fa8c704a46f', + page: 0, + perPage: 1, + sort: { timestamp: { order: 'desc' } }, + }) + ); + + wrapper + .find('[data-test-subj="superDatePickerToggleQuickMenuButton"] button') + .simulate('click'); + + wrapper + .find('[data-test-subj="superDatePickerCommonlyUsed_Last_15 minutes"] button') + .simulate('click'); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(loadExecutionLogAggregationsMock).toHaveBeenLastCalledWith( + expect.objectContaining({ + dateStart: '1969-12-31T18:45:00-05:00', + dateEnd: '1969-12-31T19:00:00-05:00', + id: '56b61397-13d7-43d0-a583-0fa8c704a46f', + page: 0, + perPage: 1, + sort: { timestamp: { order: 'desc' } }, + }) + ); + + nowMock.mockRestore(); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_error_log.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_error_log.tsx new file mode 100644 index 0000000000000..e47c65ff4e3e9 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_error_log.tsx @@ -0,0 +1,266 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useEffect, useState, useMemo, useRef } from 'react'; +import { i18n } from '@kbn/i18n'; +import datemath from '@elastic/datemath'; +import { + EuiFlexItem, + EuiFlexGroup, + EuiProgress, + EuiSpacer, + Pagination, + EuiSuperDatePicker, + OnTimeChangeProps, + EuiBasicTable, + EuiTableSortingType, + EuiBasicTableColumn, +} from '@elastic/eui'; +import { useKibana } from '../../../../common/lib/kibana'; + +import { LoadExecutionLogAggregationsProps } from '../../../lib/rule_api'; +import { Rule } from '../../../../types'; +import { IExecutionErrors } from '../../../../../../alerting/common'; +import { + ComponentOpts as RuleApis, + withBulkRuleOperations, +} from '../../common/components/with_bulk_rule_api_operations'; +import { RuleEventLogListCellRenderer } from './rule_event_log_list_cell_renderer'; + +const getParsedDate = (date: string) => { + if (date.includes('now')) { + return datemath.parse(date)?.format() || date; + } + return date; +}; + +const API_FAILED_MESSAGE = i18n.translate( + 'xpack.triggersActionsUI.sections.ruleDetails.errorLogColumn.apiError', + { + defaultMessage: 'Failed to fetch error log', + } +); + +const updateButtonProps = { + iconOnly: true, + fill: false, +}; + +const sortErrorLog = ( + a: IExecutionErrors, + b: IExecutionErrors, + direction: 'desc' | 'asc' = 'desc' +) => + direction === 'desc' + ? new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() + : new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(); + +export type RuleErrorLogProps = { + rule: Rule; + refreshToken?: number; + requestRefresh?: () => Promise; +} & Pick; + +export const RuleErrorLog = (props: RuleErrorLogProps) => { + const { rule, loadExecutionLogAggregations, refreshToken } = props; + + const { uiSettings, notifications } = useKibana().services; + + // Data grid states + const [logs, setLogs] = useState([]); + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + totalItemCount: 0, + }); + const [sort, setSort] = useState['sort']>({ + field: 'timestamp', + direction: 'desc', + }); + + // Date related states + const [isLoading, setIsLoading] = useState(false); + const [dateStart, setDateStart] = useState('now-24h'); + const [dateEnd, setDateEnd] = useState('now'); + const [dateFormat] = useState(() => uiSettings?.get('dateFormat')); + const [commonlyUsedRanges] = useState(() => { + return ( + uiSettings + ?.get('timepicker:quickRanges') + ?.map(({ from, to, display }: { from: string; to: string; display: string }) => ({ + start: from, + end: to, + label: display, + })) || [] + ); + }); + + const isInitialized = useRef(false); + + const loadEventLogs = async () => { + setIsLoading(true); + try { + const result = await loadExecutionLogAggregations({ + id: rule.id, + sort: { + [sort?.field || 'timestamp']: { order: sort?.direction || 'desc' }, + } as unknown as LoadExecutionLogAggregationsProps['sort'], + dateStart: getParsedDate(dateStart), + dateEnd: getParsedDate(dateEnd), + page: 0, + perPage: 1, + }); + setLogs(result.errors); + setPagination({ + ...pagination, + totalItemCount: result.totalErrors, + }); + } catch (e) { + notifications.toasts.addDanger({ + title: API_FAILED_MESSAGE, + text: e.body.message, + }); + } + setIsLoading(false); + }; + + const onTimeChange = useCallback( + ({ start, end, isInvalid }: OnTimeChangeProps) => { + if (isInvalid) { + return; + } + setDateStart(start); + setDateEnd(end); + }, + [setDateStart, setDateEnd] + ); + + const onRefresh = () => { + loadEventLogs(); + }; + + const columns: Array> = useMemo( + () => [ + { + field: 'timestamp', + name: i18n.translate( + 'xpack.triggersActionsUI.sections.ruleDetails.errorLogColumn.timestamp', + { + defaultMessage: 'Timestamp', + } + ), + render: (date: string) => ( + + ), + sortable: true, + width: '250px', + }, + { + field: 'type', + name: i18n.translate('xpack.triggersActionsUI.sections.ruleDetails.errorLogColumn.type', { + defaultMessage: 'Type', + }), + sortable: false, + width: '100px', + }, + { + field: 'message', + name: i18n.translate( + 'xpack.triggersActionsUI.sections.ruleDetails.errorLogColumn.message', + { + defaultMessage: 'Message', + } + ), + sortable: false, + }, + ], + [dateFormat] + ); + + const logList = useMemo(() => { + const start = pagination.pageIndex * pagination.pageSize; + const logsSortDesc = logs.sort((a, b) => sortErrorLog(a, b, sort?.direction)); + return logsSortDesc.slice(start, start + pagination.pageSize); + }, [logs, pagination.pageIndex, pagination.pageSize, sort?.direction]); + + useEffect(() => { + loadEventLogs(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dateStart, dateEnd]); + + useEffect(() => { + if (isInitialized.current) { + loadEventLogs(); + } + isInitialized.current = true; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [refreshToken]); + + return ( +
+ + + + + + + + {isLoading && ( + + )} + { + if (changedPage) { + setPagination((prevPagination) => { + if ( + prevPagination.pageIndex !== changedPage.index || + prevPagination.pageSize !== changedPage.size + ) { + return { + ...prevPagination, + pageIndex: changedPage.index, + pageSize: changedPage.size, + }; + } + return prevPagination; + }); + } + if (changedSort) { + setSort((prevSort) => { + if (prevSort?.direction !== changedSort.direction) { + return changedSort; + } + return prevSort; + }); + } + }} + /> +
+ ); +}; + +export const RuleErrorLogWithApi = withBulkRuleOperations(RuleErrorLog); + +// eslint-disable-next-line import/no-default-export +export { RuleErrorLogWithApi as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.tsx index 7b9ade9b5f192..cc3bb0b20a203 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useEffect, useState, useMemo } from 'react'; +import React, { useCallback, useEffect, useState, useMemo, useRef } from 'react'; import { i18n } from '@kbn/i18n'; import datemath from '@elastic/datemath'; import { @@ -233,6 +233,8 @@ const updateButtonProps = { export type RuleEventLogListProps = { rule: Rule; localStorageKey?: string; + refreshToken?: number; + requestRefresh?: () => Promise; } & Pick; export const RuleEventLogList = (props: RuleEventLogListProps) => { @@ -240,6 +242,7 @@ export const RuleEventLogList = (props: RuleEventLogListProps) => { rule, localStorageKey = RULE_EVENT_LOG_LIST_STORAGE_KEY, loadExecutionLogAggregations, + refreshToken, } = props; const { uiSettings, notifications } = useKibana().services; @@ -277,6 +280,8 @@ export const RuleEventLogList = (props: RuleEventLogListProps) => { ); }); + const isInitialized = useRef(false); + // Main cell renderer, renders durations, statuses, etc. const renderCell = ({ rowIndex, columnId }: EuiDataGridCellValueElementProps) => { const { pageIndex, pageSize } = pagination; @@ -406,6 +411,14 @@ export const RuleEventLogList = (props: RuleEventLogListProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [sortingColumns, dateStart, dateEnd, filter, pagination.pageIndex, pagination.pageSize]); + useEffect(() => { + if (isInitialized.current) { + loadEventLogs(); + } + isInitialized.current = true; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [refreshToken]); + useEffect(() => { localStorage.setItem(localStorageKey, JSON.stringify(visibleColumns)); }, [localStorageKey, visibleColumns]); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_route.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_route.tsx index 393cdc404db9e..3e11f987138d2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_route.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_route.tsx @@ -77,6 +77,7 @@ export const RuleRoute: React.FunctionComponent = ({ return ruleSummary ? ( Promise; isEditable: boolean; previousSnoozeInterval: string | null; + direction?: 'column' | 'row'; } const COMMON_SNOOZE_TIMES: Array<[number, SnoozeUnit]> = [ @@ -63,6 +64,7 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ unsnoozeRule, isEditable, previousSnoozeInterval, + direction = 'column', }: ComponentOpts) => { const [isEnabled, setIsEnabled] = useState(item.enabled); const [isSnoozed, setIsSnoozed] = useState(isItemSnoozed(item)); @@ -80,6 +82,9 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ const onChangeEnabledStatus = useCallback( async (enable: boolean) => { + if (item.enabled === enable) { + return; + } setIsUpdating(true); try { if (enable) { @@ -93,7 +98,7 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ setIsUpdating(false); } }, - [setIsUpdating, isEnabled, setIsEnabled, onRuleChanged, enableRule, disableRule] + [item.enabled, isEnabled, onRuleChanged, enableRule, disableRule] ); const onChangeSnooze = useCallback( async (value: number, unit?: SnoozeUnit) => { @@ -152,10 +157,11 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ return ( {isEditable ? ( @@ -279,7 +285,7 @@ const RuleStatusMenu: React.FunctionComponent = ({ }, ]; - return ; + return ; }; interface SnoozePanelProps { @@ -340,7 +346,6 @@ const SnoozePanel: React.FunctionComponent = ({ ); - return ( @@ -374,7 +379,7 @@ const SnoozePanel: React.FunctionComponent = ({ /> - + {i18n.translate('xpack.triggersActionsUI.sections.rulesList.applySnooze', { defaultMessage: 'Apply', })} @@ -405,7 +410,7 @@ const SnoozePanel: React.FunctionComponent = ({ - + {i18n.translate('xpack.triggersActionsUI.sections.rulesList.snoozeIndefinitely', { defaultMessage: 'Snooze indefinitely', })} @@ -417,7 +422,7 @@ const SnoozePanel: React.FunctionComponent = ({ - + Cancel snooze diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx index 9d56462670b67..c8638015a2942 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx @@ -834,7 +834,10 @@ export const RulesList: React.FunctionComponent = () => { }, { field: 'enabled', - name: '', + name: i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.triggerActionsTitle', + { defaultMessage: 'Trigger actions' } + ), sortable: true, truncateText: false, width: '10%', diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index 22c98b189a590..3813a8686826e 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -180,75 +180,90 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('should disable the rule', async () => { - const enableSwitch = await testSubjects.find('enableSwitch'); + const actionsDropdown = await testSubjects.find('statusDropdown'); - const isChecked = await enableSwitch.getAttribute('aria-checked'); - expect(isChecked).to.eql('true'); + expect(await actionsDropdown.getVisibleText()).to.eql('Enabled'); - await enableSwitch.click(); + await actionsDropdown.click(); + const actionsMenuElem = await testSubjects.find('ruleStatusMenu'); + const actionsMenuItemElem = await actionsMenuElem.findAllByClassName('euiContextMenuItem'); - const disableSwitchAfterDisabling = await testSubjects.find('enableSwitch'); - const isCheckedAfterDisabling = await disableSwitchAfterDisabling.getAttribute( - 'aria-checked' - ); - expect(isCheckedAfterDisabling).to.eql('false'); + await actionsMenuItemElem.at(1)?.click(); + + await retry.try(async () => { + expect(await actionsDropdown.getVisibleText()).to.eql('Disabled'); + }); }); - it('shouldnt allow you to mute a disabled rule', async () => { - const disabledEnableSwitch = await testSubjects.find('enableSwitch'); - expect(await disabledEnableSwitch.getAttribute('aria-checked')).to.eql('false'); + it('shouldnt allow you to snooze a disabled rule', async () => { + const actionsDropdown = await testSubjects.find('statusDropdown'); - const muteSwitch = await testSubjects.find('muteSwitch'); - expect(await muteSwitch.getAttribute('aria-checked')).to.eql('false'); + expect(await actionsDropdown.getVisibleText()).to.eql('Disabled'); - await muteSwitch.click(); + await actionsDropdown.click(); + const actionsMenuElem = await testSubjects.find('ruleStatusMenu'); + const actionsMenuItemElem = await actionsMenuElem.findAllByClassName('euiContextMenuItem'); - const muteSwitchAfterTryingToMute = await testSubjects.find('muteSwitch'); - const isDisabledMuteAfterDisabling = await muteSwitchAfterTryingToMute.getAttribute( - 'aria-checked' - ); - expect(isDisabledMuteAfterDisabling).to.eql('false'); + expect(await actionsMenuItemElem.at(2)?.getVisibleText()).to.eql('Snooze'); + expect(await actionsMenuItemElem.at(2)?.getAttribute('disabled')).to.eql('true'); + // close the dropdown + await actionsDropdown.click(); }); it('should reenable a disabled the rule', async () => { - const enableSwitch = await testSubjects.find('enableSwitch'); + const actionsDropdown = await testSubjects.find('statusDropdown'); - const isChecked = await enableSwitch.getAttribute('aria-checked'); - expect(isChecked).to.eql('false'); + expect(await actionsDropdown.getVisibleText()).to.eql('Disabled'); - await enableSwitch.click(); + await actionsDropdown.click(); + const actionsMenuElem = await testSubjects.find('ruleStatusMenu'); + const actionsMenuItemElem = await actionsMenuElem.findAllByClassName('euiContextMenuItem'); - const disableSwitchAfterReenabling = await testSubjects.find('enableSwitch'); - const isCheckedAfterDisabling = await disableSwitchAfterReenabling.getAttribute( - 'aria-checked' - ); - expect(isCheckedAfterDisabling).to.eql('true'); + await actionsMenuItemElem.at(0)?.click(); + + await retry.try(async () => { + expect(await actionsDropdown.getVisibleText()).to.eql('Enabled'); + }); }); - it('should mute the rule', async () => { - const muteSwitch = await testSubjects.find('muteSwitch'); + it('should snooze the rule', async () => { + const actionsDropdown = await testSubjects.find('statusDropdown'); - const isChecked = await muteSwitch.getAttribute('aria-checked'); - expect(isChecked).to.eql('false'); + expect(await actionsDropdown.getVisibleText()).to.eql('Enabled'); - await muteSwitch.click(); + await actionsDropdown.click(); + const actionsMenuElem = await testSubjects.find('ruleStatusMenu'); + const actionsMenuItemElem = await actionsMenuElem.findAllByClassName('euiContextMenuItem'); - const muteSwitchAfterDisabling = await testSubjects.find('muteSwitch'); - const isCheckedAfterDisabling = await muteSwitchAfterDisabling.getAttribute('aria-checked'); - expect(isCheckedAfterDisabling).to.eql('true'); + await actionsMenuItemElem.at(2)?.click(); + + const snoozeIndefinite = await testSubjects.find('ruleSnoozeIndefiniteApply'); + await snoozeIndefinite.click(); + + await retry.try(async () => { + expect(await actionsDropdown.getVisibleText()).to.eql('Snoozed'); + const remainingSnoozeTime = await testSubjects.find('remainingSnoozeTime'); + expect(await remainingSnoozeTime.getVisibleText()).to.eql('Indefinitely'); + }); }); - it('should unmute the rule', async () => { - const muteSwitch = await testSubjects.find('muteSwitch'); + it('should unsnooze the rule', async () => { + const actionsDropdown = await testSubjects.find('statusDropdown'); - const isChecked = await muteSwitch.getAttribute('aria-checked'); - expect(isChecked).to.eql('true'); + expect(await actionsDropdown.getVisibleText()).to.eql('Snoozed'); - await muteSwitch.click(); + await actionsDropdown.click(); + const actionsMenuElem = await testSubjects.find('ruleStatusMenu'); + const actionsMenuItemElem = await actionsMenuElem.findAllByClassName('euiContextMenuItem'); - const muteSwitchAfterUnmuting = await testSubjects.find('muteSwitch'); - const isCheckedAfterDisabling = await muteSwitchAfterUnmuting.getAttribute('aria-checked'); - expect(isCheckedAfterDisabling).to.eql('false'); + await actionsMenuItemElem.at(2)?.click(); + + const snoozeCancel = await testSubjects.find('ruleSnoozeCancel'); + await snoozeCancel.click(); + + await retry.try(async () => { + expect(await actionsDropdown.getVisibleText()).to.eql('Enabled'); + }); }); }); From 98306f162dc18a2e1022e30f22b24203bc33588a Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Mon, 4 Apr 2022 09:11:56 +0200 Subject: [PATCH 49/65] Remove invalid syntax from .eslintrc.js (#129164) The exclamation mark prefix doesn't have any effect in the `files` list. You instead need to override the matched path later with a different rule in order to change the behavior. --- .eslintrc.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index af9d77c4a9662..37c951e7e0763 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -274,12 +274,7 @@ module.exports = { * Licence headers */ { - files: [ - '**/*.{js,mjs,ts,tsx}', - '!plugins/**/*', - '!packages/elastic-datemath/**/*', - '!packages/elastic-eslint-config-kibana/**/*', - ], + files: ['**/*.{js,mjs,ts,tsx}'], rules: { '@kbn/eslint/require-license-header': [ 'error', From 158c6170ae0c584eb1cf3a1473fcf0506d948a2e Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Mon, 4 Apr 2022 09:49:11 +0200 Subject: [PATCH 50/65] Update @elastic/elasticsearch to v8.2.0-canary.2 (#128633) * Update @elastic/elasticsearch to v8.2.0-canary.2 * fix core violation * add optional properties to our type * update generated doc * add another ts-ignore * remove unused ts-expect-error * add ts-expect-error for type typo * add ts-expect-error infra code * fix more errors --- .../kibana-plugin-core-server.savedobjectsfieldmapping.md | 1 + package.json | 2 +- src/core/server/saved_objects/mappings/types.ts | 5 +++++ src/core/server/server.api.md | 1 + .../get_data_telemetry/get_data_telemetry.ts | 1 + .../plugins/enterprise_search/server/lib/fetch_indices.ts | 1 - .../plugins/index_management/server/lib/fetch_indices.ts | 1 - .../lib/adapters/framework/kibana_framework_adapter.ts | 1 + .../server/models/data_frame_analytics/models_provider.ts | 1 + .../detection_engine/migrations/create_migration_index.ts | 2 +- yarn.lock | 8 ++++---- 11 files changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfieldmapping.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfieldmapping.md index 85b52bacafa25..cf5b5d7e6e339 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfieldmapping.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfieldmapping.md @@ -13,5 +13,6 @@ Please refer to [elasticsearch documentation](https://www.elastic.co/guide/en/el ```typescript export declare type SavedObjectsFieldMapping = estypes.MappingProperty & { dynamic?: false | 'strict'; + properties?: Record; }; ``` diff --git a/package.json b/package.json index bb5a1ac61bb5d..6e7886823fca1 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "@elastic/apm-synthtrace": "link:bazel-bin/packages/elastic-apm-synthtrace", "@elastic/charts": "45.1.1", "@elastic/datemath": "link:bazel-bin/packages/elastic-datemath", - "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.2.0-canary.1", + "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.2.0-canary.2", "@elastic/ems-client": "8.2.0", "@elastic/eui": "53.0.1", "@elastic/filesaver": "1.1.2", diff --git a/src/core/server/saved_objects/mappings/types.ts b/src/core/server/saved_objects/mappings/types.ts index e225d0ff31022..3fc088d0e82c4 100644 --- a/src/core/server/saved_objects/mappings/types.ts +++ b/src/core/server/saved_objects/mappings/types.ts @@ -107,6 +107,11 @@ export type SavedObjectsFieldMapping = estypes.MappingProperty & { * *never* use `dynamic: true`. */ dynamic?: false | 'strict'; + /** + * Some mapping types do not accept the `properties` attributes. Explicitly adding it as optional to our type + * to avoid type failures on all code using accessing them via `SavedObjectsFieldMapping.properties`. + */ + properties?: Record; }; /** @internal */ diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index c89a5fc89d2fa..7337a3809172e 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -2366,6 +2366,7 @@ export interface SavedObjectsExportTransformContext { // @public export type SavedObjectsFieldMapping = estypes.MappingProperty & { dynamic?: false | 'strict'; + properties?: Record; }; // @public (undocumented) diff --git a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts index 3f98ead25ff4e..174e200a6c996 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts @@ -228,6 +228,7 @@ export async function getDataTelemetry(esClient: ElasticsearchClient) { const indices = indexNames.map((name) => { const baseIndexInfo = { name, + // @ts-expect-error 'properties' does not exist on type 'MappingMatchOnlyTextProperty' isECS: !!indexMappings[name]?.mappings?.properties?.ecs?.properties?.version?.type, shipper: indexMappings[name]?.mappings?._meta?.beat, packageName: indexMappings[name]?.mappings?._meta?.package?.name, diff --git a/x-pack/plugins/enterprise_search/server/lib/fetch_indices.ts b/x-pack/plugins/enterprise_search/server/lib/fetch_indices.ts index 4b8c2612f6467..b355df2c56843 100644 --- a/x-pack/plugins/enterprise_search/server/lib/fetch_indices.ts +++ b/x-pack/plugins/enterprise_search/server/lib/fetch_indices.ts @@ -23,7 +23,6 @@ export const fetchIndices = async (client: IScopedClusterClient): Promise { // TODO set node_id to ml:true when elasticsearch client is updated. + // @ts-expect-error typo in type definition: MlGetMemoryStatsResponse.cluser_name const response = (await mlClient.getMemoryStats()) as MemoryStatsResponse; const { trained_model_stats: trainedModelStats } = await mlClient.getTrainedModelsStats({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/create_migration_index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/create_migration_index.ts index 0c3da2a114595..b1f1ecf85746a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/create_migration_index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/create_migration_index.ts @@ -37,8 +37,8 @@ export const createMigrationIndex = async ({ body: { settings: { index: { + // @ts-expect-error `name` is required on IndicesIndexSettingsLifecycle lifecycle: { - // @ts-expect-error typings don't contain the property yet indexing_complete: true, }, }, diff --git a/yarn.lock b/yarn.lock index f90363032c43b..eca2c6c07f4ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1485,10 +1485,10 @@ dependencies: "@elastic/ecs-helpers" "^1.1.0" -"@elastic/elasticsearch@npm:@elastic/elasticsearch-canary@8.2.0-canary.1": - version "8.2.0-canary.1" - resolved "https://registry.yarnpkg.com/@elastic/elasticsearch-canary/-/elasticsearch-canary-8.2.0-canary.1.tgz#da547aaf0a39846cda4484bc021dea2117acaf0c" - integrity sha512-MxDCQjcKgxQulX+PJiPWdwFJwYq5J1EVycU5EaE1sDODLnnJp5dvQFPtRRla9MM5Elyy52swtfzQA5ktGixyRg== +"@elastic/elasticsearch@npm:@elastic/elasticsearch-canary@8.2.0-canary.2": + version "8.2.0-canary.2" + resolved "https://registry.yarnpkg.com/@elastic/elasticsearch-canary/-/elasticsearch-canary-8.2.0-canary.2.tgz#2513926cdbfe7c070e1fa6926f7829171b27cdba" + integrity sha512-Ki2lQ3/UlOnBaf5EjNw0WmCdXiW+J020aYtdVnIuCNhPSLoNPKoM7P+MlggdfeRnENvINlStrMy4bkYF/h6Vbw== dependencies: "@elastic/transport" "^8.0.2" tslib "^2.3.0" From 5b829712674c4347caa104ca1211dd36156b0ab7 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Mon, 4 Apr 2022 11:59:46 +0200 Subject: [PATCH 51/65] [Security Solution] Fix URI param encoding on user and host details page (#129064) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/security_solution/public/hosts/pages/index.tsx | 7 ++++++- .../plugins/security_solution/public/users/pages/index.tsx | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx index e42be25941ea7..00afec5da3756 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx @@ -50,7 +50,12 @@ export const HostsContainer = React.memo(() => ( match: { params: { detailName }, }, - }) => } + }) => ( + + )} /> { match: { params: { detailName }, }, - }) => } + }) => ( + + )} /> Date: Mon, 4 Apr 2022 12:03:11 +0200 Subject: [PATCH 52/65] [Discover] Fix inconsistent usage of arrow icons on Surrounding documents page (#129292) --- .../application/context/components/action_bar/action_bar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/discover/public/application/context/components/action_bar/action_bar.tsx b/src/plugins/discover/public/application/context/components/action_bar/action_bar.tsx index 9b7ba6345111f..07cb6029a77de 100644 --- a/src/plugins/discover/public/application/context/components/action_bar/action_bar.tsx +++ b/src/plugins/discover/public/application/context/components/action_bar/action_bar.tsx @@ -92,7 +92,6 @@ export function ActionBar({ { From fa7403073112df63c1ed9ab1369fa10a1edf194d Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Mon, 4 Apr 2022 12:31:10 +0200 Subject: [PATCH 53/65] [Snapshot & Restore] Remove `axios` dependency in tests (#128614) * wip: start refactoring test helpers * commit using @elastic.co * Fix more tests * Finish off refactoring all cits * Add docs * Fix linter issues * Fix ts issues --- .../client_integration/helpers/constant.ts | 2 + .../helpers/home.helpers.ts | 9 +- .../helpers/http_requests.ts | 199 +++++++++--------- .../helpers/policy_add.helpers.ts | 13 +- .../helpers/policy_edit.helpers.ts | 13 +- .../helpers/repository_add.helpers.ts | 13 +- .../helpers/repository_edit.helpers.ts | 13 +- .../helpers/restore_snapshot.helpers.ts | 18 +- .../helpers/setup_environment.tsx | 25 +-- .../helpers/snapshot_list.helpers.ts | 1 + .../__jest__/client_integration/home.test.ts | 125 ++++++----- .../client_integration/policy_add.test.ts | 51 +++-- .../client_integration/policy_edit.test.ts | 90 ++++---- .../client_integration/repository_add.test.ts | 194 ++++++++++------- .../repository_edit.test.ts | 18 +- .../restore_snapshot.test.ts | 60 ++++-- 16 files changed, 462 insertions(+), 382 deletions(-) diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/constant.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/constant.ts index 6f10f25934afc..a04c8aed403d9 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/constant.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/constant.ts @@ -13,4 +13,6 @@ export const REPOSITORY_EDIT = getRepository({ name: REPOSITORY_NAME }); export const POLICY_NAME = 'my-test-policy'; +export const SNAPSHOT_NAME = 'my-test-snapshot'; + export const POLICY_EDIT = getPolicy({ name: POLICY_NAME, retention: { minCount: 1 } }); diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts index 45b31a19693b7..9970b0100ceb8 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts @@ -14,6 +14,7 @@ import { AsyncTestBedConfig, delay, } from '@kbn/test-jest-helpers'; +import { HttpSetup } from 'src/core/public'; import { SnapshotRestoreHome } from '../../../public/application/sections/home/home'; import { BASE_PATH } from '../../../public/application/constants'; import { WithAppDependencies } from './setup_environment'; @@ -26,8 +27,6 @@ const testBedConfig: AsyncTestBedConfig = { doMountAsync: true, }; -const initTestBed = registerTestBed(WithAppDependencies(SnapshotRestoreHome), testBedConfig); - export interface HomeTestBed extends TestBed { actions: { clickReloadButton: () => void; @@ -40,7 +39,11 @@ export interface HomeTestBed extends TestBed { }; } -export const setup = async (): Promise => { +export const setup = async (httpSetup: HttpSetup): Promise => { + const initTestBed = registerTestBed( + WithAppDependencies(SnapshotRestoreHome, httpSetup), + testBedConfig + ); const testBed = await initTestBed(); const REPOSITORY_TABLE = 'repositoryTable'; const SNAPSHOT_TABLE = 'snapshotTable'; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts index 662c50a98bfe8..3c14c444d4664 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts @@ -5,117 +5,119 @@ * 2.0. */ -import sinon, { SinonFakeServer } from 'sinon'; +import { httpServiceMock } from '../../../../../../src/core/public/mocks'; import { API_BASE_PATH } from '../../../common'; +type HttpMethod = 'GET' | 'PUT' | 'POST'; type HttpResponse = Record | any[]; -const mockResponse = (defaultResponse: HttpResponse, response?: HttpResponse) => [ - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({ ...defaultResponse, ...response }), -]; +export interface ResponseError { + statusCode: number; + message: string | Error; +} // Register helpers to mock HTTP Requests -const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { - const setLoadRepositoriesResponse = (response: HttpResponse = {}) => { - const defaultResponse = { repositories: [] }; - - server.respondWith( - 'GET', - `${API_BASE_PATH}repositories`, - mockResponse(defaultResponse, response) - ); +const registerHttpRequestMockHelpers = ( + httpSetup: ReturnType +) => { + const mockResponses = new Map>>( + ['GET', 'PUT', 'POST'].map( + (method) => [method, new Map()] as [HttpMethod, Map>] + ) + ); + + const mockMethodImplementation = (method: HttpMethod, path: string) => + mockResponses.get(method)?.get(path) ?? Promise.resolve({}); + + httpSetup.get.mockImplementation((path) => + mockMethodImplementation('GET', path as unknown as string) + ); + httpSetup.post.mockImplementation((path) => + mockMethodImplementation('POST', path as unknown as string) + ); + httpSetup.put.mockImplementation((path) => + mockMethodImplementation('PUT', path as unknown as string) + ); + + const mockResponse = (method: HttpMethod, path: string, response?: unknown, error?: unknown) => { + const defuse = (promise: Promise) => { + promise.catch(() => {}); + return promise; + }; + + return mockResponses + .get(method)! + .set(path, error ? defuse(Promise.reject({ body: error })) : Promise.resolve(response)); }; - const setLoadRepositoryTypesResponse = (response: HttpResponse = []) => { - server.respondWith('GET', `${API_BASE_PATH}repository_types`, JSON.stringify(response)); - }; + const setLoadRepositoriesResponse = ( + response: HttpResponse = { repositories: [] }, + error?: ResponseError + ) => mockResponse('GET', `${API_BASE_PATH}repositories`, response, error); - const setGetRepositoryResponse = (response?: HttpResponse, delay = 0) => { - const defaultResponse = {}; - - server.respondWith( - 'GET', - /api\/snapshot_restore\/repositories\/.+/, - mockResponse(defaultResponse, response) - ); - }; + const setLoadRepositoryTypesResponse = (response: HttpResponse = [], error?: ResponseError) => + mockResponse('GET', `${API_BASE_PATH}repository_types`, response, error); - const setSaveRepositoryResponse = (response?: HttpResponse, error?: any) => { - const status = error ? error.status || 400 : 200; - const body = error ? JSON.stringify(error.body) : JSON.stringify(response); + const setGetRepositoryResponse = ( + repositoryName: string, + response?: HttpResponse, + error?: ResponseError + ) => mockResponse('GET', `${API_BASE_PATH}repositories/${repositoryName}`, response, error); - server.respondWith('PUT', `${API_BASE_PATH}repositories`, [ - status, - { 'Content-Type': 'application/json' }, - body, - ]); - }; + const setSaveRepositoryResponse = (response?: HttpResponse, error?: ResponseError) => + mockResponse('PUT', `${API_BASE_PATH}repositories`, response, error); - const setLoadSnapshotsResponse = (response: HttpResponse = {}) => { + const setLoadSnapshotsResponse = (response?: HttpResponse, error?: ResponseError) => { const defaultResponse = { errors: {}, snapshots: [], repositories: [], total: 0 }; - - server.respondWith('GET', `${API_BASE_PATH}snapshots`, mockResponse(defaultResponse, response)); + return mockResponse('GET', `${API_BASE_PATH}snapshots`, response ?? defaultResponse, error); }; - const setGetSnapshotResponse = (response?: HttpResponse) => { - const defaultResponse = {}; - - server.respondWith( + const setGetSnapshotResponse = ( + repositoryName: string, + snapshotName: string, + response?: HttpResponse, + error?: ResponseError + ) => + mockResponse( 'GET', - /\/api\/snapshot_restore\/snapshots\/.+/, - mockResponse(defaultResponse, response) + `${API_BASE_PATH}snapshots/${repositoryName}/${snapshotName}`, + response, + error ); - }; - - const setLoadIndicesResponse = (response: HttpResponse = {}) => { - const defaultResponse = { indices: [] }; - server.respondWith( - 'GET', - `${API_BASE_PATH}policies/indices`, - mockResponse(defaultResponse, response) + const setLoadIndicesResponse = ( + response: HttpResponse = { indices: [] }, + error?: ResponseError + ) => mockResponse('GET', `${API_BASE_PATH}policies/indices`, response, error); + + const setAddPolicyResponse = (response?: HttpResponse, error?: ResponseError) => + mockResponse('POST', `${API_BASE_PATH}policies`, response, error); + + const setCleanupRepositoryResponse = ( + repositoryName: string, + response?: HttpResponse, + error?: ResponseError + ) => + mockResponse('POST', `${API_BASE_PATH}repositories/${repositoryName}/cleanup`, response, error); + + const setGetPolicyResponse = ( + policyName: string, + response?: HttpResponse, + error?: ResponseError + ) => mockResponse('GET', `${API_BASE_PATH}policy/${policyName}`, response, error); + + const setRestoreSnapshotResponse = ( + repositoryName: string, + snapshotId: string, + response?: HttpResponse, + error?: ResponseError + ) => + mockResponse( + 'POST', + `${API_BASE_PATH}restore/${repositoryName}/${snapshotId}`, + response, + error ); - }; - - const setAddPolicyResponse = (response?: HttpResponse, error?: any) => { - const status = error ? error.status || 400 : 200; - const body = error ? JSON.stringify(error.body) : JSON.stringify(response); - - server.respondWith('POST', `${API_BASE_PATH}policies`, [ - status, - { 'Content-Type': 'application/json' }, - body, - ]); - }; - - const setCleanupRepositoryResponse = (response?: HttpResponse, error?: any) => { - const status = error ? error.status || 503 : 200; - const body = error ? JSON.stringify(error) : JSON.stringify(response); - - server.respondWith('POST', `${API_BASE_PATH}repositories/:name/cleanup`, [ - status, - { 'Content-Type': 'application/json' }, - body, - ]); - }; - - const setGetPolicyResponse = (response?: HttpResponse) => { - server.respondWith('GET', `${API_BASE_PATH}policy/:name`, [ - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify(response), - ]); - }; - - const setRestoreSnapshotResponse = (response?: HttpResponse) => { - server.respondWith('POST', `${API_BASE_PATH}restore/:repository/:snapshot`, [ - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify(response), - ]); - }; return { setLoadRepositoriesResponse, @@ -133,18 +135,11 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { }; export const init = () => { - const server = sinon.fakeServer.create(); - server.respondImmediately = true; - - // Define default response for unhandled requests. - // We make requests to APIs which don't impact the component under test, e.g. UI metric telemetry, - // and we can mock them all with a 200 instead of mocking each one individually. - server.respondWith([200, {}, 'DefaultResponse']); - - const httpRequestsMockHelpers = registerHttpRequestMockHelpers(server); + const httpSetup = httpServiceMock.createSetupContract(); + const httpRequestsMockHelpers = registerHttpRequestMockHelpers(httpSetup); return { - server, + httpSetup, httpRequestsMockHelpers, }; }; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_add.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_add.helpers.ts index d921b96781d0c..a5cf9aceff8c4 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_add.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_add.helpers.ts @@ -6,6 +6,7 @@ */ import { registerTestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; +import { HttpSetup } from 'src/core/public'; import { PolicyAdd } from '../../../public/application/sections/policy_add'; import { formSetup, PolicyFormTestSubjects } from './policy_form.helpers'; import { WithAppDependencies } from './setup_environment'; @@ -18,9 +19,11 @@ const testBedConfig: AsyncTestBedConfig = { doMountAsync: true, }; -const initTestBed = registerTestBed( - WithAppDependencies(PolicyAdd), - testBedConfig -); +export const setup = async (httpSetup: HttpSetup) => { + const initTestBed = registerTestBed( + WithAppDependencies(PolicyAdd, httpSetup), + testBedConfig + ); -export const setup = formSetup.bind(null, initTestBed); + return formSetup(initTestBed); +}; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_edit.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_edit.helpers.ts index 461351e744125..3334dc5337a4d 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_edit.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_edit.helpers.ts @@ -6,6 +6,7 @@ */ import { registerTestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; +import { HttpSetup } from 'src/core/public'; import { PolicyEdit } from '../../../public/application/sections/policy_edit'; import { WithAppDependencies } from './setup_environment'; import { POLICY_NAME } from './constant'; @@ -19,9 +20,11 @@ const testBedConfig: AsyncTestBedConfig = { doMountAsync: true, }; -const initTestBed = registerTestBed( - WithAppDependencies(PolicyEdit), - testBedConfig -); +export const setup = async (httpSetup: HttpSetup) => { + const initTestBed = registerTestBed( + WithAppDependencies(PolicyEdit, httpSetup), + testBedConfig + ); -export const setup = formSetup.bind(null, initTestBed); + return formSetup(initTestBed); +}; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_add.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_add.helpers.ts index c6460078482bf..c4a5fc4b7be93 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_add.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_add.helpers.ts @@ -6,14 +6,11 @@ */ import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; +import { HttpSetup } from 'src/core/public'; import { RepositoryType } from '../../../common/types'; import { RepositoryAdd } from '../../../public/application/sections/repository_add'; import { WithAppDependencies } from './setup_environment'; -const initTestBed = registerTestBed(WithAppDependencies(RepositoryAdd), { - doMountAsync: true, -}); - export interface RepositoryAddTestBed extends TestBed { actions: { clickNextButton: () => void; @@ -23,7 +20,13 @@ export interface RepositoryAddTestBed extends TestBed }; } -export const setup = async (): Promise => { +export const setup = async (httpSetup: HttpSetup): Promise => { + const initTestBed = registerTestBed( + WithAppDependencies(RepositoryAdd, httpSetup), + { + doMountAsync: true, + } + ); const testBed = await initTestBed(); // User actions diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_edit.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_edit.helpers.ts index 275c216d70664..3e8bbf95d5ea9 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_edit.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_edit.helpers.ts @@ -6,6 +6,7 @@ */ import { registerTestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; +import { HttpSetup } from 'src/core/public'; import { RepositoryEdit } from '../../../public/application/sections/repository_edit'; import { WithAppDependencies } from './setup_environment'; import { REPOSITORY_NAME } from './constant'; @@ -18,10 +19,14 @@ const testBedConfig: AsyncTestBedConfig = { doMountAsync: true, }; -export const setup = registerTestBed( - WithAppDependencies(RepositoryEdit), - testBedConfig -); +export const setup = async (httpSetup: HttpSetup) => { + const initTestBed = registerTestBed( + WithAppDependencies(RepositoryEdit, httpSetup), + testBedConfig + ); + + return await initTestBed(); +}; export type RepositoryEditTestSubjects = TestSubjects | ThreeLevelDepth | NonVisibleTestSubjects; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/restore_snapshot.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/restore_snapshot.helpers.ts index 86c93a6811bd4..fe5a82f421def 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/restore_snapshot.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/restore_snapshot.helpers.ts @@ -6,23 +6,20 @@ */ import { act } from 'react-dom/test-utils'; +import { HttpSetup } from 'src/core/public'; import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { RestoreSnapshot } from '../../../public/application/sections/restore_snapshot'; import { WithAppDependencies } from './setup_environment'; +import { REPOSITORY_NAME, SNAPSHOT_NAME } from '../helpers/constant'; const testBedConfig: AsyncTestBedConfig = { memoryRouter: { - initialEntries: ['/add_policy'], - componentRoutePath: '/add_policy', + initialEntries: [`/restore/${REPOSITORY_NAME}/${SNAPSHOT_NAME}`], + componentRoutePath: '/restore/:repositoryName?/:snapshotId*', }, doMountAsync: true, }; -const initTestBed = registerTestBed( - WithAppDependencies(RestoreSnapshot), - testBedConfig -); - const setupActions = (testBed: TestBed) => { const { find, component, form, exists } = testBed; @@ -84,7 +81,12 @@ export type RestoreSnapshotTestBed = TestBed & { actions: Actions; }; -export const setup = async (): Promise => { +export const setup = async (httpSetup: HttpSetup): Promise => { + const initTestBed = registerTestBed( + WithAppDependencies(RestoreSnapshot, httpSetup), + testBedConfig + ); + const testBed = await initTestBed(); return { diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx index 66ba21b136816..0c961d5e151bc 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx @@ -6,11 +6,10 @@ */ import React from 'react'; -import axios from 'axios'; -import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import { i18n } from '@kbn/i18n'; import { LocationDescriptorObject } from 'history'; +import { HttpSetup } from 'src/core/public'; import { coreMock, scopedHistoryMock } from 'src/core/public/mocks'; import { setUiMetricService, httpService } from '../../../public/application/services/http'; import { @@ -22,8 +21,6 @@ import { textService } from '../../../public/application/services/text'; import { init as initHttpRequests } from './http_requests'; import { UiMetricService } from '../../../public/application/services'; -const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); - const history = scopedHistoryMock.create(); history.createHref.mockImplementation((location: LocationDescriptorObject) => { return `${location.pathname}?${location.search}`; @@ -48,18 +45,11 @@ const appDependencies = { }; export const setupEnvironment = () => { - // @ts-ignore - httpService.setup(mockHttpClient); breadcrumbService.setup(() => undefined); textService.setup(i18n); docTitleService.setup(() => undefined); - const { server, httpRequestsMockHelpers } = initHttpRequests(); - - return { - server, - httpRequestsMockHelpers, - }; + return initHttpRequests(); }; /** @@ -70,9 +60,16 @@ export const setupEnvironment = () => { this.terminate = () => {}; }; -export const WithAppDependencies = (Comp: any) => (props: any) => - ( +export const WithAppDependencies = (Comp: any, httpSetup?: HttpSetup) => (props: any) => { + // We need to optionally setup the httpService since some cit helpers (such as snapshot_list.helpers) + // use jest mocks to stub the fetch hooks instead of mocking api responses. + if (httpSetup) { + httpService.setup(httpSetup); + } + + return ( ); +}; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/snapshot_list.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/snapshot_list.helpers.ts index 261976623144b..d81db28c5353a 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/snapshot_list.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/snapshot_list.helpers.ts @@ -35,6 +35,7 @@ const searchErrorSelector = 'snapshotListSearchError'; export const setup = async (query?: string): Promise => { const testBed = await initTestBed(query); + const { form, component, find, exists } = testBed; const setSearchText = async (value: string, advanceTime = true) => { diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts index a99a6fdb81167..09a726a6b9abe 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts @@ -41,16 +41,12 @@ const removeWhiteSpaceOnArrayValues = (array: any[]) => }); describe('', () => { - const { server, httpRequestsMockHelpers } = setupEnvironment(); + const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); let testBed: HomeTestBed; - afterAll(() => { - server.restore(); - }); - describe('on component mount', () => { beforeEach(async () => { - testBed = await setup(); + testBed = await setup(httpSetup); }); test('should set the correct app title', () => { @@ -79,7 +75,7 @@ describe('', () => { describe('tabs', () => { beforeEach(async () => { - testBed = await setup(); + testBed = await setup(httpSetup); await act(async () => { await nextTick(); @@ -132,7 +128,7 @@ describe('', () => { }); test('should display an empty prompt', async () => { - const { component, exists } = await setup(); + const { component, exists } = await setup(httpSetup); await act(async () => { await nextTick(); @@ -164,7 +160,7 @@ describe('', () => { beforeEach(async () => { httpRequestsMockHelpers.setLoadRepositoriesResponse({ repositories }); - testBed = await setup(); + testBed = await setup(httpSetup); await act(async () => { await nextTick(); @@ -208,7 +204,6 @@ describe('', () => { test('should have a button to reload the repositories', async () => { const { component, exists, actions } = testBed; - const totalRequests = server.requests.length; expect(exists('reloadButton')).toBe(true); await act(async () => { @@ -217,9 +212,9 @@ describe('', () => { component.update(); }); - expect(server.requests.length).toBe(totalRequests + 1); - expect(server.requests[server.requests.length - 1].url).toBe( - `${API_BASE_PATH}repositories` + expect(httpSetup.get).toHaveBeenLastCalledWith( + `${API_BASE_PATH}repositories`, + expect.anything() ); }); @@ -273,10 +268,10 @@ describe('', () => { component.update(); }); - const latestRequest = server.requests[server.requests.length - 1]; - - expect(latestRequest.method).toBe('DELETE'); - expect(latestRequest.url).toBe(`${API_BASE_PATH}repositories/${repo1.name}`); + expect(httpSetup.delete).toHaveBeenLastCalledWith( + `${API_BASE_PATH}repositories/${repo1.name}`, + expect.anything() + ); }); }); @@ -304,23 +299,20 @@ describe('', () => { }); test('should show a loading state while fetching the repository', async () => { - server.respondImmediately = false; const { find, exists, actions } = testBed; // By providing undefined, the "loading section" will be displayed - httpRequestsMockHelpers.setGetRepositoryResponse(undefined); + httpRequestsMockHelpers.setGetRepositoryResponse(repo1.name, undefined); await actions.clickRepositoryAt(0); expect(exists('repositoryDetail.sectionLoading')).toBe(true); expect(find('repositoryDetail.sectionLoading').text()).toEqual('Loading repository…'); - - server.respondImmediately = true; }); describe('when the repository has been fetched', () => { beforeEach(async () => { - httpRequestsMockHelpers.setGetRepositoryResponse({ + httpRequestsMockHelpers.setGetRepositoryResponse(repo1.name, { repository: { name: 'my-repo', type: 'fs', @@ -357,15 +349,15 @@ describe('', () => { component.update(); }); - const latestRequest = server.requests[server.requests.length - 1]; - - expect(latestRequest.method).toBe('GET'); - expect(latestRequest.url).toBe(`${API_BASE_PATH}repositories/${repo1.name}/verify`); + expect(httpSetup.get).toHaveBeenLastCalledWith( + `${API_BASE_PATH}repositories/${repo1.name}/verify`, + expect.anything() + ); }); describe('clean repository', () => { test('shows results when request succeeds', async () => { - httpRequestsMockHelpers.setCleanupRepositoryResponse({ + httpRequestsMockHelpers.setCleanupRepositoryResponse(repo1.name, { cleanup: { cleaned: true, response: { @@ -383,16 +375,17 @@ describe('', () => { }); component.update(); - const latestRequest = server.requests[server.requests.length - 1]; - expect(latestRequest.method).toBe('POST'); - expect(latestRequest.url).toBe(`${API_BASE_PATH}repositories/${repo1.name}/cleanup`); + expect(httpSetup.post).toHaveBeenLastCalledWith( + `${API_BASE_PATH}repositories/${repo1.name}/cleanup`, + expect.anything() + ); expect(exists('repositoryDetail.cleanupCodeBlock')).toBe(true); expect(exists('repositoryDetail.cleanupError')).toBe(false); }); test('shows error when success fails', async () => { - httpRequestsMockHelpers.setCleanupRepositoryResponse({ + httpRequestsMockHelpers.setCleanupRepositoryResponse(repo1.name, { cleanup: { cleaned: false, error: { @@ -408,9 +401,10 @@ describe('', () => { }); component.update(); - const latestRequest = server.requests[server.requests.length - 1]; - expect(latestRequest.method).toBe('POST'); - expect(latestRequest.url).toBe(`${API_BASE_PATH}repositories/${repo1.name}/cleanup`); + expect(httpSetup.post).toHaveBeenLastCalledWith( + `${API_BASE_PATH}repositories/${repo1.name}/cleanup`, + expect.anything() + ); expect(exists('repositoryDetail.cleanupCodeBlock')).toBe(false); expect(exists('repositoryDetail.cleanupError')).toBe(true); @@ -420,7 +414,7 @@ describe('', () => { describe('when the repository has been fetched (and has snapshots)', () => { beforeEach(async () => { - httpRequestsMockHelpers.setGetRepositoryResponse({ + httpRequestsMockHelpers.setGetRepositoryResponse(repo1.name, { repository: { name: 'my-repo', type: 'fs', @@ -448,7 +442,7 @@ describe('', () => { }); beforeEach(async () => { - testBed = await setup(); + testBed = await setup(httpSetup); await act(async () => { testBed.actions.selectTab('snapshots'); @@ -478,7 +472,7 @@ describe('', () => { total: 0, }); - testBed = await setup(); + testBed = await setup(httpSetup); await act(async () => { testBed.actions.selectTab('snapshots'); @@ -517,7 +511,7 @@ describe('', () => { total: 2, }); - testBed = await setup(); + testBed = await setup(httpSetup); await act(async () => { testBed.actions.selectTab('snapshots'); @@ -561,7 +555,7 @@ describe('', () => { }, }); - testBed = await setup(); + testBed = await setup(httpSetup); await act(async () => { testBed.actions.selectTab('snapshots'); @@ -589,7 +583,7 @@ describe('', () => { }, }); - testBed = await setup(); + testBed = await setup(httpSetup); await act(async () => { testBed.actions.selectTab('snapshots'); @@ -625,7 +619,6 @@ describe('', () => { test('should have a button to reload the snapshots', async () => { const { component, exists, actions } = testBed; - const totalRequests = server.requests.length; expect(exists('reloadButton')).toBe(true); await act(async () => { @@ -634,13 +627,19 @@ describe('', () => { component.update(); }); - expect(server.requests.length).toBe(totalRequests + 1); - expect(server.requests[server.requests.length - 1].url).toBe(`${API_BASE_PATH}snapshots`); + expect(httpSetup.get).toHaveBeenLastCalledWith( + `${API_BASE_PATH}snapshots`, + expect.anything() + ); }); describe('detail panel', () => { beforeEach(async () => { - httpRequestsMockHelpers.setGetSnapshotResponse(snapshot1); + httpRequestsMockHelpers.setGetSnapshotResponse( + snapshot1.repository, + snapshot1.snapshot, + snapshot1 + ); }); test('should show the detail when clicking on a snapshot', async () => { @@ -656,17 +655,18 @@ describe('', () => { // that makes the component crash. I tried a few things with no luck so, as this // is a low impact test, I prefer to skip it and move on. test.skip('should show a loading while fetching the snapshot', async () => { - server.respondImmediately = false; const { find, exists, actions } = testBed; // By providing undefined, the "loading section" will be displayed - httpRequestsMockHelpers.setGetSnapshotResponse(undefined); + httpRequestsMockHelpers.setGetSnapshotResponse( + snapshot1.repository, + snapshot1.snapshot, + undefined + ); await actions.clickSnapshotAt(0); expect(exists('snapshotDetail.sectionLoading')).toBe(true); expect(find('snapshotDetail.sectionLoading').text()).toEqual('Loading snapshot…'); - - server.respondImmediately = true; }); describe('on mount', () => { @@ -757,7 +757,11 @@ describe('', () => { const setSnapshotStateAndUpdateDetail = async (state: string) => { const updatedSnapshot = { ...snapshot1, state }; - httpRequestsMockHelpers.setGetSnapshotResponse(updatedSnapshot); + httpRequestsMockHelpers.setGetSnapshotResponse( + itemIndexToClickOn === 0 ? snapshot1.repository : snapshot2.repository, + itemIndexToClickOn === 0 ? snapshot1.snapshot : snapshot2.snapshot, + updatedSnapshot + ); await actions.clickSnapshotAt(itemIndexToClickOn); // click another snapshot to trigger the HTTP call }; @@ -787,9 +791,12 @@ describe('', () => { }; // Call sequentially each state and verify that the message is ok - return Object.entries(mapStateToMessage).reduce((promise, [state, message]) => { - return promise.then(async () => expectMessageForSnapshotState(state, message)); - }, Promise.resolve()); + return Object.entries(mapStateToMessage).reduce( + async (promise, [state, message]) => { + return promise.then(async () => expectMessageForSnapshotState(state, message)); + }, + Promise.resolve() + ); }); }); @@ -805,8 +812,12 @@ describe('', () => { test('should display a message when snapshot in progress ', async () => { const { find, actions } = testBed; - const updatedSnapshot = { ...snapshot1, state: 'IN_PROGRESS' }; - httpRequestsMockHelpers.setGetSnapshotResponse(updatedSnapshot); + const updatedSnapshot = { ...snapshot2, state: 'IN_PROGRESS' }; + httpRequestsMockHelpers.setGetSnapshotResponse( + snapshot2.repository, + snapshot2.snapshot, + updatedSnapshot + ); await actions.clickSnapshotAt(1); // click another snapshot to trigger the HTTP call actions.selectSnapshotDetailTab('failedIndices'); @@ -825,7 +836,11 @@ describe('', () => { beforeEach(async () => { const updatedSnapshot = { ...snapshot1, indexFailures }; - httpRequestsMockHelpers.setGetSnapshotResponse(updatedSnapshot); + httpRequestsMockHelpers.setGetSnapshotResponse( + updatedSnapshot.repository, + updatedSnapshot.snapshot, + updatedSnapshot + ); await testBed.actions.clickSnapshotAt(0); testBed.actions.selectSnapshotDetailTab('failedIndices'); }); diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_add.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_add.test.ts index 1e8546bef50e5..0950eda8fc630 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_add.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_add.test.ts @@ -12,6 +12,7 @@ import { ReactElement } from 'react'; import { act } from 'react-dom/test-utils'; import * as fixtures from '../../test/fixtures'; +import { API_BASE_PATH } from '../../common'; import { PolicyFormTestBed } from './helpers/policy_form.helpers'; import { DEFAULT_POLICY_SCHEDULE } from '../../public/application/constants'; @@ -36,12 +37,7 @@ const repository = fixtures.getRepository({ name: `a${getRandomString()}`, type: describe('', () => { let testBed: PolicyFormTestBed; - - const { server, httpRequestsMockHelpers } = setupEnvironment(); - - afterAll(() => { - server.restore(); - }); + const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); describe('on component mount', () => { beforeEach(async () => { @@ -51,7 +47,7 @@ describe('', () => { dataStreams: ['my_data_stream', 'my_other_data_stream'], }); - testBed = await setup(); + testBed = await setup(httpSetup); await nextTick(); testBed.component.update(); }); @@ -241,36 +237,37 @@ describe('', () => { await nextTick(); }); - const latestRequest = server.requests[server.requests.length - 1]; - - const expected = { - config: {}, - isManagedPolicy: false, - name: POLICY_NAME, - repository: repository.name, - retention: { - expireAfterUnit: 'd', // default - expireAfterValue: Number(EXPIRE_AFTER_VALUE), - maxCount: Number(MAX_COUNT), - minCount: Number(MIN_COUNT), - }, - schedule: DEFAULT_POLICY_SCHEDULE, - snapshotName: SNAPSHOT_NAME, - }; - - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual(expected); + expect(httpSetup.post).toHaveBeenLastCalledWith( + `${API_BASE_PATH}policies`, + expect.objectContaining({ + body: JSON.stringify({ + name: POLICY_NAME, + snapshotName: SNAPSHOT_NAME, + schedule: DEFAULT_POLICY_SCHEDULE, + repository: repository.name, + config: {}, + retention: { + expireAfterValue: Number(EXPIRE_AFTER_VALUE), + expireAfterUnit: 'd', // default + maxCount: Number(MAX_COUNT), + minCount: Number(MIN_COUNT), + }, + isManagedPolicy: false, + }), + }) + ); }); it('should surface the API errors from the put HTTP request', async () => { const { component, actions, find, exists } = testBed; const error = { - status: 409, + statusCode: 409, error: 'Conflict', message: `There is already a policy with name '${POLICY_NAME}'`, }; - httpRequestsMockHelpers.setAddPolicyResponse(undefined, { body: error }); + httpRequestsMockHelpers.setAddPolicyResponse(undefined, error); await act(async () => { actions.clickSubmitButton(); diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts index 7d5d605b216bc..117d6f0e0e223 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts @@ -8,6 +8,7 @@ import { act } from 'react-dom/test-utils'; import { setupEnvironment, pageHelpers, nextTick } from './helpers'; +import { API_BASE_PATH } from '../../common'; import { PolicyForm } from '../../public/application/components/policy_form'; import { PolicyFormTestBed } from './helpers/policy_form.helpers'; import { POLICY_EDIT } from './helpers/constant'; @@ -22,15 +23,11 @@ const EXPIRE_AFTER_UNIT = TIME_UNITS.MINUTE; describe('', () => { let testBed: PolicyFormTestBed; let testBedPolicyAdd: PolicyFormTestBed; - const { server, httpRequestsMockHelpers } = setupEnvironment(); - - afterAll(() => { - server.restore(); - }); + const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); describe('on component mount', () => { beforeEach(async () => { - httpRequestsMockHelpers.setGetPolicyResponse({ policy: POLICY_EDIT }); + httpRequestsMockHelpers.setGetPolicyResponse(POLICY_EDIT.name, { policy: POLICY_EDIT }); httpRequestsMockHelpers.setLoadIndicesResponse({ indices: ['my_index'], dataStreams: ['my_data_stream'], @@ -39,7 +36,7 @@ describe('', () => { repositories: [{ name: POLICY_EDIT.repository }], }); - testBed = await setup(); + testBed = await setup(httpSetup); await act(async () => { await nextTick(); @@ -55,7 +52,7 @@ describe('', () => { describe('policy with pre-existing repository that was deleted', () => { beforeEach(async () => { - httpRequestsMockHelpers.setGetPolicyResponse({ policy: POLICY_EDIT }); + httpRequestsMockHelpers.setGetPolicyResponse(POLICY_EDIT.name, { policy: POLICY_EDIT }); httpRequestsMockHelpers.setLoadIndicesResponse({ indices: ['my_index'], dataStreams: ['my_data_stream'], @@ -64,7 +61,7 @@ describe('', () => { repositories: [{ name: 'this-is-a-new-repository' }], }); - testBed = await setup(); + testBed = await setup(httpSetup); await act(async () => { await nextTick(); @@ -97,7 +94,7 @@ describe('', () => { * the same form component is indeed shared between the 2 app sections. */ test('should use the same Form component as the "" section', async () => { - testBedPolicyAdd = await setupPolicyAdd(); + testBedPolicyAdd = await setupPolicyAdd(httpSetup); await act(async () => { await nextTick(); @@ -143,27 +140,28 @@ describe('', () => { await nextTick(); }); - const latestRequest = server.requests[server.requests.length - 1]; - const { name, isManagedPolicy, schedule, repository, retention } = POLICY_EDIT; - const expected = { - name, - isManagedPolicy, - schedule, - repository, - config: { - ignoreUnavailable: true, - }, - retention: { - ...retention, - expireAfterValue: Number(EXPIRE_AFTER_VALUE), - expireAfterUnit: EXPIRE_AFTER_UNIT, - }, - snapshotName: editedSnapshotName, - }; - - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual(expected); + expect(httpSetup.put).toHaveBeenLastCalledWith( + `${API_BASE_PATH}policies/${name}`, + expect.objectContaining({ + body: JSON.stringify({ + name, + snapshotName: editedSnapshotName, + schedule, + repository, + config: { + ignoreUnavailable: true, + }, + retention: { + ...retention, + expireAfterUnit: EXPIRE_AFTER_UNIT, + expireAfterValue: Number(EXPIRE_AFTER_VALUE), + }, + isManagedPolicy, + }), + }) + ); }); it('should provide a default time unit value for retention', async () => { @@ -184,25 +182,27 @@ describe('', () => { await nextTick(); }); - const latestRequest = server.requests[server.requests.length - 1]; - const { name, isManagedPolicy, schedule, repository, retention, config, snapshotName } = POLICY_EDIT; - const expected = { - name, - isManagedPolicy, - schedule, - repository, - config, - snapshotName, - retention: { - ...retention, - expireAfterValue: Number(EXPIRE_AFTER_VALUE), - expireAfterUnit: TIME_UNITS.DAY, // default value - }, - }; - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual(expected); + expect(httpSetup.put).toHaveBeenLastCalledWith( + `${API_BASE_PATH}policies/${name}`, + expect.objectContaining({ + body: JSON.stringify({ + name, + snapshotName, + schedule, + repository, + config, + retention: { + ...retention, + expireAfterUnit: TIME_UNITS.DAY, // default value + expireAfterValue: Number(EXPIRE_AFTER_VALUE), + }, + isManagedPolicy, + }), + }) + ); }); }); }); diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts index 85d438fc5f3ae..3a34926272e07 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts @@ -8,6 +8,7 @@ import { act } from 'react-dom/test-utils'; import { INVALID_NAME_CHARS } from '../../public/application/services/validation/validate_repository'; +import { API_BASE_PATH } from '../../common'; import { getRepository } from '../../test/fixtures'; import { RepositoryType } from '../../common/types'; import { setupEnvironment, pageHelpers, nextTick, delay } from './helpers'; @@ -18,18 +19,13 @@ const repositoryTypes = ['fs', 'url', 'source', 'azure', 'gcs', 's3', 'hdfs']; describe('', () => { let testBed: RepositoryAddTestBed; - - const { server, httpRequestsMockHelpers } = setupEnvironment(); - - afterAll(() => { - server.restore(); - }); + const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); describe('on component mount', () => { beforeEach(async () => { httpRequestsMockHelpers.setLoadRepositoryTypesResponse(repositoryTypes); - testBed = await setup(); + testBed = await setup(httpSetup); }); test('should set the correct page title', () => { @@ -65,7 +61,7 @@ describe('', () => { describe('when no repository types are not found', () => { beforeEach(async () => { httpRequestsMockHelpers.setLoadRepositoryTypesResponse([]); - testBed = await setup(); + testBed = await setup(httpSetup); await nextTick(); testBed.component.update(); }); @@ -81,7 +77,7 @@ describe('', () => { describe('when repository types are found', () => { beforeEach(async () => { httpRequestsMockHelpers.setLoadRepositoryTypesResponse(repositoryTypes); - testBed = await setup(); + testBed = await setup(httpSetup); await nextTick(); testBed.component.update(); }); @@ -104,7 +100,7 @@ describe('', () => { beforeEach(async () => { httpRequestsMockHelpers.setLoadRepositoryTypesResponse(repositoryTypes); - testBed = await setup(); + testBed = await setup(httpSetup); await nextTick(); testBed.component.update(); }); @@ -205,7 +201,7 @@ describe('', () => { beforeEach(async () => { httpRequestsMockHelpers.setLoadRepositoryTypesResponse(repositoryTypes); - testBed = await setup(); + testBed = await setup(httpSetup); }); describe('not source only', () => { @@ -231,17 +227,23 @@ describe('', () => { component.update(); - const latestRequest = server.requests[server.requests.length - 1]; - - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ - name: fsRepository.name, - type: fsRepository.type, - settings: { - ...fsRepository.settings, - compress: true, - readonly: true, - }, - }); + expect(httpSetup.put).toHaveBeenLastCalledWith( + `${API_BASE_PATH}repositories`, + expect.objectContaining({ + body: JSON.stringify({ + name: fsRepository.name, + type: fsRepository.type, + settings: { + location: fsRepository.settings.location, + compress: true, + chunkSize: fsRepository.settings.chunkSize, + maxSnapshotBytesPerSec: fsRepository.settings.maxSnapshotBytesPerSec, + maxRestoreBytesPerSec: fsRepository.settings.maxRestoreBytesPerSec, + readonly: true, + }, + }), + }) + ); }); test('should send the correct payload for Azure repository', async () => { @@ -283,17 +285,25 @@ describe('', () => { component.update(); - const latestRequest = server.requests[server.requests.length - 1]; - - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ - name: azureRepository.name, - type: azureRepository.type, - settings: { - ...azureRepository.settings, - compress: false, - readonly: true, - }, - }); + expect(httpSetup.put).toHaveBeenLastCalledWith( + `${API_BASE_PATH}repositories`, + expect.objectContaining({ + body: JSON.stringify({ + name: azureRepository.name, + type: azureRepository.type, + settings: { + client: azureRepository.settings.client, + container: azureRepository.settings.container, + basePath: azureRepository.settings.basePath, + compress: false, + chunkSize: azureRepository.settings.chunkSize, + maxSnapshotBytesPerSec: azureRepository.settings.maxSnapshotBytesPerSec, + maxRestoreBytesPerSec: azureRepository.settings.maxRestoreBytesPerSec, + readonly: true, + }, + }), + }) + ); }); test('should send the correct payload for GCS repository', async () => { @@ -332,17 +342,25 @@ describe('', () => { component.update(); - const latestRequest = server.requests[server.requests.length - 1]; - - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ - name: gcsRepository.name, - type: gcsRepository.type, - settings: { - ...gcsRepository.settings, - compress: false, - readonly: true, - }, - }); + expect(httpSetup.put).toHaveBeenLastCalledWith( + `${API_BASE_PATH}repositories`, + expect.objectContaining({ + body: JSON.stringify({ + name: gcsRepository.name, + type: gcsRepository.type, + settings: { + client: gcsRepository.settings.client, + bucket: gcsRepository.settings.bucket, + basePath: gcsRepository.settings.basePath, + compress: false, + chunkSize: gcsRepository.settings.chunkSize, + maxSnapshotBytesPerSec: gcsRepository.settings.maxSnapshotBytesPerSec, + maxRestoreBytesPerSec: gcsRepository.settings.maxRestoreBytesPerSec, + readonly: true, + }, + }), + }) + ); }); test('should send the correct payload for HDFS repository', async () => { @@ -379,18 +397,24 @@ describe('', () => { component.update(); - const latestRequest = server.requests[server.requests.length - 1]; - - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ - name: hdfsRepository.name, - type: hdfsRepository.type, - settings: { - ...hdfsRepository.settings, - uri: `hdfs://${hdfsRepository.settings.uri}`, - compress: false, - readonly: true, - }, - }); + expect(httpSetup.put).toHaveBeenLastCalledWith( + `${API_BASE_PATH}repositories`, + expect.objectContaining({ + body: JSON.stringify({ + name: hdfsRepository.name, + type: hdfsRepository.type, + settings: { + uri: `hdfs://${hdfsRepository.settings.uri}`, + path: hdfsRepository.settings.path, + compress: false, + chunkSize: hdfsRepository.settings.chunkSize, + maxSnapshotBytesPerSec: hdfsRepository.settings.maxSnapshotBytesPerSec, + maxRestoreBytesPerSec: hdfsRepository.settings.maxRestoreBytesPerSec, + readonly: true, + }, + }), + }) + ); }); test('should send the correct payload for S3 repository', async () => { @@ -431,17 +455,26 @@ describe('', () => { component.update(); - const latestRequest = server.requests[server.requests.length - 1]; - - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ - name: s3Repository.name, - type: s3Repository.type, - settings: { - ...s3Repository.settings, - compress: false, - readonly: true, - }, - }); + expect(httpSetup.put).toHaveBeenLastCalledWith( + `${API_BASE_PATH}repositories`, + expect.objectContaining({ + body: JSON.stringify({ + name: s3Repository.name, + type: s3Repository.type, + settings: { + bucket: s3Repository.settings.bucket, + client: s3Repository.settings.client, + basePath: s3Repository.settings.basePath, + bufferSize: s3Repository.settings.bufferSize, + compress: false, + chunkSize: s3Repository.settings.chunkSize, + maxSnapshotBytesPerSec: s3Repository.settings.maxSnapshotBytesPerSec, + maxRestoreBytesPerSec: s3Repository.settings.maxRestoreBytesPerSec, + readonly: true, + }, + }), + }) + ); }); test('should surface the API errors from the "save" HTTP request', async () => { @@ -457,12 +490,12 @@ describe('', () => { form.toggleEuiSwitch('compressToggle'); const error = { - status: 400, + statusCode: 400, error: 'Bad request', message: 'Repository payload is invalid', }; - httpRequestsMockHelpers.setSaveRepositoryResponse(undefined, { body: error }); + httpRequestsMockHelpers.setSaveRepositoryResponse(undefined, error); await act(async () => { actions.clickSubmitButton(); @@ -496,16 +529,19 @@ describe('', () => { component.update(); - const latestRequest = server.requests[server.requests.length - 1]; - - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ - name: fsRepository.name, - type: 'source', - settings: { - delegateType: fsRepository.type, - location: fsRepository.settings.location, - }, - }); + expect(httpSetup.put).toHaveBeenLastCalledWith( + `${API_BASE_PATH}repositories`, + expect.objectContaining({ + body: JSON.stringify({ + name: fsRepository.name, + type: 'source', + settings: { + delegateType: fsRepository.type, + location: fsRepository.settings.location, + }, + }), + }) + ); }); }); }); diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_edit.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_edit.test.ts index 8adf1e988ff1e..102c0d13a012b 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_edit.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_edit.test.ts @@ -11,7 +11,7 @@ import { setupEnvironment, pageHelpers, nextTick, TestBed, getRandomString } fro import { RepositoryForm } from '../../public/application/components/repository_form'; import { RepositoryEditTestSubjects } from './helpers/repository_edit.helpers'; import { RepositoryAddTestSubjects } from './helpers/repository_add.helpers'; -import { REPOSITORY_EDIT } from './helpers/constant'; +import { REPOSITORY_EDIT, REPOSITORY_NAME } from './helpers/constant'; const { setup } = pageHelpers.repositoryEdit; const { setup: setupRepositoryAdd } = pageHelpers.repositoryAdd; @@ -19,19 +19,15 @@ const { setup: setupRepositoryAdd } = pageHelpers.repositoryAdd; describe('', () => { let testBed: TestBed; let testBedRepositoryAdd: TestBed; - const { server, httpRequestsMockHelpers } = setupEnvironment(); - - afterAll(() => { - server.restore(); - }); + const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); describe('on component mount', () => { beforeEach(async () => { - httpRequestsMockHelpers.setGetRepositoryResponse({ + httpRequestsMockHelpers.setGetRepositoryResponse(REPOSITORY_EDIT.name, { repository: REPOSITORY_EDIT, snapshots: { count: 0 }, }); - testBed = await setup(); + testBed = await setup(httpSetup); await act(async () => { await nextTick(); @@ -54,7 +50,7 @@ describe('', () => { test('should use the same Form component as the "" section', async () => { httpRequestsMockHelpers.setLoadRepositoryTypesResponse(['fs', 'url']); - testBedRepositoryAdd = await setupRepositoryAdd(); + testBedRepositoryAdd = await setupRepositoryAdd(httpSetup); const formEdit = testBed.component.find(RepositoryForm); const formAdd = testBedRepositoryAdd.component.find(RepositoryForm); @@ -66,11 +62,11 @@ describe('', () => { describe('should populate the correct values', () => { const mountComponentWithMock = async (repository: any) => { - httpRequestsMockHelpers.setGetRepositoryResponse({ + httpRequestsMockHelpers.setGetRepositoryResponse(REPOSITORY_NAME, { repository: { name: getRandomString(), ...repository }, snapshots: { count: 0 }, }); - testBed = await setup(); + testBed = await setup(httpSetup); await act(async () => { await nextTick(); diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/restore_snapshot.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/restore_snapshot.test.ts index 2d8c734af3605..1bd9898f9f1b2 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/restore_snapshot.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/restore_snapshot.test.ts @@ -6,8 +6,10 @@ */ import { act } from 'react-dom/test-utils'; +import { API_BASE_PATH } from '../../common'; import { pageHelpers, setupEnvironment } from './helpers'; import { RestoreSnapshotTestBed } from './helpers/restore_snapshot.helpers'; +import { REPOSITORY_NAME, SNAPSHOT_NAME } from './helpers/constant'; import * as fixtures from '../../test/fixtures'; const { @@ -15,19 +17,19 @@ const { } = pageHelpers; describe('', () => { - const { server, httpRequestsMockHelpers } = setupEnvironment(); + const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); let testBed: RestoreSnapshotTestBed; - afterAll(() => { - server.restore(); - }); - describe('wizard navigation', () => { beforeEach(async () => { - httpRequestsMockHelpers.setGetSnapshotResponse(fixtures.getSnapshot()); + httpRequestsMockHelpers.setGetSnapshotResponse( + REPOSITORY_NAME, + SNAPSHOT_NAME, + fixtures.getSnapshot() + ); await act(async () => { - testBed = await setup(); + testBed = await setup(httpSetup); }); testBed.component.update(); @@ -44,10 +46,14 @@ describe('', () => { describe('with data streams', () => { beforeEach(async () => { - httpRequestsMockHelpers.setGetSnapshotResponse(fixtures.getSnapshot()); + httpRequestsMockHelpers.setGetSnapshotResponse( + REPOSITORY_NAME, + SNAPSHOT_NAME, + fixtures.getSnapshot() + ); await act(async () => { - testBed = await setup(); + testBed = await setup(httpSetup); }); testBed.component.update(); @@ -61,9 +67,13 @@ describe('', () => { describe('without data streams', () => { beforeEach(async () => { - httpRequestsMockHelpers.setGetSnapshotResponse(fixtures.getSnapshot({ totalDataStreams: 0 })); + httpRequestsMockHelpers.setGetSnapshotResponse( + REPOSITORY_NAME, + SNAPSHOT_NAME, + fixtures.getSnapshot({ totalDataStreams: 0 }) + ); await act(async () => { - testBed = await setup(); + testBed = await setup(httpSetup); }); testBed.component.update(); @@ -77,9 +87,13 @@ describe('', () => { describe('global state', () => { beforeEach(async () => { - httpRequestsMockHelpers.setGetSnapshotResponse(fixtures.getSnapshot()); + httpRequestsMockHelpers.setGetSnapshotResponse( + REPOSITORY_NAME, + SNAPSHOT_NAME, + fixtures.getSnapshot() + ); await act(async () => { - testBed = await setup(); + testBed = await setup(httpSetup); }); testBed.component.update(); @@ -100,11 +114,14 @@ describe('', () => { // the form controls and asserting that the correct payload is sent to the API. describe('include aliases', () => { beforeEach(async () => { - httpRequestsMockHelpers.setGetSnapshotResponse(fixtures.getSnapshot()); - httpRequestsMockHelpers.setRestoreSnapshotResponse({}); + httpRequestsMockHelpers.setGetSnapshotResponse( + REPOSITORY_NAME, + SNAPSHOT_NAME, + fixtures.getSnapshot() + ); await act(async () => { - testBed = await setup(); + testBed = await setup(httpSetup); }); testBed.component.update(); @@ -116,9 +133,14 @@ describe('', () => { actions.goToStep(3); await actions.clickRestore(); - const expectedPayload = { includeAliases: false }; - const latestRequest = server.requests[server.requests.length - 1]; - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual(expectedPayload); + expect(httpSetup.post).toHaveBeenLastCalledWith( + `${API_BASE_PATH}restore/${REPOSITORY_NAME}/${SNAPSHOT_NAME}`, + expect.objectContaining({ + body: JSON.stringify({ + includeAliases: false, + }), + }) + ); }); }); }); From 5d9aaeb4680bce10ad4c106eb74371b4d78393af Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Mon, 4 Apr 2022 12:43:44 +0200 Subject: [PATCH 54/65] [Osquery] Fix small issues in alerts (#128676) --- .../cypress/integration/all/alerts.spec.ts | 9 +++ .../integration/all/live_query.spec.ts | 9 ++- .../cypress/integration/roles/reader.spec.ts | 5 ++ .../osquery/cypress/screens/live_query.ts | 1 + .../osquery/cypress/tasks/live_query.ts | 1 + .../osquery/public/agents/agents_table.tsx | 68 +++++++++---------- .../public/components/layouts/header.tsx | 7 +- .../public/components/layouts/with_header.tsx | 6 +- .../public/components/osquery_icon/index.tsx | 19 ++---- .../public/live_queries/form/index.tsx | 17 +++-- .../osquery/public/live_queries/index.tsx | 3 + .../osquery/public/packs/form/index.tsx | 8 ++- .../queries/ecs_mapping_editor_field.tsx | 60 +++++++++++----- .../osquery/public/results/results_table.tsx | 6 +- .../public/routes/live_queries/index.tsx | 3 +- .../public/routes/packs/edit/index.tsx | 32 ++++++++- .../public/routes/saved_queries/edit/tabs.tsx | 5 +- .../form/use_saved_query_form.tsx | 2 +- .../saved_queries/saved_query_flyout.tsx | 9 ++- .../osquery_action/index.tsx | 13 ++-- .../components/osquery/osquery_flyout.tsx | 2 +- 21 files changed, 196 insertions(+), 89 deletions(-) diff --git a/x-pack/plugins/osquery/cypress/integration/all/alerts.spec.ts b/x-pack/plugins/osquery/cypress/integration/all/alerts.spec.ts index 153fd5d58791e..90492838bd2c3 100644 --- a/x-pack/plugins/osquery/cypress/integration/all/alerts.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/all/alerts.spec.ts @@ -17,6 +17,7 @@ import { import { preparePack } from '../../tasks/packs'; import { closeModalIfVisible } from '../../tasks/integrations'; import { navigateTo } from '../../tasks/navigation'; +import { RESULTS_TABLE, RESULTS_TABLE_BUTTON } from '../../screens/live_query'; describe('Alert Event Details', () => { before(() => { @@ -58,8 +59,16 @@ describe('Alert Event Details', () => { cy.getBySel('expand-event').first().click(); cy.getBySel('take-action-dropdown-btn').click(); cy.getBySel('osquery-action-item').click(); + cy.contains('1 agent selected.'); inputQuery('select * from uptime;'); submitQuery(); checkResults(); + + cy.getBySel(RESULTS_TABLE).within(() => { + cy.getBySel(RESULTS_TABLE_BUTTON).should('not.exist'); + }); + cy.contains('Save for later').click(); + cy.contains('Save query'); + cy.contains(/^Save$/); }); }); diff --git a/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts b/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts index d6af17596d89a..c33763bb2bff4 100644 --- a/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts @@ -15,7 +15,11 @@ import { typeInECSFieldInput, typeInOsqueryFieldInput, } from '../../tasks/live_query'; -import { RESULTS_TABLE_CELL_WRRAPER } from '../../screens/live_query'; +import { + RESULTS_TABLE, + RESULTS_TABLE_BUTTON, + RESULTS_TABLE_CELL_WRRAPER, +} from '../../screens/live_query'; import { getAdvancedButton } from '../../screens/integrations'; describe('ALL - Live Query', () => { @@ -48,6 +52,9 @@ describe('ALL - Live Query', () => { submitQuery(); checkResults(); + cy.getBySel(RESULTS_TABLE).within(() => { + cy.getBySel(RESULTS_TABLE_BUTTON).should('exist'); + }); cy.react(RESULTS_TABLE_CELL_WRRAPER, { props: { id: 'message', index: 1 }, }); diff --git a/x-pack/plugins/osquery/cypress/integration/roles/reader.spec.ts b/x-pack/plugins/osquery/cypress/integration/roles/reader.spec.ts index d3a00f970322b..ddbca76efcf16 100644 --- a/x-pack/plugins/osquery/cypress/integration/roles/reader.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/roles/reader.spec.ts @@ -46,6 +46,11 @@ describe('Reader - only READ', () => { cy.contains('Update query').should('not.exist'); cy.contains(`Delete query`).should('not.exist'); }); + it('should not be able to enter live queries with just read and no run saved queries', () => { + navigateTo('/app/osquery/live_queries/new'); + cy.waitForReact(1000); + cy.contains('Permission denied'); + }); it('should not be able to play in live queries history', () => { navigateTo('/app/osquery/live_queries'); cy.waitForReact(1000); diff --git a/x-pack/plugins/osquery/cypress/screens/live_query.ts b/x-pack/plugins/osquery/cypress/screens/live_query.ts index 54c19fe508705..ce29edc2c9187 100644 --- a/x-pack/plugins/osquery/cypress/screens/live_query.ts +++ b/x-pack/plugins/osquery/cypress/screens/live_query.ts @@ -10,6 +10,7 @@ export const ALL_AGENTS_OPTION = '[title="All agents"]'; export const LIVE_QUERY_EDITOR = '#osquery_editor'; export const SUBMIT_BUTTON = '#submit-button'; +export const RESULTS_TABLE = 'osqueryResultsTable'; export const RESULTS_TABLE_BUTTON = 'dataGridFullScreenButton'; export const RESULTS_TABLE_CELL_WRRAPER = 'EuiDataGridHeaderCellWrapper'; export const getSavedQueriesDropdown = () => diff --git a/x-pack/plugins/osquery/cypress/tasks/live_query.ts b/x-pack/plugins/osquery/cypress/tasks/live_query.ts index 2e199ae453f1b..b3006a3d1074a 100644 --- a/x-pack/plugins/osquery/cypress/tasks/live_query.ts +++ b/x-pack/plugins/osquery/cypress/tasks/live_query.ts @@ -16,6 +16,7 @@ export const selectAllAgents = () => { cy.react('EuiComboBox', { props: { placeholder: 'Select agents or groups' } }).type( '{downArrow}{enter}{esc}' ); + cy.contains('1 agent selected.'); }; export const inputQuery = (query: string) => cy.get(LIVE_QUERY_EDITOR).type(query); diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx index 105518537384f..fac74086670c2 100644 --- a/x-pack/plugins/osquery/public/agents/agents_table.tsx +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -83,6 +83,37 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh const [numAgentsSelected, setNumAgentsSelected] = useState(0); const defaultValueInitialized = useRef(false); + const onSelection = useCallback( + (selection: GroupOption[]) => { + // TODO?: optimize this by making the selection computation incremental + const { + newAgentSelection, + selectedAgents, + selectedGroups, + }: { + newAgentSelection: AgentSelection; + selectedAgents: AgentOptionValue[]; + selectedGroups: SelectedGroups; + } = generateAgentSelection(selection); + if (newAgentSelection.allAgentsSelected) { + setNumAgentsSelected(totalNumAgents); + } else { + const checkAgent = generateAgentCheck(selectedGroups); + setNumAgentsSelected( + // filter out all the agents counted by selected policies and platforms + selectedAgents.filter(checkAgent).length + + // add the number of agents added via policy and platform groups + getNumAgentsInGrouping(selectedGroups) - + // subtract the number of agents double counted by policy/platform selections + getNumOverlapped(selectedGroups, groups.overlap) + ); + } + onChange(newAgentSelection); + setSelectedOptions(selection); + }, + [groups, onChange, totalNumAgents] + ); + useEffect(() => { const handleSelectedOptions = (selection: string[], label: string) => { const agentOptions = find(['label', label], options); @@ -95,7 +126,7 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh }); if (defaultOptions?.length) { - setSelectedOptions(defaultOptions); + onSelection(defaultOptions); defaultValueInitialized.current = true; } } @@ -105,7 +136,7 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh const allAgentsOptions = find(['label', ALL_AGENTS_LABEL], options); if (allAgentsOptions?.options) { - setSelectedOptions(allAgentsOptions.options); + onSelection(allAgentsOptions.options); defaultValueInitialized.current = true; } } @@ -118,7 +149,7 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh handleSelectedOptions(agentSelection.agents, AGENT_SELECTION_LABEL); } } - }, [agentSelection, options, selectedOptions]); + }, [agentSelection, onSelection, options, selectedOptions]); useEffect(() => { if (agentsFetched && groupsFetched) { @@ -142,37 +173,6 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh grouper, ]); - const onSelection = useCallback( - (selection: GroupOption[]) => { - // TODO?: optimize this by making the selection computation incremental - const { - newAgentSelection, - selectedAgents, - selectedGroups, - }: { - newAgentSelection: AgentSelection; - selectedAgents: AgentOptionValue[]; - selectedGroups: SelectedGroups; - } = generateAgentSelection(selection); - if (newAgentSelection.allAgentsSelected) { - setNumAgentsSelected(totalNumAgents); - } else { - const checkAgent = generateAgentCheck(selectedGroups); - setNumAgentsSelected( - // filter out all the agents counted by selected policies and platforms - selectedAgents.filter(checkAgent).length + - // add the number of agents added via policy and platform groups - getNumAgentsInGrouping(selectedGroups) - - // subtract the number of agents double counted by policy/platform selections - getNumOverlapped(selectedGroups, groups.overlap) - ); - } - onChange(newAgentSelection); - setSelectedOptions(selection); - }, - [groups, onChange, totalNumAgents] - ); - const renderOption = useCallback((option, searchVal, contentClassName) => { const { label, value } = option; return value?.groupType === AGENT_GROUP_KEY.Agent ? ( diff --git a/x-pack/plugins/osquery/public/components/layouts/header.tsx b/x-pack/plugins/osquery/public/components/layouts/header.tsx index 5e8ed0923a0d9..6049e1d7bacb0 100644 --- a/x-pack/plugins/osquery/public/components/layouts/header.tsx +++ b/x-pack/plugins/osquery/public/components/layouts/header.tsx @@ -35,6 +35,7 @@ const Tabs = styled(EuiTabs)` `; export interface HeaderProps { + children?: React.ReactNode; maxWidth?: number; leftColumn?: JSX.Element; rightColumn?: JSX.Element; @@ -55,7 +56,8 @@ const HeaderColumns: React.FC> = memo( HeaderColumns.displayName = 'HeaderColumns'; -export const Header: React.FC = ({ +const HeaderComponent: React.FC = ({ + children, leftColumn, rightColumn, rightColumnGrow, @@ -71,6 +73,7 @@ export const Header: React.FC = ({ rightColumn={rightColumn} rightColumnGrow={rightColumnGrow} /> + {children} {tabs ? ( @@ -92,3 +95,5 @@ export const Header: React.FC = ({ ); + +export const Header = React.memo(HeaderComponent); diff --git a/x-pack/plugins/osquery/public/components/layouts/with_header.tsx b/x-pack/plugins/osquery/public/components/layouts/with_header.tsx index a620194b37877..97db914fedcf2 100644 --- a/x-pack/plugins/osquery/public/components/layouts/with_header.tsx +++ b/x-pack/plugins/osquery/public/components/layouts/with_header.tsx @@ -16,12 +16,14 @@ export interface WithHeaderLayoutProps extends HeaderProps { restrictHeaderWidth?: number; 'data-test-subj'?: string; children?: React.ReactNode; + headerChildren?: React.ReactNode; } export const WithHeaderLayout: React.FC = ({ restrictWidth, restrictHeaderWidth, children, + headerChildren, 'data-test-subj': dataTestSubj, ...rest }) => ( @@ -30,7 +32,9 @@ export const WithHeaderLayout: React.FC = ({ maxWidth={restrictHeaderWidth} data-test-subj={dataTestSubj ? `${dataTestSubj}_header` : undefined} {...rest} - /> + > + {headerChildren} + ; -const OsqueryIconComponent: React.FC = (props) => { - const [Icon, setIcon] = useState(null); - - // FIXME: This is a hack to force the icon to be loaded asynchronously. - useEffect(() => { - const interval = setInterval(() => { - setIcon(); - }, 0); - - return () => clearInterval(interval); - }, [props, setIcon]); - - return Icon; -}; +const OsqueryIconComponent: React.FC = (props) => ( + +); export const OsqueryIcon = React.memo(OsqueryIconComponent); diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index 9164266d6a8c5..a6481d420f4d1 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -62,6 +62,7 @@ interface LiveQueryFormProps { ecsMappingField?: boolean; formType?: FormType; enabled?: boolean; + hideFullscreen?: true; } const LiveQueryFormComponent: React.FC = ({ @@ -72,6 +73,7 @@ const LiveQueryFormComponent: React.FC = ({ ecsMappingField = true, formType = 'steps', enabled = true, + hideFullscreen, }) => { const ecsFieldRef = useRef(); const permissions = useKibana().services.application.capabilities.osquery; @@ -146,7 +148,7 @@ const LiveQueryFormComponent: React.FC = ({ onSubmit: async (formData, isValid) => { const ecsFieldValue = await ecsFieldRef?.current?.validate(); - if (isValid) { + if (isValid && !!ecsFieldValue) { try { await mutateAsync( pickBy( @@ -268,9 +270,9 @@ const LiveQueryFormComponent: React.FC = ({ const ecsFieldProps = useMemo( () => ({ - isDisabled: !permissions.writeSavedQueries, + isDisabled: !permissions.writeLiveQueries, }), - [permissions.writeSavedQueries] + [permissions.writeLiveQueries] ); const isSavedQueryDisabled = useMemo( @@ -388,9 +390,14 @@ const LiveQueryFormComponent: React.FC = ({ const resultsStepContent = useMemo( () => actionId ? ( - + ) : null, - [actionId, agentIds, data?.actions] + [actionId, agentIds, data?.actions, hideFullscreen] ); const formSteps: EuiContainedStepProps[] = useMemo( diff --git a/x-pack/plugins/osquery/public/live_queries/index.tsx b/x-pack/plugins/osquery/public/live_queries/index.tsx index bf2186c1a3e50..fdde03d6076a6 100644 --- a/x-pack/plugins/osquery/public/live_queries/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/index.tsx @@ -28,6 +28,7 @@ interface LiveQueryProps { ecsMappingField?: boolean; enabled?: boolean; formType?: 'steps' | 'simple'; + hideFullscreen?: true; } const LiveQueryComponent: React.FC = ({ @@ -44,6 +45,7 @@ const LiveQueryComponent: React.FC = ({ ecsMappingField, formType, enabled, + hideFullscreen, }) => { const { data: hasActionResultsPrivileges, isLoading } = useActionResultsPrivileges(); @@ -113,6 +115,7 @@ const LiveQueryComponent: React.FC = ({ onSuccess={onSuccess} formType={formType} enabled={enabled} + hideFullscreen={hideFullscreen} /> ); }; diff --git a/x-pack/plugins/osquery/public/packs/form/index.tsx b/x-pack/plugins/osquery/public/packs/form/index.tsx index f327239560345..41f51181aa304 100644 --- a/x-pack/plugins/osquery/public/packs/form/index.tsx +++ b/x-pack/plugins/osquery/public/packs/form/index.tsx @@ -48,10 +48,14 @@ const CommonUseField = getUseField({ component: Field }); interface PackFormProps { defaultValue?: OsqueryManagerPackagePolicy; editMode?: boolean; + isReadOnly?: boolean; } -const PackFormComponent: React.FC = ({ defaultValue, editMode = false }) => { - const isReadOnly = !!defaultValue?.read_only; +const PackFormComponent: React.FC = ({ + defaultValue, + editMode = false, + isReadOnly = false, +}) => { const [showConfirmationModal, setShowConfirmationModal] = useState(false); const handleHideConfirmationModal = useCallback(() => setShowConfirmationModal(false), []); diff --git a/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx b/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx index df8e083737559..b5bfe1a234d91 100644 --- a/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx +++ b/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx @@ -16,6 +16,7 @@ import { isArray, map, reduce, + trim, get, } from 'lodash'; import React, { @@ -127,6 +128,10 @@ const StyledFieldSpan = styled.span` padding-bottom: 0 !important; `; +const DescriptionWrapper = styled(EuiFlexItem)` + overflow: hidden; +`; + // align the icon to the inputs const StyledSemicolonWrapper = styled.div` margin-top: 8px; @@ -181,9 +186,9 @@ const ECSComboboxFieldComponent: React.FC = ({ const renderOption = useCallback( (option, searchValue, contentClassName) => ( { @@ -197,11 +202,11 @@ const ECSComboboxFieldComponent: React.FC = ({ - - + + {option.value.description} - - + + ), [] @@ -344,9 +349,9 @@ const OsqueryColumnFieldComponent: React.FC = ({ const renderOsqueryOption = useCallback( (option, searchValue, contentClassName) => ( @@ -354,11 +359,11 @@ const OsqueryColumnFieldComponent: React.FC = ({ - - + + {option.value.description} - - + + ), [] @@ -367,7 +372,11 @@ const OsqueryColumnFieldComponent: React.FC = ({ const handleChange = useCallback( (newSelectedOptions) => { setSelected(newSelectedOptions); - setValue(newSelectedOptions[0]?.label ?? ''); + setValue( + isArray(newSelectedOptions) + ? map(newSelectedOptions, 'label') + : newSelectedOptions[0]?.label ?? '' + ); }, [setValue, setSelected] ); @@ -384,16 +393,20 @@ const OsqueryColumnFieldComponent: React.FC = ({ const handleCreateOption = useCallback( (newOption: string) => { + const trimmedNewOption = trim(newOption); + + if (!trimmedNewOption.length) return; + if (euiFieldProps.singleSelection === false) { - setValue([newOption]); + setValue([trimmedNewOption]); if (resultValue.value.length) { - setValue([...castArray(resultValue.value), newOption]); + setValue([...castArray(resultValue.value), trimmedNewOption]); } else { - setValue([newOption]); + setValue([trimmedNewOption]); } inputRef.current?.blur(); } else { - setValue(newOption); + setValue(trimmedNewOption); } }, [euiFieldProps.singleSelection, resultValue.value, setValue] @@ -416,6 +429,17 @@ const OsqueryColumnFieldComponent: React.FC = ({ [onTypeChange, resultType.value] ); + useEffect(() => { + if (euiFieldProps?.singleSelection && isArray(resultValue.value)) { + setValue(resultValue.value.join(' ')); + } + + if (!euiFieldProps?.singleSelection && !isArray(resultValue.value)) { + setValue(resultValue.value.length ? [resultValue.value] : []); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [euiFieldProps?.singleSelection, setValue]); + useEffect(() => { setSelected(() => { if (!resultValue.value.length) return []; @@ -705,7 +729,7 @@ export const ECSMappingEditorForm = forwardRef = ({ @@ -53,6 +54,7 @@ const ResultsTableComponent: React.FC = ({ agentIds, startDate, endDate, + hideFullscreen, }) => { const [isLive, setIsLive] = useState(true); const { data: hasActionResultsPrivileges } = useActionResultsPrivileges(); @@ -307,6 +309,7 @@ const ResultsTableComponent: React.FC = ({ const toolbarVisibility = useMemo( () => ({ showDisplaySelector: false, + showFullScreenSelector: !hideFullscreen, additionalControls: ( <> = ({ ), }), - [actionId, endDate, startDate] + [actionId, endDate, startDate, hideFullscreen] ); useEffect( @@ -368,6 +371,7 @@ const ResultsTableComponent: React.FC = ({ // @ts-expect-error update types { return ( - {permissions.runSavedQueries || permissions.writeLiveQueries ? ( + {(permissions.runSavedQueries && permissions.readSavedQueries) || + permissions.writeLiveQueries ? ( ) : ( diff --git a/x-pack/plugins/osquery/public/routes/packs/edit/index.tsx b/x-pack/plugins/osquery/public/routes/packs/edit/index.tsx index 341312a45ae8a..64d3c4699708f 100644 --- a/x-pack/plugins/osquery/public/routes/packs/edit/index.tsx +++ b/x-pack/plugins/osquery/public/routes/packs/edit/index.tsx @@ -8,10 +8,12 @@ import { EuiButton, EuiButtonEmpty, + EuiCallOut, EuiConfirmModal, EuiFlexGroup, EuiFlexItem, EuiLoadingContent, + EuiSpacer, EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -33,6 +35,7 @@ const EditPackPageComponent = () => { const { isLoading, data } = usePack({ packId }); const deletePackMutation = useDeletePack({ packId, withRedirect: true }); + const isReadOnly = useMemo(() => !!data?.read_only, [data]); useBreadcrumbs('pack_edit', { packId: data?.id ?? '', @@ -97,11 +100,36 @@ const EditPackPageComponent = () => { [handleDeleteClick] ); + const HeaderContent = useMemo( + () => + isReadOnly ? ( + <> + + + + + + ) : null, + [isReadOnly] + ); + if (isLoading) return null; return ( - - {!data ? : } + + {!data ? ( + + ) : ( + + )} {isDeleteModalVisible ? ( = ({ @@ -24,6 +25,7 @@ const ResultTabsComponent: React.FC = ({ agentIds, endDate, startDate, + hideFullscreen, }) => { const tabs = useMemo( () => [ @@ -38,6 +40,7 @@ const ResultTabsComponent: React.FC = ({ agentIds={agentIds} startDate={startDate} endDate={endDate} + hideFullscreen={hideFullscreen} /> ), @@ -57,7 +60,7 @@ const ResultTabsComponent: React.FC = ({ ), }, ], - [actionId, agentIds, endDate, startDate] + [actionId, agentIds, endDate, startDate, hideFullscreen] ); return ( diff --git a/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx b/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx index b3e0cab60851e..39e2f6ca51cbc 100644 --- a/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx @@ -50,7 +50,7 @@ export const useSavedQueryForm = ({ onSubmit: async (formData, isValid) => { const ecsFieldValue = await savedQueryFormRef?.current?.validateEcsMapping(); - if (isValid) { + if (isValid && !!ecsFieldValue) { try { await handleSubmit({ ...formData, diff --git a/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx b/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx index 7339ff20093fc..69fe9442a1cbb 100644 --- a/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx @@ -48,7 +48,14 @@ const SavedQueryFlyoutComponent: React.FC = ({ defaultValue return ( - +

diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx index 0ffd1cb6cc8a1..a0ee8bf314e57 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx @@ -20,9 +20,14 @@ import { useIsOsqueryAvailable } from './use_is_osquery_available'; interface OsqueryActionProps { agentId?: string; formType: 'steps' | 'simple'; + hideFullscreen?: true; } -const OsqueryActionComponent: React.FC = ({ agentId, formType = 'simple' }) => { +const OsqueryActionComponent: React.FC = ({ + agentId, + formType = 'simple', + hideFullscreen, +}) => { const permissions = useKibana().services.application.capabilities.osquery; const emptyPrompt = useMemo( @@ -134,18 +139,18 @@ const OsqueryActionComponent: React.FC = ({ agentId, formTyp ); } - return ; + return ; }; const OsqueryAction = React.memo(OsqueryActionComponent); // @ts-expect-error update types -const OsqueryActionWrapperComponent = ({ services, agentId, formType }) => ( +const OsqueryActionWrapperComponent = ({ services, agentId, formType, hideFullscreen }) => ( - + diff --git a/x-pack/plugins/security_solution/public/detections/components/osquery/osquery_flyout.tsx b/x-pack/plugins/security_solution/public/detections/components/osquery/osquery_flyout.tsx index 3262fc36abf75..0001c2966c1bc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/osquery/osquery_flyout.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/osquery/osquery_flyout.tsx @@ -45,7 +45,7 @@ export const OsqueryFlyout: React.FC = ({ agentId, onClose } - + From 9b3abe5d2e5c4a5205f3780d54050db857f3266f Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 4 Apr 2022 12:45:25 +0200 Subject: [PATCH 55/65] [Vega] Add back setMapView function (#128914) --- .../vega_view/vega_map_view/view.test.ts | 111 ++++++++++++++---- .../public/vega_view/vega_map_view/view.ts | 108 ++++++++++++++++- 2 files changed, 195 insertions(+), 24 deletions(-) diff --git a/src/plugins/vis_types/vega/public/vega_view/vega_map_view/view.test.ts b/src/plugins/vis_types/vega/public/vega_view/vega_map_view/view.test.ts index a09e92fe7dd80..c0453f470de4b 100644 --- a/src/plugins/vis_types/vega/public/vega_view/vega_map_view/view.test.ts +++ b/src/plugins/vis_types/vega/public/vega_view/vega_map_view/view.test.ts @@ -31,30 +31,43 @@ import { initVegaLayer, initTmsRasterLayer } from './layers'; import { mapboxgl } from '@kbn/mapbox-gl'; -jest.mock('@kbn/mapbox-gl', () => ({ - mapboxgl: { - setRTLTextPlugin: jest.fn(), - Map: jest.fn().mockImplementation(() => ({ - getLayer: () => '', - removeLayer: jest.fn(), - once: (eventName: string, handler: Function) => handler(), - remove: () => jest.fn(), - getCanvas: () => ({ clientWidth: 512, clientHeight: 512 }), - getCenter: () => ({ lat: 20, lng: 20 }), - getZoom: () => 3, - addControl: jest.fn(), - addLayer: jest.fn(), - dragRotate: { - disable: jest.fn(), +jest.mock('@kbn/mapbox-gl', () => { + const zoomTo = jest.fn(); + const setCenter = jest.fn(); + const fitBounds = jest.fn(); + return { + mapboxgl: { + mocks: { + zoomTo, + setCenter, + fitBounds, }, - touchZoomRotate: { - disableRotation: jest.fn(), - }, - })), - MapboxOptions: jest.fn(), - NavigationControl: jest.fn(), - }, -})); + setRTLTextPlugin: jest.fn(), + Map: jest.fn().mockImplementation(() => ({ + getLayer: () => '', + removeLayer: jest.fn(), + once: (eventName: string, handler: Function) => handler(), + remove: () => jest.fn(), + getCanvas: () => ({ clientWidth: 512, clientHeight: 512 }), + getCenter: () => ({ lat: 20, lng: 20 }), + getZoom: () => 3, + zoomTo, + setCenter, + fitBounds, + addControl: jest.fn(), + addLayer: jest.fn(), + dragRotate: { + disable: jest.fn(), + }, + touchZoomRotate: { + disableRotation: jest.fn(), + }, + })), + MapboxOptions: jest.fn(), + NavigationControl: jest.fn(), + }, + }; +}); jest.mock('./layers', () => ({ initVegaLayer: jest.fn(), @@ -206,5 +219,57 @@ describe('vega_map_view/view', () => { expect(mapboxgl.NavigationControl).toHaveBeenCalled(); }); + + describe('setMapView', () => { + let vegaMapView: VegaMapView; + beforeEach(async () => { + vegaMapView = await createVegaMapView(); + await vegaMapView.init(); + mapboxgl.mocks.setCenter.mockReset(); + mapboxgl.mocks.zoomTo.mockReset(); + mapboxgl.mocks.fitBounds.mockReset(); + }); + + test('should set just lat lng', async () => { + vegaMapView.setMapViewHandler(1, 2); + expect(mapboxgl.mocks.setCenter).toHaveBeenCalledWith({ lat: 1, lng: 2 }); + }); + + test('should set just lng lat via array', async () => { + vegaMapView.setMapViewHandler([1, 2]); + expect(mapboxgl.mocks.setCenter).toHaveBeenCalledWith({ lat: 2, lng: 1 }); + }); + + test('should set lat lng and zoom', async () => { + vegaMapView.setMapViewHandler(1, 2, 6); + expect(mapboxgl.mocks.setCenter).toHaveBeenCalledWith({ lat: 1, lng: 2 }); + expect(mapboxgl.mocks.zoomTo).toHaveBeenCalledWith(6); + }); + + test('should set bounds', async () => { + vegaMapView.setMapViewHandler([ + [1, 2], + [6, 7], + ]); + expect(mapboxgl.mocks.fitBounds).toHaveBeenCalledWith([ + { lat: 2, lng: 1 }, + { lat: 7, lng: 6 }, + ]); + }); + + test('should throw on invalid input', async () => { + expect(() => { + vegaMapView.setMapViewHandler(undefined); + }).toThrow(); + + expect(() => { + vegaMapView.setMapViewHandler(['a', 'b'] as unknown as [number, number]); + }).toThrow(); + + expect(() => { + vegaMapView.setMapViewHandler([1, 2, 3, 4, 5] as unknown as [number, number]); + }).toThrow(); + }); + }); }); }); diff --git a/src/plugins/vis_types/vega/public/vega_view/vega_map_view/view.ts b/src/plugins/vis_types/vega/public/vega_view/vega_map_view/view.ts index fe8d85a011442..2050e0271d03f 100644 --- a/src/plugins/vis_types/vega/public/vega_view/vega_map_view/view.ts +++ b/src/plugins/vis_types/vega/public/vega_view/vega_map_view/view.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import type { Map, Style, MapboxOptions } from '@kbn/mapbox-gl'; -import { View, parse } from 'vega'; +import { View, parse, expressionFunction } from 'vega'; import { mapboxgl } from '@kbn/mapbox-gl'; @@ -45,6 +45,30 @@ async function updateVegaView(mapBoxInstance: Map, vegaView: View) { } } +type SetMapViewArgs = + | [number, number, number] + | [number, number] + | [[number, number], number] + | [[number, number]] + | [[[number, number], [number, number]]]; + +expressionFunction( + 'setMapView', + function handlerFwd( + this: { + context: { dataflow: { _kibanaView: VegaMapView; runAfter: (fn: () => void) => void } }; + }, + ...args: SetMapViewArgs + ) { + const view = this.context.dataflow; + if (!('setMapViewHandler' in view._kibanaView)) { + // not a map view, don't do anything + return; + } + view.runAfter(() => view._kibanaView.setMapViewHandler(...args)); + } +); + export class VegaMapView extends VegaBaseView { private mapBoxInstance?: Map; @@ -200,4 +224,86 @@ export class VegaMapView extends VegaBaseView { protected async onViewContainerResize() { this.mapBoxInstance?.resize(); } + + public setMapViewHandler(...args: SetMapViewArgs) { + if (!this.mapBoxInstance) { + return; + } + function throwError() { + throw new Error( + i18n.translate('visTypeVega.visualization.setMapViewErrorMessage', { + defaultMessage: + 'Unexpected setMapView() parameters. It could be called with a bounding box setMapView([[longitude1,latitude1],[longitude2,latitude2]]), or it could be the center point setMapView([longitude, latitude], optional_zoom), or it can be used as setMapView(latitude, longitude, optional_zoom)', + }) + ); + } + + function checkArray( + val: number | [number, number] | [[number, number], [number, number]] + ): [number, number] { + if ( + !Array.isArray(val) || + val.length !== 2 || + typeof val[0] !== 'number' || + typeof val[1] !== 'number' + ) { + throwError(); + } + return val as [number, number]; + } + + let lng: number | undefined; + let lat: number | undefined; + let zoom: number | undefined; + switch (args.length) { + default: + throwError(); + break; + case 1: { + const arg = args[0]; + if ( + Array.isArray(arg) && + arg.length === 2 && + Array.isArray(arg[0]) && + Array.isArray(arg[1]) + ) { + // called with a bounding box, need to reverse order + const [lng1, lat1] = checkArray(arg[0]); + const [lng2, lat2] = checkArray(arg[1]); + this.mapBoxInstance.fitBounds([ + { lat: lat1, lng: lng1 }, + { lat: lat2, lng: lng2 }, + ]); + } else { + // called with a center point and no zoom + [lng, lat] = checkArray(arg); + } + break; + } + case 2: + if (Array.isArray(args[0])) { + [lng, lat] = checkArray(args[0]); + zoom = args[1]; + } else { + [lat, lng] = args; + } + break; + case 3: + [lat, lng, zoom] = args; + break; + } + + if (lat !== undefined && lng !== undefined) { + if (typeof lat !== 'number' || typeof lng !== 'number') { + throwError(); + } + if (zoom !== undefined && typeof zoom !== 'number') { + throwError(); + } + this.mapBoxInstance.setCenter({ lat, lng }); + if (zoom !== undefined) { + this.mapBoxInstance.zoomTo(zoom); + } + } + } } From 9e1fd5e17d7a3c646b3cf521cbc4d275e37aa69f Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Mon, 4 Apr 2022 12:50:06 +0200 Subject: [PATCH 56/65] [Security Solutions] Move authentication UI and hooks to 'public/common' (#128924) * Move authentication table, server, and hooks to 'public/common' --- .../security_solution/hosts/index.ts | 3 - .../security_solution/index.ts | 15 +- .../matrix_histogram/index.ts | 3 +- .../{hosts => users}/authentications/index.ts | 20 +- .../security_solution/users/details/index.ts | 5 - .../security_solution/users/index.ts | 8 + .../security_solution/users/kpi/index.ts | 9 + .../cypress/screens/hosts/authentications.ts | 2 +- .../screens/users/user_authentications.ts | 2 +- .../authentications_host_table.test.tsx.snap | 538 ++++++++++++++++++ .../authentications_user_table.test.tsx.snap | 538 ++++++++++++++++++ .../authentications_host_table.test.tsx | 118 ++++ .../authentications_host_table.tsx | 137 +++++ .../authentications_user_table.test.tsx | 85 +++ .../authentications_user_table.tsx | 127 +++++ .../components/authentication/helpers.tsx | 217 +++++++ .../authentication}/translations.ts | 0 .../common/components/authentication/types.ts | 31 + .../components/paginated_table/index.tsx | 2 +- .../containers/authentications/index.test.tsx | 6 +- .../containers/authentications/index.tsx | 44 +- .../authentications/translations.ts | 0 .../__snapshots__/index.test.tsx.snap | 113 ---- .../authentications_table/index.test.tsx | 94 --- .../authentications_table/index.tsx | 330 ----------- .../components/authentications_table/mock.ts | 119 ---- .../hosts/components/hosts_table/index.tsx | 2 - .../authentications_query_tab_body.test.tsx | 68 --- .../authentications_query_tab_body.tsx | 61 +- .../utils/get_transform_changes.test.ts | 5 +- .../transforms/utils/get_transform_changes.ts | 6 + .../get_transform_changes_for_hosts.test.ts | 12 - .../utils/get_transform_changes_for_hosts.ts | 9 - .../get_transform_changes_for_users.test.ts | 36 ++ .../utils/get_transform_changes_for_users.ts | 37 ++ ...et_transform_changes_if_they_exist.test.ts | 50 +- .../authentications_query_tab_body.test.tsx | 4 +- .../authentications_query_tab_body.tsx | 56 +- .../public/users/store/selectors.ts | 3 + .../factory/hosts/index.test.ts | 4 - .../security_solution/factory/hosts/index.ts | 3 - .../authentications/__mocks__/index.ts | 49 +- .../authentications/dsl/query.dsl.test.ts | 0 .../authentications/dsl/query.dsl.ts | 23 +- .../authentications/dsl/query_entities.dsl.ts | 24 +- .../authentications/helpers.test.ts | 22 +- .../authentications/helpers.ts | 22 +- .../authentications/index.test.tsx | 4 +- .../authentications/index.tsx | 30 +- .../security_solution/factory/users/index.ts | 3 + .../apis/security_solution/authentications.ts | 87 +-- 51 files changed, 2121 insertions(+), 1065 deletions(-) rename x-pack/plugins/security_solution/common/search_strategy/security_solution/{hosts => users}/authentications/index.ts (82%) create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/index.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/authentication/__snapshots__/authentications_host_table.test.tsx.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/authentication/__snapshots__/authentications_user_table.test.tsx.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/authentication/authentications_host_table.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/authentication/authentications_host_table.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/authentication/authentications_user_table.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/authentication/authentications_user_table.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/authentication/helpers.tsx rename x-pack/plugins/security_solution/public/{hosts/components/authentications_table => common/components/authentication}/translations.ts (100%) create mode 100644 x-pack/plugins/security_solution/public/common/components/authentication/types.ts rename x-pack/plugins/security_solution/public/{hosts => common}/containers/authentications/index.test.tsx (82%) rename x-pack/plugins/security_solution/public/{hosts => common}/containers/authentications/index.tsx (85%) rename x-pack/plugins/security_solution/public/{hosts => common}/containers/authentications/translations.ts (100%) delete mode 100644 x-pack/plugins/security_solution/public/hosts/components/authentications_table/__snapshots__/index.test.tsx.snap delete mode 100644 x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/hosts/components/authentications_table/mock.ts delete mode 100644 x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.test.tsx create mode 100644 x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_users.test.ts create mode 100644 x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_users.ts rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/{hosts => users}/authentications/__mocks__/index.ts (99%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/{hosts => users}/authentications/dsl/query.dsl.test.ts (100%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/{hosts => users}/authentications/dsl/query.dsl.ts (90%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/{hosts => users}/authentications/dsl/query_entities.dsl.ts (83%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/{hosts => users}/authentications/helpers.test.ts (90%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/{hosts => users}/authentications/helpers.ts (91%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/{hosts => users}/authentications/index.test.tsx (90%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/{hosts => users}/authentications/index.tsx (78%) diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts index bae99649c2e01..245abdd49a62d 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts @@ -6,7 +6,6 @@ */ export * from './all'; -export * from './authentications'; export * from './common'; export * from './details'; export * from './first_last_seen'; @@ -15,8 +14,6 @@ export * from './overview'; export * from './uncommon_processes'; export enum HostsQueries { - authentications = 'authentications', - authenticationsEntities = 'authenticationsEntities', details = 'hostDetails', firstOrLastSeen = 'firstOrLastSeen', hosts = 'hosts', diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts index 1902f0a0618ff..e59aec9e000d9 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts @@ -11,8 +11,6 @@ import { HostDetailsStrategyResponse, HostDetailsRequestOptions, HostsOverviewStrategyResponse, - HostAuthenticationsRequestOptions, - HostAuthenticationsStrategyResponse, HostOverviewRequestOptions, HostFirstLastSeenStrategyResponse, HostsQueries, @@ -88,12 +86,17 @@ import { TotalUsersKpiStrategyResponse, } from './users/kpi/total_users'; import { UsersRequestOptions, UsersStrategyResponse } from './users/all'; +import { + UserAuthenticationsRequestOptions, + UserAuthenticationsStrategyResponse, +} from './users/authentications'; export * from './cti'; export * from './hosts'; export * from './risk_score'; export * from './matrix_histogram'; export * from './network'; +export * from './users'; export type FactoryQueryTypes = | HostsQueries @@ -132,8 +135,6 @@ export type StrategyResponseType = T extends HostsQ ? HostDetailsStrategyResponse : T extends HostsQueries.overview ? HostsOverviewStrategyResponse - : T extends HostsQueries.authentications - ? HostAuthenticationsStrategyResponse : T extends HostsQueries.firstOrLastSeen ? HostFirstLastSeenStrategyResponse : T extends HostsQueries.uncommonProcesses @@ -148,6 +149,8 @@ export type StrategyResponseType = T extends HostsQ ? UserDetailsStrategyResponse : T extends UsersQueries.kpiTotalUsers ? TotalUsersKpiStrategyResponse + : T extends UsersQueries.authentications + ? UserAuthenticationsStrategyResponse : T extends UsersQueries.users ? UsersStrategyResponse : T extends NetworkQueries.details @@ -194,8 +197,6 @@ export type StrategyRequestType = T extends HostsQu ? HostDetailsRequestOptions : T extends HostsQueries.overview ? HostOverviewRequestOptions - : T extends HostsQueries.authentications - ? HostAuthenticationsRequestOptions : T extends HostsQueries.firstOrLastSeen ? HostFirstLastSeenRequestOptions : T extends HostsQueries.uncommonProcesses @@ -206,6 +207,8 @@ export type StrategyRequestType = T extends HostsQu ? HostsKpiHostsRequestOptions : T extends HostsKpiQueries.kpiUniqueIps ? HostsKpiUniqueIpsRequestOptions + : T extends UsersQueries.authentications + ? UserAuthenticationsRequestOptions : T extends UsersQueries.details ? UserDetailsRequestOptions : T extends UsersQueries.kpiTotalUsers diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts index ae1ace6ab9090..ef637031dd899 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts @@ -7,9 +7,8 @@ import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { IEsSearchResponse } from '../../../../../../../src/plugins/data/common'; -import { AuthenticationHit } from '../hosts'; import { Inspect, Maybe, TimerangeInput } from '../../common'; -import { RequestBasicOptions } from '../'; +import { AuthenticationHit, RequestBasicOptions } from '../'; import { AlertsGroupData } from './alerts'; import { AnomaliesActionGroupData, AnomalyHit } from './anomalies'; import { DnsHistogramGroupData } from './dns'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/authentications/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/authentications/index.ts similarity index 82% rename from x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/authentications/index.ts rename to x-pack/plugins/security_solution/common/search_strategy/security_solution/users/authentications/index.ts index 85255f51382fa..205169c8e6b0e 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/authentications/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/authentications/index.ts @@ -19,17 +19,23 @@ import { Hit, TotalHit, } from '../../../common'; -import { RequestOptionsPaginated } from '../../'; +import { RequestOptionsPaginated } from '../..'; -export interface HostAuthenticationsStrategyResponse extends IEsSearchResponse { +export interface UserAuthenticationsStrategyResponse extends IEsSearchResponse { edges: AuthenticationsEdges[]; totalCount: number; pageInfo: PageInfoPaginated; inspect?: Maybe; } -export interface HostAuthenticationsRequestOptions extends RequestOptionsPaginated { +export interface UserAuthenticationsRequestOptions extends RequestOptionsPaginated { defaultIndex: string[]; + stackByField: AuthStackByField; +} + +export enum AuthStackByField { + userName = 'user.name', + hostName = 'host.name', } export interface AuthenticationsEdges { @@ -41,7 +47,7 @@ export interface AuthenticationItem { _id: string; failures: number; successes: number; - user: UserEcs; + stackedValue: UserEcs['name'] | HostEcs['name']; lastSuccess?: Maybe; lastFailure?: Maybe; } @@ -58,7 +64,7 @@ export interface AuthenticationHit extends Hit { lastSuccess?: LastSourceHost; lastFailure?: LastSourceHost; }; - user: string; + stackedValue: string; failures: number; successes: number; cursor?: string; @@ -66,9 +72,7 @@ export interface AuthenticationHit extends Hit { } export interface AuthenticationBucket { - key: { - user_uid: string; - }; + key: string; doc_count: number; failures: { doc_count: number; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/details/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/details/index.ts index 43de36a50802d..2b74a1c251385 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/details/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/details/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; import { Inspect, Maybe, TimerangeInput } from '../../../common'; @@ -23,7 +22,3 @@ export interface UserDetailsRequestOptions extends Partial timerange: TimerangeInput; inspect?: Maybe; } - -export interface AggregationRequest { - [aggField: string]: estypes.AggregationsAggregationContainer; -} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts index d7b50ded32039..7b3937de2913e 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts @@ -7,10 +7,18 @@ import { TotalUsersKpiStrategyResponse } from './kpi/total_users'; +export * from './all'; +export * from './common'; +export * from './kpi'; +export * from './details'; +export * from './authentications'; + export enum UsersQueries { details = 'userDetails', kpiTotalUsers = 'usersKpiTotalUsers', users = 'allUsers', + authentications = 'authentications', + authenticationsEntities = 'authenticationsEntities', } export type UserskKpiStrategyResponse = Omit; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/index.ts new file mode 100644 index 0000000000000..bc735313a7314 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './common'; +export * from './total_users'; diff --git a/x-pack/plugins/security_solution/cypress/screens/hosts/authentications.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/authentications.ts index 82509c041deba..eeddb79d345d2 100644 --- a/x-pack/plugins/security_solution/cypress/screens/hosts/authentications.ts +++ b/x-pack/plugins/security_solution/cypress/screens/hosts/authentications.ts @@ -5,4 +5,4 @@ * 2.0. */ -export const AUTHENTICATIONS_TABLE = '[data-test-subj="table-authentications-loading-false"]'; +export const AUTHENTICATIONS_TABLE = '[data-test-subj="table-users-authentications-loading-false"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/users/user_authentications.ts b/x-pack/plugins/security_solution/cypress/screens/users/user_authentications.ts index 32340803c4eb1..2a91b5197b783 100644 --- a/x-pack/plugins/security_solution/cypress/screens/users/user_authentications.ts +++ b/x-pack/plugins/security_solution/cypress/screens/users/user_authentications.ts @@ -8,4 +8,4 @@ export const AUTHENTICATIONS_TAB = '[data-test-subj="navigation-authentications"]'; export const HEADER_SUBTITLE = '[data-test-subj="header-panel-subtitle"]'; export const USER_NAME_CELL = '[data-test-subj="render-content-user.name"]'; -export const AUTHENTICATIONS_TABLE = '[data-test-subj="table-authentications-loading-false"]'; +export const AUTHENTICATIONS_TABLE = '[data-test-subj="table-users-authentications-loading-false"]'; diff --git a/x-pack/plugins/security_solution/public/common/components/authentication/__snapshots__/authentications_host_table.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/authentication/__snapshots__/authentications_host_table.test.tsx.snap new file mode 100644 index 0000000000000..b0a55bb72acb8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/authentication/__snapshots__/authentications_host_table.test.tsx.snap @@ -0,0 +1,538 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Authentication Host Table Component rendering it renders the host authentication table 1`] = ` +.c2 { + margin-top: 8px; +} + +.c2 .siemSubtitle__item { + color: #7a7f89; + font-size: 12px; + line-height: 1.5; +} + +.c1 { + margin-bottom: 24px; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; +} + +.c0 { + position: relative; +} + +.c3 tbody th, +.c3 tbody td { + vertical-align: top; +} + +.c3 tbody .euiTableCellContent { + display: block; +} + +.c4 { + margin-top: 4px; +} + +@media only screen and (min-width:575px) { + .c2 .siemSubtitle__item { + display: inline-block; + margin-right: 16px; + } + + .c2 .siemSubtitle__item:last-child { + margin-right: 0; + } +} + +
+
+
+
+
+
+
+
+ +
+
+

+ + Authentications + +

+
+
+
+

+ Showing: 0 users +

+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + +
+
+ + + User + + + + + + Successes + + + + + + Failures + + + + + + Last success + + + + + + Last successful source + + + + + + Last successful destination + + + + + + Last failure + + + + + + Last failed source + + + + + + Last failed destination + + +
+
+ + No items found + +
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+`; diff --git a/x-pack/plugins/security_solution/public/common/components/authentication/__snapshots__/authentications_user_table.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/authentication/__snapshots__/authentications_user_table.test.tsx.snap new file mode 100644 index 0000000000000..0796eebbe95fc --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/authentication/__snapshots__/authentications_user_table.test.tsx.snap @@ -0,0 +1,538 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Authentication User Table Component rendering it renders the user authentication table 1`] = ` +.c2 { + margin-top: 8px; +} + +.c2 .siemSubtitle__item { + color: #7a7f89; + font-size: 12px; + line-height: 1.5; +} + +.c1 { + margin-bottom: 24px; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; +} + +.c0 { + position: relative; +} + +.c3 tbody th, +.c3 tbody td { + vertical-align: top; +} + +.c3 tbody .euiTableCellContent { + display: block; +} + +.c4 { + margin-top: 4px; +} + +@media only screen and (min-width:575px) { + .c2 .siemSubtitle__item { + display: inline-block; + margin-right: 16px; + } + + .c2 .siemSubtitle__item:last-child { + margin-right: 0; + } +} + +
+
+
+
+
+
+
+
+ +
+
+

+ + Authentications + +

+
+
+
+

+ Showing: 0 users +

+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + +
+
+ + + User + + + + + + Successes + + + + + + Failures + + + + + + Last success + + + + + + Last successful source + + + + + + Last successful destination + + + + + + Last failure + + + + + + Last failed source + + + + + + Last failed destination + + +
+
+ + No items found + +
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+`; diff --git a/x-pack/plugins/security_solution/public/common/components/authentication/authentications_host_table.test.tsx b/x-pack/plugins/security_solution/public/common/components/authentication/authentications_host_table.test.tsx new file mode 100644 index 0000000000000..9138e35252597 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/authentication/authentications_host_table.test.tsx @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import '../../mock/match_media'; + +import * as i18n from './translations'; +import { AuthenticationsHostTable } from './authentications_host_table'; +import { hostsModel } from '../../../hosts/store'; +import { TestProviders } from '../../../common/mock'; +import { useAuthentications } from '../../../common/containers/authentications'; +import { useQueryToggle } from '../../../common/containers/query_toggle'; + +jest.mock('../../../common/containers/query_toggle', () => ({ + useQueryToggle: jest.fn().mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() }), +})); +jest.mock('../../containers/authentications', () => ({ + useAuthentications: jest.fn().mockReturnValue([ + false, + { + authentications: [], + totalCount: 0, + pageInfo: {}, + loadPage: jest.fn(), + inspect: {}, + isInspected: false, + refetch: jest.fn(), + }, + ]), +})); + +describe('Authentication Host Table Component', () => { + const mockUseAuthentications = useAuthentications as jest.Mock; + const mockUseQueryToggle = useQueryToggle as jest.Mock; + + const startDate = '2020-07-07T08:20:18.966Z'; + const endDate = '3000-01-01T00:00:00.000Z'; + const defaultProps = { + type: hostsModel.HostsType.page, + startDate, + endDate, + skip: false, + setQuery: jest.fn(), + indexNames: [], + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('rendering', () => { + test('it renders the host authentication table', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('authentications-host-table-loading-false')).toMatchSnapshot(); + }); + }); + + describe('columns', () => { + test('on hosts page, we expect to get all 9 columns', () => { + const { queryAllByRole, queryByText } = render( + + + + ); + + expect(queryAllByRole('columnheader').length).toEqual(9); + + // it should have Last Successful Destination column + expect(queryByText(i18n.LAST_SUCCESSFUL_DESTINATION)).toBeInTheDocument(); + // it should have Last Failed Destination column + expect(queryByText(i18n.LAST_FAILED_DESTINATION)).toBeInTheDocument(); + }); + + test('on hosts page, we expect to get 7 user details columns', () => { + const { queryAllByRole, queryByText } = render( + + + + ); + + expect(queryAllByRole('columnheader').length).toEqual(7); + + // it should not have Successful Destination column + expect(queryByText(i18n.LAST_SUCCESSFUL_DESTINATION)).not.toBeInTheDocument(); + // it should not have Failed Destination column + expect(queryByText(i18n.LAST_FAILED_DESTINATION)).not.toBeInTheDocument(); + }); + }); + + it('toggleStatus=true, do not skip', () => { + render( + + + + ); + expect(mockUseAuthentications.mock.calls[0][0].skip).toEqual(false); + }); + + it('toggleStatus=false, skip', () => { + mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() }); + render( + + + + ); + expect(mockUseAuthentications.mock.calls[0][0].skip).toEqual(true); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/authentication/authentications_host_table.tsx b/x-pack/plugins/security_solution/public/common/components/authentication/authentications_host_table.tsx new file mode 100644 index 0000000000000..710b862570086 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/authentication/authentications_host_table.tsx @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useEffect, useState } from 'react'; + +import { getOr } from 'lodash/fp'; +import { useDispatch } from 'react-redux'; +import { PaginatedTable } from '../paginated_table'; + +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; + +import * as i18n from './translations'; +import { + getHostDetailsAuthenticationColumns, + getHostsPageAuthenticationColumns, + rowItems, +} from './helpers'; +import { useAuthentications } from '../../containers/authentications'; +import { useQueryInspector } from '../page/manage_query'; +import { HostsComponentsQueryProps } from '../../../hosts/pages/navigation/types'; +import { hostsActions, hostsModel, hostsSelectors } from '../../../hosts/store'; +import { useQueryToggle } from '../../containers/query_toggle'; +import { useDeepEqualSelector } from '../../hooks/use_selector'; +import { AuthStackByField } from '../../../../common/search_strategy'; + +const TABLE_QUERY_ID = 'authenticationsHostsTableQuery'; + +const tableType = hostsModel.HostsTableType.authentications; + +const AuthenticationsHostTableComponent: React.FC = ({ + docValueFields, + endDate, + filterQuery, + indexNames, + skip, + startDate, + type, + setQuery, + deleteQuery, +}) => { + const usersEnabled = useIsExperimentalFeatureEnabled('usersEnabled'); + const dispatch = useDispatch(); + const { toggleStatus } = useQueryToggle(TABLE_QUERY_ID); + const [querySkip, setQuerySkip] = useState(skip || !toggleStatus); + useEffect(() => { + setQuerySkip(skip || !toggleStatus); + }, [skip, toggleStatus]); + + const getAuthenticationsSelector = hostsSelectors.authenticationsSelector(); + const { activePage, limit } = useDeepEqualSelector((state) => + getAuthenticationsSelector(state, type) + ); + + const [ + loading, + { authentications, totalCount, pageInfo, loadPage, inspect, isInspected, refetch }, + ] = useAuthentications({ + docValueFields, + endDate, + filterQuery, + indexNames, + skip: querySkip, + startDate, + stackByField: AuthStackByField.userName, + activePage, + limit, + }); + + const columns = + type === hostsModel.HostsType.details + ? getHostDetailsAuthenticationColumns(usersEnabled) + : getHostsPageAuthenticationColumns(usersEnabled); + + const updateLimitPagination = useCallback( + (newLimit) => + dispatch( + hostsActions.updateTableLimit({ + hostsType: type, + limit: newLimit, + tableType, + }) + ), + [type, dispatch] + ); + + const updateActivePage = useCallback( + (newPage) => + dispatch( + hostsActions.updateTableActivePage({ + activePage: newPage, + hostsType: type, + tableType, + }) + ), + [type, dispatch] + ); + + useQueryInspector({ + queryId: TABLE_QUERY_ID, + loading, + refetch, + setQuery, + deleteQuery, + inspect, + }); + + return ( + + ); +}; + +AuthenticationsHostTableComponent.displayName = 'AuthenticationsHostTableComponent'; + +export const AuthenticationsHostTable = React.memo(AuthenticationsHostTableComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/authentication/authentications_user_table.test.tsx b/x-pack/plugins/security_solution/public/common/components/authentication/authentications_user_table.test.tsx new file mode 100644 index 0000000000000..6be9c630a20bf --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/authentication/authentications_user_table.test.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import '../../mock/match_media'; + +import { TestProviders } from '../../../common/mock'; +import { useAuthentications } from '../../../common/containers/authentications'; +import { useQueryToggle } from '../../../common/containers/query_toggle'; +import { AuthenticationsUserTable } from './authentications_user_table'; +import { usersModel } from '../../../users/store'; + +jest.mock('../../../common/containers/query_toggle', () => ({ + useQueryToggle: jest.fn().mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() }), +})); +jest.mock('../../containers/authentications', () => ({ + useAuthentications: jest.fn().mockReturnValue([ + false, + { + authentications: [], + totalCount: 0, + pageInfo: {}, + loadPage: jest.fn(), + inspect: {}, + isInspected: false, + refetch: jest.fn(), + }, + ]), +})); + +describe('Authentication User Table Component', () => { + const mockUseAuthentications = useAuthentications as jest.Mock; + const mockUseQueryToggle = useQueryToggle as jest.Mock; + + const startDate = '2020-07-07T08:20:18.966Z'; + const endDate = '3000-01-01T00:00:00.000Z'; + const defaultProps = { + type: usersModel.UsersType.page, + startDate, + endDate, + skip: false, + setQuery: jest.fn(), + indexNames: [], + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('rendering', () => { + test('it renders the user authentication table', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('table-users-authentications-loading-false')).toMatchSnapshot(); + }); + }); + + it('toggleStatus=true, do not skip', () => { + render( + + + + ); + expect(mockUseAuthentications.mock.calls[0][0].skip).toEqual(false); + }); + + it('toggleStatus=false, skip', () => { + mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() }); + render( + + + + ); + expect(mockUseAuthentications.mock.calls[0][0].skip).toEqual(true); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/authentication/authentications_user_table.tsx b/x-pack/plugins/security_solution/public/common/components/authentication/authentications_user_table.tsx new file mode 100644 index 0000000000000..572c06ef4da90 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/authentication/authentications_user_table.tsx @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useEffect, useMemo, useState } from 'react'; + +import { getOr } from 'lodash/fp'; +import { useDispatch } from 'react-redux'; +import { AuthStackByField } from '../../../../common/search_strategy/security_solution/users/authentications'; +import { PaginatedTable } from '../paginated_table'; + +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; + +import * as i18n from './translations'; +import { getHostsPageAuthenticationColumns, rowItems } from './helpers'; +import { useAuthentications } from '../../containers/authentications'; +import { useQueryInspector } from '../page/manage_query'; +import { useQueryToggle } from '../../containers/query_toggle'; +import { useDeepEqualSelector } from '../../hooks/use_selector'; +import { usersActions, usersModel, usersSelectors } from '../../../users/store'; +import { UsersComponentsQueryProps } from '../../../users/pages/navigation/types'; + +const TABLE_QUERY_ID = 'authenticationsUsersTableQuery'; + +const AuthenticationsUserTableComponent: React.FC = ({ + docValueFields, + endDate, + filterQuery, + indexNames, + skip, + startDate, + type, + setQuery, + deleteQuery, +}) => { + const usersEnabled = useIsExperimentalFeatureEnabled('usersEnabled'); + + const dispatch = useDispatch(); + const { toggleStatus } = useQueryToggle(TABLE_QUERY_ID); + const [querySkip, setQuerySkip] = useState(skip || !toggleStatus); + useEffect(() => { + setQuerySkip(skip || !toggleStatus); + }, [skip, toggleStatus]); + + const getAuthenticationsSelector = useMemo(() => usersSelectors.authenticationsSelector(), []); + const { activePage, limit } = useDeepEqualSelector((state) => getAuthenticationsSelector(state)); + + const [ + loading, + { authentications, totalCount, pageInfo, loadPage, inspect, isInspected, refetch }, + ] = useAuthentications({ + docValueFields, + endDate, + filterQuery, + indexNames, + skip: querySkip, + startDate, + activePage, + limit, + stackByField: AuthStackByField.userName, + }); + + const columns = getHostsPageAuthenticationColumns(usersEnabled); + + const updateLimitPagination = useCallback( + (newLimit) => + dispatch( + usersActions.updateTableLimit({ + usersType: type, + limit: newLimit, + tableType: usersModel.UsersTableType.authentications, + }) + ), + [type, dispatch] + ); + + const updateActivePage = useCallback( + (newPage) => + dispatch( + usersActions.updateTableActivePage({ + activePage: newPage, + usersType: type, + tableType: usersModel.UsersTableType.authentications, + }) + ), + [type, dispatch] + ); + + useQueryInspector({ + queryId: TABLE_QUERY_ID, + loading, + refetch, + setQuery, + deleteQuery, + inspect, + }); + + return ( + + ); +}; + +AuthenticationsUserTableComponent.displayName = 'AuthenticationsUserTableComponent'; + +export const AuthenticationsUserTable = React.memo(AuthenticationsUserTableComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/authentication/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/authentication/helpers.tsx new file mode 100644 index 0000000000000..d541e0e452943 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/authentication/helpers.tsx @@ -0,0 +1,217 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { has } from 'lodash/fp'; +import React from 'react'; + +import { DragEffects, DraggableWrapper } from '../drag_and_drop/draggable_wrapper'; +import { escapeDataProviderId } from '../drag_and_drop/helpers'; +import { getEmptyTagValue } from '../empty_value'; +import { FormattedRelativePreferenceDate } from '../formatted_date'; +import { Columns, ItemsPerRow } from '../paginated_table'; +import { IS_OPERATOR } from '../../../timelines/components/timeline/data_providers/data_provider'; +import { Provider } from '../../../timelines/components/timeline/data_providers/provider'; +import { getRowItemDraggables } from '../tables/helpers'; + +import * as i18n from './translations'; +import { HostDetailsLink, NetworkDetailsLink, UserDetailsLink } from '../links'; +import { AuthenticationsEdges } from '../../../../common/search_strategy'; +import { AuthTableColumns } from './types'; + +export const getHostDetailsAuthenticationColumns = (usersEnabled: boolean): AuthTableColumns => [ + getUserColumn(usersEnabled), + SUCCESS_COLUMN, + FAILURES_COLUMN, + LAST_SUCCESSFUL_TIME_COLUMN, + LAST_SUCCESSFUL_SOURCE_COLUMN, + LAST_FAILED_TIME_COLUMN, + LAST_FAILED_SOURCE_COLUMN, +]; + +export const getHostsPageAuthenticationColumns = (usersEnabled: boolean): AuthTableColumns => [ + getUserColumn(usersEnabled), + SUCCESS_COLUMN, + FAILURES_COLUMN, + LAST_SUCCESSFUL_TIME_COLUMN, + LAST_SUCCESSFUL_SOURCE_COLUMN, + LAST_SUCCESSFUL_DESTINATION_COLUMN, + LAST_FAILED_TIME_COLUMN, + LAST_FAILED_SOURCE_COLUMN, + LAST_FAILED_DESTINATION_COLUMN, +]; + +export const rowItems: ItemsPerRow[] = [ + { + text: i18n.ROWS_5, + numberOfRow: 5, + }, + { + text: i18n.ROWS_10, + numberOfRow: 10, + }, +]; + +const FAILURES_COLUMN: Columns = { + name: i18n.FAILURES, + truncateText: false, + mobileOptions: { show: true }, + render: ({ node }) => { + const id = escapeDataProviderId(`authentications-table-${node._id}-failures-${node.failures}`); + return ( + + snapshot.isDragging ? ( + + + + ) : ( + node.failures + ) + } + /> + ); + }, + width: '8%', +}; +const LAST_SUCCESSFUL_TIME_COLUMN: Columns = { + name: i18n.LAST_SUCCESSFUL_TIME, + truncateText: false, + mobileOptions: { show: true }, + render: ({ node }) => + has('lastSuccess.timestamp', node) && node.lastSuccess?.timestamp != null ? ( + + ) : ( + getEmptyTagValue() + ), +}; +const LAST_SUCCESSFUL_SOURCE_COLUMN: Columns = { + name: i18n.LAST_SUCCESSFUL_SOURCE, + truncateText: false, + mobileOptions: { show: true }, + render: ({ node }) => + getRowItemDraggables({ + rowItems: node.lastSuccess?.source?.ip || null, + attrName: 'source.ip', + idPrefix: `authentications-table-${node._id}-lastSuccessSource`, + render: (item) => , + }), +}; +const LAST_SUCCESSFUL_DESTINATION_COLUMN: Columns = { + name: i18n.LAST_SUCCESSFUL_DESTINATION, + truncateText: false, + mobileOptions: { show: true }, + render: ({ node }) => + getRowItemDraggables({ + rowItems: node.lastSuccess?.host?.name ?? null, + attrName: 'host.name', + idPrefix: `authentications-table-${node._id}-lastSuccessfulDestination`, + render: (item) => , + }), +}; +const LAST_FAILED_TIME_COLUMN: Columns = { + name: i18n.LAST_FAILED_TIME, + truncateText: false, + mobileOptions: { show: true }, + render: ({ node }) => + has('lastFailure.timestamp', node) && node.lastFailure?.timestamp != null ? ( + + ) : ( + getEmptyTagValue() + ), +}; +const LAST_FAILED_SOURCE_COLUMN: Columns = { + name: i18n.LAST_FAILED_SOURCE, + truncateText: false, + mobileOptions: { show: true }, + render: ({ node }) => + getRowItemDraggables({ + rowItems: node.lastFailure?.source?.ip || null, + attrName: 'source.ip', + idPrefix: `authentications-table-${node._id}-lastFailureSource`, + render: (item) => , + }), +}; +const LAST_FAILED_DESTINATION_COLUMN: Columns = { + name: i18n.LAST_FAILED_DESTINATION, + truncateText: false, + mobileOptions: { show: true }, + render: ({ node }) => + getRowItemDraggables({ + rowItems: node.lastFailure?.host?.name || null, + attrName: 'host.name', + idPrefix: `authentications-table-${node._id}-lastFailureDestination`, + render: (item) => , + }), +}; + +const getUserColumn = ( + usersEnabled: boolean +): Columns => ({ + name: i18n.USER, + truncateText: false, + mobileOptions: { show: true }, + render: ({ node }) => + getRowItemDraggables({ + rowItems: node.stackedValue, + attrName: 'user.name', + idPrefix: `authentications-table-${node._id}-userName`, + render: (item) => (usersEnabled ? : <>{item}), + }), +}); + +const SUCCESS_COLUMN: Columns = { + name: i18n.SUCCESSES, + truncateText: false, + mobileOptions: { show: true }, + render: ({ node }) => { + const id = escapeDataProviderId( + `authentications-table-${node._id}-node-successes-${node.successes}` + ); + return ( + + snapshot.isDragging ? ( + + + + ) : ( + node.successes + ) + } + /> + ); + }, + width: '8%', +}; diff --git a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts b/x-pack/plugins/security_solution/public/common/components/authentication/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts rename to x-pack/plugins/security_solution/public/common/components/authentication/translations.ts diff --git a/x-pack/plugins/security_solution/public/common/components/authentication/types.ts b/x-pack/plugins/security_solution/public/common/components/authentication/types.ts new file mode 100644 index 0000000000000..c686263fa8d12 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/authentication/types.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AuthenticationsEdges } from '../../../../common/search_strategy'; +import { Columns } from '../paginated_table'; + +export type AuthTableColumns = + | [ + Columns, + Columns, + Columns, + Columns, + Columns, + Columns, + Columns, + Columns, + Columns + ] + | [ + Columns, + Columns, + Columns, + Columns, + Columns, + Columns, + Columns + ]; diff --git a/x-pack/plugins/security_solution/public/common/components/paginated_table/index.tsx b/x-pack/plugins/security_solution/public/common/components/paginated_table/index.tsx index 3b27dc0fcfac6..fc2fe1ac5b361 100644 --- a/x-pack/plugins/security_solution/public/common/components/paginated_table/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/paginated_table/index.tsx @@ -25,7 +25,6 @@ import styled from 'styled-components'; import { Direction } from '../../../../common/search_strategy'; import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../common/constants'; -import { AuthTableColumns } from '../../../hosts/components/authentications_table'; import { HostsTableColumns } from '../../../hosts/components/hosts_table'; import { NetworkDnsColumns } from '../../../network/components/network_dns_table/columns'; import { NetworkHttpColumns } from '../../../network/components/network_http_table/columns'; @@ -51,6 +50,7 @@ import { Panel } from '../panel'; import { InspectButtonContainer } from '../inspect'; import { useQueryToggle } from '../../containers/query_toggle'; import { UsersTableColumns } from '../../../users/components/all_users'; +import { AuthTableColumns } from '../authentication/types'; const DEFAULT_DATA_TEST_SUBJ = 'paginated-table'; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/authentications/index.test.tsx similarity index 82% rename from x-pack/plugins/security_solution/public/hosts/containers/authentications/index.test.tsx rename to x-pack/plugins/security_solution/public/common/containers/authentications/index.test.tsx index 1f6ee4cb276ec..cf7953bb8922c 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/authentications/index.test.tsx @@ -6,9 +6,10 @@ */ import { act, renderHook } from '@testing-library/react-hooks'; +import { AuthStackByField } from '../../../../common/search_strategy'; import { TestProviders } from '../../../common/mock'; +import { HostsType } from '../../../hosts/store/model'; import { useAuthentications } from './index'; -import { HostsType } from '../../store/model'; describe('authentications', () => { it('skip = true will cancel any running request', () => { @@ -19,6 +20,9 @@ describe('authentications', () => { indexNames: ['cool'], type: HostsType.page, skip: false, + stackByField: AuthStackByField.hostName, + activePage: 0, + limit: 10, }; const { rerender } = renderHook(() => useAuthentications(localProps), { wrapper: TestProviders, diff --git a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx b/x-pack/plugins/security_solution/public/common/containers/authentications/index.tsx similarity index 85% rename from x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx rename to x-pack/plugins/security_solution/public/common/containers/authentications/index.tsx index 1ff27e4b29917..593d5e4186e29 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/authentications/index.tsx @@ -5,24 +5,22 @@ * 2.0. */ -import { noop, pick } from 'lodash/fp'; +import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import deepEqual from 'fast-deep-equal'; import { Subscription } from 'rxjs'; import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; -import { HostsQueries } from '../../../../common/search_strategy/security_solution'; import { - HostAuthenticationsRequestOptions, - HostAuthenticationsStrategyResponse, AuthenticationsEdges, - PageInfoPaginated, - DocValueFields, - SortField, -} from '../../../../common/search_strategy'; + AuthStackByField, + UserAuthenticationsRequestOptions, + UserAuthenticationsStrategyResponse, + UsersQueries, +} from '../../../../common/search_strategy/security_solution'; +import { PageInfoPaginated, DocValueFields, SortField } from '../../../../common/search_strategy'; import { ESTermQuery } from '../../../../common/typed_json'; -import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; import { inputsModel } from '../../../common/store'; import { createFilter } from '../../../common/containers/helpers'; import { generateTablePaginationOptions } from '../../../common/components/paginated_table/helpers'; @@ -30,17 +28,12 @@ import { useKibana } from '../../../common/lib/kibana'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; -import { hostsModel, hostsSelectors } from '../../store'; - import * as i18n from './translations'; import { useTransforms } from '../../../transforms/containers/use_transforms'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; -export const ID = 'hostsAuthenticationsQuery'; - export interface AuthenticationArgs { authentications: AuthenticationsEdges[]; - id: string; inspect: InspectResponse; isInspected: boolean; loading: boolean; @@ -56,8 +49,10 @@ interface UseAuthentications { endDate: string; indexNames: string[]; startDate: string; - type: hostsModel.HostsType; skip: boolean; + stackByField: AuthStackByField; + activePage: number; + limit: number; } export const useAuthentications = ({ @@ -66,20 +61,18 @@ export const useAuthentications = ({ endDate, indexNames, startDate, - type, + activePage, + limit, skip, + stackByField, }: UseAuthentications): [boolean, AuthenticationArgs] => { - const getAuthenticationsSelector = hostsSelectors.authenticationsSelector(); - const { activePage, limit } = useDeepEqualSelector((state) => - pick(['activePage', 'limit'], getAuthenticationsSelector(state, type)) - ); const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); const [authenticationsRequest, setAuthenticationsRequest] = - useState(null); + useState(null); const { getTransformChangesIfTheyExist } = useTransforms(); const { addError, addWarning } = useAppToasts(); @@ -101,7 +94,6 @@ export const useAuthentications = ({ const [authenticationsResponse, setAuthenticationsResponse] = useState({ authentications: [], - id: ID, inspect: { dsl: [], response: [], @@ -119,7 +111,7 @@ export const useAuthentications = ({ }); const authenticationsSearch = useCallback( - (request: HostAuthenticationsRequestOptions | null) => { + (request: UserAuthenticationsRequestOptions | null) => { if (request == null || skip) { return; } @@ -128,7 +120,7 @@ export const useAuthentications = ({ setLoading(true); searchSubscription$.current = data.search - .search(request, { + .search(request, { strategy: 'securitySolutionSearchStrategy', abortSignal: abortCtrl.current.signal, }) @@ -171,7 +163,7 @@ export const useAuthentications = ({ useEffect(() => { setAuthenticationsRequest((prevRequest) => { const { indices, factoryQueryType, timerange } = getTransformChangesIfTheyExist({ - factoryQueryType: HostsQueries.authentications, + factoryQueryType: UsersQueries.authentications, indices: indexNames, filterQuery, timerange: { @@ -187,6 +179,7 @@ export const useAuthentications = ({ docValueFields: docValueFields ?? [], factoryQueryType, filterQuery: createFilter(filterQuery), + stackByField, pagination: generateTablePaginationOptions(activePage, limit), timerange, sort: {} as SortField, @@ -202,6 +195,7 @@ export const useAuthentications = ({ endDate, filterQuery, indexNames, + stackByField, limit, startDate, getTransformChangesIfTheyExist, diff --git a/x-pack/plugins/security_solution/public/hosts/containers/authentications/translations.ts b/x-pack/plugins/security_solution/public/common/containers/authentications/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/hosts/containers/authentications/translations.ts rename to x-pack/plugins/security_solution/public/common/containers/authentications/translations.ts diff --git a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/__snapshots__/index.test.tsx.snap deleted file mode 100644 index bffd5e2261ad9..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,113 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Authentication Table Component rendering it renders the authentication table 1`] = ` - -`; diff --git a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.test.tsx deleted file mode 100644 index 2ec333e335639..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.test.tsx +++ /dev/null @@ -1,94 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { shallow } from 'enzyme'; -import { getOr } from 'lodash/fp'; -import React from 'react'; -import { Provider as ReduxStoreProvider } from 'react-redux'; - -import '../../../common/mock/match_media'; -import { - mockGlobalState, - SUB_PLUGINS_REDUCER, - kibanaObservable, - createSecuritySolutionStorageMock, -} from '../../../common/mock'; -import { createStore, State } from '../../../common/store'; -import { hostsModel } from '../../store'; -import { mockData } from './mock'; -import * as i18n from './translations'; -import { AuthenticationTable, getAuthenticationColumnsCurated } from '.'; - -describe('Authentication Table Component', () => { - const loadPage = jest.fn(); - const state: State = mockGlobalState; - - const { storage } = createSecuritySolutionStorageMock(); - let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); - - beforeEach(() => { - store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); - }); - - describe('rendering', () => { - test('it renders the authentication table', () => { - const wrapper = shallow( - - - - ); - - expect(wrapper.find('Memo(AuthenticationTableComponent)')).toMatchSnapshot(); - }); - }); - - describe('columns', () => { - test('on hosts page, we expect to get all columns', () => { - expect(getAuthenticationColumnsCurated(hostsModel.HostsType.page, false).length).toEqual(9); - }); - - test('on host details page, we expect to remove two columns', () => { - const columns = getAuthenticationColumnsCurated(hostsModel.HostsType.details, false); - expect(columns.length).toEqual(7); - }); - - test('on host details page, we should have Last Failed Destination column', () => { - const columns = getAuthenticationColumnsCurated(hostsModel.HostsType.page, false); - expect(columns.some((col) => col.name === i18n.LAST_FAILED_DESTINATION)).toEqual(true); - }); - - test('on host details page, we should not have Last Failed Destination column', () => { - const columns = getAuthenticationColumnsCurated(hostsModel.HostsType.details, false); - expect(columns.some((col) => col.name === i18n.LAST_FAILED_DESTINATION)).toEqual(false); - }); - - test('on host page, we should have Last Successful Destination column', () => { - const columns = getAuthenticationColumnsCurated(hostsModel.HostsType.page, false); - expect(columns.some((col) => col.name === i18n.LAST_SUCCESSFUL_DESTINATION)).toEqual(true); - }); - - test('on host details page, we should not have Last Successful Destination column', () => { - const columns = getAuthenticationColumnsCurated(hostsModel.HostsType.details, false); - expect(columns.some((col) => col.name === i18n.LAST_SUCCESSFUL_DESTINATION)).toEqual(false); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.tsx deleted file mode 100644 index 2bbda82e15315..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.tsx +++ /dev/null @@ -1,330 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { has } from 'lodash/fp'; -import React, { useCallback, useMemo } from 'react'; -import { useDispatch } from 'react-redux'; - -import { AuthenticationsEdges } from '../../../../common/search_strategy/security_solution/hosts/authentications'; - -import { - DragEffects, - DraggableWrapper, -} from '../../../common/components/drag_and_drop/draggable_wrapper'; -import { escapeDataProviderId } from '../../../common/components/drag_and_drop/helpers'; -import { getEmptyTagValue } from '../../../common/components/empty_value'; -import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date'; -import { - HostDetailsLink, - NetworkDetailsLink, - UserDetailsLink, -} from '../../../common/components/links'; -import { Columns, ItemsPerRow, PaginatedTable } from '../../../common/components/paginated_table'; -import { IS_OPERATOR } from '../../../timelines/components/timeline/data_providers/data_provider'; -import { Provider } from '../../../timelines/components/timeline/data_providers/provider'; -import { getRowItemDraggables } from '../../../common/components/tables/helpers'; -import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; - -import { hostsActions, hostsModel, hostsSelectors } from '../../store'; - -import * as i18n from './translations'; - -const tableType = hostsModel.HostsTableType.authentications; - -interface AuthenticationTableProps { - data: AuthenticationsEdges[]; - fakeTotalCount: number; - loading: boolean; - loadPage: (newActivePage: number) => void; - id: string; - isInspect: boolean; - setQuerySkip: (skip: boolean) => void; - showMorePagesIndicator: boolean; - totalCount: number; - type: hostsModel.HostsType; -} - -export type AuthTableColumns = [ - Columns, - Columns, - Columns, - Columns, - Columns, - Columns, - Columns, - Columns, - Columns -]; - -const rowItems: ItemsPerRow[] = [ - { - text: i18n.ROWS_5, - numberOfRow: 5, - }, - { - text: i18n.ROWS_10, - numberOfRow: 10, - }, -]; - -const AuthenticationTableComponent: React.FC = ({ - data, - fakeTotalCount, - id, - isInspect, - loading, - loadPage, - setQuerySkip, - showMorePagesIndicator, - totalCount, - type, -}) => { - const dispatch = useDispatch(); - const getAuthenticationsSelector = useMemo(() => hostsSelectors.authenticationsSelector(), []); - const { activePage, limit } = useDeepEqualSelector((state) => - getAuthenticationsSelector(state, type) - ); - - const updateLimitPagination = useCallback( - (newLimit) => - dispatch( - hostsActions.updateTableLimit({ - hostsType: type, - limit: newLimit, - tableType, - }) - ), - [type, dispatch] - ); - - const updateActivePage = useCallback( - (newPage) => - dispatch( - hostsActions.updateTableActivePage({ - activePage: newPage, - hostsType: type, - tableType, - }) - ), - [type, dispatch] - ); - - const usersEnabled = useIsExperimentalFeatureEnabled('usersEnabled'); - const columns = useMemo( - () => getAuthenticationColumnsCurated(type, usersEnabled), - [type, usersEnabled] - ); - - return ( - - ); -}; - -AuthenticationTableComponent.displayName = 'AuthenticationTableComponent'; - -export const AuthenticationTable = React.memo(AuthenticationTableComponent); - -const getAuthenticationColumns = (usersEnabled: boolean): AuthTableColumns => [ - { - name: i18n.USER, - truncateText: false, - mobileOptions: { show: true }, - render: ({ node }) => - getRowItemDraggables({ - rowItems: node.user.name, - attrName: 'user.name', - idPrefix: `authentications-table-${node._id}-userName`, - render: (item) => (usersEnabled ? : <>{item}), - }), - }, - { - name: i18n.SUCCESSES, - truncateText: false, - mobileOptions: { show: true }, - render: ({ node }) => { - const id = escapeDataProviderId( - `authentications-table-${node._id}-node-successes-${node.successes}` - ); - return ( - - snapshot.isDragging ? ( - - - - ) : ( - node.successes - ) - } - /> - ); - }, - width: '8%', - }, - { - name: i18n.FAILURES, - truncateText: false, - mobileOptions: { show: true }, - render: ({ node }) => { - const id = escapeDataProviderId( - `authentications-table-${node._id}-failures-${node.failures}` - ); - return ( - - snapshot.isDragging ? ( - - - - ) : ( - node.failures - ) - } - /> - ); - }, - width: '8%', - }, - { - name: i18n.LAST_SUCCESSFUL_TIME, - truncateText: false, - mobileOptions: { show: true }, - render: ({ node }) => - has('lastSuccess.timestamp', node) && node.lastSuccess?.timestamp != null ? ( - - ) : ( - getEmptyTagValue() - ), - }, - { - name: i18n.LAST_SUCCESSFUL_SOURCE, - truncateText: false, - mobileOptions: { show: true }, - render: ({ node }) => - getRowItemDraggables({ - rowItems: node.lastSuccess?.source?.ip || null, - attrName: 'source.ip', - idPrefix: `authentications-table-${node._id}-lastSuccessSource`, - render: (item) => , - }), - }, - { - name: i18n.LAST_SUCCESSFUL_DESTINATION, - truncateText: false, - mobileOptions: { show: true }, - render: ({ node }) => - getRowItemDraggables({ - rowItems: node.lastSuccess?.host?.name ?? null, - attrName: 'host.name', - idPrefix: `authentications-table-${node._id}-lastSuccessfulDestination`, - render: (item) => , - }), - }, - { - name: i18n.LAST_FAILED_TIME, - truncateText: false, - mobileOptions: { show: true }, - render: ({ node }) => - has('lastFailure.timestamp', node) && node.lastFailure?.timestamp != null ? ( - - ) : ( - getEmptyTagValue() - ), - }, - { - name: i18n.LAST_FAILED_SOURCE, - truncateText: false, - mobileOptions: { show: true }, - render: ({ node }) => - getRowItemDraggables({ - rowItems: node.lastFailure?.source?.ip || null, - attrName: 'source.ip', - idPrefix: `authentications-table-${node._id}-lastFailureSource`, - render: (item) => , - }), - }, - { - name: i18n.LAST_FAILED_DESTINATION, - truncateText: false, - mobileOptions: { show: true }, - render: ({ node }) => - getRowItemDraggables({ - rowItems: node.lastFailure?.host?.name || null, - attrName: 'host.name', - idPrefix: `authentications-table-${node._id}-lastFailureDestination`, - render: (item) => , - }), - }, -]; - -export const getAuthenticationColumnsCurated = ( - pageType: hostsModel.HostsType, - usersEnabled: boolean -): AuthTableColumns => { - const columns = getAuthenticationColumns(usersEnabled); - - // Columns to exclude from host details pages - if (pageType === hostsModel.HostsType.details) { - return [i18n.LAST_FAILED_DESTINATION, i18n.LAST_SUCCESSFUL_DESTINATION].reduce((acc, name) => { - acc.splice( - acc.findIndex((column) => column.name === name), - 1 - ); - return acc; - }, columns); - } - - return columns; -}; diff --git a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/mock.ts b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/mock.ts deleted file mode 100644 index caf441b34ca90..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/mock.ts +++ /dev/null @@ -1,119 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { HostAuthenticationsStrategyResponse } from '../../../../common/search_strategy/security_solution/hosts/authentications'; - -export const mockData: { Authentications: HostAuthenticationsStrategyResponse } = { - Authentications: { - rawResponse: { - took: 880, - timed_out: false, - _shards: { - total: 26, - successful: 26, - skipped: 0, - failed: 0, - }, - hits: { - total: 2, - hits: [], - }, - aggregations: { - group_by_users: { - buckets: [ - { - key: 'SYSTEM', - doc_count: 4, - failures: { - doc_count: 0, - lastFailure: { hits: { total: 0, max_score: null, hits: [] } }, - hits: { total: 0, max_score: null, hits: [] }, - }, - successes: { - doc_count: 4, - lastSuccess: { hits: { total: 4, max_score: null } }, - }, - }, - ], - doc_count_error_upper_bound: -1, - sum_other_doc_count: 566, - }, - }, - } as estypes.SearchResponse, - totalCount: 54, - edges: [ - { - node: { - _id: 'cPsuhGcB0WOhS6qyTKC0', - failures: 10, - successes: 0, - user: { name: ['Evan Hassanabad'] }, - lastSuccess: { - timestamp: '2019-01-23T22:35:32.222Z', - source: { - ip: ['127.0.0.1'], - }, - host: { - id: ['host-id-1'], - name: ['host-1'], - }, - }, - lastFailure: { - timestamp: '2019-01-23T22:35:32.222Z', - source: { - ip: ['8.8.8.8'], - }, - host: { - id: ['host-id-1'], - name: ['host-2'], - }, - }, - }, - cursor: { - value: '98966fa2013c396155c460d35c0902be', - }, - }, - { - node: { - _id: 'KwQDiWcB0WOhS6qyXmrW', - failures: 10, - successes: 0, - user: { name: ['Braden Hassanabad'] }, - lastSuccess: { - timestamp: '2019-01-23T22:35:32.222Z', - source: { - ip: ['127.0.0.1'], - }, - host: { - id: ['host-id-1'], - name: ['host-1'], - }, - }, - lastFailure: { - timestamp: '2019-01-23T22:35:32.222Z', - source: { - ip: ['8.8.8.8'], - }, - host: { - id: ['host-id-1'], - name: ['host-2'], - }, - }, - }, - cursor: { - value: 'aa7ca589f1b8220002f2fc61c64cfbf1', - }, - }, - ], - pageInfo: { - activePage: 1, - fakeTotalCount: 50, - showMorePagesIndicator: true, - }, - }, -}; diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.tsx index 42c8254ffd183..1326f24b5335f 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.tsx @@ -8,7 +8,6 @@ import React, { useMemo, useCallback } from 'react'; import { useDispatch } from 'react-redux'; -import { assertUnreachable } from '../../../../common/utility_types'; import { Columns, Criteria, @@ -204,7 +203,6 @@ const getNodeField = (field: HostsFields): string => { case HostsFields.lastSeen: return 'node.lastSeen'; } - assertUnreachable(field); }; export const HostsTable = React.memo(HostsTableComponent); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.test.tsx deleted file mode 100644 index 9d31b477a851a..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.test.tsx +++ /dev/null @@ -1,68 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { render } from '@testing-library/react'; -import { TestProviders } from '../../../common/mock'; -import { useAuthentications } from '../../containers/authentications'; -import { useQueryToggle } from '../../../common/containers/query_toggle'; -import { AuthenticationsQueryTabBody } from './authentications_query_tab_body'; -import { HostsType } from '../../store/model'; - -jest.mock('../../containers/authentications'); -jest.mock('../../../common/containers/query_toggle'); -jest.mock('../../../common/lib/kibana'); - -describe('Authentications query tab body', () => { - const mockUseAuthentications = useAuthentications as jest.Mock; - const mockUseQueryToggle = useQueryToggle as jest.Mock; - const defaultProps = { - indexNames: [], - setQuery: jest.fn(), - skip: false, - startDate: '2019-06-25T04:31:59.345Z', - endDate: '2019-06-25T06:31:59.345Z', - type: HostsType.page, - }; - beforeEach(() => { - jest.clearAllMocks(); - mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() }); - mockUseAuthentications.mockReturnValue([ - false, - { - authentications: [], - id: '123', - inspect: { - dsl: [], - response: [], - }, - isInspected: false, - totalCount: 0, - pageInfo: { activePage: 1, fakeTotalCount: 100, showMorePagesIndicator: false }, - loadPage: jest.fn(), - refetch: jest.fn(), - }, - ]); - }); - it('toggleStatus=true, do not skip', () => { - render( - - - - ); - expect(mockUseAuthentications.mock.calls[0][0].skip).toEqual(false); - }); - it('toggleStatus=false, skip', () => { - mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() }); - render( - - - - ); - expect(mockUseAuthentications.mock.calls[0][0].skip).toEqual(true); - }); -}); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx index 1096085b93016..0f6de80343b11 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx @@ -5,11 +5,7 @@ * 2.0. */ -import { getOr } from 'lodash/fp'; -import React, { useEffect, useState } from 'react'; -import { AuthenticationTable } from '../../components/authentications_table'; -import { manageQuery } from '../../../common/components/page/manage_query'; -import { useAuthentications } from '../../containers/authentications'; +import React from 'react'; import { HostsComponentsQueryProps } from './types'; import { MatrixHistogramOption, @@ -22,11 +18,9 @@ import * as i18n from '../translations'; import { MatrixHistogramType } from '../../../../common/search_strategy/security_solution'; import { authenticationLensAttributes } from '../../../common/components/visualization_actions/lens_attributes/hosts/authentication'; import { LensAttributes } from '../../../common/components/visualization_actions/types'; -import { useQueryToggle } from '../../../common/containers/query_toggle'; +import { AuthenticationsHostTable } from '../../../common/components/authentication/authentications_host_table'; -const AuthenticationTableManage = manageQuery(AuthenticationTable); - -const ID = 'authenticationsHistogramQuery'; +const HISTOGRAM_QUERY_ID = 'authenticationsHistogramQuery'; const authenticationsStackByOptions: MatrixHistogramOption[] = [ { @@ -77,59 +71,28 @@ const AuthenticationsQueryTabBodyComponent: React.FC startDate, type, }) => { - const { toggleStatus } = useQueryToggle(ID); - const [querySkip, setQuerySkip] = useState(skip || !toggleStatus); - useEffect(() => { - setQuerySkip(skip || !toggleStatus); - }, [skip, toggleStatus]); - const [ - loading, - { authentications, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch }, - ] = useAuthentications({ - docValueFields, - endDate, - filterQuery, - indexNames, - skip: querySkip, - startDate, - type, - }); - - useEffect(() => { - return () => { - if (deleteQuery) { - deleteQuery({ id: ID }); - } - }; - }, [deleteQuery]); - return ( <> - ); diff --git a/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes.test.ts b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes.test.ts index 5c6e10ae8f7ce..702d259f98615 100644 --- a/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes.test.ts +++ b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes.test.ts @@ -15,6 +15,7 @@ import { MatrixHistogramType, NetworkKpiQueries, NetworkQueries, + UsersQueries, } from '../../../common/search_strategy'; /** Get the return type of createIndicesFromPrefix for TypeScript checks against expected */ @@ -75,11 +76,11 @@ describe('get_transform_changes', () => { test('it gets a transform change for authentications', () => { expect( getTransformChanges({ - factoryQueryType: HostsQueries.authentications, + factoryQueryType: UsersQueries.authentications, settings: getTransformConfigSchemaMock().settings[0], }) ).toEqual({ - factoryQueryType: HostsQueries.authenticationsEntities, + factoryQueryType: UsersQueries.authenticationsEntities, indices: ['.estc_all_user_ent*'], }); }); diff --git a/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes.ts b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes.ts index 6e327457a683d..1a72c556490a5 100644 --- a/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes.ts +++ b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes.ts @@ -9,6 +9,7 @@ import { getTransformChangesForHosts } from './get_transform_changes_for_hosts'; import { getTransformChangesForKpi } from './get_transform_changes_for_kpi'; import { getTransformChangesForMatrixHistogram } from './get_transform_changes_for_matrix_histogram'; import { getTransformChangesForNetwork } from './get_transform_changes_for_network'; +import { getTransformChangesForUsers } from './get_transform_changes_for_users'; import { GetTransformChanges } from './types'; export const getTransformChanges: GetTransformChanges = ({ @@ -26,6 +27,11 @@ export const getTransformChanges: GetTransformChanges = ({ return hostTransform; } + const userTransform = getTransformChangesForUsers({ factoryQueryType, settings }); + if (userTransform != null) { + return userTransform; + } + const networkTransform = getTransformChangesForNetwork({ factoryQueryType, settings, diff --git a/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_hosts.test.ts b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_hosts.test.ts index e76c2ee2575ff..8223e3a9cd6e6 100644 --- a/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_hosts.test.ts +++ b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_hosts.test.ts @@ -25,18 +25,6 @@ describe('get_transform_changes_for_host', () => { }); }); - test('it gets a transform change for authentications', () => { - expect( - getTransformChangesForHosts({ - factoryQueryType: HostsQueries.authentications, - settings: getTransformConfigSchemaMock().settings[0], - }) - ).toEqual({ - factoryQueryType: HostsQueries.authenticationsEntities, - indices: ['.estc_all_user_ent*'], - }); - }); - test('it returns an "undefined" for another value', () => { expect( getTransformChangesForHosts({ diff --git a/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_hosts.ts b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_hosts.ts index 95a5e04bd9e51..265b2857d5397 100644 --- a/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_hosts.ts +++ b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_hosts.ts @@ -30,15 +30,6 @@ export const getTransformChangesForHosts: GetTransformChanges = ({ factoryQueryType: HostsQueries.hostsEntities, }; } - case HostsQueries.authentications: { - return { - indices: createIndicesFromPrefix({ - prefix: settings.prefix, - transformIndices: ['user_ent*'], - }), - factoryQueryType: HostsQueries.authenticationsEntities, - }; - } default: { return undefined; } diff --git a/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_users.test.ts b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_users.test.ts new file mode 100644 index 0000000000000..ae3690d72baba --- /dev/null +++ b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_users.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { UsersQueries } from '../../../common/search_strategy'; +import { getTransformChangesForUsers } from './get_transform_changes_for_users'; +import { getTransformConfigSchemaMock } from './transform_config_schema.mock'; + +/** Get the return type of getTransformChangesForUsers for TypeScript checks against expected */ +type ReturnTypeGetTransformChangesForUsers = ReturnType; + +describe('get_transform_changes_for_user', () => { + test('it gets a transform change for authentications', () => { + expect( + getTransformChangesForUsers({ + factoryQueryType: UsersQueries.authentications, + settings: getTransformConfigSchemaMock().settings[0], + }) + ).toEqual({ + factoryQueryType: UsersQueries.authenticationsEntities, + indices: ['.estc_all_user_ent*'], + }); + }); + + test('it returns an "undefined" for another value', () => { + expect( + getTransformChangesForUsers({ + factoryQueryType: UsersQueries.details, + settings: getTransformConfigSchemaMock().settings[0], + }) + ).toEqual(undefined); + }); +}); diff --git a/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_users.ts b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_users.ts new file mode 100644 index 0000000000000..a91bc05bb7383 --- /dev/null +++ b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_for_users.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { UsersQueries } from '../../../common/search_strategy'; +import { createIndicesFromPrefix } from './create_indices_from_prefix'; +import { GetTransformChanges } from './types'; + +/** + * Given a factory query type this will return the transform changes such as the transform indices if it matches + * the correct type, otherwise it will return "undefined" + * @param factoryQueryType The query type to check if we have a transform for it and are capable of rendering one or not + * @param settings The settings configuration to get the prefix from + * @returns The transform type if we have one, otherwise undefined + */ +export const getTransformChangesForUsers: GetTransformChanges = ({ + factoryQueryType, + settings, +}) => { + switch (factoryQueryType) { + case UsersQueries.authentications: { + return { + indices: createIndicesFromPrefix({ + prefix: settings.prefix, + transformIndices: ['user_ent*'], + }), + factoryQueryType: UsersQueries.authenticationsEntities, + }; + } + default: { + return undefined; + } + } +}; diff --git a/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_if_they_exist.test.ts b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_if_they_exist.test.ts index 7a4e11526d83e..19c9ba5596027 100644 --- a/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_if_they_exist.test.ts +++ b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_if_they_exist.test.ts @@ -35,7 +35,7 @@ describe('get_transform_changes_if_they_exist', () => { test('returns transformed settings if our settings is enabled', () => { expect( getTransformChangesIfTheyExist({ - factoryQueryType: HostsQueries.authentications, + factoryQueryType: HostsQueries.hosts, indices: ['auditbeat-*'], transformSettings: { ...getTransformConfigSchemaMock(), enabled: true }, // sets enabled to true filterQuery: undefined, @@ -47,15 +47,15 @@ describe('get_transform_changes_if_they_exist', () => { }, }) ).toMatchObject>({ - indices: ['.estc_all_user_ent*'], - factoryQueryType: HostsQueries.authenticationsEntities, + indices: ['.estc_all_host_ent*'], + factoryQueryType: HostsQueries.hostsEntities, }); }); test('returns regular settings if our settings is disabled', () => { expect( getTransformChangesIfTheyExist({ - factoryQueryType: HostsQueries.authentications, + factoryQueryType: HostsQueries.hosts, indices: ['auditbeat-*'], transformSettings: { ...getTransformConfigSchemaMock(), enabled: false }, // sets enabled to false filterQuery: undefined, @@ -68,7 +68,7 @@ describe('get_transform_changes_if_they_exist', () => { }) ).toMatchObject>({ indices: ['auditbeat-*'], - factoryQueryType: HostsQueries.authentications, + factoryQueryType: HostsQueries.hosts, }); }); }); @@ -77,7 +77,7 @@ describe('get_transform_changes_if_they_exist', () => { test('returns regular settings if filter is set to something other than match_all', () => { expect( getTransformChangesIfTheyExist({ - factoryQueryType: HostsQueries.authentications, + factoryQueryType: HostsQueries.hosts, indices: ['auditbeat-*'], transformSettings: getTransformConfigSchemaMock(), filterQuery: { @@ -97,14 +97,14 @@ describe('get_transform_changes_if_they_exist', () => { }) ).toMatchObject>({ indices: ['auditbeat-*'], - factoryQueryType: HostsQueries.authentications, + factoryQueryType: HostsQueries.hosts, }); }); test('returns transformed settings if filter is set to something such as match_all', () => { expect( getTransformChangesIfTheyExist({ - factoryQueryType: HostsQueries.authentications, + factoryQueryType: HostsQueries.hosts, indices: ['auditbeat-*'], transformSettings: getTransformConfigSchemaMock(), filterQuery: { @@ -123,15 +123,15 @@ describe('get_transform_changes_if_they_exist', () => { }, }) ).toMatchObject>({ - indices: ['.estc_all_user_ent*'], - factoryQueryType: HostsQueries.authenticationsEntities, + indices: ['.estc_all_host_ent*'], + factoryQueryType: HostsQueries.hostsEntities, }); }); test('returns transformed settings if filter is set to undefined', () => { expect( getTransformChangesIfTheyExist({ - factoryQueryType: HostsQueries.authentications, + factoryQueryType: HostsQueries.hosts, indices: ['auditbeat-*'], transformSettings: getTransformConfigSchemaMock(), filterQuery: undefined, // undefined should return transform @@ -143,8 +143,8 @@ describe('get_transform_changes_if_they_exist', () => { }, }) ).toMatchObject>({ - indices: ['.estc_all_user_ent*'], - factoryQueryType: HostsQueries.authenticationsEntities, + indices: ['.estc_all_host_ent*'], + factoryQueryType: HostsQueries.hostsEntities, }); }); }); @@ -153,7 +153,7 @@ describe('get_transform_changes_if_they_exist', () => { test('returns regular settings if timerange is less than an hour', () => { expect( getTransformChangesIfTheyExist({ - factoryQueryType: HostsQueries.authentications, + factoryQueryType: HostsQueries.hosts, indices: ['auditbeat-*'], transformSettings: getTransformConfigSchemaMock(), filterQuery: undefined, @@ -166,14 +166,14 @@ describe('get_transform_changes_if_they_exist', () => { }) ).toMatchObject>({ indices: ['auditbeat-*'], - factoryQueryType: HostsQueries.authentications, + factoryQueryType: HostsQueries.hosts, }); }); test('returns regular settings if timerange is invalid', () => { expect( getTransformChangesIfTheyExist({ - factoryQueryType: HostsQueries.authentications, + factoryQueryType: HostsQueries.hosts, indices: ['auditbeat-*'], transformSettings: getTransformConfigSchemaMock(), filterQuery: undefined, @@ -186,14 +186,14 @@ describe('get_transform_changes_if_they_exist', () => { }) ).toMatchObject>({ indices: ['auditbeat-*'], - factoryQueryType: HostsQueries.authentications, + factoryQueryType: HostsQueries.hosts, }); }); test('returns transformed settings if timerange is greater than an hour', () => { expect( getTransformChangesIfTheyExist({ - factoryQueryType: HostsQueries.authentications, + factoryQueryType: HostsQueries.hosts, indices: ['auditbeat-*'], transformSettings: getTransformConfigSchemaMock(), filterQuery: undefined, @@ -205,8 +205,8 @@ describe('get_transform_changes_if_they_exist', () => { }, }) ).toMatchObject>({ - indices: ['.estc_all_user_ent*'], - factoryQueryType: HostsQueries.authenticationsEntities, + indices: ['.estc_all_host_ent*'], + factoryQueryType: HostsQueries.hostsEntities, }); }); }); @@ -215,7 +215,7 @@ describe('get_transform_changes_if_they_exist', () => { test('it returns regular settings if settings do not match', () => { expect( getTransformChangesIfTheyExist({ - factoryQueryType: HostsQueries.authentications, + factoryQueryType: HostsQueries.hosts, indices: ['should-not-match-*'], // index doesn't match anything transformSettings: getTransformConfigSchemaMock(), filterQuery: undefined, @@ -228,14 +228,14 @@ describe('get_transform_changes_if_they_exist', () => { }) ).toMatchObject>({ indices: ['should-not-match-*'], - factoryQueryType: HostsQueries.authentications, + factoryQueryType: HostsQueries.hosts, }); }); test('it returns transformed settings if settings do match', () => { expect( getTransformChangesIfTheyExist({ - factoryQueryType: HostsQueries.authentications, + factoryQueryType: HostsQueries.hosts, indices: [ 'auditbeat-*', 'endgame-*', @@ -255,8 +255,8 @@ describe('get_transform_changes_if_they_exist', () => { }, }) ).toMatchObject>({ - indices: ['.estc_all_user_ent*'], - factoryQueryType: HostsQueries.authenticationsEntities, + indices: ['.estc_all_host_ent*'], + factoryQueryType: HostsQueries.hostsEntities, }); }); }); diff --git a/x-pack/plugins/security_solution/public/users/pages/navigation/authentications_query_tab_body.test.tsx b/x-pack/plugins/security_solution/public/users/pages/navigation/authentications_query_tab_body.test.tsx index 83c2b0dfa6b72..b7ffb68e2fc2d 100644 --- a/x-pack/plugins/security_solution/public/users/pages/navigation/authentications_query_tab_body.test.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/navigation/authentications_query_tab_body.test.tsx @@ -8,12 +8,12 @@ import React from 'react'; import { render } from '@testing-library/react'; import { TestProviders } from '../../../common/mock'; -import { useAuthentications } from '../../../hosts/containers/authentications'; import { useQueryToggle } from '../../../common/containers/query_toggle'; import { AuthenticationsQueryTabBody } from './authentications_query_tab_body'; import { UsersType } from '../../store/model'; +import { useAuthentications } from '../../../common/containers/authentications'; -jest.mock('../../../hosts/containers/authentications'); +jest.mock('../../../common/containers/authentications'); jest.mock('../../../common/containers/query_toggle'); jest.mock('../../../common/lib/kibana'); diff --git a/x-pack/plugins/security_solution/public/users/pages/navigation/authentications_query_tab_body.tsx b/x-pack/plugins/security_solution/public/users/pages/navigation/authentications_query_tab_body.tsx index 6926a1e755f01..453ae6e9a8f3f 100644 --- a/x-pack/plugins/security_solution/public/users/pages/navigation/authentications_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/navigation/authentications_query_tab_body.tsx @@ -5,15 +5,11 @@ * 2.0. */ -import { getOr } from 'lodash/fp'; -import React, { useEffect, useState } from 'react'; -import { useAuthentications, ID } from '../../../hosts/containers/authentications'; +import React from 'react'; import { UsersComponentsQueryProps } from './types'; -import { AuthenticationTable } from '../../../hosts/components/authentications_table'; -import { manageQuery } from '../../../common/components/page/manage_query'; -import { useQueryToggle } from '../../../common/containers/query_toggle'; +import { AuthenticationsUserTable } from '../../../common/components/authentication/authentications_user_table'; -const AuthenticationTableManage = manageQuery(AuthenticationTable); +export const ID = 'usersAuthenticationsQuery'; export const AuthenticationsQueryTabBody = ({ endDate, @@ -26,47 +22,17 @@ export const AuthenticationsQueryTabBody = ({ docValueFields, deleteQuery, }: UsersComponentsQueryProps) => { - const { toggleStatus } = useQueryToggle(ID); - const [querySkip, setQuerySkip] = useState(skip || !toggleStatus); - useEffect(() => { - setQuerySkip(skip || !toggleStatus); - }, [skip, toggleStatus]); - - const [ - loading, - { authentications, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch }, - ] = useAuthentications({ - docValueFields, - endDate, - filterQuery, - indexNames, - skip: querySkip, - startDate, - // TODO Move authentication table and hook store to 'public/common' folder when 'usersEnabled' FF is removed - // @ts-ignore - type, - deleteQuery, - }); return ( - ); }; diff --git a/x-pack/plugins/security_solution/public/users/store/selectors.ts b/x-pack/plugins/security_solution/public/users/store/selectors.ts index bdeacef2bf774..1ccedf3b5da20 100644 --- a/x-pack/plugins/security_solution/public/users/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/users/store/selectors.ts @@ -21,3 +21,6 @@ export const userRiskScoreSelector = () => export const usersRiskScoreSeverityFilterSelector = () => createSelector(selectUserPage, (users) => users.queries[UsersTableType.risk].severitySelection); + +export const authenticationsSelector = () => + createSelector(selectUserPage, (users) => users.queries[UsersTableType.authentications]); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts index fda1e1c166ce3..901d577fdbf5a 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts @@ -13,7 +13,6 @@ import { hostOverview } from './overview'; import { firstOrLastSeenHost } from './last_first_seen'; import { uncommonProcesses } from './uncommon_processes'; -import { authentications, authenticationsEntities } from './authentications'; import { hostsKpiAuthentications, hostsKpiAuthenticationsEntities } from './kpi/authentications'; import { hostsKpiHosts, hostsKpiHostsEntities } from './kpi/hosts'; import { hostsKpiUniqueIps, hostsKpiUniqueIpsEntities } from './kpi/unique_ips'; @@ -23,7 +22,6 @@ jest.mock('./details'); jest.mock('./overview'); jest.mock('./last_first_seen'); jest.mock('./uncommon_processes'); -jest.mock('./authentications'); jest.mock('./kpi/authentications'); jest.mock('./kpi/hosts'); jest.mock('./kpi/unique_ips'); @@ -36,8 +34,6 @@ describe('hostsFactory', () => { [HostsQueries.overview]: hostOverview, [HostsQueries.firstOrLastSeen]: firstOrLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, - [HostsQueries.authentications]: authentications, - [HostsQueries.authenticationsEntities]: authenticationsEntities, [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications, [HostsKpiQueries.kpiAuthenticationsEntities]: hostsKpiAuthenticationsEntities, [HostsKpiQueries.kpiHosts]: hostsKpiHosts, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts index cd95a38ec3092..5d40e5e153599 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts @@ -17,7 +17,6 @@ import { hostDetails } from './details'; import { hostOverview } from './overview'; import { firstOrLastSeenHost } from './last_first_seen'; import { uncommonProcesses } from './uncommon_processes'; -import { authentications, authenticationsEntities } from './authentications'; import { hostsKpiAuthentications, hostsKpiAuthenticationsEntities } from './kpi/authentications'; import { hostsKpiHosts, hostsKpiHostsEntities } from './kpi/hosts'; import { hostsKpiUniqueIps, hostsKpiUniqueIpsEntities } from './kpi/unique_ips'; @@ -32,8 +31,6 @@ export const hostsFactory: Record< [HostsQueries.overview]: hostOverview, [HostsQueries.firstOrLastSeen]: firstOrLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, - [HostsQueries.authentications]: authentications, - [HostsQueries.authenticationsEntities]: authenticationsEntities, [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications, [HostsKpiQueries.kpiAuthenticationsEntities]: hostsKpiAuthenticationsEntities, [HostsKpiQueries.kpiHosts]: hostsKpiHosts, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/__mocks__/index.ts similarity index 99% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/__mocks__/index.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/__mocks__/index.ts index 6e43d771d1a02..6b57ff7fdb7cd 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/__mocks__/index.ts @@ -6,15 +6,15 @@ */ import type { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; - import { + UserAuthenticationsRequestOptions, AuthenticationHit, Direction, - HostAuthenticationsRequestOptions, - HostsQueries, + UsersQueries, + AuthStackByField, } from '../../../../../../../common/search_strategy'; -export const mockOptions: HostAuthenticationsRequestOptions = { +export const mockOptions: UserAuthenticationsRequestOptions = { defaultIndex: [ 'apm-*-transaction*', 'traces-apm*', @@ -25,6 +25,7 @@ export const mockOptions: HostAuthenticationsRequestOptions = { 'packetbeat-*', 'winlogbeat-*', ], + stackByField: AuthStackByField.userName, docValueFields: [ { field: '@timestamp', @@ -427,7 +428,7 @@ export const mockOptions: HostAuthenticationsRequestOptions = { format: 'date_time', }, ], - factoryQueryType: HostsQueries.authentications, + factoryQueryType: UsersQueries.authentications, filterQuery: '{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}}', pagination: { activePage: 0, @@ -456,7 +457,7 @@ export const mockSearchStrategyResponse: IEsSearchResponse = { _shards: { total: 21, successful: 21, skipped: 0, failed: 0 }, hits: { total: -1, max_score: 0, hits: [] }, aggregations: { - group_by_users: { + stack_by: { doc_count_error_upper_bound: -1, sum_other_doc_count: 408, buckets: [ @@ -1290,7 +1291,7 @@ export const mockSearchStrategyResponse: IEsSearchResponse = { }, ], }, - user_count: { value: 188 }, + stack_by_count: { value: 188 }, }, }, total: 21, @@ -1306,7 +1307,7 @@ export const formattedSearchStrategyResponse = { _shards: { total: 21, successful: 21, skipped: 0, failed: 0 }, hits: { total: -1, max_score: 0, hits: [] }, aggregations: { - group_by_users: { + stack_by: { doc_count_error_upper_bound: -1, sum_other_doc_count: 408, buckets: [ @@ -2140,7 +2141,7 @@ export const formattedSearchStrategyResponse = { }, ], }, - user_count: { value: 188 }, + stack_by_count: { value: 188 }, }, }, total: 21, @@ -2164,8 +2165,8 @@ export const formattedSearchStrategyResponse = { body: { docvalue_fields: mockOptions.docValueFields, aggregations: { - user_count: { cardinality: { field: 'user.name' } }, - group_by_users: { + stack_by_count: { cardinality: { field: 'user.name' } }, + stack_by: { terms: { size: 10, field: 'user.name', @@ -2231,7 +2232,7 @@ export const formattedSearchStrategyResponse = { failures: 0, successes: 4, _id: 'SYSTEM+281', - user: { name: ['SYSTEM'] }, + stackedValue: ['SYSTEM'], lastSuccess: { timestamp: ['2020-09-04T13:08:02.532Z'], host: { id: ['ce1d3c9b-a815-4643-9641-ada0f2c00609'], name: ['siem-windows'] }, @@ -2244,7 +2245,7 @@ export const formattedSearchStrategyResponse = { failures: 0, successes: 1, _id: 'tsg+1', - user: { name: ['tsg'] }, + stackedValue: ['tsg'], lastSuccess: { timestamp: ['2020-09-04T11:49:21.000Z'], source: { ip: ['77.183.42.188'] }, @@ -2258,7 +2259,7 @@ export const formattedSearchStrategyResponse = { failures: 23, successes: 0, _id: 'admin+23', - user: { name: ['admin'] }, + stackedValue: ['admin'], lastFailure: { timestamp: ['2020-09-04T13:40:46.000Z'], source: { ip: ['59.15.3.197'] }, @@ -2272,7 +2273,7 @@ export const formattedSearchStrategyResponse = { failures: 21, successes: 0, _id: 'user+21', - user: { name: ['user'] }, + stackedValue: ['user'], lastFailure: { timestamp: ['2020-09-04T13:25:43.000Z'], source: { ip: ['64.227.88.245'] }, @@ -2286,7 +2287,7 @@ export const formattedSearchStrategyResponse = { failures: 18, successes: 0, _id: 'ubuntu+18', - user: { name: ['ubuntu'] }, + stackedValue: ['ubuntu'], lastFailure: { timestamp: ['2020-09-04T13:25:07.000Z'], source: { ip: ['64.227.88.245'] }, @@ -2300,7 +2301,7 @@ export const formattedSearchStrategyResponse = { failures: 17, successes: 0, _id: 'odoo+17', - user: { name: ['odoo'] }, + stackedValue: ['odoo'], lastFailure: { timestamp: ['2020-09-04T12:26:36.000Z'], source: { ip: ['180.151.228.166'] }, @@ -2314,7 +2315,7 @@ export const formattedSearchStrategyResponse = { failures: 17, successes: 0, _id: 'pi+17', - user: { name: ['pi'] }, + stackedValue: ['pi'], lastFailure: { timestamp: ['2020-09-04T11:37:22.000Z'], source: { ip: ['178.174.148.58'] }, @@ -2328,7 +2329,7 @@ export const formattedSearchStrategyResponse = { failures: 14, successes: 0, _id: 'demo+14', - user: { name: ['demo'] }, + stackedValue: ['demo'], lastFailure: { timestamp: ['2020-09-04T07:23:22.000Z'], source: { ip: ['45.95.168.157'] }, @@ -2342,7 +2343,7 @@ export const formattedSearchStrategyResponse = { failures: 13, successes: 0, _id: 'git+13', - user: { name: ['git'] }, + stackedValue: ['git'], lastFailure: { timestamp: ['2020-09-04T11:20:26.000Z'], source: { ip: ['123.206.30.76'] }, @@ -2356,7 +2357,7 @@ export const formattedSearchStrategyResponse = { failures: 13, successes: 0, _id: 'webadmin+13', - user: { name: ['webadmin'] }, + stackedValue: ['webadmin'], lastFailure: { timestamp: ['2020-09-04T07:25:28.000Z'], source: { ip: ['45.95.168.157'] }, @@ -2386,8 +2387,8 @@ export const expectedDsl = { body: { docvalue_fields: mockOptions.docValueFields, aggregations: { - user_count: { cardinality: { field: 'user.name' } }, - group_by_users: { + stack_by_count: { cardinality: { field: 'user.name' } }, + stack_by: { terms: { size: 10, field: 'user.name', @@ -2445,7 +2446,7 @@ export const mockHit: AuthenticationHit = { }, cursor: 'cursor-1', sort: [0], - user: 'Evan', + stackedValue: 'Evan', failures: 10, successes: 20, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query.dsl.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/dsl/query.dsl.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query.dsl.test.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/dsl/query.dsl.test.ts diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/dsl/query.dsl.ts similarity index 90% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query.dsl.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/dsl/query.dsl.ts index c88104745ba06..e018716d4c216 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/dsl/query.dsl.ts @@ -7,7 +7,7 @@ import { isEmpty } from 'lodash/fp'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { HostAuthenticationsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts/authentications'; +import { UserAuthenticationsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/users/authentications'; import { sourceFieldsMap, hostFieldsMap } from '../../../../../../../common/ecs/ecs_fields'; import { createQueryFilterClauses } from '../../../../../../utils/build_query'; @@ -28,11 +28,12 @@ export const auditdFieldsMap: Readonly> = { export const buildQuery = ({ filterQuery, + stackByField, timerange: { from, to }, pagination: { querySize }, defaultIndex, docValueFields, -}: HostAuthenticationsRequestOptions) => { +}: UserAuthenticationsRequestOptions) => { const esFields = reduceFields(authenticationsFields, { ...hostFieldsMap, ...sourceFieldsMap, @@ -52,14 +53,6 @@ export const buildQuery = ({ }, ]; - const agg = { - user_count: { - cardinality: { - field: 'user.name', - }, - }, - }; - const dslQuery = { allow_no_indices: true, index: defaultIndex, @@ -67,11 +60,15 @@ export const buildQuery = ({ body: { ...(!isEmpty(docValueFields) ? { docvalue_fields: docValueFields } : {}), aggregations: { - ...agg, - group_by_users: { + stack_by_count: { + cardinality: { + field: stackByField, + }, + }, + stack_by: { terms: { size: querySize, - field: 'user.name', + field: stackByField, order: [ { 'successes.doc_count': 'desc' as const }, { 'failures.doc_count': 'desc' as const }, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query_entities.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/dsl/query_entities.dsl.ts similarity index 83% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query_entities.dsl.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/dsl/query_entities.dsl.ts index ab726b41ae01b..7c9c29a1efdc4 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query_entities.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/dsl/query_entities.dsl.ts @@ -8,9 +8,8 @@ import { isEmpty } from 'lodash/fp'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { HostAuthenticationsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts/authentications'; - import { createQueryFilterClauses } from '../../../../../../utils/build_query'; +import { UserAuthenticationsRequestOptions } from '../../../../../../../common/search_strategy'; export const buildQueryEntities = ({ filterQuery, @@ -18,7 +17,8 @@ export const buildQueryEntities = ({ pagination: { querySize }, defaultIndex, docValueFields, -}: HostAuthenticationsRequestOptions) => { + stackByField, +}: UserAuthenticationsRequestOptions) => { const filter = [ ...createQueryFilterClauses(filterQuery), { @@ -32,14 +32,6 @@ export const buildQueryEntities = ({ }, ]; - const agg = { - user_count: { - cardinality: { - field: 'user.name', - }, - }, - }; - const dslQuery = { allow_no_indices: true, index: defaultIndex, @@ -47,11 +39,15 @@ export const buildQueryEntities = ({ body: { ...(!isEmpty(docValueFields) ? { docvalue_fields: docValueFields } : {}), aggregations: { - ...agg, - group_by_users: { + stack_by_count: { + cardinality: { + field: stackByField, + }, + }, + stack_by: { terms: { size: querySize, - field: 'user.name', + field: stackByField, order: [ { successes: 'desc' }, { failures: 'desc' }, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/helpers.test.ts similarity index 90% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.test.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/helpers.test.ts index a8cce8a4c2bcf..1e745ffcbf2ed 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/helpers.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AuthenticationsEdges } from '../../../../../../common/search_strategy/security_solution/hosts/authentications'; +import { AuthenticationsEdges } from '../../../../../../common/search_strategy'; import { auditdFieldsMap } from './dsl/query.dsl'; import { formatAuthenticationData } from './helpers'; @@ -24,9 +24,7 @@ describe('#formatAuthenticationsData', () => { _id: 'id-123', failures: 10, successes: 20, - user: { - name: ['Evan'], - }, + stackedValue: ['Evan'], }, }; @@ -45,9 +43,7 @@ describe('#formatAuthenticationsData', () => { _id: 'id-123', failures: 10, successes: 20, - user: { - name: ['Evan'], - }, + stackedValue: ['Evan'], }, }; @@ -66,9 +62,7 @@ describe('#formatAuthenticationsData', () => { _id: 'id-123', failures: 10, successes: 20, - user: { - name: ['Evan'], - }, + stackedValue: ['Evan'], }, }; @@ -87,9 +81,7 @@ describe('#formatAuthenticationsData', () => { _id: 'id-123', failures: 10, successes: 20, - user: { - name: ['Evan'], - }, + stackedValue: ['Evan'], }, }; @@ -108,9 +100,7 @@ describe('#formatAuthenticationsData', () => { _id: 'id-123', failures: 10, successes: 20, - user: { - name: ['Evan'], - }, + stackedValue: ['Evan'], }, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/helpers.ts similarity index 91% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/helpers.ts index 7517d112aebdc..02a89dd08a222 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/helpers.ts @@ -21,7 +21,7 @@ export const authenticationsFields = [ '_id', 'failures', 'successes', - 'user.name', + 'stackedValue', 'lastSuccess.timestamp', 'lastSuccess.source.ip', 'lastSuccess.host.id', @@ -46,7 +46,7 @@ export const formatAuthenticationData = ( ...flattenedFields.node, ...{ _id: hit._id, - user: { name: [hit.user] }, + stackedValue: [hit.stackedValue], failures: hit.failures, successes: hit.successes, }, @@ -69,9 +69,7 @@ export const formatAuthenticationData = ( failures: 0, successes: 0, _id: '', - user: { - name: [''], - }, + stackedValue: [''], }, cursor: { value: '', @@ -81,7 +79,7 @@ export const formatAuthenticationData = ( ); export const getHits = (response: StrategyResponseType) => - getOr([], 'aggregations.group_by_users.buckets', response.rawResponse).map( + getOr([], 'aggregations.stack_by.buckets', response.rawResponse).map( (bucket: AuthenticationBucket) => ({ _id: getOr( `${bucket.key}+${bucket.doc_count}`, @@ -92,14 +90,14 @@ export const getHits = (response: StrategyResponseT lastSuccess: getOr(null, 'successes.lastSuccess.hits.hits[0]._source', bucket), lastFailure: getOr(null, 'failures.lastFailure.hits.hits[0]._source', bucket), }, - user: bucket.key, + stackedValue: bucket.key, failures: bucket.failures.doc_count, successes: bucket.successes.doc_count, }) ); export const getHitsEntities = (response: StrategyResponseType) => - getOr([], 'aggregations.group_by_users.buckets', response.rawResponse).map( + getOr([], 'aggregations.stack_by.buckets', response.rawResponse).map( (bucket: AuthenticationBucket) => ({ _id: getOr( `${bucket.key}+${bucket.doc_count}`, @@ -110,7 +108,7 @@ export const getHitsEntities = (response: StrategyR lastSuccess: getOr(null, 'successes.lastSuccess.hits.hits[0]._source', bucket), lastFailure: getOr(null, 'failures.lastFailure.hits.hits[0]._source', bucket), }, - user: bucket.key, + stackedValue: bucket.key, failures: bucket.failures.value, successes: bucket.successes.value, }) @@ -130,7 +128,7 @@ export const formatAuthenticationEntitiesData = ( ...flattenedFields.node, ...{ _id: hit._id, - user: { name: [hit.user] }, + stackedValue: [hit.stackedValue], failures: hit.failures, successes: hit.successes, }, @@ -153,9 +151,7 @@ export const formatAuthenticationEntitiesData = ( failures: 0, successes: 0, _id: '', - user: { - name: [''], - }, + stackedValue: [''], }, cursor: { value: '', diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.test.tsx b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.test.tsx similarity index 90% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.test.tsx rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.test.tsx index 960f1a516e814..e4342cf266474 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.test.tsx +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.test.tsx @@ -7,7 +7,6 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; -import { HostAuthenticationsRequestOptions } from '../../../../../../common/search_strategy/security_solution/hosts/authentications'; import * as buildQuery from './dsl/query.dsl'; import { authentications } from '.'; import { @@ -15,6 +14,7 @@ import { mockSearchStrategyResponse, formattedSearchStrategyResponse, } from './__mocks__'; +import { UserAuthenticationsRequestOptions } from '../../../../../../common/search_strategy'; describe('authentications search strategy', () => { const buildAuthenticationQuery = jest.spyOn(buildQuery, 'buildQuery'); @@ -36,7 +36,7 @@ describe('authentications search strategy', () => { ...mockOptions.pagination, querySize: DEFAULT_MAX_TABLE_QUERY_SIZE, }, - } as HostAuthenticationsRequestOptions; + } as UserAuthenticationsRequestOptions; expect(() => { authentications.buildDsl(overSizeOptions); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.tsx similarity index 78% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.tsx index e32d3592d3417..11400166e3344 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/index.tsx @@ -11,12 +11,12 @@ import type { IEsSearchResponse } from '../../../../../../../../../src/plugins/d import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; import { - HostsQueries, - AuthenticationsEdges, - HostAuthenticationsRequestOptions, - HostAuthenticationsStrategyResponse, AuthenticationHit, -} from '../../../../../../common/search_strategy/security_solution/hosts'; + AuthenticationsEdges, + UserAuthenticationsRequestOptions, + UserAuthenticationsStrategyResponse, +} from '../../../../../../common/search_strategy'; +import { UsersQueries } from '../../../../../../common/search_strategy/security_solution/users'; import { inspectStringifyObject } from '../../../../../utils/build_query'; import { SecuritySolutionFactory } from '../../types'; @@ -32,8 +32,8 @@ import { getHitsEntities, } from './helpers'; -export const authentications: SecuritySolutionFactory = { - buildDsl: (options: HostAuthenticationsRequestOptions) => { +export const authentications: SecuritySolutionFactory = { + buildDsl: (options: UserAuthenticationsRequestOptions) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); } @@ -41,11 +41,11 @@ export const authentications: SecuritySolutionFactory - ): Promise => { + ): Promise => { const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; - const totalCount = getOr(0, 'aggregations.user_count.value', response.rawResponse); + const totalCount = getOr(0, 'aggregations.stack_by_count.value', response.rawResponse); const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; const hits: AuthenticationHit[] = getHits(response); @@ -72,8 +72,8 @@ export const authentications: SecuritySolutionFactory = { - buildDsl: (options: HostAuthenticationsRequestOptions) => { +export const authenticationsEntities: SecuritySolutionFactory = { + buildDsl: (options: UserAuthenticationsRequestOptions) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); } @@ -81,11 +81,11 @@ export const authenticationsEntities: SecuritySolutionFactory - ): Promise => { + ): Promise => { const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; - const totalCount = getOr(0, 'aggregations.user_count.value', response.rawResponse); + const totalCount = getOr(0, 'aggregations.stack_by_count.value', response.rawResponse); const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; const hits: AuthenticationHit[] = getHitsEntities(response); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts index dce2195867358..0b13319cc11c7 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts @@ -10,6 +10,7 @@ import { UsersQueries } from '../../../../../common/search_strategy/security_sol import { SecuritySolutionFactory } from '../types'; import { allUsers } from './all'; +import { authentications, authenticationsEntities } from './authentications'; import { userDetails } from './details'; import { totalUsersKpi } from './kpi/total_users'; @@ -17,4 +18,6 @@ export const usersFactory: Record { - const authentications = await bsearch.send({ - supertest, - options: { - factoryQueryType: HostsQueries.authentications, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 3, - querySize: 1, - }, - defaultIndex: ['auditbeat-*'], - docValueFields: [], - inspect: false, + const requestOptions: UserAuthenticationsRequestOptions = { + factoryQueryType: UsersQueries.authentications, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 3, + querySize: 1, }, + defaultIndex: ['auditbeat-*'], + docValueFields: [], + stackByField: AuthStackByField.userName, + sort: { field: 'timestamp', direction: Direction.asc }, + filterQuery: '', + }; + + const authentications = await bsearch.send({ + supertest, + options: requestOptions, strategy: 'securitySolutionSearchStrategy', }); @@ -63,25 +70,29 @@ export default function ({ getService }: FtrProviderContext) { }); it('Make sure that pagination is working in Authentications query', async () => { - const authentications = await bsearch.send({ - supertest, - options: { - factoryQueryType: HostsQueries.authentications, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - pagination: { - activePage: 2, - cursorStart: 1, - fakePossibleCount: 5, - querySize: 2, - }, - defaultIndex: ['auditbeat-*'], - docValueFields: [], - inspect: false, + const requestOptions: UserAuthenticationsRequestOptions = { + factoryQueryType: UsersQueries.authentications, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + pagination: { + activePage: 2, + cursorStart: 1, + fakePossibleCount: 5, + querySize: 2, }, + defaultIndex: ['auditbeat-*'], + docValueFields: [], + stackByField: AuthStackByField.userName, + sort: { field: 'timestamp', direction: Direction.asc }, + filterQuery: '', + }; + + const authentications = await bsearch.send({ + supertest, + options: requestOptions, strategy: 'securitySolutionSearchStrategy', }); From 25d9f5d97edc2f2b5bdf77a42b41b1cbc9a2c23d Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 4 Apr 2022 07:32:32 -0400 Subject: [PATCH 57/65] [Response Ops] Renaming `Alert` to `Rule` (#129136) * Rename all the things * Fixing checks * Removing unnecessary change * Fixing checks --- .../src/actions/index.ts | 4 +- .../view_alert/view_alert_route.tsx | 4 +- .../view_alert/view_alert_utils.tsx | 10 +- x-pack/examples/alerting_example/README.md | 2 +- .../alerting_example/common/constants.ts | 4 +- .../public/alert_types/astros.tsx | 8 +- .../public/alert_types/index.ts | 4 +- x-pack/plugins/alerting/README.md | 22 +-- x-pack/plugins/alerting/common/index.ts | 10 +- .../alerting/common/{alert.ts => rule.ts} | 48 +++--- ...alert_navigation.ts => rule_navigation.ts} | 6 +- ....test.ts => rule_notify_when_type.test.ts} | 4 +- ..._when_type.ts => rule_notify_when_type.ts} | 8 +- .../alerting/common/rule_task_instance.ts | 2 +- .../plugins/alerting/public/alert_api.test.ts | 22 +-- x-pack/plugins/alerting/public/alert_api.ts | 22 +-- .../alert_navigation_registry.test.ts | 92 +++++----- .../public/alert_navigation_registry/types.ts | 4 +- .../public/lib/common_transformations.test.ts | 18 +- .../public/lib/common_transformations.ts | 18 +- x-pack/plugins/alerting/public/plugin.ts | 26 +-- .../alerting/server/health/get_health.test.ts | 14 +- .../alerting/server/health/get_health.ts | 8 +- x-pack/plugins/alerting/server/index.ts | 12 +- .../lib/alert_summary_from_event_log.test.ts | 6 +- .../lib/alert_summary_from_event_log.ts | 4 +- .../server/lib/error_with_reason.test.ts | 8 +- .../alerting/server/lib/error_with_reason.ts | 10 +- .../alerting/server/lib/errors/index.ts | 4 +- ...type_disabled.ts => rule_type_disabled.ts} | 8 +- ...t.ts => get_rule_notify_when_type.test.ts} | 10 +- ...n_type.ts => get_rule_notify_when_type.ts} | 8 +- x-pack/plugins/alerting/server/lib/index.ts | 6 +- .../server/lib/is_alerting_error.test.ts | 6 +- .../alerting/server/lib/license_state.ts | 18 +- .../server/lib/rule_execution_status.test.ts | 12 +- .../server/lib/rule_execution_status.ts | 28 ++-- .../server/lib/validate_rule_type_params.ts | 6 +- .../server/lib/wrap_scoped_cluster_client.ts | 2 +- x-pack/plugins/alerting/server/mocks.ts | 6 +- x-pack/plugins/alerting/server/plugin.ts | 16 +- .../server/routes/create_rule.test.ts | 10 +- .../alerting/server/routes/create_rule.ts | 20 +-- .../server/routes/disable_rule.test.ts | 4 +- .../alerting/server/routes/disable_rule.ts | 4 +- .../server/routes/enable_rule.test.ts | 4 +- .../alerting/server/routes/enable_rule.ts | 4 +- .../alerting/server/routes/find_rules.ts | 4 +- .../alerting/server/routes/get_rule.test.ts | 6 +- .../alerting/server/routes/get_rule.ts | 6 +- .../server/routes/legacy/create.test.ts | 8 +- .../alerting/server/routes/legacy/create.ts | 21 ++- .../server/routes/legacy/disable.test.ts | 4 +- .../alerting/server/routes/legacy/disable.ts | 4 +- .../server/routes/legacy/enable.test.ts | 4 +- .../alerting/server/routes/legacy/enable.ts | 4 +- .../alerting/server/routes/legacy/get.test.ts | 4 +- .../server/routes/legacy/mute_all.test.ts | 4 +- .../alerting/server/routes/legacy/mute_all.ts | 4 +- .../routes/legacy/mute_instance.test.ts | 4 +- .../server/routes/legacy/mute_instance.ts | 4 +- .../server/routes/legacy/unmute_all.test.ts | 4 +- .../server/routes/legacy/unmute_all.ts | 4 +- .../routes/legacy/unmute_instance.test.ts | 4 +- .../server/routes/legacy/unmute_instance.ts | 4 +- .../server/routes/legacy/update.test.ts | 8 +- .../alerting/server/routes/legacy/update.ts | 8 +- .../routes/legacy/update_api_key.test.ts | 4 +- .../server/routes/legacy/update_api_key.ts | 4 +- .../alerting/server/routes/mute_alert.test.ts | 4 +- .../alerting/server/routes/mute_alert.ts | 4 +- .../server/routes/mute_all_rule.test.ts | 4 +- .../alerting/server/routes/mute_all_rule.ts | 4 +- .../alerting/server/routes/resolve_rule.ts | 4 +- .../server/routes/snooze_rule.test.ts | 4 +- .../server/routes/unmute_alert.test.ts | 4 +- .../alerting/server/routes/unmute_alert.ts | 4 +- .../server/routes/unmute_all_rule.test.ts | 4 +- .../alerting/server/routes/unmute_all_rule.ts | 4 +- .../server/routes/unsnooze_rule.test.ts | 4 +- .../server/routes/update_rule.test.ts | 12 +- .../alerting/server/routes/update_rule.ts | 16 +- .../server/routes/update_rule_api_key.test.ts | 4 +- .../server/routes/update_rule_api_key.ts | 4 +- .../alerting/server/rule_type_registry.ts | 34 ++-- .../rules_client/lib/mapped_params_utils.ts | 4 +- .../server/rules_client/rules_client.ts | 24 +-- .../geo_containment/migrations.ts | 4 +- .../server/saved_objects/migrations.ts | 16 +- .../task_runner/alert_task_instance.test.ts | 4 +- .../server/task_runner/alert_task_instance.ts | 8 +- .../create_execution_handler.test.ts | 19 +-- .../task_runner/create_execution_handler.ts | 13 +- .../alerting/server/task_runner/fixtures.ts | 10 +- .../task_runner/inject_action_params.ts | 4 +- .../server/task_runner/task_runner.test.ts | 158 +++++++++--------- .../server/task_runner/task_runner.ts | 60 +++---- .../task_runner/task_runner_cancel.test.ts | 30 ++-- .../server/task_runner/task_runner_factory.ts | 10 +- .../task_runner/transform_action_params.ts | 10 +- .../alerting/server/task_runner/types.ts | 46 ++--- x-pack/plugins/alerting/server/types.ts | 97 +++++------ .../routes/alerts/alerting_es_client.ts | 4 +- .../infra/public/alerting/inventory/index.ts | 2 +- .../public/alerting/metric_anomaly/index.ts | 2 +- .../public/alerting/metric_threshold/index.ts | 2 +- .../inventory_metric_threshold_executor.ts | 2 +- .../log_threshold/log_threshold_executor.ts | 2 +- .../metric_anomaly/metric_anomaly_executor.ts | 2 +- .../metric_threshold_executor.test.ts | 7 +- .../metric_threshold_executor.ts | 2 +- x-pack/plugins/ml/common/types/alerts.ts | 8 +- .../register_anomaly_detection_alert_type.ts | 4 +- .../register_jobs_monitoring_rule_type.ts | 8 +- .../plugins/monitoring/common/types/alerts.ts | 6 +- .../ccr_read_exceptions_alert/index.tsx | 4 +- .../param_details_form/validation.tsx | 4 +- .../alerts/large_shard_size_alert/index.tsx | 4 +- .../lib/get_alert_panels_by_category.test.tsx | 4 +- .../lib/get_alert_panels_by_node.test.tsx | 4 +- .../server/alerts/alerts_factory.ts | 4 +- .../monitoring/server/alerts/base_rule.ts | 23 +-- .../server/alerts/ccr_read_exceptions_rule.ts | 4 +- .../server/alerts/cluster_health_rule.ts | 4 +- .../server/alerts/cpu_usage_rule.ts | 4 +- .../server/alerts/disk_usage_rule.ts | 4 +- .../elasticsearch_version_mismatch_rule.ts | 4 +- .../alerts/kibana_version_mismatch_rule.ts | 4 +- .../server/alerts/large_shard_size_rule.ts | 4 +- .../server/alerts/license_expiration_rule.ts | 8 +- .../alerts/logstash_version_mismatch_rule.ts | 4 +- .../server/alerts/memory_usage_rule.ts | 4 +- .../alerts/missing_monitoring_data_rule.ts | 4 +- .../server/alerts/nodes_changed_rule.ts | 4 +- .../thread_pool_rejections_rule_base.ts | 2 +- .../thread_pool_search_rejections_rule.ts | 4 +- .../thread_pool_write_rejections_rule.ts | 4 +- .../server/routes/api/v1/alerts/enable.ts | 4 +- .../rules/components/execution_status.tsx | 4 +- .../rules/components/last_response_filter.tsx | 4 +- .../public/pages/rules/config.ts | 4 +- .../public/pages/rules/index.tsx | 4 +- .../observability/public/pages/rules/types.ts | 4 +- .../services/get_observability_alerts.ts | 4 +- .../alerts_client/classes/alertsclient.md | 6 +- .../interfaces/bulkupdateoptions.md | 2 +- .../alerts_client/interfaces/updateoptions.md | 2 +- .../server/alert_data_client/alerts_client.ts | 12 +- x-pack/plugins/rule_registry/server/types.ts | 26 +-- .../server/utils/create_lifecycle_executor.ts | 22 +-- .../create_lifecycle_rule_executor_mock.ts | 8 +- .../create_lifecycle_rule_type_factory.ts | 8 +- ...eate_persistence_rule_type_wrapper.mock.ts | 2 +- .../server/utils/get_common_alert_fields.ts | 4 +- .../utils/lifecycle_alert_services_mock.ts | 2 +- .../server/utils/persistence_types.ts | 16 +- .../server/utils/rule_executor_test_utils.ts | 15 +- .../utils/with_rule_data_client_factory.ts | 6 +- .../transform_actions.test.ts | 4 +- .../detection_engine/transform_actions.ts | 6 +- .../common/detection_engine/types.ts | 4 +- .../description_step/actions_description.tsx | 8 +- .../rules/rule_actions_field/index.tsx | 10 +- .../detection_engine/rules/types.ts | 4 +- .../pages/detection_engine/rules/types.ts | 4 +- .../legacy_create_notifications.ts | 4 +- .../legacy_find_notifications.ts | 4 +- .../legacy_read_notifications.ts | 4 +- ...gacy_rules_notification_alert_type.test.ts | 6 +- .../notifications/legacy_types.ts | 28 ++-- .../schedule_notification_actions.test.ts | 4 +- .../routes/__mocks__/request_responses.ts | 4 +- .../routes/rules/perform_bulk_action_route.ts | 4 +- .../routes/rules/preview_rules_route.ts | 4 +- .../routes/rules/utils.test.ts | 4 +- .../detection_engine/routes/rules/utils.ts | 4 +- .../detection_engine/routes/rules/validate.ts | 8 +- ...legacy_create_rule_actions_saved_object.ts | 8 +- ...gacy_get_bulk_rule_actions_saved_object.ts | 4 +- .../legacy_get_rule_actions_saved_object.ts | 4 +- .../rule_actions/legacy_types.ts | 6 +- ...ate_or_create_rule_actions_saved_object.ts | 8 +- ...legacy_update_rule_actions_saved_object.ts | 8 +- .../rule_actions/legacy_utils.test.ts | 4 +- .../rule_actions/legacy_utils.ts | 4 +- .../lib/detection_engine/rule_types/types.ts | 12 +- .../rule_types/utils/index.ts | 4 +- .../detection_engine/rules/create_rules.ts | 6 +- .../detection_engine/rules/duplicate_rule.ts | 4 +- .../detection_engine/rules/get_export_all.ts | 4 +- .../rules/get_export_by_object_ids.ts | 6 +- .../rules/install_prepacked_rules.ts | 6 +- .../lib/detection_engine/rules/patch_rules.ts | 4 +- .../lib/detection_engine/rules/read_rules.ts | 4 +- .../lib/detection_engine/rules/types.ts | 12 +- .../rules/update_prepacked_rules.ts | 6 +- .../detection_engine/rules/update_rules.ts | 4 +- .../lib/detection_engine/rules/utils.test.ts | 24 +-- .../lib/detection_engine/rules/utils.ts | 14 +- .../schemas/rule_converters.ts | 4 +- .../signals/bulk_create_ml_signals.ts | 4 +- .../signals/executors/eql.test.ts | 6 +- .../detection_engine/signals/executors/eql.ts | 4 +- .../signals/executors/ml.test.ts | 6 +- .../detection_engine/signals/executors/ml.ts | 4 +- .../signals/executors/query.ts | 4 +- .../signals/executors/threat_match.ts | 4 +- .../signals/executors/threshold.test.ts | 6 +- .../signals/executors/threshold.ts | 4 +- .../signals/get_filter.test.ts | 6 +- .../detection_engine/signals/get_filter.ts | 4 +- .../signals/get_input_output_index.test.ts | 6 +- .../signals/get_input_output_index.ts | 4 +- .../preview/alert_instance_factory_stub.ts | 4 +- .../signals/search_after_bulk_create.test.ts | 6 +- .../signals/single_search_after.test.ts | 4 +- .../signals/single_search_after.ts | 4 +- .../signals/threat_mapping/types.ts | 12 +- .../bulk_create_threshold_signals.ts | 4 +- .../find_previous_threshold_signals.ts | 4 +- .../threshold/find_threshold_signals.test.ts | 6 +- .../threshold/find_threshold_signals.ts | 4 +- .../threshold/get_threshold_signal_history.ts | 4 +- .../lib/detection_engine/signals/types.ts | 18 +- .../detection_engine/signals/utils.test.ts | 6 +- .../lib/detection_engine/signals/utils.ts | 6 +- .../server/lib/detection_engine/types.ts | 4 +- .../security_solution/server/usage/types.ts | 4 +- .../public/alert_types/es_query/index.ts | 4 +- .../public/alert_types/es_query/types.ts | 4 +- .../alert_types/geo_containment/types.ts | 4 +- .../public/alert_types/threshold/types.ts | 4 +- .../alert_types/es_query/action_context.ts | 4 +- .../alert_types/es_query/alert_type.test.ts | 28 ++-- .../alert_types/es_query/alert_type_params.ts | 4 +- .../server/alert_types/es_query/types.ts | 4 +- .../alert_types/geo_containment/alert_type.ts | 8 +- .../geo_containment/geo_containment.ts | 4 +- .../tests/geo_containment.test.ts | 6 +- .../index_threshold/action_context.ts | 4 +- .../index_threshold/alert_type.test.ts | 20 ++- .../alert_types/index_threshold/alert_type.ts | 4 +- x-pack/plugins/stack_alerts/server/types.ts | 4 +- .../transform/common/types/alerting.ts | 6 +- .../register_transform_health_rule_type.ts | 4 +- x-pack/plugins/triggers_actions_ui/README.md | 10 +- .../lib/get_defaults_for_action_params.ts | 4 +- .../lib/rule_api/common_transformations.ts | 4 +- .../action_connector_form/action_form.tsx | 4 +- .../action_type_form.tsx | 4 +- .../sections/rule_details/components/rule.tsx | 4 +- .../components/rule_details.test.tsx | 8 +- .../rule_details/components/rule_details.tsx | 4 +- .../rule_details/components/view_in_app.tsx | 6 +- .../sections/rule_form/rule_form.tsx | 2 +- .../sections/rule_form/rule_reducer.ts | 5 +- .../components/rule_status_filter.tsx | 8 +- .../rules_list/components/rules_list.test.tsx | 10 +- .../rules_list/components/rules_list.tsx | 27 ++- .../triggers_actions_ui/public/types.ts | 22 +-- .../server/data/lib/time_series_query.test.ts | 2 +- .../plugins/uptime/public/state/api/alerts.ts | 4 +- .../server/lib/alerts/test_utils/index.ts | 2 +- .../plugins/uptime/server/lib/alerts/types.ts | 8 +- .../plugins/alerts/server/alert_types.ts | 18 +- .../tests/alerting/execution_status.ts | 4 +- .../spaces_only/tests/alerting/migrations.ts | 4 +- .../test/functional/services/ml/alerting.ts | 6 +- .../fixtures/plugins/alerts/public/plugin.ts | 4 +- .../lib/helpers/wait_until_next_execution.ts | 8 +- x-pack/test/rule_registry/common/types.ts | 6 +- .../spaces_only/tests/trial/create_rule.ts | 4 +- .../tests/trial/lifecycle_executor.ts | 2 +- 273 files changed, 1176 insertions(+), 1211 deletions(-) rename x-pack/plugins/alerting/common/{alert.ts => rule.ts} (72%) rename x-pack/plugins/alerting/common/{alert_navigation.ts => rule_navigation.ts} (69%) rename x-pack/plugins/alerting/common/{alert_notify_when_type.test.ts => rule_notify_when_type.test.ts} (83%) rename x-pack/plugins/alerting/common/{alert_notify_when_type.ts => rule_notify_when_type.ts} (61%) rename x-pack/plugins/alerting/server/lib/errors/{alert_type_disabled.ts => rule_type_disabled.ts} (72%) rename x-pack/plugins/alerting/server/lib/{get_alert_notify_when_type.test.ts => get_rule_notify_when_type.test.ts} (64%) rename x-pack/plugins/alerting/server/lib/{get_alert_notify_when_type.ts => get_rule_notify_when_type.ts} (77%) diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts index f984484dd4091..023af9fc7050e 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts +++ b/packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts @@ -12,8 +12,8 @@ import * as t from 'io-ts'; import { saved_object_attributes } from '../saved_object_attributes'; /** - * Params is an "object", since it is a type of AlertActionParams which is action templates. - * @see x-pack/plugins/alerting/common/alert.ts + * Params is an "object", since it is a type of RuleActionParams which is action templates. + * @see x-pack/plugins/alerting/common/rule.ts */ export const action_group = t.string; export const action_id = t.string; diff --git a/src/plugins/discover/public/application/view_alert/view_alert_route.tsx b/src/plugins/discover/public/application/view_alert/view_alert_route.tsx index 82481660d339c..2a6fa70ef0e2b 100644 --- a/src/plugins/discover/public/application/view_alert/view_alert_route.tsx +++ b/src/plugins/discover/public/application/view_alert/view_alert_route.tsx @@ -9,7 +9,7 @@ import { useEffect, useMemo } from 'react'; import { useHistory, useLocation, useParams } from 'react-router-dom'; import { sha256 } from 'js-sha256'; -import type { Alert } from '../../../../../../x-pack/plugins/alerting/common'; +import type { Rule } from '../../../../../../x-pack/plugins/alerting/common'; import { getTime, IndexPattern } from '../../../../data/common'; import type { Filter } from '../../../../data/public'; import { DiscoverAppLocatorParams } from '../../locator'; @@ -27,7 +27,7 @@ const isActualAlert = (queryParams: QueryParams): queryParams is NonNullableEntr const buildTimeRangeFilter = ( dataView: IndexPattern, - fetchedAlert: Alert, + fetchedAlert: Rule, timeFieldName: string ) => { const filter = getTime(dataView, { diff --git a/src/plugins/discover/public/application/view_alert/view_alert_utils.tsx b/src/plugins/discover/public/application/view_alert/view_alert_utils.tsx index b61f0c9a8720c..585da8c676417 100644 --- a/src/plugins/discover/public/application/view_alert/view_alert_utils.tsx +++ b/src/plugins/discover/public/application/view_alert/view_alert_utils.tsx @@ -9,13 +9,13 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { CoreStart, ToastsStart } from 'kibana/public'; -import type { Alert } from '../../../../../../x-pack/plugins/alerting/common'; -import type { AlertTypeParams } from '../../../../../../x-pack/plugins/alerting/common'; +import type { Rule } from '../../../../../../x-pack/plugins/alerting/common'; +import type { RuleTypeParams } from '../../../../../../x-pack/plugins/alerting/common'; import { SerializedSearchSourceFields } from '../../../../data/common'; import type { DataPublicPluginStart } from '../../../../data/public'; import { MarkdownSimple, toMountPoint } from '../../../../kibana_react/public'; -export interface SearchThresholdAlertParams extends AlertTypeParams { +export interface SearchThresholdAlertParams extends RuleTypeParams { searchConfiguration: SerializedSearchSourceFields; } @@ -78,7 +78,7 @@ export const getAlertUtils = ( const fetchAlert = async (id: string) => { try { - return await core.http.get>( + return await core.http.get>( `${LEGACY_BASE_ALERT_API_PATH}/alert/${id}` ); } catch (error) { @@ -92,7 +92,7 @@ export const getAlertUtils = ( } }; - const fetchSearchSource = async (fetchedAlert: Alert) => { + const fetchSearchSource = async (fetchedAlert: Rule) => { try { return await data.search.searchSource.create(fetchedAlert.params.searchConfiguration); } catch (error) { diff --git a/x-pack/examples/alerting_example/README.md b/x-pack/examples/alerting_example/README.md index bf963c64586d3..f9e2f6009b12f 100644 --- a/x-pack/examples/alerting_example/README.md +++ b/x-pack/examples/alerting_example/README.md @@ -1,5 +1,5 @@ ## Alerting Example -This example plugin shows you how to create a custom Alert Type, create alerts based on that type and corresponding UI for viewing the details of all the alerts within the custom plugin. +This example plugin shows you how to create a custom Rule Type, create rules based on that type and corresponding UI for viewing the details of all the rules within the custom plugin. To run this example, use the command `yarn start --run-examples`. \ No newline at end of file diff --git a/x-pack/examples/alerting_example/common/constants.ts b/x-pack/examples/alerting_example/common/constants.ts index 14342e3381531..83cb074328f42 100644 --- a/x-pack/examples/alerting_example/common/constants.ts +++ b/x-pack/examples/alerting_example/common/constants.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AlertTypeParams } from '../../../plugins/alerting/common'; +import { RuleTypeParams } from '../../../plugins/alerting/common'; export const ALERTING_EXAMPLE_APP_ID = 'AlertingExample'; @@ -16,7 +16,7 @@ export interface AlwaysFiringThresholds { medium?: number; large?: number; } -export interface AlwaysFiringParams extends AlertTypeParams { +export interface AlwaysFiringParams extends RuleTypeParams { instances?: number; thresholds?: AlwaysFiringThresholds; } diff --git a/x-pack/examples/alerting_example/public/alert_types/astros.tsx b/x-pack/examples/alerting_example/public/alert_types/astros.tsx index 63ffa48c94399..d7fe322a083b1 100644 --- a/x-pack/examples/alerting_example/public/alert_types/astros.tsx +++ b/x-pack/examples/alerting_example/public/alert_types/astros.tsx @@ -20,7 +20,7 @@ import { import { i18n } from '@kbn/i18n'; import { flatten } from 'lodash'; import { ALERTING_EXAMPLE_APP_ID, Craft, Operator } from '../../common/constants'; -import { SanitizedAlert } from '../../../../plugins/alerting/common'; +import { SanitizedRule } from '../../../../plugins/alerting/common'; import { PluginSetupContract as AlertingSetup } from '../../../../plugins/alerting/public'; import { RuleTypeModel } from '../../../../plugins/triggers_actions_ui/public'; @@ -28,7 +28,7 @@ export function registerNavigation(alerting: AlertingSetup) { alerting.registerNavigation( ALERTING_EXAMPLE_APP_ID, 'example.people-in-space', - (alert: SanitizedAlert) => `/astros/${alert.id}` + (rule: SanitizedRule) => `/astros/${rule.id}` ); } @@ -49,8 +49,8 @@ export function getAlertType(): RuleTypeModel { iconClass: 'globe', documentationUrl: null, ruleParamsExpression: PeopleinSpaceExpression, - validate: (alertParams: PeopleinSpaceParamsProps['ruleParams']) => { - const { outerSpaceCapacity, craft, op } = alertParams; + validate: (ruleParams: PeopleinSpaceParamsProps['ruleParams']) => { + const { outerSpaceCapacity, craft, op } = ruleParams; const validationResult = { errors: { diff --git a/x-pack/examples/alerting_example/public/alert_types/index.ts b/x-pack/examples/alerting_example/public/alert_types/index.ts index 93c59c55bf3ce..5b62fe138c018 100644 --- a/x-pack/examples/alerting_example/public/alert_types/index.ts +++ b/x-pack/examples/alerting_example/public/alert_types/index.ts @@ -7,14 +7,14 @@ import { registerNavigation as registerPeopleInSpaceNavigation } from './astros'; import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; -import { SanitizedAlert } from '../../../../plugins/alerting/common'; +import { SanitizedRule } from '../../../../plugins/alerting/common'; import { PluginSetupContract as AlertingSetup } from '../../../../plugins/alerting/public'; export function registerNavigation(alerting: AlertingSetup) { // register default navigation alerting.registerDefaultNavigation( ALERTING_EXAMPLE_APP_ID, - (alert: SanitizedAlert) => `/rule/${alert.id}` + (rule: SanitizedRule) => `/rule/${rule.id}` ); registerPeopleInSpaceNavigation(alerting); diff --git a/x-pack/plugins/alerting/README.md b/x-pack/plugins/alerting/README.md index 6dde7de84aab4..c77e600df5f0e 100644 --- a/x-pack/plugins/alerting/README.md +++ b/x-pack/plugins/alerting/README.md @@ -224,28 +224,28 @@ This example rule type receives server and threshold as parameters. It will read ```typescript import { schema } from '@kbn/config-schema'; -import { RuleType, AlertExecutorOptions } from '../../../alerting/server'; +import { RuleType, RuleExecutorOptions } from '../../../alerting/server'; // These type names will eventually be updated to reflect the new terminology import { - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, } from '../../../alerting/common'; ... -interface MyRuleTypeParams extends AlertTypeParams { +interface MyRuleTypeParams extends RuleTypeParams { server: string; threshold: number; testSavedObjectId: string; } -interface MyRuleTypeExtractedParams extends AlertTypeParams { +interface MyRuleTypeExtractedParams extends RuleTypeParams { server: string; threshold: number; testSavedObjectRef: string; } -interface MyRuleTypeState extends AlertTypeState { +interface MyRuleTypeState extends RuleTypeState { lastChecked: Date; } @@ -306,7 +306,7 @@ const myRuleType: RuleType< params, state, rule, - }: AlertExecutorOptions< + }: RuleExecutorOptions< MyRuleTypeParams, MyRuleTypeExtractedParams, MyRuleTypeState, @@ -677,8 +677,8 @@ The signature of such a handler is: ```typescript type AlertNavigationHandler = ( - alert: SanitizedAlert, - alertType: RuleType + rule: SanitizedRule, + ruleType: RuleType ) => string; ``` @@ -692,7 +692,7 @@ The _registerNavigation_ api allows you to register a handler for a specific ale alerting.registerNavigation( 'my-application-id', 'my-application-id.my-rule-type', - (alert: SanitizedAlert) => `/my-unique-rule/${rule.id}` + (rule: SanitizedRule) => `/my-unique-rule/${rule.id}` ); ``` @@ -708,7 +708,7 @@ The _registerDefaultNavigation_ API allows you to register a handler for any rul ``` alerting.registerDefaultNavigation( 'my-application-id', - (alert: SanitizedAlert) => `/my-other-rules/${rule.id}` + (rule: SanitizedRule) => `/my-other-rules/${rule.id}` ); ``` diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts index 732d9061e58da..f056ad7e0e4b7 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerting/common/index.ts @@ -8,17 +8,17 @@ // TODO: https://github.com/elastic/kibana/issues/110895 /* eslint-disable @kbn/eslint/no_export_all */ -import { AlertsHealth } from './alert'; +import { AlertsHealth } from './rule'; -export * from './alert'; +export * from './rule'; export * from './rule_type'; -export * from './alert_instance'; export * from './rule_task_instance'; -export * from './alert_navigation'; +export * from './rule_navigation'; +export * from './alert_instance'; export * from './alert_summary'; export * from './builtin_action_groups'; export * from './disabled_action_groups'; -export * from './alert_notify_when_type'; +export * from './rule_notify_when_type'; export * from './parse_duration'; export * from './execution_log_types'; diff --git a/x-pack/plugins/alerting/common/alert.ts b/x-pack/plugins/alerting/common/rule.ts similarity index 72% rename from x-pack/plugins/alerting/common/alert.ts rename to x-pack/plugins/alerting/common/rule.ts index 9e48acd523d22..d6904c7164600 100644 --- a/x-pack/plugins/alerting/common/alert.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -11,10 +11,10 @@ import { SavedObjectsResolveResponse, } from 'kibana/server'; import { RuleExecutionMetrics } from '.'; -import { AlertNotifyWhenType } from './alert_notify_when_type'; +import { RuleNotifyWhenType } from './rule_notify_when_type'; -export type AlertTypeState = Record; -export type AlertTypeParams = Record; +export type RuleTypeState = Record; +export type RuleTypeParams = Record; export interface IntervalSchedule extends SavedObjectAttributes { interval: string; @@ -22,7 +22,7 @@ export interface IntervalSchedule extends SavedObjectAttributes { // for the `typeof ThingValues[number]` types below, become string types that // only accept the values in the associated string arrays -export const AlertExecutionStatusValues = [ +export const RuleExecutionStatusValues = [ 'ok', 'active', 'error', @@ -30,9 +30,9 @@ export const AlertExecutionStatusValues = [ 'unknown', 'warning', ] as const; -export type AlertExecutionStatuses = typeof AlertExecutionStatusValues[number]; +export type RuleExecutionStatuses = typeof RuleExecutionStatusValues[number]; -export enum AlertExecutionStatusErrorReasons { +export enum RuleExecutionStatusErrorReasons { Read = 'read', Decrypt = 'decrypt', Execute = 'execute', @@ -42,38 +42,38 @@ export enum AlertExecutionStatusErrorReasons { Disabled = 'disabled', } -export enum AlertExecutionStatusWarningReasons { +export enum RuleExecutionStatusWarningReasons { MAX_EXECUTABLE_ACTIONS = 'maxExecutableActions', } -export interface AlertExecutionStatus { - status: AlertExecutionStatuses; +export interface RuleExecutionStatus { + status: RuleExecutionStatuses; numberOfTriggeredActions?: number; numberOfScheduledActions?: number; metrics?: RuleExecutionMetrics; lastExecutionDate: Date; lastDuration?: number; error?: { - reason: AlertExecutionStatusErrorReasons; + reason: RuleExecutionStatusErrorReasons; message: string; }; warning?: { - reason: AlertExecutionStatusWarningReasons; + reason: RuleExecutionStatusWarningReasons; message: string; }; } -export type AlertActionParams = SavedObjectAttributes; -export type AlertActionParam = SavedObjectAttribute; +export type RuleActionParams = SavedObjectAttributes; +export type RuleActionParam = SavedObjectAttribute; -export interface AlertAction { +export interface RuleAction { group: string; id: string; actionTypeId: string; - params: AlertActionParams; + params: RuleActionParams; } -export interface AlertAggregations { +export interface RuleAggregations { alertExecutionStatus: { [status: string]: number }; ruleEnabledStatus: { enabled: number; disabled: number }; ruleMutedStatus: { muted: number; unmuted: number }; @@ -87,15 +87,15 @@ export interface MappedParamsProperties { export type MappedParams = SavedObjectAttributes & MappedParamsProperties; -export interface Alert { +export interface Rule { id: string; enabled: boolean; name: string; tags: string[]; - alertTypeId: string; + alertTypeId: string; // this is persisted in the Rule saved object so we would need a migration to change this to ruleTypeId consumer: string; schedule: IntervalSchedule; - actions: AlertAction[]; + actions: RuleAction[]; params: Params; mapped_params?: MappedParams; scheduledTaskId?: string; @@ -106,20 +106,20 @@ export interface Alert { apiKey: string | null; apiKeyOwner: string | null; throttle: string | null; - notifyWhen: AlertNotifyWhenType | null; + notifyWhen: RuleNotifyWhenType | null; muteAll: boolean; mutedInstanceIds: string[]; - executionStatus: AlertExecutionStatus; + executionStatus: RuleExecutionStatus; monitoring?: RuleMonitoring; snoozeEndTime?: Date | null; // Remove ? when this parameter is made available in the public API } -export type SanitizedAlert = Omit, 'apiKey'>; -export type ResolvedSanitizedRule = SanitizedAlert & +export type SanitizedRule = Omit, 'apiKey'>; +export type ResolvedSanitizedRule = SanitizedRule & Omit; export type SanitizedRuleConfig = Pick< - SanitizedAlert, + SanitizedRule, | 'name' | 'tags' | 'consumer' diff --git a/x-pack/plugins/alerting/common/alert_navigation.ts b/x-pack/plugins/alerting/common/rule_navigation.ts similarity index 69% rename from x-pack/plugins/alerting/common/alert_navigation.ts rename to x-pack/plugins/alerting/common/rule_navigation.ts index 6ac21232b51a5..abc109a31c432 100644 --- a/x-pack/plugins/alerting/common/alert_navigation.ts +++ b/x-pack/plugins/alerting/common/rule_navigation.ts @@ -6,10 +6,10 @@ */ import { JsonObject } from '@kbn/utility-types'; -export interface AlertUrlNavigation { +export interface RuleUrlNavigation { path: string; } -export interface AlertStateNavigation { +export interface RuleStateNavigation { state: JsonObject; } -export type AlertNavigation = AlertUrlNavigation | AlertStateNavigation; +export type RuleNavigation = RuleUrlNavigation | RuleStateNavigation; diff --git a/x-pack/plugins/alerting/common/alert_notify_when_type.test.ts b/x-pack/plugins/alerting/common/rule_notify_when_type.test.ts similarity index 83% rename from x-pack/plugins/alerting/common/alert_notify_when_type.test.ts rename to x-pack/plugins/alerting/common/rule_notify_when_type.test.ts index 3bfaf0ea7cc7e..dca2296aaa7e5 100644 --- a/x-pack/plugins/alerting/common/alert_notify_when_type.test.ts +++ b/x-pack/plugins/alerting/common/rule_notify_when_type.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { validateNotifyWhenType } from './alert_notify_when_type'; +import { validateNotifyWhenType } from './rule_notify_when_type'; test('validates valid notify when type', () => { expect(validateNotifyWhenType('onActionGroupChange')).toBeUndefined(); @@ -14,6 +14,6 @@ test('validates valid notify when type', () => { }); test('returns error string if input is not valid notify when type', () => { expect(validateNotifyWhenType('randomString')).toEqual( - `string is not a valid AlertNotifyWhenType: randomString` + `string is not a valid RuleNotifyWhenType: randomString` ); }); diff --git a/x-pack/plugins/alerting/common/alert_notify_when_type.ts b/x-pack/plugins/alerting/common/rule_notify_when_type.ts similarity index 61% rename from x-pack/plugins/alerting/common/alert_notify_when_type.ts rename to x-pack/plugins/alerting/common/rule_notify_when_type.ts index fe7ca715e2bfb..700c87acdbdbb 100644 --- a/x-pack/plugins/alerting/common/alert_notify_when_type.ts +++ b/x-pack/plugins/alerting/common/rule_notify_when_type.ts @@ -5,16 +5,16 @@ * 2.0. */ -const AlertNotifyWhenTypeValues = [ +const RuleNotifyWhenTypeValues = [ 'onActionGroupChange', 'onActiveAlert', 'onThrottleInterval', ] as const; -export type AlertNotifyWhenType = typeof AlertNotifyWhenTypeValues[number]; +export type RuleNotifyWhenType = typeof RuleNotifyWhenTypeValues[number]; export function validateNotifyWhenType(notifyWhen: string) { - if (AlertNotifyWhenTypeValues.includes(notifyWhen as AlertNotifyWhenType)) { + if (RuleNotifyWhenTypeValues.includes(notifyWhen as RuleNotifyWhenType)) { return; } - return `string is not a valid AlertNotifyWhenType: ${notifyWhen}`; + return `string is not a valid RuleNotifyWhenType: ${notifyWhen}`; } diff --git a/x-pack/plugins/alerting/common/rule_task_instance.ts b/x-pack/plugins/alerting/common/rule_task_instance.ts index 54483babdfdd3..01d97efd57b15 100644 --- a/x-pack/plugins/alerting/common/rule_task_instance.ts +++ b/x-pack/plugins/alerting/common/rule_task_instance.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; import { rawAlertInstance } from './alert_instance'; import { DateFromString } from './date_from_string'; -import { IntervalSchedule, RuleMonitoring } from './alert'; +import { IntervalSchedule, RuleMonitoring } from './rule'; export const ruleStateSchema = t.partial({ alertTypeState: t.record(t.string, t.unknown), diff --git a/x-pack/plugins/alerting/public/alert_api.test.ts b/x-pack/plugins/alerting/public/alert_api.test.ts index 8e6331a29c1c5..eae4bc72a02e8 100644 --- a/x-pack/plugins/alerting/public/alert_api.test.ts +++ b/x-pack/plugins/alerting/public/alert_api.test.ts @@ -5,19 +5,19 @@ * 2.0. */ -import { Alert, RuleType } from '../common'; +import { Rule, RuleType } from '../common'; import { httpServiceMock } from '../../../../src/core/public/mocks'; -import { loadAlert, loadAlertType, loadAlertTypes } from './alert_api'; +import { loadRule, loadRuleType, loadRuleTypes } from './alert_api'; const http = httpServiceMock.createStartContract(); beforeEach(() => jest.resetAllMocks()); -describe('loadAlertTypes', () => { - test('should call get alert types API', async () => { +describe('loadRuleTypes', () => { + test('should call get rule types API', async () => { http.get.mockResolvedValueOnce([getApiRuleType()]); - const result = await loadAlertTypes({ http }); + const result = await loadRuleTypes({ http }); expect(result).toMatchInlineSnapshot(` Array [ Object { @@ -77,22 +77,22 @@ describe('loadAlertTypes', () => { }); }); -describe('loadAlertType', () => { - test('should call get alert types API', async () => { +describe('loadRuleType', () => { + test('should call get rule types API', async () => { const ruleType = getApiRuleType(); http.get.mockResolvedValueOnce([ruleType]); - const result = await loadAlertType({ http, id: ruleType.id }); + const result = await loadRuleType({ http, id: ruleType.id }); expect(result).toEqual(getRuleType()); }); }); -describe('loadAlert', () => { +describe('loadRule', () => { test('should call get API with base parameters', async () => { const apiRule = getApiRule(); http.get.mockResolvedValueOnce(apiRule); - const res = await loadAlert({ http, alertId: apiRule.id }); + const res = await loadRule({ http, ruleId: apiRule.id }); expect(res).toEqual(getRule()); const fixedDate = new Date('2021-12-11T16:59:50.152Z'); @@ -292,7 +292,7 @@ function getApiRule() { }; } -function getRule(): Alert<{ x: number }> { +function getRule(): Rule<{ x: number }> { return { id: '3d534c70-582b-11ec-8995-2b1578a3bc5d', notifyWhen: 'onActiveAlert', diff --git a/x-pack/plugins/alerting/public/alert_api.ts b/x-pack/plugins/alerting/public/alert_api.ts index e64a236d5302b..93ea8c79bdd34 100644 --- a/x-pack/plugins/alerting/public/alert_api.ts +++ b/x-pack/plugins/alerting/public/alert_api.ts @@ -7,35 +7,35 @@ import { HttpSetup } from 'kibana/public'; import { BASE_ALERTING_API_PATH, INTERNAL_BASE_ALERTING_API_PATH } from '../common'; -import type { Alert, RuleType } from '../common'; +import type { Rule, RuleType } from '../common'; import { AsApiContract } from '../../actions/common'; -import { transformAlert, transformRuleType, ApiAlert } from './lib/common_transformations'; +import { transformRule, transformRuleType, ApiRule } from './lib/common_transformations'; -export async function loadAlertTypes({ http }: { http: HttpSetup }): Promise { +export async function loadRuleTypes({ http }: { http: HttpSetup }): Promise { const res = await http.get>>( `${BASE_ALERTING_API_PATH}/rule_types` ); return res.map((ruleType) => transformRuleType(ruleType)); } -export async function loadAlertType({ +export async function loadRuleType({ http, id, }: { http: HttpSetup; id: RuleType['id']; }): Promise { - const ruleTypes = await loadAlertTypes({ http }); + const ruleTypes = await loadRuleTypes({ http }); return ruleTypes.find((type) => type.id === id); } -export async function loadAlert({ +export async function loadRule({ http, - alertId, + ruleId, }: { http: HttpSetup; - alertId: string; -}): Promise { - const res = await http.get(`${INTERNAL_BASE_ALERTING_API_PATH}/rule/${alertId}`); - return transformAlert(res); + ruleId: string; +}): Promise { + const res = await http.get(`${INTERNAL_BASE_ALERTING_API_PATH}/rule/${ruleId}`); + return transformRule(res); } diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts b/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts index 0d07b708825cb..204076151614b 100644 --- a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts +++ b/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts @@ -6,12 +6,12 @@ */ import { AlertNavigationRegistry } from './alert_navigation_registry'; -import { RuleType, RecoveredActionGroup, SanitizedAlert } from '../../common'; +import { RuleType, RecoveredActionGroup, SanitizedRule } from '../../common'; import uuid from 'uuid'; beforeEach(() => jest.resetAllMocks()); -const mockAlertType = (id: string): RuleType => ({ +const mockRuleType = (id: string): RuleType => ({ id, name: id, actionGroups: [], @@ -30,33 +30,33 @@ const mockAlertType = (id: string): RuleType => ({ }); describe('AlertNavigationRegistry', () => { - function handler(alert: SanitizedAlert) { + function handler(rule: SanitizedRule) { return {}; } describe('has()', () => { test('returns false for unregistered consumer handlers', () => { const registry = new AlertNavigationRegistry(); - expect(registry.has('siem', mockAlertType(uuid.v4()))).toEqual(false); + expect(registry.has('siem', mockRuleType(uuid.v4()))).toEqual(false); }); - test('returns false for unregistered alert types handlers', () => { + test('returns false for unregistered rule types handlers', () => { const registry = new AlertNavigationRegistry(); - expect(registry.has('siem', mockAlertType('index_threshold'))).toEqual(false); + expect(registry.has('siem', mockRuleType('index_threshold'))).toEqual(false); }); - test('returns true for registered consumer & alert types handlers', () => { + test('returns true for registered consumer & rule types handlers', () => { const registry = new AlertNavigationRegistry(); - const alertType = mockAlertType('index_threshold'); - registry.register('siem', alertType.id, handler); - expect(registry.has('siem', alertType)).toEqual(true); + const ruleType = mockRuleType('index_threshold'); + registry.register('siem', ruleType.id, handler); + expect(registry.has('siem', ruleType)).toEqual(true); }); test('returns true for registered consumer with default handler', () => { const registry = new AlertNavigationRegistry(); - const alertType = mockAlertType('index_threshold'); + const ruleType = mockRuleType('index_threshold'); registry.registerDefault('siem', handler); - expect(registry.has('siem', alertType)).toEqual(true); + expect(registry.has('siem', ruleType)).toEqual(true); }); }); @@ -75,42 +75,42 @@ describe('AlertNavigationRegistry', () => { }); describe('register()', () => { - test('registers a handler by consumer & Alert Type', () => { + test('registers a handler by consumer & Rule Type', () => { const registry = new AlertNavigationRegistry(); - const alertType = mockAlertType('index_threshold'); - registry.register('siem', alertType.id, handler); - expect(registry.has('siem', alertType)).toEqual(true); + const ruleType = mockRuleType('index_threshold'); + registry.register('siem', ruleType.id, handler); + expect(registry.has('siem', ruleType)).toEqual(true); }); test('allows registeration of multiple handlers for the same consumer', () => { const registry = new AlertNavigationRegistry(); - const indexThresholdAlertType = mockAlertType('index_threshold'); + const indexThresholdAlertType = mockRuleType('index_threshold'); registry.register('siem', indexThresholdAlertType.id, handler); expect(registry.has('siem', indexThresholdAlertType)).toEqual(true); - const geoAlertType = mockAlertType('geogrid'); - registry.register('siem', geoAlertType.id, handler); - expect(registry.has('siem', geoAlertType)).toEqual(true); + const geoRuleType = mockRuleType('geogrid'); + registry.register('siem', geoRuleType.id, handler); + expect(registry.has('siem', geoRuleType)).toEqual(true); }); - test('allows registeration of multiple handlers for the same Alert Type', () => { + test('allows registeration of multiple handlers for the same Rule Type', () => { const registry = new AlertNavigationRegistry(); - const indexThresholdAlertType = mockAlertType('geogrid'); - registry.register('siem', indexThresholdAlertType.id, handler); - expect(registry.has('siem', indexThresholdAlertType)).toEqual(true); + const indexThresholdRuleType = mockRuleType('geogrid'); + registry.register('siem', indexThresholdRuleType.id, handler); + expect(registry.has('siem', indexThresholdRuleType)).toEqual(true); - registry.register('apm', indexThresholdAlertType.id, handler); - expect(registry.has('apm', indexThresholdAlertType)).toEqual(true); + registry.register('apm', indexThresholdRuleType.id, handler); + expect(registry.has('apm', indexThresholdRuleType)).toEqual(true); }); test('throws if an existing handler is registered', () => { const registry = new AlertNavigationRegistry(); - const alertType = mockAlertType('index_threshold'); - registry.register('siem', alertType.id, handler); + const ruleType = mockRuleType('index_threshold'); + registry.register('siem', ruleType.id, handler); expect(() => { - registry.register('siem', alertType.id, handler); + registry.register('siem', ruleType.id, handler); }).toThrowErrorMatchingInlineSnapshot( `"Navigation for Alert type \\"index_threshold\\" within \\"siem\\" is already registered."` ); @@ -130,9 +130,9 @@ describe('AlertNavigationRegistry', () => { registry.registerDefault('siem', handler); expect(registry.hasDefaultHandler('siem')).toEqual(true); - const geoAlertType = mockAlertType('geogrid'); - registry.register('siem', geoAlertType.id, handler); - expect(registry.has('siem', geoAlertType)).toEqual(true); + const geoRuleType = mockRuleType('geogrid'); + registry.register('siem', geoRuleType.id, handler); + expect(registry.has('siem', geoRuleType)).toEqual(true); }); test('throws if an existing handler is registered', () => { @@ -147,47 +147,47 @@ describe('AlertNavigationRegistry', () => { }); describe('get()', () => { - test('returns registered handlers by consumer & Alert Type', () => { + test('returns registered handlers by consumer & Rule Type', () => { const registry = new AlertNavigationRegistry(); - function indexThresholdHandler(alert: SanitizedAlert) { + function indexThresholdHandler(rule: SanitizedRule) { return {}; } - const indexThresholdAlertType = mockAlertType('indexThreshold'); - registry.register('siem', indexThresholdAlertType.id, indexThresholdHandler); - expect(registry.get('siem', indexThresholdAlertType)).toEqual(indexThresholdHandler); + const indexThresholdRuleType = mockRuleType('indexThreshold'); + registry.register('siem', indexThresholdRuleType.id, indexThresholdHandler); + expect(registry.get('siem', indexThresholdRuleType)).toEqual(indexThresholdHandler); }); - test('returns default handlers by consumer when there is no handler for requested alert type', () => { + test('returns default handlers by consumer when there is no handler for requested rule type', () => { const registry = new AlertNavigationRegistry(); - function defaultHandler(alert: SanitizedAlert) { + function defaultHandler(rule: SanitizedRule) { return {}; } registry.registerDefault('siem', defaultHandler); - expect(registry.get('siem', mockAlertType('geogrid'))).toEqual(defaultHandler); + expect(registry.get('siem', mockRuleType('geogrid'))).toEqual(defaultHandler); }); - test('returns default handlers by consumer when there are other alert type handler', () => { + test('returns default handlers by consumer when there are other rule type handler', () => { const registry = new AlertNavigationRegistry(); - registry.register('siem', mockAlertType('indexThreshold').id, () => ({})); + registry.register('siem', mockRuleType('indexThreshold').id, () => ({})); - function defaultHandler(alert: SanitizedAlert) { + function defaultHandler(rule: SanitizedRule) { return {}; } registry.registerDefault('siem', defaultHandler); - expect(registry.get('siem', mockAlertType('geogrid'))).toEqual(defaultHandler); + expect(registry.get('siem', mockRuleType('geogrid'))).toEqual(defaultHandler); }); test('throws if a handler isnt registered', () => { const registry = new AlertNavigationRegistry(); - const alertType = mockAlertType('index_threshold'); + const ruleType = mockRuleType('index_threshold'); - expect(() => registry.get('siem', alertType)).toThrowErrorMatchingInlineSnapshot( + expect(() => registry.get('siem', ruleType)).toThrowErrorMatchingInlineSnapshot( `"Navigation for Alert type \\"index_threshold\\" within \\"siem\\" is not registered."` ); }); diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts b/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts index ea36d0edd1366..3c7b7aa3c8c06 100644 --- a/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts +++ b/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts @@ -6,7 +6,7 @@ */ import { JsonObject } from '@kbn/utility-types'; -import { SanitizedAlert } from '../../common'; +import { SanitizedRule } from '../../common'; /** * Returns information that can be used to navigate to a specific page to view the given rule. @@ -17,4 +17,4 @@ import { SanitizedAlert } from '../../common'; * originally registered to {@link PluginSetupContract.registerNavigation}. * */ -export type AlertNavigationHandler = (alert: SanitizedAlert) => JsonObject | string; +export type AlertNavigationHandler = (rule: SanitizedRule) => JsonObject | string; diff --git a/x-pack/plugins/alerting/public/lib/common_transformations.test.ts b/x-pack/plugins/alerting/public/lib/common_transformations.test.ts index 00d830bcf7611..51d24538b449e 100644 --- a/x-pack/plugins/alerting/public/lib/common_transformations.test.ts +++ b/x-pack/plugins/alerting/public/lib/common_transformations.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { ApiAlert, transformAlert } from './common_transformations'; -import { AlertExecutionStatusErrorReasons } from '../../common'; +import { ApiRule, transformRule } from './common_transformations'; +import { RuleExecutionStatusErrorReasons } from '../../common'; beforeEach(() => jest.resetAllMocks()); @@ -16,8 +16,8 @@ const dateUpdated = new Date(dateFixed - 1000); const dateExecuted = new Date(dateFixed); describe('common_transformations', () => { - test('transformAlert() with all optional fields', () => { - const apiAlert: ApiAlert = { + test('transformRule() with all optional fields', () => { + const apiRule: ApiRule = { id: 'some-id', name: 'some-name', enabled: true, @@ -50,12 +50,12 @@ describe('common_transformations', () => { last_duration: 42, status: 'error', error: { - reason: AlertExecutionStatusErrorReasons.Unknown, + reason: RuleExecutionStatusErrorReasons.Unknown, message: 'this is just a test', }, }, }; - expect(transformAlert(apiAlert)).toMatchInlineSnapshot(` + expect(transformRule(apiRule)).toMatchInlineSnapshot(` Object { "actions": Array [ Object { @@ -120,8 +120,8 @@ describe('common_transformations', () => { `); }); - test('transformAlert() with no optional fields', () => { - const apiAlert: ApiAlert = { + test('transformRule() with no optional fields', () => { + const apiRule: ApiRule = { id: 'some-id', name: 'some-name', enabled: true, @@ -153,7 +153,7 @@ describe('common_transformations', () => { status: 'error', }, }; - expect(transformAlert(apiAlert)).toMatchInlineSnapshot(` + expect(transformRule(apiRule)).toMatchInlineSnapshot(` Object { "actions": Array [ Object { diff --git a/x-pack/plugins/alerting/public/lib/common_transformations.ts b/x-pack/plugins/alerting/public/lib/common_transformations.ts index 4d2d0e3387082..8f1f0c5c72d84 100644 --- a/x-pack/plugins/alerting/public/lib/common_transformations.ts +++ b/x-pack/plugins/alerting/public/lib/common_transformations.ts @@ -4,21 +4,21 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { AlertExecutionStatus, Alert, AlertAction, RuleType } from '../../common'; +import { RuleExecutionStatus, Rule, RuleAction, RuleType } from '../../common'; import { AsApiContract } from '../../../actions/common'; -function transformAction(input: AsApiContract): AlertAction { +function transformAction(input: AsApiContract): RuleAction { const { connector_type_id: actionTypeId, ...rest } = input; return { actionTypeId, ...rest }; } // AsApiContract does not deal with object properties that are dates - the // API version needs to be a string, and the non-API version needs to be a Date -type ApiAlertExecutionStatus = Omit, 'last_execution_date'> & { +type ApiRuleExecutionStatus = Omit, 'last_execution_date'> & { last_execution_date: string; }; -function transformExecutionStatus(input: ApiAlertExecutionStatus): AlertExecutionStatus { +function transformExecutionStatus(input: ApiRuleExecutionStatus): RuleExecutionStatus { const { last_execution_date: lastExecutionDate, last_duration: lastDuration, ...rest } = input; return { lastExecutionDate: new Date(lastExecutionDate), @@ -29,8 +29,8 @@ function transformExecutionStatus(input: ApiAlertExecutionStatus): AlertExecutio // AsApiContract does not deal with object properties that also // need snake -> camel conversion, Dates, are renamed, etc, so we do by hand -export type ApiAlert = Omit< - AsApiContract, +export type ApiRule = Omit< + AsApiContract, | 'execution_status' | 'actions' | 'created_at' @@ -38,15 +38,15 @@ export type ApiAlert = Omit< | 'alert_type_id' | 'muted_instance_ids' > & { - execution_status: ApiAlertExecutionStatus; - actions: Array>; + execution_status: ApiRuleExecutionStatus; + actions: Array>; created_at: string; updated_at: string; rule_type_id: string; muted_alert_ids: string[]; }; -export function transformAlert(input: ApiAlert): Alert { +export function transformRule(input: ApiRule): Rule { const { rule_type_id: alertTypeId, created_by: createdBy, diff --git a/x-pack/plugins/alerting/public/plugin.ts b/x-pack/plugins/alerting/public/plugin.ts index 71fb0c7fe32b2..e1d683d74cc9f 100644 --- a/x-pack/plugins/alerting/public/plugin.ts +++ b/x-pack/plugins/alerting/public/plugin.ts @@ -8,8 +8,8 @@ import { CoreSetup, Plugin, CoreStart } from 'src/core/public'; import { AlertNavigationRegistry, AlertNavigationHandler } from './alert_navigation_registry'; -import { loadAlert, loadAlertType } from './alert_api'; -import { Alert, AlertNavigation } from '../common'; +import { loadRule, loadRuleType } from './alert_api'; +import { Rule, RuleNavigation } from '../common'; export interface PluginSetupContract { /** @@ -18,7 +18,7 @@ export interface PluginSetupContract { * anything with this information, but it can be used by other plugins via the `getNavigation` functionality. Currently * the trigger_actions_ui plugin uses it to expose the link from the generic rule details view in Stack Management. * - * @param applicationId The application id that the user should be navigated to, to view a particular alert in a custom way. + * @param applicationId The application id that the user should be navigated to, to view a particular rule in a custom way. * @param ruleType The rule type that has been registered with Alerting.Server.PluginSetupContract.registerType. If * no such rule with that id exists, a warning is output to the console log. It used to throw an error, but that was temporarily moved * because it was causing flaky test failures with https://github.com/elastic/kibana/issues/59229 and needs to be @@ -39,14 +39,14 @@ export interface PluginSetupContract { * anything with this information, but it can be used by other plugins via the `getNavigation` functionality. Currently * the trigger_actions_ui plugin uses it to expose the link from the generic rule details view in Stack Management. * - * @param applicationId The application id that the user should be navigated to, to view a particular alert in a custom way. + * @param applicationId The application id that the user should be navigated to, to view a particular rule in a custom way. * @param handler The navigation handler should return either a relative URL, or a state object. This information can be used, * in conjunction with the consumer id, to navigate the user to a custom URL to view a rule's details. */ registerDefaultNavigation: (applicationId: string, handler: AlertNavigationHandler) => void; } export interface PluginStartContract { - getNavigation: (alertId: Alert['id']) => Promise; + getNavigation: (ruleId: Rule['id']) => Promise; } export class AlertingPublicPlugin implements Plugin { @@ -75,21 +75,21 @@ export class AlertingPublicPlugin implements Plugin { - const alert = await loadAlert({ http: core.http, alertId }); - const alertType = await loadAlertType({ http: core.http, id: alert.alertTypeId }); + getNavigation: async (ruleId: Rule['id']) => { + const rule = await loadRule({ http: core.http, ruleId }); + const ruleType = await loadRuleType({ http: core.http, id: rule.alertTypeId }); - if (!alertType) { + if (!ruleType) { // eslint-disable-next-line no-console console.log( - `Unable to get navigation for alert type "${alert.alertTypeId}" because it is not registered on the server side.` + `Unable to get navigation for rule type "${rule.alertTypeId}" because it is not registered on the server side.` ); return; } - if (this.alertNavigationRegistry!.has(alert.consumer, alertType)) { - const navigationHandler = this.alertNavigationRegistry!.get(alert.consumer, alertType); - const state = navigationHandler(alert); + if (this.alertNavigationRegistry!.has(rule.consumer, ruleType)) { + const navigationHandler = this.alertNavigationRegistry!.get(rule.consumer, ruleType); + const state = navigationHandler(rule); return typeof state === 'string' ? { path: state } : { state }; } }, diff --git a/x-pack/plugins/alerting/server/health/get_health.test.ts b/x-pack/plugins/alerting/server/health/get_health.test.ts index c31a71138248b..19d528d44e54f 100644 --- a/x-pack/plugins/alerting/server/health/get_health.test.ts +++ b/x-pack/plugins/alerting/server/health/get_health.test.ts @@ -9,13 +9,13 @@ import { savedObjectsRepositoryMock, savedObjectsServiceMock, } from '../../../../../src/core/server/mocks'; -import { AlertExecutionStatusErrorReasons, HealthStatus } from '../types'; +import { RuleExecutionStatusErrorReasons, HealthStatus } from '../types'; import { getAlertingHealthStatus, getHealth } from './get_health'; const savedObjectsRepository = savedObjectsRepositoryMock.create(); describe('getHealth()', () => { - test('return true if some of alerts has a decryption error', async () => { + test('return true if some rules has a decryption error', async () => { const lastExecutionDateError = new Date().toISOString(); const lastExecutionDate = new Date().toISOString(); savedObjectsRepository.find.mockResolvedValueOnce({ @@ -46,7 +46,7 @@ describe('getHealth()', () => { status: 'error', lastExecutionDate: lastExecutionDateError, error: { - reason: AlertExecutionStatusErrorReasons.Decrypt, + reason: RuleExecutionStatusErrorReasons.Decrypt, message: 'Failed decrypt', }, }, @@ -120,7 +120,7 @@ describe('getHealth()', () => { expect(savedObjectsRepository.find).toHaveBeenCalledTimes(4); }); - test('return false if no alerts with a decryption error', async () => { + test('return false if no rules with a decryption error', async () => { const lastExecutionDateError = new Date().toISOString(); const lastExecutionDate = new Date().toISOString(); savedObjectsRepository.find.mockResolvedValueOnce({ @@ -158,7 +158,7 @@ describe('getHealth()', () => { status: 'error', lastExecutionDate: lastExecutionDateError, error: { - reason: AlertExecutionStatusErrorReasons.Execute, + reason: RuleExecutionStatusErrorReasons.Execute, message: 'Failed', }, }, @@ -226,7 +226,7 @@ describe('getHealth()', () => { }); describe('getAlertingHealthStatus()', () => { - test('return the proper framework state if some of alerts has a decryption error', async () => { + test('return the proper framework state if some rules has a decryption error', async () => { const savedObjects = savedObjectsServiceMock.createStartContract(); const lastExecutionDateError = new Date().toISOString(); savedObjectsRepository.find.mockResolvedValueOnce({ @@ -257,7 +257,7 @@ describe('getAlertingHealthStatus()', () => { status: 'error', lastExecutionDate: lastExecutionDateError, error: { - reason: AlertExecutionStatusErrorReasons.Decrypt, + reason: RuleExecutionStatusErrorReasons.Decrypt, message: 'Failed decrypt', }, }, diff --git a/x-pack/plugins/alerting/server/health/get_health.ts b/x-pack/plugins/alerting/server/health/get_health.ts index 09a5922576192..2bf554894917d 100644 --- a/x-pack/plugins/alerting/server/health/get_health.ts +++ b/x-pack/plugins/alerting/server/health/get_health.ts @@ -6,7 +6,7 @@ */ import { ISavedObjectsRepository, SavedObjectsServiceStart } from 'src/core/server'; -import { AlertsHealth, HealthStatus, RawRule, AlertExecutionStatusErrorReasons } from '../types'; +import { AlertsHealth, HealthStatus, RawRule, RuleExecutionStatusErrorReasons } from '../types'; export const getHealth = async ( internalSavedObjectsRepository: ISavedObjectsRepository @@ -27,7 +27,7 @@ export const getHealth = async ( }; const { saved_objects: decryptErrorData } = await internalSavedObjectsRepository.find({ - filter: `alert.attributes.executionStatus.status:error and alert.attributes.executionStatus.error.reason:${AlertExecutionStatusErrorReasons.Decrypt}`, + filter: `alert.attributes.executionStatus.status:error and alert.attributes.executionStatus.error.reason:${RuleExecutionStatusErrorReasons.Decrypt}`, fields: ['executionStatus'], type: 'alert', sortField: 'executionStatus.lastExecutionDate', @@ -45,7 +45,7 @@ export const getHealth = async ( } const { saved_objects: executeErrorData } = await internalSavedObjectsRepository.find({ - filter: `alert.attributes.executionStatus.status:error and alert.attributes.executionStatus.error.reason:${AlertExecutionStatusErrorReasons.Execute}`, + filter: `alert.attributes.executionStatus.status:error and alert.attributes.executionStatus.error.reason:${RuleExecutionStatusErrorReasons.Execute}`, fields: ['executionStatus'], type: 'alert', sortField: 'executionStatus.lastExecutionDate', @@ -63,7 +63,7 @@ export const getHealth = async ( } const { saved_objects: readErrorData } = await internalSavedObjectsRepository.find({ - filter: `alert.attributes.executionStatus.status:error and alert.attributes.executionStatus.error.reason:${AlertExecutionStatusErrorReasons.Read}`, + filter: `alert.attributes.executionStatus.status:error and alert.attributes.executionStatus.error.reason:${RuleExecutionStatusErrorReasons.Read}`, fields: ['executionStatus'], type: 'alert', sortField: 'executionStatus.lastExecutionDate', diff --git a/x-pack/plugins/alerting/server/index.ts b/x-pack/plugins/alerting/server/index.ts index b44df6c3d1c86..f73fb7f964b25 100644 --- a/x-pack/plugins/alerting/server/index.ts +++ b/x-pack/plugins/alerting/server/index.ts @@ -18,12 +18,12 @@ export type { ActionGroup, ActionGroupIdsOf, AlertingPlugin, - AlertExecutorOptions, - AlertActionParams, - AlertServices, - AlertTypeState, - AlertTypeParams, - PartialAlert, + RuleExecutorOptions, + RuleExecutorServices, + RuleActionParams, + RuleTypeState, + RuleTypeParams, + PartialRule, AlertInstanceState, AlertInstanceContext, AlertingApiRequestHandlerContext, diff --git a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts index 6290d5d213046..b0a46807af5c1 100644 --- a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts +++ b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts @@ -6,7 +6,7 @@ */ import { random, mean } from 'lodash'; -import { SanitizedAlert, AlertSummary } from '../types'; +import { SanitizedRule, AlertSummary } from '../types'; import { IValidatedEvent } from '../../../event_log/server'; import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER, LEGACY_EVENT_LOG_ACTIONS } from '../plugin'; import { alertSummaryFromEventLog } from './alert_summary_from_event_log'; @@ -716,11 +716,11 @@ export class EventsFactory { } } -function createRule(overrides: Partial): SanitizedAlert<{ bar: boolean }> { +function createRule(overrides: Partial): SanitizedRule<{ bar: boolean }> { return { ...BaseRule, ...overrides }; } -const BaseRule: SanitizedAlert<{ bar: boolean }> = { +const BaseRule: SanitizedRule<{ bar: boolean }> = { id: 'rule-123', alertTypeId: '123', schedule: { interval: '10s' }, diff --git a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts index 9a40c4ebf1940..e42a135b3de04 100644 --- a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts +++ b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts @@ -6,14 +6,14 @@ */ import { mean } from 'lodash'; -import { SanitizedAlert, AlertSummary, AlertStatus } from '../types'; +import { SanitizedRule, AlertSummary, AlertStatus } from '../types'; import { IEvent } from '../../../event_log/server'; import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER, LEGACY_EVENT_LOG_ACTIONS } from '../plugin'; const Millis2Nanos = 1000 * 1000; export interface AlertSummaryFromEventLogParams { - rule: SanitizedAlert<{ bar: boolean }>; + rule: SanitizedRule<{ bar: boolean }>; events: IEvent[]; executionEvents: IEvent[]; dateStart: string; diff --git a/x-pack/plugins/alerting/server/lib/error_with_reason.test.ts b/x-pack/plugins/alerting/server/lib/error_with_reason.test.ts index 0316db497124a..2370e2d6fef20 100644 --- a/x-pack/plugins/alerting/server/lib/error_with_reason.test.ts +++ b/x-pack/plugins/alerting/server/lib/error_with_reason.test.ts @@ -6,21 +6,21 @@ */ import { ErrorWithReason, getReasonFromError, isErrorWithReason } from './error_with_reason'; -import { AlertExecutionStatusErrorReasons } from '../types'; +import { RuleExecutionStatusErrorReasons } from '../types'; describe('ErrorWithReason', () => { const plainError = new Error('well, actually'); - const errorWithReason = new ErrorWithReason(AlertExecutionStatusErrorReasons.Decrypt, plainError); + const errorWithReason = new ErrorWithReason(RuleExecutionStatusErrorReasons.Decrypt, plainError); test('ErrorWithReason class', () => { expect(errorWithReason.message).toBe(plainError.message); expect(errorWithReason.error).toBe(plainError); - expect(errorWithReason.reason).toBe(AlertExecutionStatusErrorReasons.Decrypt); + expect(errorWithReason.reason).toBe(RuleExecutionStatusErrorReasons.Decrypt); }); test('getReasonFromError()', () => { expect(getReasonFromError(plainError)).toBe('unknown'); - expect(getReasonFromError(errorWithReason)).toBe(AlertExecutionStatusErrorReasons.Decrypt); + expect(getReasonFromError(errorWithReason)).toBe(RuleExecutionStatusErrorReasons.Decrypt); }); test('isErrorWithReason()', () => { diff --git a/x-pack/plugins/alerting/server/lib/error_with_reason.ts b/x-pack/plugins/alerting/server/lib/error_with_reason.ts index 018694cb32c46..4c474d39426e3 100644 --- a/x-pack/plugins/alerting/server/lib/error_with_reason.ts +++ b/x-pack/plugins/alerting/server/lib/error_with_reason.ts @@ -5,24 +5,24 @@ * 2.0. */ -import { AlertExecutionStatusErrorReasons } from '../types'; +import { RuleExecutionStatusErrorReasons } from '../types'; export class ErrorWithReason extends Error { - public readonly reason: AlertExecutionStatusErrorReasons; + public readonly reason: RuleExecutionStatusErrorReasons; public readonly error: Error; - constructor(reason: AlertExecutionStatusErrorReasons, error: Error) { + constructor(reason: RuleExecutionStatusErrorReasons, error: Error) { super(error.message); this.error = error; this.reason = reason; } } -export function getReasonFromError(error: Error): AlertExecutionStatusErrorReasons { +export function getReasonFromError(error: Error): RuleExecutionStatusErrorReasons { if (isErrorWithReason(error)) { return error.reason; } - return AlertExecutionStatusErrorReasons.Unknown; + return RuleExecutionStatusErrorReasons.Unknown; } export function isErrorWithReason(error: Error | ErrorWithReason): error is ErrorWithReason { diff --git a/x-pack/plugins/alerting/server/lib/errors/index.ts b/x-pack/plugins/alerting/server/lib/errors/index.ts index 7ac8d9fced5cd..a2dcd45bbb63b 100644 --- a/x-pack/plugins/alerting/server/lib/errors/index.ts +++ b/x-pack/plugins/alerting/server/lib/errors/index.ts @@ -16,6 +16,6 @@ export function isErrorThatHandlesItsOwnResponse( export type { ErrorThatHandlesItsOwnResponse, ElasticsearchError }; export { getEsErrorMessage }; -export type { AlertTypeDisabledReason } from './alert_type_disabled'; -export { AlertTypeDisabledError } from './alert_type_disabled'; +export type { RuleTypeDisabledReason } from './rule_type_disabled'; +export { RuleTypeDisabledError } from './rule_type_disabled'; export { RuleMutedError } from './rule_muted'; diff --git a/x-pack/plugins/alerting/server/lib/errors/alert_type_disabled.ts b/x-pack/plugins/alerting/server/lib/errors/rule_type_disabled.ts similarity index 72% rename from x-pack/plugins/alerting/server/lib/errors/alert_type_disabled.ts rename to x-pack/plugins/alerting/server/lib/errors/rule_type_disabled.ts index 554497cc6c22b..f907436ddf955 100644 --- a/x-pack/plugins/alerting/server/lib/errors/alert_type_disabled.ts +++ b/x-pack/plugins/alerting/server/lib/errors/rule_type_disabled.ts @@ -8,16 +8,16 @@ import { KibanaResponseFactory } from '../../../../../../src/core/server'; import { ErrorThatHandlesItsOwnResponse } from './types'; -export type AlertTypeDisabledReason = +export type RuleTypeDisabledReason = | 'config' | 'license_unavailable' | 'license_invalid' | 'license_expired'; -export class AlertTypeDisabledError extends Error implements ErrorThatHandlesItsOwnResponse { - public readonly reason: AlertTypeDisabledReason; +export class RuleTypeDisabledError extends Error implements ErrorThatHandlesItsOwnResponse { + public readonly reason: RuleTypeDisabledReason; - constructor(message: string, reason: AlertTypeDisabledReason) { + constructor(message: string, reason: RuleTypeDisabledReason) { super(message); this.reason = reason; } diff --git a/x-pack/plugins/alerting/server/lib/get_alert_notify_when_type.test.ts b/x-pack/plugins/alerting/server/lib/get_rule_notify_when_type.test.ts similarity index 64% rename from x-pack/plugins/alerting/server/lib/get_alert_notify_when_type.test.ts rename to x-pack/plugins/alerting/server/lib/get_rule_notify_when_type.test.ts index 92e7673c2e48e..747f5a8a8cd21 100644 --- a/x-pack/plugins/alerting/server/lib/get_alert_notify_when_type.test.ts +++ b/x-pack/plugins/alerting/server/lib/get_rule_notify_when_type.test.ts @@ -5,20 +5,20 @@ * 2.0. */ -import { getAlertNotifyWhenType } from './get_alert_notify_when_type'; +import { getRuleNotifyWhenType } from './get_rule_notify_when_type'; test(`should return 'notifyWhen' value if value is set and throttle is null`, () => { - expect(getAlertNotifyWhenType('onActionGroupChange', null)).toEqual('onActionGroupChange'); + expect(getRuleNotifyWhenType('onActionGroupChange', null)).toEqual('onActionGroupChange'); }); test(`should return 'notifyWhen' value if value is set and throttle is defined`, () => { - expect(getAlertNotifyWhenType('onActionGroupChange', '10m')).toEqual('onActionGroupChange'); + expect(getRuleNotifyWhenType('onActionGroupChange', '10m')).toEqual('onActionGroupChange'); }); test(`should return 'onThrottleInterval' value if 'notifyWhen' is null and throttle is defined`, () => { - expect(getAlertNotifyWhenType(null, '10m')).toEqual('onThrottleInterval'); + expect(getRuleNotifyWhenType(null, '10m')).toEqual('onThrottleInterval'); }); test(`should return 'onActiveAlert' value if 'notifyWhen' is null and throttle is null`, () => { - expect(getAlertNotifyWhenType(null, null)).toEqual('onActiveAlert'); + expect(getRuleNotifyWhenType(null, null)).toEqual('onActiveAlert'); }); diff --git a/x-pack/plugins/alerting/server/lib/get_alert_notify_when_type.ts b/x-pack/plugins/alerting/server/lib/get_rule_notify_when_type.ts similarity index 77% rename from x-pack/plugins/alerting/server/lib/get_alert_notify_when_type.ts rename to x-pack/plugins/alerting/server/lib/get_rule_notify_when_type.ts index a4bb0fe68a25b..53ccacde75e5c 100644 --- a/x-pack/plugins/alerting/server/lib/get_alert_notify_when_type.ts +++ b/x-pack/plugins/alerting/server/lib/get_rule_notify_when_type.ts @@ -5,12 +5,12 @@ * 2.0. */ -import { AlertNotifyWhenType } from '../types'; +import { RuleNotifyWhenType } from '../types'; -export function getAlertNotifyWhenType( - notifyWhen: AlertNotifyWhenType | null, +export function getRuleNotifyWhenType( + notifyWhen: RuleNotifyWhenType | null, throttle: string | null -): AlertNotifyWhenType { +): RuleNotifyWhenType { // We allow notifyWhen to be null for backwards compatibility. If it is null, determine its // value based on whether the throttle is set to a value or null return notifyWhen ? notifyWhen! : throttle ? 'onThrottleInterval' : 'onActiveAlert'; diff --git a/x-pack/plugins/alerting/server/lib/index.ts b/x-pack/plugins/alerting/server/lib/index.ts index 22dbeff82b2d1..57c9a92a8d915 100644 --- a/x-pack/plugins/alerting/server/lib/index.ts +++ b/x-pack/plugins/alerting/server/lib/index.ts @@ -9,15 +9,15 @@ export { parseDuration, validateDurationSchema } from '../../common/parse_durati export type { ILicenseState } from './license_state'; export { LicenseState } from './license_state'; export { validateRuleTypeParams } from './validate_rule_type_params'; -export { getAlertNotifyWhenType } from './get_alert_notify_when_type'; +export { getRuleNotifyWhenType } from './get_rule_notify_when_type'; export { verifyApiAccess } from './license_api_access'; export { ErrorWithReason, getReasonFromError, isErrorWithReason } from './error_with_reason'; export type { - AlertTypeDisabledReason, + RuleTypeDisabledReason, ErrorThatHandlesItsOwnResponse, ElasticsearchError, } from './errors'; -export { AlertTypeDisabledError, RuleMutedError, isErrorThatHandlesItsOwnResponse } from './errors'; +export { RuleTypeDisabledError, RuleMutedError, isErrorThatHandlesItsOwnResponse } from './errors'; export { executionStatusFromState, executionStatusFromError, diff --git a/x-pack/plugins/alerting/server/lib/is_alerting_error.test.ts b/x-pack/plugins/alerting/server/lib/is_alerting_error.test.ts index 643ca9b3f752b..214b95c5ef50c 100644 --- a/x-pack/plugins/alerting/server/lib/is_alerting_error.test.ts +++ b/x-pack/plugins/alerting/server/lib/is_alerting_error.test.ts @@ -9,7 +9,7 @@ import { isAlertSavedObjectNotFoundError, isEsUnavailableError } from './is_aler import { ErrorWithReason } from './error_with_reason'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; import uuid from 'uuid'; -import { AlertExecutionStatusErrorReasons } from '../types'; +import { RuleExecutionStatusErrorReasons } from '../types'; describe('isAlertSavedObjectNotFoundError', () => { const id = uuid.v4(); @@ -27,7 +27,7 @@ describe('isAlertSavedObjectNotFoundError', () => { }); test('identifies SavedObjects Not Found errors wrapped in an ErrorWithReason', () => { - const error = new ErrorWithReason(AlertExecutionStatusErrorReasons.Read, errorSONF); + const error = new ErrorWithReason(RuleExecutionStatusErrorReasons.Read, errorSONF); expect(isAlertSavedObjectNotFoundError(error, id)).toBe(true); }); }); @@ -48,7 +48,7 @@ describe('isEsUnavailableError', () => { }); test('identifies es unavailable errors wrapped in an ErrorWithReason', () => { - const error = new ErrorWithReason(AlertExecutionStatusErrorReasons.Read, errorSONF); + const error = new ErrorWithReason(RuleExecutionStatusErrorReasons.Read, errorSONF); expect(isEsUnavailableError(error, id)).toBe(true); }); }); diff --git a/x-pack/plugins/alerting/server/lib/license_state.ts b/x-pack/plugins/alerting/server/lib/license_state.ts index 162823f8d5850..340d608002fc5 100644 --- a/x-pack/plugins/alerting/server/lib/license_state.ts +++ b/x-pack/plugins/alerting/server/lib/license_state.ts @@ -17,12 +17,12 @@ import { PLUGIN } from '../constants/plugin'; import { getRuleTypeFeatureUsageName } from './get_rule_type_feature_usage_name'; import { RuleType, - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, } from '../types'; -import { AlertTypeDisabledError } from './errors/alert_type_disabled'; +import { RuleTypeDisabledError } from './errors/rule_type_disabled'; export type ILicenseState = PublicMethodsOf; @@ -148,9 +148,9 @@ export class LicenseState { } public ensureLicenseForRuleType< - Params extends AlertTypeParams, - ExtractedParams extends AlertTypeParams, - State extends AlertTypeState, + Params extends RuleTypeParams, + ExtractedParams extends RuleTypeParams, + State extends RuleTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, ActionGroupIds extends string, @@ -179,7 +179,7 @@ export class LicenseState { } switch (check.reason) { case 'unavailable': - throw new AlertTypeDisabledError( + throw new RuleTypeDisabledError( i18n.translate('xpack.alerting.serverSideErrors.unavailableLicenseErrorMessage', { defaultMessage: 'Rule type {ruleTypeId} is disabled because license information is not available at this time.', @@ -190,7 +190,7 @@ export class LicenseState { 'license_unavailable' ); case 'expired': - throw new AlertTypeDisabledError( + throw new RuleTypeDisabledError( i18n.translate('xpack.alerting.serverSideErrors.expirerdLicenseErrorMessage', { defaultMessage: 'Rule type {ruleTypeId} is disabled because your {licenseType} license has expired.', @@ -199,7 +199,7 @@ export class LicenseState { 'license_expired' ); case 'invalid': - throw new AlertTypeDisabledError( + throw new RuleTypeDisabledError( i18n.translate('xpack.alerting.serverSideErrors.invalidLicenseErrorMessage', { defaultMessage: 'Rule {ruleTypeId} is disabled because it requires a {licenseType} license. Go to License Management to view upgrade options.', diff --git a/x-pack/plugins/alerting/server/lib/rule_execution_status.test.ts b/x-pack/plugins/alerting/server/lib/rule_execution_status.test.ts index 44a9e41c89052..ff43f4ffac8a9 100644 --- a/x-pack/plugins/alerting/server/lib/rule_execution_status.test.ts +++ b/x-pack/plugins/alerting/server/lib/rule_execution_status.test.ts @@ -7,8 +7,8 @@ import { loggingSystemMock } from '../../../../../src/core/server/mocks'; import { - AlertExecutionStatusErrorReasons, - AlertExecutionStatusWarningReasons, + RuleExecutionStatusErrorReasons, + RuleExecutionStatusWarningReasons, RuleExecutionState, } from '../types'; import { @@ -115,7 +115,7 @@ describe('RuleExecutionStatus', () => { checkDateIsNearNow(status.lastExecutionDate); expect(status.warning).toEqual({ message: translations.taskRunner.warning.maxExecutableActions, - reason: AlertExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, + reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, }); expect(status.status).toBe('warning'); expect(status.error).toBe(undefined); @@ -136,7 +136,7 @@ describe('RuleExecutionStatus', () => { test('error with a reason', () => { const status = executionStatusFromError( - new ErrorWithReason(AlertExecutionStatusErrorReasons.Execute, new Error('hoo!')) + new ErrorWithReason(RuleExecutionStatusErrorReasons.Execute, new Error('hoo!')) ); expect(status.status).toBe('error'); expect(status.error).toMatchInlineSnapshot(` @@ -151,7 +151,7 @@ describe('RuleExecutionStatus', () => { describe('ruleExecutionStatusToRaw()', () => { const date = new Date('2020-09-03T16:26:58Z'); const status = 'ok'; - const reason = AlertExecutionStatusErrorReasons.Decrypt; + const reason = RuleExecutionStatusErrorReasons.Decrypt; const error = { reason, message: 'wops' }; test('status without an error', () => { @@ -213,7 +213,7 @@ describe('RuleExecutionStatus', () => { describe('ruleExecutionStatusFromRaw()', () => { const date = new Date('2020-09-03T16:26:58Z').toISOString(); const status = 'active'; - const reason = AlertExecutionStatusErrorReasons.Execute; + const reason = RuleExecutionStatusErrorReasons.Execute; const error = { reason, message: 'wops' }; test('no input', () => { diff --git a/x-pack/plugins/alerting/server/lib/rule_execution_status.ts b/x-pack/plugins/alerting/server/lib/rule_execution_status.ts index 9a446d2383c66..a87aed321b16b 100644 --- a/x-pack/plugins/alerting/server/lib/rule_execution_status.ts +++ b/x-pack/plugins/alerting/server/lib/rule_execution_status.ts @@ -7,29 +7,29 @@ import { Logger } from 'src/core/server'; import { - AlertExecutionStatus, - AlertExecutionStatusValues, - AlertExecutionStatusWarningReasons, + RuleExecutionStatus, + RuleExecutionStatusValues, + RuleExecutionStatusWarningReasons, RawRuleExecutionStatus, RuleExecutionState, } from '../types'; import { getReasonFromError } from './error_with_reason'; import { getEsErrorMessage } from './errors'; -import { AlertExecutionStatuses } from '../../common'; +import { RuleExecutionStatuses } from '../../common'; import { translations } from '../constants/translations'; import { ActionsCompletion } from '../task_runner/types'; -export function executionStatusFromState(state: RuleExecutionState): AlertExecutionStatus { +export function executionStatusFromState(state: RuleExecutionState): RuleExecutionStatus { const alertIds = Object.keys(state.alertInstances ?? {}); const hasIncompleteAlertExecution = state.alertExecutionStore.triggeredActionsStatus === ActionsCompletion.PARTIAL; - let status: AlertExecutionStatuses = - alertIds.length === 0 ? AlertExecutionStatusValues[0] : AlertExecutionStatusValues[1]; + let status: RuleExecutionStatuses = + alertIds.length === 0 ? RuleExecutionStatusValues[0] : RuleExecutionStatusValues[1]; if (hasIncompleteAlertExecution) { - status = AlertExecutionStatusValues[5]; + status = RuleExecutionStatusValues[5]; } return { @@ -40,14 +40,14 @@ export function executionStatusFromState(state: RuleExecutionState): AlertExecut status, ...(hasIncompleteAlertExecution && { warning: { - reason: AlertExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, + reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, message: translations.taskRunner.warning.maxExecutableActions, }, }), }; } -export function executionStatusFromError(error: Error): AlertExecutionStatus { +export function executionStatusFromError(error: Error): RuleExecutionStatus { return { lastExecutionDate: new Date(), status: 'error', @@ -64,7 +64,7 @@ export function ruleExecutionStatusToRaw({ status, error, warning, -}: AlertExecutionStatus): RawRuleExecutionStatus { +}: RuleExecutionStatus): RawRuleExecutionStatus { return { lastExecutionDate: lastExecutionDate.toISOString(), lastDuration: lastDuration ?? 0, @@ -79,7 +79,7 @@ export function ruleExecutionStatusFromRaw( logger: Logger, ruleId: string, rawRuleExecutionStatus?: Partial | null | undefined -): AlertExecutionStatus | undefined { +): RuleExecutionStatus | undefined { if (!rawRuleExecutionStatus) return undefined; const { @@ -98,7 +98,7 @@ export function ruleExecutionStatusFromRaw( parsedDateMillis = Date.now(); } - const executionStatus: AlertExecutionStatus = { + const executionStatus: RuleExecutionStatus = { status, lastExecutionDate: new Date(parsedDateMillis), }; @@ -119,7 +119,7 @@ export function ruleExecutionStatusFromRaw( } export const getRuleExecutionStatusPending = (lastExecutionDate: string) => ({ - status: 'pending' as AlertExecutionStatuses, + status: 'pending' as RuleExecutionStatuses, lastExecutionDate, error: null, warning: null, diff --git a/x-pack/plugins/alerting/server/lib/validate_rule_type_params.ts b/x-pack/plugins/alerting/server/lib/validate_rule_type_params.ts index eef6ecb32c1b1..b791b05499263 100644 --- a/x-pack/plugins/alerting/server/lib/validate_rule_type_params.ts +++ b/x-pack/plugins/alerting/server/lib/validate_rule_type_params.ts @@ -6,11 +6,11 @@ */ import Boom from '@hapi/boom'; -import { AlertTypeParams, AlertTypeParamsValidator } from '../types'; +import { RuleTypeParams, RuleTypeParamsValidator } from '../types'; -export function validateRuleTypeParams( +export function validateRuleTypeParams( params: Record, - validator?: AlertTypeParamsValidator + validator?: RuleTypeParamsValidator ): Params { if (!validator) { return params as Params; diff --git a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts index 69d4817f21a4e..914d6f8c6da30 100644 --- a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts +++ b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts @@ -22,7 +22,7 @@ import type { } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { IScopedClusterClient, ElasticsearchClient, Logger } from 'src/core/server'; import { RuleExecutionMetrics } from '../types'; -import { Alert as Rule } from '../types'; +import { Rule } from '../types'; type RuleInfo = Pick & { spaceId: string }; interface WrapScopedClusterClientFactoryOpts { diff --git a/x-pack/plugins/alerting/server/mocks.ts b/x-pack/plugins/alerting/server/mocks.ts index c952e9182190c..d9951b4a79759 100644 --- a/x-pack/plugins/alerting/server/mocks.ts +++ b/x-pack/plugins/alerting/server/mocks.ts @@ -96,7 +96,7 @@ const createAbortableSearchServiceMock = () => { }; }; -const createAlertServicesMock = < +const createRuleExecutorServicesMock = < InstanceState extends AlertInstanceState = AlertInstanceState, InstanceContext extends AlertInstanceContext = AlertInstanceContext >() => { @@ -120,11 +120,11 @@ const createAlertServicesMock = < ), }; }; -export type AlertServicesMock = ReturnType; +export type RuleExecutorServicesMock = ReturnType; export const alertsMock = { createAlertFactory: createAlertFactoryMock, createSetup: createSetupMock, createStart: createStartMock, - createAlertServices: createAlertServicesMock, + createRuleExecutorServices: createRuleExecutorServicesMock, }; diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 47e2450b7a85c..85279ec615331 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -45,8 +45,8 @@ import { AlertInstanceState, AlertsHealth, RuleType, - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeState, } from './types'; import { registerAlertingUsageCollector } from './usage'; import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/task'; @@ -85,9 +85,9 @@ export const LEGACY_EVENT_LOG_ACTIONS = { export interface PluginSetupContract { registerType< - Params extends AlertTypeParams = AlertTypeParams, - ExtractedParams extends AlertTypeParams = AlertTypeParams, - State extends AlertTypeState = AlertTypeState, + Params extends RuleTypeParams = RuleTypeParams, + ExtractedParams extends RuleTypeParams = RuleTypeParams, + State extends RuleTypeState = RuleTypeState, InstanceState extends AlertInstanceState = AlertInstanceState, InstanceContext extends AlertInstanceContext = AlertInstanceContext, ActionGroupIds extends string = never, @@ -287,9 +287,9 @@ export class AlertingPlugin { return { registerType: < - Params extends AlertTypeParams = never, - ExtractedParams extends AlertTypeParams = never, - State extends AlertTypeState = never, + Params extends RuleTypeParams = never, + ExtractedParams extends RuleTypeParams = never, + State extends RuleTypeState = never, InstanceState extends AlertInstanceState = never, InstanceContext extends AlertInstanceContext = never, ActionGroupIds extends string = never, diff --git a/x-pack/plugins/alerting/server/routes/create_rule.test.ts b/x-pack/plugins/alerting/server/routes/create_rule.test.ts index c878d8da218b1..62c7819fb5b05 100644 --- a/x-pack/plugins/alerting/server/routes/create_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/create_rule.test.ts @@ -13,9 +13,9 @@ import { verifyApiAccess } from '../lib/license_api_access'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { CreateOptions } from '../rules_client'; import { rulesClientMock } from '../rules_client.mock'; -import { AlertTypeDisabledError } from '../lib'; +import { RuleTypeDisabledError } from '../lib'; import { AsApiContract } from './lib'; -import { SanitizedAlert } from '../types'; +import { SanitizedRule } from '../types'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; import { usageCountersServiceMock } from 'src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock'; @@ -33,7 +33,7 @@ describe('createRuleRoute', () => { const createdAt = new Date(); const updatedAt = new Date(); - const mockedAlert: SanitizedAlert<{ bar: boolean }> = { + const mockedAlert: SanitizedRule<{ bar: boolean }> = { alertTypeId: '1', consumer: 'bar', name: 'abc', @@ -82,7 +82,7 @@ describe('createRuleRoute', () => { ], }; - const createResult: AsApiContract> = { + const createResult: AsApiContract> = { ...ruleToCreate, mute_all: mockedAlert.muteAll, created_by: mockedAlert.createdBy, @@ -471,7 +471,7 @@ describe('createRuleRoute', () => { const [, handler] = router.post.mock.calls[0]; - rulesClient.create.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + rulesClient.create.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ rulesClient }, { body: ruleToCreate }, [ 'ok', diff --git a/x-pack/plugins/alerting/server/routes/create_rule.ts b/x-pack/plugins/alerting/server/routes/create_rule.ts index ed124bfbd3a2d..5278f748f0ce4 100644 --- a/x-pack/plugins/alerting/server/routes/create_rule.ts +++ b/x-pack/plugins/alerting/server/routes/create_rule.ts @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import { validateDurationSchema, AlertTypeDisabledError } from '../lib'; +import { validateDurationSchema, RuleTypeDisabledError } from '../lib'; import { CreateOptions } from '../rules_client'; import { RewriteRequestCase, @@ -16,11 +16,11 @@ import { countUsageOfPredefinedIds, } from './lib'; import { - SanitizedAlert, + SanitizedRule, validateNotifyWhenType, - AlertTypeParams, + RuleTypeParams, BASE_ALERTING_API_PATH, - AlertNotifyWhenType, + RuleNotifyWhenType, } from '../types'; import { RouteOptions } from '.'; @@ -46,7 +46,7 @@ export const bodySchema = schema.object({ notify_when: schema.string({ validate: validateNotifyWhenType }), }); -const rewriteBodyReq: RewriteRequestCase['data']> = ({ +const rewriteBodyReq: RewriteRequestCase['data']> = ({ rule_type_id: alertTypeId, notify_when: notifyWhen, ...rest @@ -55,7 +55,7 @@ const rewriteBodyReq: RewriteRequestCase['data']> alertTypeId, notifyWhen, }); -const rewriteBodyRes: RewriteResponseCase> = ({ +const rewriteBodyRes: RewriteResponseCase> = ({ actions, alertTypeId, scheduledTaskId, @@ -121,11 +121,11 @@ export const createRuleRoute = ({ router, licenseState, usageCounter }: RouteOpt }); try { - const createdRule: SanitizedAlert = - await rulesClient.create({ + const createdRule: SanitizedRule = + await rulesClient.create({ data: rewriteBodyReq({ ...rule, - notify_when: rule.notify_when as AlertNotifyWhenType, + notify_when: rule.notify_when as RuleNotifyWhenType, }), options: { id: params?.id }, }); @@ -133,7 +133,7 @@ export const createRuleRoute = ({ router, licenseState, usageCounter }: RouteOpt body: rewriteBodyRes(createdRule), }); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/disable_rule.test.ts b/x-pack/plugins/alerting/server/routes/disable_rule.test.ts index 173f1df7e1e73..baecc8f198fff 100644 --- a/x-pack/plugins/alerting/server/routes/disable_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/disable_rule.test.ts @@ -10,7 +10,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { rulesClientMock } from '../rules_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; const rulesClient = rulesClientMock.create(); @@ -67,7 +67,7 @@ describe('disableRuleRoute', () => { const [, handler] = router.post.mock.calls[0]; - rulesClient.disable.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + rulesClient.disable.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ 'ok', diff --git a/x-pack/plugins/alerting/server/routes/disable_rule.ts b/x-pack/plugins/alerting/server/routes/disable_rule.ts index 81f77cb130d50..74bf9d11166be 100644 --- a/x-pack/plugins/alerting/server/routes/disable_rule.ts +++ b/x-pack/plugins/alerting/server/routes/disable_rule.ts @@ -7,7 +7,7 @@ import { IRouter } from 'kibana/server'; import { schema } from '@kbn/config-schema'; -import { ILicenseState, AlertTypeDisabledError } from '../lib'; +import { ILicenseState, RuleTypeDisabledError } from '../lib'; import { verifyAccessAndContext } from './lib'; import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; @@ -34,7 +34,7 @@ export const disableRuleRoute = ( await rulesClient.disable({ id }); return res.noContent(); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/enable_rule.test.ts b/x-pack/plugins/alerting/server/routes/enable_rule.test.ts index 4c0bae2587924..9a8b3fe1b4226 100644 --- a/x-pack/plugins/alerting/server/routes/enable_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/enable_rule.test.ts @@ -10,7 +10,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { rulesClientMock } from '../rules_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; const rulesClient = rulesClientMock.create(); @@ -67,7 +67,7 @@ describe('enableRuleRoute', () => { const [, handler] = router.post.mock.calls[0]; - rulesClient.enable.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + rulesClient.enable.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ 'ok', diff --git a/x-pack/plugins/alerting/server/routes/enable_rule.ts b/x-pack/plugins/alerting/server/routes/enable_rule.ts index 5d77e375cfce6..e62e6aa30221f 100644 --- a/x-pack/plugins/alerting/server/routes/enable_rule.ts +++ b/x-pack/plugins/alerting/server/routes/enable_rule.ts @@ -7,7 +7,7 @@ import { IRouter } from 'kibana/server'; import { schema } from '@kbn/config-schema'; -import { ILicenseState, AlertTypeDisabledError } from '../lib'; +import { ILicenseState, RuleTypeDisabledError } from '../lib'; import { verifyAccessAndContext } from './lib'; import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; @@ -34,7 +34,7 @@ export const enableRuleRoute = ( await rulesClient.enable({ id }); return res.noContent(); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/find_rules.ts b/x-pack/plugins/alerting/server/routes/find_rules.ts index 1202d56edb21c..7d7f878780ac3 100644 --- a/x-pack/plugins/alerting/server/routes/find_rules.ts +++ b/x-pack/plugins/alerting/server/routes/find_rules.ts @@ -13,7 +13,7 @@ import { ILicenseState } from '../lib'; import { FindOptions, FindResult } from '../rules_client'; import { RewriteRequestCase, RewriteResponseCase, verifyAccessAndContext } from './lib'; import { - AlertTypeParams, + RuleTypeParams, AlertingRequestHandlerContext, BASE_ALERTING_API_PATH, INTERNAL_BASE_ALERTING_API_PATH, @@ -62,7 +62,7 @@ const rewriteQueryReq: RewriteRequestCase = ({ ...(hasReference ? { hasReference } : {}), ...(searchFields ? { searchFields } : {}), }); -const rewriteBodyRes: RewriteResponseCase> = ({ +const rewriteBodyRes: RewriteResponseCase> = ({ perPage, data, ...restOfResult diff --git a/x-pack/plugins/alerting/server/routes/get_rule.test.ts b/x-pack/plugins/alerting/server/routes/get_rule.test.ts index 47503374394f8..4384e02352d95 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule.test.ts @@ -12,7 +12,7 @@ import { licenseStateMock } from '../lib/license_state.mock'; import { verifyApiAccess } from '../lib/license_api_access'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { rulesClientMock } from '../rules_client.mock'; -import { SanitizedAlert } from '../types'; +import { SanitizedRule } from '../types'; import { AsApiContract } from './lib'; const rulesClient = rulesClientMock.create(); @@ -25,7 +25,7 @@ beforeEach(() => { }); describe('getRuleRoute', () => { - const mockedAlert: SanitizedAlert<{ + const mockedAlert: SanitizedRule<{ bar: boolean; }> = { id: '1', @@ -63,7 +63,7 @@ describe('getRuleRoute', () => { }, }; - const getResult: AsApiContract> = { + const getResult: AsApiContract> = { ...pick(mockedAlert, 'consumer', 'name', 'schedule', 'tags', 'params', 'throttle', 'enabled'), rule_type_id: mockedAlert.alertTypeId, notify_when: mockedAlert.notifyWhen, diff --git a/x-pack/plugins/alerting/server/routes/get_rule.ts b/x-pack/plugins/alerting/server/routes/get_rule.ts index 8e0e89c379fcc..52b73d22680f9 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule.ts @@ -11,18 +11,18 @@ import { IRouter } from 'kibana/server'; import { ILicenseState } from '../lib'; import { verifyAccessAndContext, RewriteResponseCase } from './lib'; import { - AlertTypeParams, + RuleTypeParams, AlertingRequestHandlerContext, BASE_ALERTING_API_PATH, INTERNAL_BASE_ALERTING_API_PATH, - SanitizedAlert, + SanitizedRule, } from '../types'; const paramSchema = schema.object({ id: schema.string(), }); -const rewriteBodyRes: RewriteResponseCase> = ({ +const rewriteBodyRes: RewriteResponseCase> = ({ alertTypeId, createdBy, updatedBy, diff --git a/x-pack/plugins/alerting/server/routes/legacy/create.test.ts b/x-pack/plugins/alerting/server/routes/legacy/create.test.ts index cfbef73ea58db..cd584543d57e0 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/create.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/create.test.ts @@ -12,8 +12,8 @@ import { licenseStateMock } from '../../lib/license_state.mock'; import { verifyApiAccess } from '../../lib/license_api_access'; import { mockHandlerArguments } from './../_mock_handler_arguments'; import { rulesClientMock } from '../../rules_client.mock'; -import { Alert } from '../../../common/alert'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { Rule } from '../../../common/rule'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; @@ -57,7 +57,7 @@ describe('createAlertRoute', () => { ], }; - const createResult: Alert<{ bar: boolean }> = { + const createResult: Rule<{ bar: boolean }> = { ...mockedAlert, enabled: true, muteAll: false, @@ -436,7 +436,7 @@ describe('createAlertRoute', () => { const [, handler] = router.post.mock.calls[0]; - rulesClient.create.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + rulesClient.create.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ rulesClient }, {}, ['ok', 'forbidden']); diff --git a/x-pack/plugins/alerting/server/routes/legacy/create.ts b/x-pack/plugins/alerting/server/routes/legacy/create.ts index 8deeb733fd3b5..b6669a605022d 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/create.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/create.ts @@ -10,13 +10,13 @@ import { verifyApiAccess } from '../../lib/license_api_access'; import { validateDurationSchema } from '../../lib'; import { handleDisabledApiKeysError } from './../lib/error_handler'; import { - SanitizedAlert, - AlertNotifyWhenType, - AlertTypeParams, + SanitizedRule, + RuleNotifyWhenType, + RuleTypeParams, LEGACY_BASE_ALERT_API_PATH, validateNotifyWhenType, } from '../../types'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { RouteOptions } from '..'; import { countUsageOfPredefinedIds } from '../lib'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; @@ -67,7 +67,7 @@ export const createAlertRoute = ({ router, licenseState, usageCounter }: RouteOp const rulesClient = context.alerting.getRulesClient(); const alert = req.body; const params = req.params; - const notifyWhen = alert?.notifyWhen ? (alert.notifyWhen as AlertNotifyWhenType) : null; + const notifyWhen = alert?.notifyWhen ? (alert.notifyWhen as RuleNotifyWhenType) : null; trackLegacyRouteUsage('create', usageCounter); @@ -78,16 +78,15 @@ export const createAlertRoute = ({ router, licenseState, usageCounter }: RouteOp }); try { - const alertRes: SanitizedAlert = - await rulesClient.create({ - data: { ...alert, notifyWhen }, - options: { id: params?.id }, - }); + const alertRes: SanitizedRule = await rulesClient.create({ + data: { ...alert, notifyWhen }, + options: { id: params?.id }, + }); return res.ok({ body: alertRes, }); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/legacy/disable.test.ts b/x-pack/plugins/alerting/server/routes/legacy/disable.test.ts index ac44ab37761bc..1e9f5531d19d2 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/disable.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/disable.test.ts @@ -10,7 +10,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../../lib/license_state.mock'; import { mockHandlerArguments } from './../_mock_handler_arguments'; import { rulesClientMock } from '../../rules_client.mock'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const rulesClient = rulesClientMock.create(); @@ -72,7 +72,7 @@ describe('disableAlertRoute', () => { const [, handler] = router.post.mock.calls[0]; - rulesClient.disable.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + rulesClient.disable.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ 'ok', diff --git a/x-pack/plugins/alerting/server/routes/legacy/disable.ts b/x-pack/plugins/alerting/server/routes/legacy/disable.ts index 1cba654e11a51..1345c4ae4428a 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/disable.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/disable.ts @@ -11,7 +11,7 @@ import type { AlertingRouter } from '../../types'; import { ILicenseState } from '../../lib/license_state'; import { verifyApiAccess } from '../../lib/license_api_access'; import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const paramSchema = schema.object({ @@ -42,7 +42,7 @@ export const disableAlertRoute = ( await rulesClient.disable({ id }); return res.noContent(); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/legacy/enable.test.ts b/x-pack/plugins/alerting/server/routes/legacy/enable.test.ts index c35fb191fae6f..eb839ba3e7b83 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/enable.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/enable.test.ts @@ -10,7 +10,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../../lib/license_state.mock'; import { mockHandlerArguments } from './../_mock_handler_arguments'; import { rulesClientMock } from '../../rules_client.mock'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const rulesClient = rulesClientMock.create(); @@ -72,7 +72,7 @@ describe('enableAlertRoute', () => { const [, handler] = router.post.mock.calls[0]; - rulesClient.enable.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + rulesClient.enable.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ 'ok', diff --git a/x-pack/plugins/alerting/server/routes/legacy/enable.ts b/x-pack/plugins/alerting/server/routes/legacy/enable.ts index 000d165ff169d..ec3b4b0a0fc20 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/enable.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/enable.ts @@ -12,7 +12,7 @@ import { ILicenseState } from '../../lib/license_state'; import { verifyApiAccess } from '../../lib/license_api_access'; import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; import { handleDisabledApiKeysError } from './../lib/error_handler'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const paramSchema = schema.object({ @@ -44,7 +44,7 @@ export const enableAlertRoute = ( await rulesClient.enable({ id }); return res.noContent(); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/legacy/get.test.ts b/x-pack/plugins/alerting/server/routes/legacy/get.test.ts index 40c7b224c7150..8c2233ae280b7 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/get.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/get.test.ts @@ -12,7 +12,7 @@ import { licenseStateMock } from '../../lib/license_state.mock'; import { verifyApiAccess } from '../../lib/license_api_access'; import { mockHandlerArguments } from './../_mock_handler_arguments'; import { rulesClientMock } from '../../rules_client.mock'; -import { Alert } from '../../../common'; +import { Rule } from '../../../common'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const rulesClient = rulesClientMock.create(); @@ -29,7 +29,7 @@ beforeEach(() => { }); describe('getAlertRoute', () => { - const mockedAlert: Alert<{ + const mockedAlert: Rule<{ bar: true; }> = { id: '1', diff --git a/x-pack/plugins/alerting/server/routes/legacy/mute_all.test.ts b/x-pack/plugins/alerting/server/routes/legacy/mute_all.test.ts index 131b5ed7960bd..9d84fd75a68c5 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/mute_all.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/mute_all.test.ts @@ -11,7 +11,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../../lib/license_state.mock'; import { mockHandlerArguments } from './../_mock_handler_arguments'; import { rulesClientMock } from '../../rules_client.mock'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const rulesClient = rulesClientMock.create(); @@ -72,7 +72,7 @@ describe('muteAllAlertRoute', () => { const [, handler] = router.post.mock.calls[0]; - rulesClient.muteAll.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + rulesClient.muteAll.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ 'ok', diff --git a/x-pack/plugins/alerting/server/routes/legacy/mute_all.ts b/x-pack/plugins/alerting/server/routes/legacy/mute_all.ts index 222abc806ede2..f35ffe93ccda4 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/mute_all.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/mute_all.ts @@ -11,7 +11,7 @@ import type { AlertingRouter } from '../../types'; import { ILicenseState } from '../../lib/license_state'; import { verifyApiAccess } from '../../lib/license_api_access'; import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const paramSchema = schema.object({ @@ -42,7 +42,7 @@ export const muteAllAlertRoute = ( await rulesClient.muteAll({ id }); return res.noContent(); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/legacy/mute_instance.test.ts b/x-pack/plugins/alerting/server/routes/legacy/mute_instance.test.ts index 19ce9e1d2b107..5e17c42528e14 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/mute_instance.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/mute_instance.test.ts @@ -11,7 +11,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../../lib/license_state.mock'; import { mockHandlerArguments } from './../_mock_handler_arguments'; import { rulesClientMock } from '../../rules_client.mock'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const rulesClient = rulesClientMock.create(); @@ -77,7 +77,7 @@ describe('muteAlertInstanceRoute', () => { const [, handler] = router.post.mock.calls[0]; rulesClient.muteInstance.mockRejectedValue( - new AlertTypeDisabledError('Fail', 'license_invalid') + new RuleTypeDisabledError('Fail', 'license_invalid') ); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ diff --git a/x-pack/plugins/alerting/server/routes/legacy/mute_instance.ts b/x-pack/plugins/alerting/server/routes/legacy/mute_instance.ts index be1c0876d5d2e..af6aea1e0f6c7 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/mute_instance.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/mute_instance.ts @@ -13,7 +13,7 @@ import { verifyApiAccess } from '../../lib/license_api_access'; import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; import { renameKeys } from './../lib/rename_keys'; import { MuteOptions } from '../../rules_client'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const paramSchema = schema.object({ @@ -53,7 +53,7 @@ export const muteAlertInstanceRoute = ( await rulesClient.muteInstance(renamedQuery); return res.noContent(); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/legacy/unmute_all.test.ts b/x-pack/plugins/alerting/server/routes/legacy/unmute_all.test.ts index 8259d2305b941..f3530786667ff 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/unmute_all.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/unmute_all.test.ts @@ -11,7 +11,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../../lib/license_state.mock'; import { mockHandlerArguments } from './../_mock_handler_arguments'; import { rulesClientMock } from '../../rules_client.mock'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const rulesClient = rulesClientMock.create(); @@ -72,7 +72,7 @@ describe('unmuteAllAlertRoute', () => { const [, handler] = router.post.mock.calls[0]; - rulesClient.unmuteAll.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + rulesClient.unmuteAll.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ 'ok', diff --git a/x-pack/plugins/alerting/server/routes/legacy/unmute_all.ts b/x-pack/plugins/alerting/server/routes/legacy/unmute_all.ts index 40c303e5d7633..a6cd687271cc4 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/unmute_all.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/unmute_all.ts @@ -11,7 +11,7 @@ import type { AlertingRouter } from '../../types'; import { ILicenseState } from '../../lib/license_state'; import { verifyApiAccess } from '../../lib/license_api_access'; import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const paramSchema = schema.object({ @@ -42,7 +42,7 @@ export const unmuteAllAlertRoute = ( await rulesClient.unmuteAll({ id }); return res.noContent(); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/legacy/unmute_instance.test.ts b/x-pack/plugins/alerting/server/routes/legacy/unmute_instance.test.ts index bbe61d715a525..fa109fa09f250 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/unmute_instance.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/unmute_instance.test.ts @@ -11,7 +11,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../../lib/license_state.mock'; import { mockHandlerArguments } from './../_mock_handler_arguments'; import { rulesClientMock } from '../../rules_client.mock'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const rulesClient = rulesClientMock.create(); @@ -77,7 +77,7 @@ describe('unmuteAlertInstanceRoute', () => { const [, handler] = router.post.mock.calls[0]; rulesClient.unmuteInstance.mockRejectedValue( - new AlertTypeDisabledError('Fail', 'license_invalid') + new RuleTypeDisabledError('Fail', 'license_invalid') ); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ diff --git a/x-pack/plugins/alerting/server/routes/legacy/unmute_instance.ts b/x-pack/plugins/alerting/server/routes/legacy/unmute_instance.ts index 1c65af9961adc..d4684bda7f0fa 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/unmute_instance.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/unmute_instance.ts @@ -11,7 +11,7 @@ import type { AlertingRouter } from '../../types'; import { ILicenseState } from '../../lib/license_state'; import { verifyApiAccess } from '../../lib/license_api_access'; import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const paramSchema = schema.object({ @@ -43,7 +43,7 @@ export const unmuteAlertInstanceRoute = ( await rulesClient.unmuteInstance({ alertId, alertInstanceId }); return res.noContent(); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/legacy/update.test.ts b/x-pack/plugins/alerting/server/routes/legacy/update.test.ts index 799672c3cf432..5396e267fa525 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/update.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/update.test.ts @@ -12,8 +12,8 @@ import { licenseStateMock } from '../../lib/license_state.mock'; import { verifyApiAccess } from '../../lib/license_api_access'; import { mockHandlerArguments } from './../_mock_handler_arguments'; import { rulesClientMock } from '../../rules_client.mock'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; -import { AlertNotifyWhenType } from '../../../common'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; +import { RuleNotifyWhenType } from '../../../common'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const rulesClient = rulesClientMock.create(); @@ -50,7 +50,7 @@ describe('updateAlertRoute', () => { }, }, ], - notifyWhen: 'onActionGroupChange' as AlertNotifyWhenType, + notifyWhen: 'onActionGroupChange' as RuleNotifyWhenType, }; it('updates an alert with proper parameters', async () => { @@ -229,7 +229,7 @@ describe('updateAlertRoute', () => { const [, handler] = router.put.mock.calls[0]; - rulesClient.update.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + rulesClient.update.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ 'ok', diff --git a/x-pack/plugins/alerting/server/routes/legacy/update.ts b/x-pack/plugins/alerting/server/routes/legacy/update.ts index d6e6a2b3c4ae8..b740be04829e8 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/update.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/update.ts @@ -12,10 +12,10 @@ import { ILicenseState } from '../../lib/license_state'; import { verifyApiAccess } from '../../lib/license_api_access'; import { validateDurationSchema } from '../../lib'; import { handleDisabledApiKeysError } from './../lib/error_handler'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; import { - AlertNotifyWhenType, + RuleNotifyWhenType, LEGACY_BASE_ALERT_API_PATH, validateNotifyWhenType, } from '../../../common'; @@ -77,14 +77,14 @@ export const updateAlertRoute = ( schedule, tags, throttle, - notifyWhen: notifyWhen as AlertNotifyWhenType, + notifyWhen: notifyWhen as RuleNotifyWhenType, }, }); return res.ok({ body: alertRes, }); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/legacy/update_api_key.test.ts b/x-pack/plugins/alerting/server/routes/legacy/update_api_key.test.ts index 7c48f5fff357d..5ae3ec2f5387f 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/update_api_key.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/update_api_key.test.ts @@ -11,7 +11,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../../lib/license_state.mock'; import { mockHandlerArguments } from './../_mock_handler_arguments'; import { rulesClientMock } from '../../rules_client.mock'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const rulesClient = rulesClientMock.create(); @@ -73,7 +73,7 @@ describe('updateApiKeyRoute', () => { const [, handler] = router.post.mock.calls[0]; rulesClient.updateApiKey.mockRejectedValue( - new AlertTypeDisabledError('Fail', 'license_invalid') + new RuleTypeDisabledError('Fail', 'license_invalid') ); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ diff --git a/x-pack/plugins/alerting/server/routes/legacy/update_api_key.ts b/x-pack/plugins/alerting/server/routes/legacy/update_api_key.ts index a45767851c5c1..ab5ff254fca86 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/update_api_key.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/update_api_key.ts @@ -12,7 +12,7 @@ import { ILicenseState } from '../../lib/license_state'; import { verifyApiAccess } from '../../lib/license_api_access'; import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; import { handleDisabledApiKeysError } from './../lib/error_handler'; -import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; const paramSchema = schema.object({ @@ -44,7 +44,7 @@ export const updateApiKeyRoute = ( await rulesClient.updateApiKey({ id }); return res.noContent(); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/mute_alert.test.ts b/x-pack/plugins/alerting/server/routes/mute_alert.test.ts index 284a89ea8a677..0fc28412abc6f 100644 --- a/x-pack/plugins/alerting/server/routes/mute_alert.test.ts +++ b/x-pack/plugins/alerting/server/routes/mute_alert.test.ts @@ -10,7 +10,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { rulesClientMock } from '../rules_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; const rulesClient = rulesClientMock.create(); jest.mock('../lib/license_api_access.ts', () => ({ @@ -71,7 +71,7 @@ describe('muteAlertRoute', () => { const [, handler] = router.post.mock.calls[0]; rulesClient.muteInstance.mockRejectedValue( - new AlertTypeDisabledError('Fail', 'license_invalid') + new RuleTypeDisabledError('Fail', 'license_invalid') ); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ diff --git a/x-pack/plugins/alerting/server/routes/mute_alert.ts b/x-pack/plugins/alerting/server/routes/mute_alert.ts index 301d04f362313..67e6f3b4002d7 100644 --- a/x-pack/plugins/alerting/server/routes/mute_alert.ts +++ b/x-pack/plugins/alerting/server/routes/mute_alert.ts @@ -7,7 +7,7 @@ import { IRouter } from 'kibana/server'; import { schema } from '@kbn/config-schema'; -import { ILicenseState, AlertTypeDisabledError } from '../lib'; +import { ILicenseState, RuleTypeDisabledError } from '../lib'; import { MuteOptions } from '../rules_client'; import { RewriteRequestCase, verifyAccessAndContext } from './lib'; import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; @@ -44,7 +44,7 @@ export const muteAlertRoute = ( await rulesClient.muteInstance(params); return res.noContent(); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/mute_all_rule.test.ts b/x-pack/plugins/alerting/server/routes/mute_all_rule.test.ts index 56e46db082514..277add6ec1647 100644 --- a/x-pack/plugins/alerting/server/routes/mute_all_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/mute_all_rule.test.ts @@ -10,7 +10,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { rulesClientMock } from '../rules_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; const rulesClient = rulesClientMock.create(); jest.mock('../lib/license_api_access.ts', () => ({ @@ -66,7 +66,7 @@ describe('muteAllRuleRoute', () => { const [, handler] = router.post.mock.calls[0]; - rulesClient.muteAll.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + rulesClient.muteAll.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ 'ok', diff --git a/x-pack/plugins/alerting/server/routes/mute_all_rule.ts b/x-pack/plugins/alerting/server/routes/mute_all_rule.ts index f6d3870ef6744..ad3c93f415ea2 100644 --- a/x-pack/plugins/alerting/server/routes/mute_all_rule.ts +++ b/x-pack/plugins/alerting/server/routes/mute_all_rule.ts @@ -7,7 +7,7 @@ import { IRouter } from 'kibana/server'; import { schema } from '@kbn/config-schema'; -import { ILicenseState, AlertTypeDisabledError } from '../lib'; +import { ILicenseState, RuleTypeDisabledError } from '../lib'; import { verifyAccessAndContext } from './lib'; import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; @@ -34,7 +34,7 @@ export const muteAllRuleRoute = ( await rulesClient.muteAll({ id }); return res.noContent(); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/resolve_rule.ts b/x-pack/plugins/alerting/server/routes/resolve_rule.ts index a31cf5059b99f..e6cacb8231b5f 100644 --- a/x-pack/plugins/alerting/server/routes/resolve_rule.ts +++ b/x-pack/plugins/alerting/server/routes/resolve_rule.ts @@ -11,7 +11,7 @@ import { IRouter } from 'kibana/server'; import { ILicenseState } from '../lib'; import { verifyAccessAndContext, RewriteResponseCase } from './lib'; import { - AlertTypeParams, + RuleTypeParams, AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH, ResolvedSanitizedRule, @@ -21,7 +21,7 @@ const paramSchema = schema.object({ id: schema.string(), }); -const rewriteBodyRes: RewriteResponseCase> = ({ +const rewriteBodyRes: RewriteResponseCase> = ({ alertTypeId, createdBy, updatedBy, diff --git a/x-pack/plugins/alerting/server/routes/snooze_rule.test.ts b/x-pack/plugins/alerting/server/routes/snooze_rule.test.ts index dbcce10cc8e3e..f7e83301385ad 100644 --- a/x-pack/plugins/alerting/server/routes/snooze_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/snooze_rule.test.ts @@ -10,7 +10,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { rulesClientMock } from '../rules_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; const rulesClient = rulesClientMock.create(); jest.mock('../lib/license_api_access.ts', () => ({ @@ -113,7 +113,7 @@ describe('snoozeAlertRoute', () => { const [, handler] = router.post.mock.calls[0]; - rulesClient.snooze.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + rulesClient.snooze.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ 'ok', diff --git a/x-pack/plugins/alerting/server/routes/unmute_alert.test.ts b/x-pack/plugins/alerting/server/routes/unmute_alert.test.ts index 09c0033542dbb..bfc281cb16b2f 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_alert.test.ts +++ b/x-pack/plugins/alerting/server/routes/unmute_alert.test.ts @@ -10,7 +10,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { rulesClientMock } from '../rules_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; const rulesClient = rulesClientMock.create(); jest.mock('../lib/license_api_access.ts', () => ({ @@ -71,7 +71,7 @@ describe('unmuteAlertRoute', () => { const [, handler] = router.post.mock.calls[0]; rulesClient.unmuteInstance.mockRejectedValue( - new AlertTypeDisabledError('Fail', 'license_invalid') + new RuleTypeDisabledError('Fail', 'license_invalid') ); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ diff --git a/x-pack/plugins/alerting/server/routes/unmute_alert.ts b/x-pack/plugins/alerting/server/routes/unmute_alert.ts index 58c3f59b836b1..0269f53fa8827 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_alert.ts +++ b/x-pack/plugins/alerting/server/routes/unmute_alert.ts @@ -7,7 +7,7 @@ import { IRouter } from 'kibana/server'; import { schema } from '@kbn/config-schema'; -import { ILicenseState, AlertTypeDisabledError } from '../lib'; +import { ILicenseState, RuleTypeDisabledError } from '../lib'; import { MuteOptions } from '../rules_client'; import { RewriteRequestCase, verifyAccessAndContext } from './lib'; import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; @@ -44,7 +44,7 @@ export const unmuteAlertRoute = ( await rulesClient.unmuteInstance(params); return res.noContent(); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/unmute_all_rule.test.ts b/x-pack/plugins/alerting/server/routes/unmute_all_rule.test.ts index 0f031c2d8cc80..ff97819506276 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_all_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/unmute_all_rule.test.ts @@ -10,7 +10,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { rulesClientMock } from '../rules_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; const rulesClient = rulesClientMock.create(); jest.mock('../lib/license_api_access.ts', () => ({ @@ -66,7 +66,7 @@ describe('unmuteAllRuleRoute', () => { const [, handler] = router.post.mock.calls[0]; - rulesClient.unmuteAll.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + rulesClient.unmuteAll.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ 'ok', diff --git a/x-pack/plugins/alerting/server/routes/unmute_all_rule.ts b/x-pack/plugins/alerting/server/routes/unmute_all_rule.ts index a0b562404b0b1..05ae9be2ba6e5 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_all_rule.ts +++ b/x-pack/plugins/alerting/server/routes/unmute_all_rule.ts @@ -7,7 +7,7 @@ import { IRouter } from 'kibana/server'; import { schema } from '@kbn/config-schema'; -import { ILicenseState, AlertTypeDisabledError } from '../lib'; +import { ILicenseState, RuleTypeDisabledError } from '../lib'; import { verifyAccessAndContext } from './lib'; import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; @@ -34,7 +34,7 @@ export const unmuteAllRuleRoute = ( await rulesClient.unmuteAll({ id }); return res.noContent(); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/unsnooze_rule.test.ts b/x-pack/plugins/alerting/server/routes/unsnooze_rule.test.ts index a0fbf9776240a..260e4b4150043 100644 --- a/x-pack/plugins/alerting/server/routes/unsnooze_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/unsnooze_rule.test.ts @@ -10,7 +10,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { rulesClientMock } from '../rules_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; const rulesClient = rulesClientMock.create(); jest.mock('../lib/license_api_access.ts', () => ({ @@ -66,7 +66,7 @@ describe('unsnoozeAlertRoute', () => { const [, handler] = router.post.mock.calls[0]; - rulesClient.unsnooze.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + rulesClient.unsnooze.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ 'ok', diff --git a/x-pack/plugins/alerting/server/routes/update_rule.test.ts b/x-pack/plugins/alerting/server/routes/update_rule.test.ts index 46f23641f70d9..f39ad53c8786d 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule.test.ts @@ -13,10 +13,10 @@ import { verifyApiAccess } from '../lib/license_api_access'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { UpdateOptions } from '../rules_client'; import { rulesClientMock } from '../rules_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; -import { AlertNotifyWhenType } from '../../common'; +import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; +import { RuleNotifyWhenType } from '../../common'; import { AsApiContract } from './lib'; -import { PartialAlert } from '../types'; +import { PartialRule } from '../types'; const rulesClient = rulesClientMock.create(); jest.mock('../lib/license_api_access.ts', () => ({ @@ -50,7 +50,7 @@ describe('updateRuleRoute', () => { }, }, ], - notifyWhen: 'onActionGroupChange' as AlertNotifyWhenType, + notifyWhen: 'onActionGroupChange' as RuleNotifyWhenType, }; const updateRequest: AsApiContract['data']> = { @@ -65,7 +65,7 @@ describe('updateRuleRoute', () => { ], }; - const updateResult: AsApiContract> = { + const updateResult: AsApiContract> = { ...updateRequest, id: mockedAlert.id, updated_at: mockedAlert.updatedAt, @@ -201,7 +201,7 @@ describe('updateRuleRoute', () => { const [, handler] = router.put.mock.calls[0]; - rulesClient.update.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + rulesClient.update.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid')); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ 'ok', diff --git a/x-pack/plugins/alerting/server/routes/update_rule.ts b/x-pack/plugins/alerting/server/routes/update_rule.ts index 007d24bb8251b..5c416e50b4ff2 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule.ts @@ -7,8 +7,8 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from 'kibana/server'; -import { ILicenseState, AlertTypeDisabledError, validateDurationSchema } from '../lib'; -import { AlertNotifyWhenType } from '../../common'; +import { ILicenseState, RuleTypeDisabledError, validateDurationSchema } from '../lib'; +import { RuleNotifyWhenType } from '../../common'; import { UpdateOptions } from '../rules_client'; import { verifyAccessAndContext, @@ -17,11 +17,11 @@ import { handleDisabledApiKeysError, } from './lib'; import { - AlertTypeParams, + RuleTypeParams, AlertingRequestHandlerContext, BASE_ALERTING_API_PATH, validateNotifyWhenType, - PartialAlert, + PartialRule, } from '../types'; const paramSchema = schema.object({ @@ -47,7 +47,7 @@ const bodySchema = schema.object({ notify_when: schema.string({ validate: validateNotifyWhenType }), }); -const rewriteBodyReq: RewriteRequestCase> = (result) => { +const rewriteBodyReq: RewriteRequestCase> = (result) => { const { notify_when: notifyWhen, ...rest } = result.data; return { ...result, @@ -57,7 +57,7 @@ const rewriteBodyReq: RewriteRequestCase> = (resu }, }; }; -const rewriteBodyRes: RewriteResponseCase> = ({ +const rewriteBodyRes: RewriteResponseCase> = ({ actions, alertTypeId, scheduledTaskId, @@ -128,7 +128,7 @@ export const updateRuleRoute = ( id, data: { ...rule, - notify_when: rule.notify_when as AlertNotifyWhenType, + notify_when: rule.notify_when as RuleNotifyWhenType, }, }) ); @@ -136,7 +136,7 @@ export const updateRuleRoute = ( body: rewriteBodyRes(alertRes), }); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/routes/update_rule_api_key.test.ts b/x-pack/plugins/alerting/server/routes/update_rule_api_key.test.ts index b4fb7602bf034..2622c260d7938 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule_api_key.test.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule_api_key.test.ts @@ -10,7 +10,7 @@ import { httpServiceMock } from 'src/core/server/mocks'; import { licenseStateMock } from '../lib/license_state.mock'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { rulesClientMock } from '../rules_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; const rulesClient = rulesClientMock.create(); jest.mock('../lib/license_api_access.ts', () => ({ @@ -67,7 +67,7 @@ describe('updateRuleApiKeyRoute', () => { const [, handler] = router.post.mock.calls[0]; rulesClient.updateApiKey.mockRejectedValue( - new AlertTypeDisabledError('Fail', 'license_invalid') + new RuleTypeDisabledError('Fail', 'license_invalid') ); const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ diff --git a/x-pack/plugins/alerting/server/routes/update_rule_api_key.ts b/x-pack/plugins/alerting/server/routes/update_rule_api_key.ts index 11d76f3992efd..a6644e455a9be 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule_api_key.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule_api_key.ts @@ -7,7 +7,7 @@ import { IRouter } from 'kibana/server'; import { schema } from '@kbn/config-schema'; -import { ILicenseState, AlertTypeDisabledError } from '../lib'; +import { ILicenseState, RuleTypeDisabledError } from '../lib'; import { verifyAccessAndContext } from './lib'; import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types'; @@ -34,7 +34,7 @@ export const updateRuleApiKeyRoute = ( await rulesClient.updateApiKey({ id }); return res.noContent(); } catch (e) { - if (e instanceof AlertTypeDisabledError) { + if (e instanceof RuleTypeDisabledError) { return e.sendResponse(res); } throw e; diff --git a/x-pack/plugins/alerting/server/rule_type_registry.ts b/x-pack/plugins/alerting/server/rule_type_registry.ts index 35e9e312e9a1a..bc548b8ae2286 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.ts @@ -16,8 +16,8 @@ import { RunContext, TaskManagerSetupContract } from '../../task_manager/server' import { TaskRunnerFactory } from './task_runner'; import { RuleType, - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, } from './types'; @@ -84,9 +84,9 @@ const ruleTypeIdSchema = schema.string({ }); export type NormalizedRuleType< - Params extends AlertTypeParams, - ExtractedParams extends AlertTypeParams, - State extends AlertTypeState, + Params extends RuleTypeParams, + ExtractedParams extends RuleTypeParams, + State extends RuleTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, ActionGroupIds extends string, @@ -121,9 +121,9 @@ export type NormalizedRuleType< >; export type UntypedNormalizedRuleType = NormalizedRuleType< - AlertTypeParams, - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string, @@ -167,9 +167,9 @@ export class RuleTypeRegistry { } public register< - Params extends AlertTypeParams, - ExtractedParams extends AlertTypeParams, - State extends AlertTypeState, + Params extends RuleTypeParams, + ExtractedParams extends RuleTypeParams, + State extends RuleTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, ActionGroupIds extends string, @@ -287,9 +287,9 @@ export class RuleTypeRegistry { } public get< - Params extends AlertTypeParams = AlertTypeParams, - ExtractedParams extends AlertTypeParams = AlertTypeParams, - State extends AlertTypeState = AlertTypeState, + Params extends RuleTypeParams = RuleTypeParams, + ExtractedParams extends RuleTypeParams = RuleTypeParams, + State extends RuleTypeState = RuleTypeState, InstanceState extends AlertInstanceState = AlertInstanceState, InstanceContext extends AlertInstanceContext = AlertInstanceContext, ActionGroupIds extends string = string, @@ -382,9 +382,9 @@ function normalizedActionVariables(actionVariables: RuleType['actionVariables']) } function augmentActionGroupsWithReserved< - Params extends AlertTypeParams, - ExtractedParams extends AlertTypeParams, - State extends AlertTypeState, + Params extends RuleTypeParams, + ExtractedParams extends RuleTypeParams, + State extends RuleTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, ActionGroupIds extends string, diff --git a/x-pack/plugins/alerting/server/rules_client/lib/mapped_params_utils.ts b/x-pack/plugins/alerting/server/rules_client/lib/mapped_params_utils.ts index e9624d4604c46..26385cfac3f78 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/mapped_params_utils.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/mapped_params_utils.ts @@ -6,7 +6,7 @@ */ import { snakeCase } from 'lodash'; -import { AlertTypeParams, MappedParams, MappedParamsProperties } from '../../types'; +import { RuleTypeParams, MappedParams, MappedParamsProperties } from '../../types'; import { SavedObjectAttribute } from '../../../../../../src/core/server'; import { iterateFilterKureyNode, @@ -32,7 +32,7 @@ const SEVERITY_MAP: Record = { * The function will match params present in MAPPED_PARAMS_PROPERTIES and * return an empty object if nothing is matched. */ -export const getMappedParams = (params: AlertTypeParams) => { +export const getMappedParams = (params: RuleTypeParams) => { return Object.entries(params).reduce((result, [key, value]) => { const snakeCaseKey = snakeCase(key); diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index 5377ec562847f..ac53486d279fd 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -22,25 +22,25 @@ import { } from '../../../../../src/core/server'; import { ActionsClient, ActionsAuthorization } from '../../../actions/server'; import { - Alert as Rule, - PartialAlert as PartialRule, + Rule, + PartialRule, RawRule, RuleTypeRegistry, - AlertAction as RuleAction, + RuleAction, IntervalSchedule, - SanitizedAlert as SanitizedRule, + SanitizedRule, RuleTaskState, AlertSummary, - AlertExecutionStatusValues as RuleExecutionStatusValues, - AlertNotifyWhenType as RuleNotifyWhenType, - AlertTypeParams as RuleTypeParams, + RuleExecutionStatusValues, + RuleNotifyWhenType, + RuleTypeParams, ResolvedSanitizedRule, - AlertWithLegacyId as RuleWithLegacyId, + RuleWithLegacyId, SanitizedRuleWithLegacyId, - PartialAlertWithLegacyId as PartialRuleWithLegacyId, + PartialRuleWithLegacyId, RawAlertInstance as RawAlert, } from '../types'; -import { validateRuleTypeParams, ruleExecutionStatusFromRaw, getAlertNotifyWhenType } from '../lib'; +import { validateRuleTypeParams, ruleExecutionStatusFromRaw, getRuleNotifyWhenType } from '../lib'; import { GrantAPIKeyResult as SecurityPluginGrantAPIKeyResult, InvalidateAPIKeyResult as SecurityPluginInvalidateAPIKeyResult, @@ -402,7 +402,7 @@ export class RulesClient { const createTime = Date.now(); const legacyId = Semver.lt(this.kibanaVersion, '8.0.0') ? id : null; - const notifyWhen = getAlertNotifyWhenType(data.notifyWhen, data.throttle); + const notifyWhen = getRuleNotifyWhenType(data.notifyWhen, data.throttle); const rawRule: RawRule = { ...data, @@ -1200,7 +1200,7 @@ export class RulesClient { } const apiKeyAttributes = this.apiKeyAsAlertAttributes(createdAPIKey, username); - const notifyWhen = getAlertNotifyWhenType(data.notifyWhen, data.throttle); + const notifyWhen = getRuleNotifyWhenType(data.notifyWhen, data.throttle); let updatedObject: SavedObject; const createAttributes = this.updateMeta({ diff --git a/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts index 50e6979d6ee72..ffed0e0b5efbc 100644 --- a/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts +++ b/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts @@ -10,14 +10,14 @@ import { SavedObjectReference, SavedObjectUnsanitizedDoc, } from 'kibana/server'; -import { AlertTypeParams } from '../../index'; +import { RuleTypeParams } from '../../index'; import { Query } from '../../../../../../src/plugins/data/common/query'; import { RawRule } from '../../types'; // These definitions are dupes of the SO-types in stack_alerts/geo_containment // There are not exported to avoid deep imports from stack_alerts plugins into here const GEO_CONTAINMENT_ID = '.geo-containment'; -interface GeoContainmentParams extends AlertTypeParams { +interface GeoContainmentParams extends RuleTypeParams { index: string; indexId: string; geoField: string; diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.ts index 0e3dc072366c2..91e03eb4da6c2 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.ts @@ -17,7 +17,7 @@ import { SavedObjectAttribute, SavedObjectReference, } from '../../../../../src/core/server'; -import { RawRule, RawAlertAction, RawRuleExecutionStatus } from '../types'; +import { RawRule, RawRuleAction, RawRuleExecutionStatus } from '../types'; import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; import type { IsMigrationNeededPredicate } from '../../../encrypted_saved_objects/server'; import { extractRefsFromGeoContainmentAlert } from './geo_containment/migrations'; @@ -353,7 +353,7 @@ function restructureConnectorsThatSupportIncident( }, }, }, - ] as RawAlertAction[]; + ] as RawRuleAction[]; } else if (action.actionTypeId === '.jira') { const { title, comments, description, issueType, priority, labels, parent, summary } = action.params.subActionParams as { @@ -385,7 +385,7 @@ function restructureConnectorsThatSupportIncident( }, }, }, - ] as RawAlertAction[]; + ] as RawRuleAction[]; } else if (action.actionTypeId === '.resilient') { const { title, comments, description, incidentTypes, severityCode, name } = action.params .subActionParams as { @@ -413,12 +413,12 @@ function restructureConnectorsThatSupportIncident( }, }, }, - ] as RawAlertAction[]; + ] as RawRuleAction[]; } } return [...acc, action]; - }, [] as RawAlertAction[]); + }, [] as RawRuleAction[]); return { ...doc, @@ -855,13 +855,13 @@ function addMappedParams( function getCorrespondingAction( actions: SavedObjectAttribute, connectorRef: string -): RawAlertAction | null { +): RawRuleAction | null { if (!Array.isArray(actions)) { return null; } else { return actions.find( - (action) => (action as RawAlertAction)?.actionRef === connectorRef - ) as RawAlertAction; + (action) => (action as RawRuleAction)?.actionRef === connectorRef + ) as RawRuleAction; } } diff --git a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts b/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts index ef48a701d0315..f5183e9e19dcc 100644 --- a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts @@ -8,9 +8,9 @@ import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager/server'; import { AlertTaskInstance, taskInstanceToAlertTaskInstance } from './alert_task_instance'; import uuid from 'uuid'; -import { SanitizedAlert } from '../types'; +import { SanitizedRule } from '../types'; -const alert: SanitizedAlert<{ +const alert: SanitizedRule<{ bar: boolean; }> = { id: 'alert-123', diff --git a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts b/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts index a6bb6a68ceae8..fd61b37a277de 100644 --- a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts +++ b/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts @@ -10,12 +10,12 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { ConcreteTaskInstance } from '../../../task_manager/server'; import { - SanitizedAlert, + SanitizedRule, RuleTaskState, ruleParamsSchema, ruleStateSchema, RuleTaskParams, - AlertTypeParams, + RuleTypeParams, } from '../../common'; export interface AlertTaskInstance extends ConcreteTaskInstance { @@ -26,9 +26,9 @@ export interface AlertTaskInstance extends ConcreteTaskInstance { const enumerateErrorFields = (e: t.Errors) => `${e.map(({ context }) => context.map(({ key }) => key).join('.'))}`; -export function taskInstanceToAlertTaskInstance( +export function taskInstanceToAlertTaskInstance( taskInstance: ConcreteTaskInstance, - alert?: SanitizedAlert + alert?: SanitizedRule ): AlertTaskInstance { return { ...taskInstance, diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts index 8b97bf9266e9a..af0220ccba987 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts @@ -18,21 +18,16 @@ import { KibanaRequest } from 'kibana/server'; import { asSavedObjectExecutionSource } from '../../../actions/server'; import { InjectActionParamsOpts } from './inject_action_params'; import { NormalizedRuleType } from '../rule_type_registry'; -import { - AlertInstanceContext, - AlertInstanceState, - AlertTypeParams, - AlertTypeState, -} from '../types'; +import { AlertInstanceContext, AlertInstanceState, RuleTypeParams, RuleTypeState } from '../types'; jest.mock('./inject_action_params', () => ({ injectActionParams: jest.fn(), })); const ruleType: NormalizedRuleType< - AlertTypeParams, - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, 'default' | 'other-group', @@ -66,9 +61,9 @@ const mockActionsPlugin = actionsMock.createStart(); const mockEventLogger = eventLoggerMock.create(); const createExecutionHandlerParams: jest.Mocked< CreateExecutionHandlerOptions< - AlertTypeParams, - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, 'default' | 'other-group', diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts index 56e1a25cb0655..7752817f17c85 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts @@ -9,12 +9,7 @@ import { asSavedObjectExecutionSource } from '../../../actions/server'; import { SAVED_OBJECT_REL_PRIMARY } from '../../../event_log/server'; import { EVENT_LOG_ACTIONS } from '../plugin'; import { injectActionParams } from './inject_action_params'; -import { - AlertInstanceContext, - AlertInstanceState, - AlertTypeParams, - AlertTypeState, -} from '../types'; +import { AlertInstanceContext, AlertInstanceState, RuleTypeParams, RuleTypeState } from '../types'; import { UntypedNormalizedRuleType } from '../rule_type_registry'; import { isEphemeralTaskRejectedDueToCapacityError } from '../../../task_manager/server'; @@ -26,9 +21,9 @@ export type ExecutionHandler = ( ) => Promise; export function createExecutionHandler< - Params extends AlertTypeParams, - ExtractedParams extends AlertTypeParams, - State extends AlertTypeState, + Params extends RuleTypeParams, + ExtractedParams extends RuleTypeParams, + State extends RuleTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, ActionGroupIds extends string, diff --git a/x-pack/plugins/alerting/server/task_runner/fixtures.ts b/x-pack/plugins/alerting/server/task_runner/fixtures.ts index 1c8e1776a523f..613f3ed9e1645 100644 --- a/x-pack/plugins/alerting/server/task_runner/fixtures.ts +++ b/x-pack/plugins/alerting/server/task_runner/fixtures.ts @@ -7,9 +7,9 @@ import { isNil } from 'lodash'; import { - Alert, - AlertExecutionStatusWarningReasons, - AlertTypeParams, + Rule, + RuleExecutionStatusWarningReasons, + RuleTypeParams, RecoveredActionGroup, } from '../../common'; import { getDefaultRuleMonitoring } from './task_runner'; @@ -121,7 +121,7 @@ export const mockRunNowResponse = { export const mockDate = new Date('2019-02-12T21:01:22.479Z'); -export const mockedRuleTypeSavedObject: Alert = { +export const mockedRuleTypeSavedObject: Rule = { id: '1', consumer: 'bar', createdAt: mockDate, @@ -335,7 +335,7 @@ const generateMessage = ({ } if ( status === 'warning' && - reason === AlertExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS + reason === RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS ) { return `The maximum number of actions for this rule type was reached; excess actions were not triggered.`; } diff --git a/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts b/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts index 11ac3f92d1071..f2a41038e257e 100644 --- a/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts +++ b/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts @@ -6,13 +6,13 @@ */ import { i18n } from '@kbn/i18n'; -import { AlertActionParams } from '../types'; +import { RuleActionParams } from '../types'; export interface InjectActionParamsOpts { ruleId: string; spaceId: string | undefined; actionTypeId: string; - actionParams: AlertActionParams; + actionParams: RuleActionParams; } export function injectActionParams({ diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index c261b4ddbba25..21109b60e012d 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -9,12 +9,12 @@ import sinon from 'sinon'; import { schema } from '@kbn/config-schema'; import { usageCountersServiceMock } from 'src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock'; import { - AlertExecutorOptions, - AlertTypeParams, - AlertTypeState, + RuleExecutorOptions, + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, - AlertExecutionStatusWarningReasons, + RuleExecutionStatusWarningReasons, } from '../types'; import { ConcreteTaskInstance, @@ -96,7 +96,7 @@ describe('Task Runner', () => { afterAll(() => fakeTimer.restore()); const encryptedSavedObjectsClient = encryptedSavedObjectsMock.createClient(); - const services = alertsMock.createAlertServices(); + const services = alertsMock.createRuleExecutorServices(); const actionsClient = actionsClientMock.create(); const rulesClient = rulesClientMock.create(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -282,9 +282,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -386,9 +386,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -499,9 +499,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -550,9 +550,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -607,9 +607,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -667,9 +667,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -707,9 +707,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -797,9 +797,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -865,9 +865,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -940,9 +940,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -1057,9 +1057,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -1210,9 +1210,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -1294,9 +1294,9 @@ describe('Task Runner', () => { ruleTypeWithCustomRecovery.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -1356,9 +1356,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -1567,9 +1567,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -1806,9 +1806,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -1957,9 +1957,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -2065,9 +2065,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -2169,9 +2169,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -2349,9 +2349,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -2578,9 +2578,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -2608,9 +2608,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -2652,7 +2652,7 @@ describe('Task Runner', () => { }; const warning = { - reason: AlertExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, + reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, message: translations.taskRunner.warning.maxExecutableActions, }; @@ -2662,9 +2662,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -2820,7 +2820,7 @@ describe('Task Runner', () => { status: 'warning', numberOfTriggeredActions: ruleTypeWithConfig.config.execution.actions.max, numberOfScheduledActions: mockActions.length, - reason: AlertExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, + reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, task: true, consumer: 'bar', }) @@ -2852,9 +2852,9 @@ describe('Task Runner', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 0e69131711067..36c8bddb1ff82 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -14,7 +14,7 @@ import { KibanaRequest, Logger } from '../../../../../src/core/server'; import { TaskRunnerContext } from './task_runner_factory'; import { ConcreteTaskInstance, throwUnrecoverableError } from '../../../task_manager/server'; import { createExecutionHandler, ExecutionHandler } from './create_execution_handler'; -import { Alert as CreatedAlert, createAlertFactory } from '../alert'; +import { Alert, createAlertFactory } from '../alert'; import { createWrappedScopedClusterClientFactory, ElasticsearchError, @@ -26,9 +26,9 @@ import { validateRuleTypeParams, } from '../lib'; import { - Alert, - AlertExecutionStatus, - AlertExecutionStatusErrorReasons, + Rule, + RuleExecutionStatus, + RuleExecutionStatusErrorReasons, IntervalSchedule, RawAlertInstance, RawRule, @@ -39,7 +39,7 @@ import { RuleMonitoringHistory, RuleTaskState, RuleTypeRegistry, - SanitizedAlert, + SanitizedRule, } from '../types'; import { asErr, asOk, map, promiseResult, resolveErr, Resultable } from '../lib/result_type'; import { getExecutionDurationPercentiles, getExecutionSuccessRatio } from '../lib/monitoring'; @@ -51,8 +51,8 @@ import { partiallyUpdateAlert } from '../saved_objects'; import { AlertInstanceContext, AlertInstanceState, - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeState, MONITORING_HISTORY_LIMIT, parseDuration, WithoutReservedActionGroups, @@ -91,9 +91,9 @@ export const getDefaultRuleMonitoring = (): RuleMonitoring => ({ }); export class TaskRunner< - Params extends AlertTypeParams, - ExtractedParams extends AlertTypeParams, - State extends AlertTypeState, + Params extends RuleTypeParams, + ExtractedParams extends RuleTypeParams, + State extends RuleTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, ActionGroupIds extends string, @@ -201,7 +201,7 @@ export class TaskRunner< spaceId: string, apiKey: RawRule['apiKey'], kibanaBaseUrl: string | undefined, - actions: Alert['actions'], + actions: Rule['actions'], ruleParams: Params, request: KibanaRequest ) { @@ -252,7 +252,7 @@ export class TaskRunner< } } - private isRuleSnoozed(rule: SanitizedAlert): boolean { + private isRuleSnoozed(rule: SanitizedRule): boolean { if (rule.muteAll) { return true; } @@ -288,7 +288,7 @@ export class TaskRunner< private async executeAlert( alertId: string, - alert: CreatedAlert, + alert: Alert, executionHandler: ExecutionHandler, alertExecutionStore: AlertExecutionStore ) { @@ -312,7 +312,7 @@ export class TaskRunner< private async executeAlerts( fakeRequest: KibanaRequest, - rule: SanitizedAlert, + rule: SanitizedRule, params: Params, executionHandler: ExecutionHandler, spaceId: string, @@ -343,10 +343,10 @@ export class TaskRunner< const alerts = mapValues< Record, - CreatedAlert + Alert >( alertRawInstances, - (rawAlert, alertId) => new CreatedAlert(alertId, rawAlert) + (rawAlert, alertId) => new Alert(alertId, rawAlert) ); const originalAlerts = cloneDeep(alerts); @@ -440,7 +440,7 @@ export class TaskRunner< event.event = event.event || {}; event.event.outcome = 'failure'; - throw new ErrorWithReason(AlertExecutionStatusErrorReasons.Execute, err); + throw new ErrorWithReason(RuleExecutionStatusErrorReasons.Execute, err); } event.message = `rule executed: ${ruleLabel}`; @@ -456,7 +456,7 @@ export class TaskRunner< // Cleanup alerts that are no longer scheduling actions to avoid over populating the alertInstances object const alertsWithScheduledActions = pickBy( alerts, - (alert: CreatedAlert) => alert.hasScheduledActions() + (alert: Alert) => alert.hasScheduledActions() ); const recoveredAlerts = getRecoveredAlerts(alerts, originalAlertIds); @@ -502,7 +502,7 @@ export class TaskRunner< const mutedAlertIdsSet = new Set(mutedInstanceIds); const alertsWithExecutableActions = Object.entries(alertsWithScheduledActions).filter( - ([alertName, alert]: [string, CreatedAlert]) => { + ([alertName, alert]: [string, Alert]) => { const throttled = alert.isThrottled(throttle); const muted = mutedAlertIdsSet.has(alertName); let shouldExecuteAction = true; @@ -530,7 +530,7 @@ export class TaskRunner< await Promise.all( alertsWithExecutableActions.map( - ([alertId, alert]: [string, CreatedAlert]) => + ([alertId, alert]: [string, Alert]) => this.executeAlert(alertId, alert, executionHandler, alertExecutionStore) ) ); @@ -570,7 +570,7 @@ export class TaskRunner< alertExecutionStore, alertTypeState: updatedRuleTypeState || undefined, alertInstances: mapValues< - Record>, + Record>, RawAlertInstance >(alertsWithScheduledActions, (alert) => alert.toRaw()), }; @@ -579,7 +579,7 @@ export class TaskRunner< private async validateAndExecuteRule( fakeRequest: KibanaRequest, apiKey: RawRule['apiKey'], - rule: SanitizedAlert, + rule: SanitizedRule, event: Event ) { const { @@ -617,14 +617,14 @@ export class TaskRunner< enabled = decryptedAttributes.enabled; consumer = decryptedAttributes.consumer; } catch (err) { - throw new ErrorWithReason(AlertExecutionStatusErrorReasons.Decrypt, err); + throw new ErrorWithReason(RuleExecutionStatusErrorReasons.Decrypt, err); } this.ruleConsumer = consumer; if (!enabled) { throw new ErrorWithReason( - AlertExecutionStatusErrorReasons.Disabled, + RuleExecutionStatusErrorReasons.Disabled, new Error(`Rule failed to execute because rule ran after it was disabled.`) ); } @@ -634,7 +634,7 @@ export class TaskRunner< // Get rules client with space level permissions const rulesClient = this.context.getRulesClientWithRequest(fakeRequest); - let rule: SanitizedAlert; + let rule: SanitizedRule; // Ensure API key is still valid and user has access try { @@ -651,7 +651,7 @@ export class TaskRunner< }); } } catch (err) { - throw new ErrorWithReason(AlertExecutionStatusErrorReasons.Read, err); + throw new ErrorWithReason(RuleExecutionStatusErrorReasons.Read, err); } this.ruleName = rule.name; @@ -659,7 +659,7 @@ export class TaskRunner< try { this.ruleTypeRegistry.ensureRuleTypeEnabled(rule.alertTypeId); } catch (err) { - throw new ErrorWithReason(AlertExecutionStatusErrorReasons.License, err); + throw new ErrorWithReason(RuleExecutionStatusErrorReasons.License, err); } if (rule.monitoring) { @@ -760,7 +760,7 @@ export class TaskRunner< return getDefaultRuleMonitoring(); }) ?? getDefaultRuleMonitoring(); - const executionStatus = map( + const executionStatus = map( state, (ruleExecutionState) => executionStatusFromState(ruleExecutionState), (err: ElasticsearchError) => executionStatusFromError(err) @@ -992,11 +992,11 @@ export class TaskRunner< this.inMemoryMetrics.increment(IN_MEMORY_METRICS.RULE_TIMEOUTS); // Update the rule saved object with execution status - const executionStatus: AlertExecutionStatus = { + const executionStatus: RuleExecutionStatus = { lastExecutionDate: new Date(), status: 'error', error: { - reason: AlertExecutionStatusErrorReasons.Timeout, + reason: RuleExecutionStatusErrorReasons.Timeout, message: `${this.ruleType.id}:${ruleId}: execution cancelled due to timeout - exceeded rule type timeout of ${this.ruleType.ruleTaskTimeout}`, }, }; diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts index 68c005cc4b765..1e24ac986f015 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts @@ -8,9 +8,9 @@ import sinon from 'sinon'; import { usageCountersServiceMock } from 'src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock'; import { - AlertExecutorOptions, - AlertTypeParams, - AlertTypeState, + RuleExecutorOptions, + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, } from '../types'; @@ -32,7 +32,7 @@ import { actionsMock, actionsClientMock } from '../../../actions/server/mocks'; import { alertsMock, rulesClientMock } from '../mocks'; import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; import { IEventLogger } from '../../../event_log/server'; -import { Alert, RecoveredActionGroup } from '../../common'; +import { Rule, RecoveredActionGroup } from '../../common'; import { UntypedNormalizedRuleType } from '../rule_type_registry'; import { ruleTypeRegistryMock } from '../rule_type_registry.mock'; import { dataPluginMock } from '../../../../../src/plugins/data/server/mocks'; @@ -98,7 +98,7 @@ describe('Task Runner Cancel', () => { afterAll(() => fakeTimer.restore()); const encryptedSavedObjectsClient = encryptedSavedObjectsMock.createClient(); - const services = alertsMock.createAlertServices(); + const services = alertsMock.createRuleExecutorServices(); const actionsClient = actionsClientMock.create(); const rulesClient = rulesClientMock.create(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -138,7 +138,7 @@ describe('Task Runner Cancel', () => { const mockDate = new Date('2019-02-12T21:01:22.479Z'); - const mockedRuleSavedObject: Alert = { + const mockedRuleSavedObject: Rule = { id: '1', consumer: 'bar', createdAt: mockDate, @@ -402,9 +402,9 @@ describe('Task Runner Cancel', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -441,9 +441,9 @@ describe('Task Runner Cancel', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string @@ -476,9 +476,9 @@ describe('Task Runner Cancel', () => { ruleType.executor.mockImplementation( async ({ services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, string diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts index 0ceced10e799b..2cba16152e198 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts @@ -21,10 +21,10 @@ import { RunContext } from '../../../task_manager/server'; import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server'; import { PluginStartContract as ActionsPluginStartContract } from '../../../actions/server'; import { - AlertTypeParams, + RuleTypeParams, RuleTypeRegistry, SpaceIdToNamespaceFunction, - AlertTypeState, + RuleTypeState, AlertInstanceState, AlertInstanceContext, } from '../types'; @@ -70,9 +70,9 @@ export class TaskRunnerFactory { } public create< - Params extends AlertTypeParams, - ExtractedParams extends AlertTypeParams, - State extends AlertTypeState, + Params extends RuleTypeParams, + ExtractedParams extends RuleTypeParams, + State extends RuleTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, ActionGroupIds extends string, diff --git a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts index 3f9fe9e9c59e0..387bdc00125f0 100644 --- a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts +++ b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts @@ -6,10 +6,10 @@ */ import { - AlertActionParams, + RuleActionParams, AlertInstanceState, AlertInstanceContext, - AlertTypeParams, + RuleTypeParams, } from '../types'; import { PluginStartContract as ActionsPluginStartContract } from '../../../actions/server'; @@ -26,8 +26,8 @@ interface TransformActionParamsOptions { alertActionGroup: string; alertActionGroupName: string; alertActionSubgroup?: string; - actionParams: AlertActionParams; - alertParams: AlertTypeParams; + actionParams: RuleActionParams; + alertParams: RuleTypeParams; state: AlertInstanceState; kibanaBaseUrl?: string; context: AlertInstanceContext; @@ -51,7 +51,7 @@ export function transformActionParams({ state, kibanaBaseUrl, alertParams, -}: TransformActionParamsOptions): AlertActionParams { +}: TransformActionParamsOptions): RuleActionParams { // when the list of variables we pass in here changes, // the UI will need to be updated as well; see: // x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts diff --git a/x-pack/plugins/alerting/server/task_runner/types.ts b/x-pack/plugins/alerting/server/task_runner/types.ts index d8483139a92b9..d5ad11f246c49 100644 --- a/x-pack/plugins/alerting/server/task_runner/types.ts +++ b/x-pack/plugins/alerting/server/task_runner/types.ts @@ -9,19 +9,19 @@ import { Dictionary } from 'lodash'; import { KibanaRequest, Logger } from 'kibana/server'; import { ActionGroup, - AlertAction, + RuleAction, AlertInstanceContext, AlertInstanceState, - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeState, IntervalSchedule, RuleExecutionState, RuleMonitoring, RuleTaskState, - SanitizedAlert, + SanitizedRule, } from '../../common'; import { ConcreteTaskInstance } from '../../../task_manager/server'; -import { Alert as CreatedAlert } from '../alert'; +import { Alert } from '../alert'; import { IEventLogger } from '../../../event_log/server'; import { NormalizedRuleType } from '../rule_type_registry'; import { ExecutionHandler } from './create_execution_handler'; @@ -48,9 +48,9 @@ export interface TrackAlertDurationsParams< InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext > { - originalAlerts: Dictionary>; - currentAlerts: Dictionary>; - recoveredAlerts: Dictionary>; + originalAlerts: Dictionary>; + currentAlerts: Dictionary>; + recoveredAlerts: Dictionary>; } export interface GenerateNewAndRecoveredAlertEventsParams< @@ -59,16 +59,16 @@ export interface GenerateNewAndRecoveredAlertEventsParams< > { eventLogger: IEventLogger; executionId: string; - originalAlerts: Dictionary>; - currentAlerts: Dictionary>; - recoveredAlerts: Dictionary>; + originalAlerts: Dictionary>; + currentAlerts: Dictionary>; + recoveredAlerts: Dictionary>; ruleId: string; ruleLabel: string; namespace: string | undefined; ruleType: NormalizedRuleType< - AlertTypeParams, - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeParams, + RuleTypeState, { [x: string]: unknown; }, @@ -78,7 +78,7 @@ export interface GenerateNewAndRecoveredAlertEventsParams< string, string >; - rule: SanitizedAlert; + rule: SanitizedRule; spaceId: string; } @@ -89,7 +89,7 @@ export interface ScheduleActionsForRecoveredAlertsParams< > { logger: Logger; recoveryActionGroup: ActionGroup; - recoveredAlerts: Dictionary>; + recoveredAlerts: Dictionary>; executionHandler: ExecutionHandler; mutedAlertIdsSet: Set; ruleLabel: string; @@ -103,8 +103,8 @@ export interface LogActiveAndRecoveredAlertsParams< RecoveryActionGroupId extends string > { logger: Logger; - activeAlerts: Dictionary>; - recoveredAlerts: Dictionary>; + activeAlerts: Dictionary>; + recoveredAlerts: Dictionary>; ruleLabel: string; canSetRecoveryContext: boolean; } @@ -112,9 +112,9 @@ export interface LogActiveAndRecoveredAlertsParams< // / ExecutionHandler export interface CreateExecutionHandlerOptions< - Params extends AlertTypeParams, - ExtractedParams extends AlertTypeParams, - State extends AlertTypeState, + Params extends RuleTypeParams, + ExtractedParams extends RuleTypeParams, + State extends RuleTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, ActionGroupIds extends string, @@ -126,7 +126,7 @@ export interface CreateExecutionHandlerOptions< executionId: string; tags?: string[]; actionsPlugin: ActionsPluginStartContract; - actions: AlertAction[]; + actions: RuleAction[]; spaceId: string; apiKey: RawRule['apiKey']; kibanaBaseUrl: string | undefined; @@ -142,7 +142,7 @@ export interface CreateExecutionHandlerOptions< logger: Logger; eventLogger: IEventLogger; request: KibanaRequest; - ruleParams: AlertTypeParams; + ruleParams: RuleTypeParams; supportsEphemeralTasks: boolean; maxEphemeralActionsPerRule: number; } diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index 10d191d9b43e4..cfd03f4edf184 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -23,23 +23,23 @@ import { SavedObjectsClientContract, } from '../../../../src/core/server'; import { - Alert, - AlertActionParams, + Rule, + RuleTypeParams, + RuleTypeState, + RuleActionParams, + RuleExecutionStatuses, + RuleExecutionStatusErrorReasons, + RuleExecutionStatusWarningReasons, + RuleNotifyWhenType, ActionGroup, - AlertTypeParams, - AlertTypeState, AlertInstanceContext, AlertInstanceState, - AlertExecutionStatuses, - AlertExecutionStatusErrorReasons, AlertsHealth, - AlertNotifyWhenType, WithoutReservedActionGroups, ActionVariable, SanitizedRuleConfig, RuleMonitoring, MappedParams, - AlertExecutionStatusWarningReasons, } from '../common'; import { LicenseType } from '../../licensing/server'; import { ISearchStartSearchSource } from '../../../../src/plugins/data/common'; @@ -69,7 +69,7 @@ export interface AlertingRequestHandlerContext extends RequestHandlerContext { */ export type AlertingRouter = IRouter; -export interface AlertServices< +export interface RuleExecutorServices< InstanceState extends AlertInstanceState = AlertInstanceState, InstanceContext extends AlertInstanceContext = AlertInstanceContext, ActionGroupIds extends string = never @@ -86,9 +86,9 @@ export interface AlertServices< shouldStopExecution: () => boolean; } -export interface AlertExecutorOptions< - Params extends AlertTypeParams = never, - State extends AlertTypeState = never, +export interface RuleExecutorOptions< + Params extends RuleTypeParams = never, + State extends RuleTypeState = never, InstanceState extends AlertInstanceState = never, InstanceContext extends AlertInstanceContext = never, ActionGroupIds extends string = never @@ -97,7 +97,7 @@ export interface AlertExecutorOptions< executionId: string; startedAt: Date; previousStartedAt: Date | null; - services: AlertServices; + services: RuleExecutorServices; params: Params; state: State; rule: SanitizedRuleConfig; @@ -109,29 +109,29 @@ export interface AlertExecutorOptions< updatedBy: string | null; } -export interface RuleParamsAndRefs { +export interface RuleParamsAndRefs { references: SavedObjectReference[]; params: Params; } export type ExecutorType< - Params extends AlertTypeParams = never, - State extends AlertTypeState = never, + Params extends RuleTypeParams = never, + State extends RuleTypeState = never, InstanceState extends AlertInstanceState = never, InstanceContext extends AlertInstanceContext = never, ActionGroupIds extends string = never > = ( - options: AlertExecutorOptions + options: RuleExecutorOptions ) => Promise; -export interface AlertTypeParamsValidator { +export interface RuleTypeParamsValidator { validate: (object: unknown) => Params; } export interface RuleType< - Params extends AlertTypeParams = never, - ExtractedParams extends AlertTypeParams = never, - State extends AlertTypeState = never, + Params extends RuleTypeParams = never, + ExtractedParams extends RuleTypeParams = never, + State extends RuleTypeState = never, InstanceState extends AlertInstanceState = never, InstanceContext extends AlertInstanceContext = never, ActionGroupIds extends string = never, @@ -140,7 +140,7 @@ export interface RuleType< id: string; name: string; validate?: { - params?: AlertTypeParamsValidator; + params?: RuleTypeParamsValidator; }; actionGroups: Array>; defaultActionGroupId: ActionGroup['id']; @@ -175,57 +175,57 @@ export interface RuleType< config?: RuleTypeConfig; } export type UntypedRuleType = RuleType< - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext >; -export interface RawAlertAction extends SavedObjectAttributes { +export interface RawRuleAction extends SavedObjectAttributes { group: string; actionRef: string; actionTypeId: string; - params: AlertActionParams; + params: RuleActionParams; } -export interface AlertMeta extends SavedObjectAttributes { +export interface RuleMeta extends SavedObjectAttributes { versionApiKeyLastmodified?: string; } // note that the `error` property is "null-able", as we're doing a partial -// update on the alert when we update this data, but need to ensure we +// update on the rule when we update this data, but need to ensure we // delete any previous error if the current status has no error export interface RawRuleExecutionStatus extends SavedObjectAttributes { - status: AlertExecutionStatuses; + status: RuleExecutionStatuses; lastExecutionDate: string; lastDuration?: number; error: null | { - reason: AlertExecutionStatusErrorReasons; + reason: RuleExecutionStatusErrorReasons; message: string; }; warning: null | { - reason: AlertExecutionStatusWarningReasons; + reason: RuleExecutionStatusWarningReasons; message: string; }; } -export type PartialAlert = Pick, 'id'> & - Partial, 'id'>>; +export type PartialRule = Pick, 'id'> & + Partial, 'id'>>; -export interface AlertWithLegacyId extends Alert { +export interface RuleWithLegacyId extends Rule { legacyId: string | null; } -export type SanitizedRuleWithLegacyId = Omit< - AlertWithLegacyId, +export type SanitizedRuleWithLegacyId = Omit< + RuleWithLegacyId, 'apiKey' >; -export type PartialAlertWithLegacyId = Pick< - AlertWithLegacyId, +export type PartialRuleWithLegacyId = Pick< + RuleWithLegacyId, 'id' > & - Partial, 'id'>>; + Partial, 'id'>>; export interface RawRule extends SavedObjectAttributes { enabled: boolean; @@ -235,7 +235,7 @@ export interface RawRule extends SavedObjectAttributes { consumer: string; legacyId: string | null; schedule: SavedObjectAttributes; - actions: RawAlertAction[]; + actions: RawRuleAction[]; params: SavedObjectAttributes; mapped_params?: MappedParams; scheduledTaskId?: string | null; @@ -246,28 +246,15 @@ export interface RawRule extends SavedObjectAttributes { apiKey: string | null; apiKeyOwner: string | null; throttle: string | null; - notifyWhen: AlertNotifyWhenType | null; + notifyWhen: RuleNotifyWhenType | null; muteAll: boolean; mutedInstanceIds: string[]; - meta?: AlertMeta; + meta?: RuleMeta; executionStatus: RawRuleExecutionStatus; monitoring?: RuleMonitoring; snoozeEndTime?: string | null; // Remove ? when this parameter is made available in the public API } -export type AlertInfoParams = Pick< - RawRule, - | 'params' - | 'throttle' - | 'notifyWhen' - | 'muteAll' - | 'mutedInstanceIds' - | 'name' - | 'tags' - | 'createdBy' - | 'updatedBy' ->; - export interface AlertingPlugin { setup: PluginSetupContract; start: PluginStartContract; diff --git a/x-pack/plugins/apm/server/routes/alerts/alerting_es_client.ts b/x-pack/plugins/apm/server/routes/alerts/alerting_es_client.ts index 876d02137c3f8..ebab01464de6b 100644 --- a/x-pack/plugins/apm/server/routes/alerts/alerting_es_client.ts +++ b/x-pack/plugins/apm/server/routes/alerts/alerting_es_client.ts @@ -9,13 +9,13 @@ import { ESSearchRequest, ESSearchResponse, } from '../../../../../../src/core/types/elasticsearch'; -import { AlertServices } from '../../../../alerting/server'; +import { RuleExecutorServices } from '../../../../alerting/server'; export async function alertingEsClient({ scopedClusterClient, params, }: { - scopedClusterClient: AlertServices< + scopedClusterClient: RuleExecutorServices< never, never, never diff --git a/x-pack/plugins/infra/public/alerting/inventory/index.ts b/x-pack/plugins/infra/public/alerting/inventory/index.ts index ca9f383aa28b0..300832d8a47ed 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/index.ts +++ b/x-pack/plugins/infra/public/alerting/inventory/index.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import { AlertTypeParams as RuleTypeParams } from '../../../../alerting/common'; +import { RuleTypeParams } from '../../../../alerting/common'; import { ObservabilityRuleTypeModel } from '../../../../observability/public'; import { InventoryMetricConditions, diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/index.ts b/x-pack/plugins/infra/public/alerting/metric_anomaly/index.ts index 95931bff65b53..7591f2e35d59e 100644 --- a/x-pack/plugins/infra/public/alerting/metric_anomaly/index.ts +++ b/x-pack/plugins/infra/public/alerting/metric_anomaly/index.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { METRIC_ANOMALY_ALERT_TYPE_ID } from '../../../common/alerting/metrics'; import { RuleTypeModel } from '../../../../triggers_actions_ui/public'; -import { AlertTypeParams as RuleTypeParams } from '../../../../alerting/common'; +import { RuleTypeParams } from '../../../../alerting/common'; import { validateMetricAnomaly } from './components/validation'; interface MetricAnomalyRuleTypeParams extends RuleTypeParams { diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts index e67ece7836595..39b632b82fd97 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import { AlertTypeParams as RuleTypeParams } from '../../../../alerting/common'; +import { RuleTypeParams } from '../../../../alerting/common'; import { ObservabilityRuleTypeModel } from '../../../../observability/public'; import { MetricExpressionParams, diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index f12cd565a6b97..f7d07c4e0e118 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -15,7 +15,7 @@ import { AlertInstanceState as AlertState, RecoveredActionGroup, } from '../../../../../alerting/common'; -import { Alert, AlertTypeState as RuleTypeState } from '../../../../../alerting/server'; +import { Alert, RuleTypeState } from '../../../../../alerting/server'; import { AlertStates, InventoryMetricThresholdParams } from '../../../../common/alerting/metrics'; import { createFormatter } from '../../../../common/formatters'; import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_metric_label'; diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts index b5bc3a15896cb..be106226d0ee4 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts @@ -19,7 +19,7 @@ import { Alert, AlertInstanceContext as AlertContext, AlertInstanceState as AlertState, - AlertTypeState as RuleTypeState, + RuleTypeState, } from '../../../../../alerting/server'; import { diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts index 0fb2ff87fd02c..78ed4cb4b7bbd 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts @@ -14,7 +14,7 @@ import { AlertInstanceContext as AlertContext, AlertInstanceState as AlertState, } from '../../../../../alerting/common'; -import { AlertExecutorOptions as RuleExecutorOptions } from '../../../../../alerting/server'; +import { RuleExecutorOptions } from '../../../../../alerting/server'; import { MlPluginSetup } from '../../../../../ml/server'; import { AlertStates, MetricAnomalyParams } from '../../../../common/alerting/metrics'; import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds'; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index 1929a91d763d1..95b10842d9b35 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -12,7 +12,7 @@ import { // import { RecoveredActionGroup } from '../../../../../alerting/common'; import { AlertInstanceMock, - AlertServicesMock, + RuleExecutorServicesMock, alertsMock, } from '../../../../../alerting/server/mocks'; import { LifecycleAlertServices } from '../../../../../rule_registry/server'; @@ -777,8 +777,9 @@ const mockLibs: any = { const executor = createMetricThresholdExecutor(mockLibs); -const alertsServices = alertsMock.createAlertServices(); -const services: AlertServicesMock & LifecycleAlertServices = { +const alertsServices = alertsMock.createRuleExecutorServices(); +const services: RuleExecutorServicesMock & + LifecycleAlertServices = { ...alertsServices, ...ruleRegistryMocks.createLifecycleAlertServices(alertsServices), }; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index 1f0e1c3d192c2..0b089c5eb5a86 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -14,7 +14,7 @@ import { AlertInstanceState as AlertState, RecoveredActionGroup, } from '../../../../../alerting/common'; -import { Alert, AlertTypeState as RuleTypeState } from '../../../../../alerting/server'; +import { Alert, RuleTypeState } from '../../../../../alerting/server'; import { AlertStates, Comparator } from '../../../../common/alerting/metrics'; import { createFormatter } from '../../../../common/formatters'; import { InfraBackendLibs } from '../../infra_types'; diff --git a/x-pack/plugins/ml/common/types/alerts.ts b/x-pack/plugins/ml/common/types/alerts.ts index 0cb0b4e388e48..8d5c6831c1ccf 100644 --- a/x-pack/plugins/ml/common/types/alerts.ts +++ b/x-pack/plugins/ml/common/types/alerts.ts @@ -7,7 +7,7 @@ import { AnomalyResultType } from './anomalies'; import { ANOMALY_RESULT_TYPE } from '../constants/anomalies'; -import type { AlertTypeParams, Alert } from '../../../alerting/common'; +import type { RuleTypeParams, Rule } from '../../../alerting/common'; export type PreviewResultsKeys = 'record_results' | 'bucket_results' | 'influencer_results'; export type TopHitsResultsKeys = 'top_record_hits' | 'top_bucket_hits' | 'top_influencer_hits'; @@ -98,14 +98,14 @@ export type MlAnomalyDetectionAlertParams = { includeInterim: boolean; lookbackInterval: string | null | undefined; topNBuckets: number | null | undefined; -} & AlertTypeParams; +} & RuleTypeParams; export type MlAnomalyDetectionAlertAdvancedSettings = Pick< MlAnomalyDetectionAlertParams, 'lookbackInterval' | 'topNBuckets' >; -export type MlAnomalyDetectionAlertRule = Omit, 'apiKey'>; +export type MlAnomalyDetectionAlertRule = Omit, 'apiKey'>; export interface JobAlertingRuleStats { alerting_rules?: MlAnomalyDetectionAlertRule[]; @@ -140,7 +140,7 @@ export type MlAnomalyDetectionJobsHealthRuleParams = { | null; errorMessages?: CommonHealthCheckConfig | null; } | null; -} & AlertTypeParams; +} & RuleTypeParams; export type JobsHealthRuleTestsConfig = MlAnomalyDetectionJobsHealthRuleParams['testsConfig']; diff --git a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts index 68a86a927ac1a..e145ab7197bb9 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts @@ -20,7 +20,7 @@ import { ActionGroup, AlertInstanceContext, AlertInstanceState, - AlertTypeState, + RuleTypeState, } from '../../../../alerting/common'; export type AnomalyDetectionAlertContext = { @@ -53,7 +53,7 @@ export function registerAnomalyDetectionAlertType({ alerting.registerType< MlAnomalyDetectionAlertParams, never, // Only use if defining useSavedObjectReferences hook - AlertTypeState, + RuleTypeState, AlertInstanceState, AnomalyDetectionAlertContext, AnomalyScoreMatchGroupId diff --git a/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts b/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts index 1173f92930128..9d1e31612b51e 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts @@ -24,9 +24,9 @@ import { ActionGroup, AlertInstanceContext, AlertInstanceState, - AlertTypeState, + RuleTypeState, } from '../../../../alerting/common'; -import type { AlertExecutorOptions } from '../../../../alerting/server'; +import type { RuleExecutorOptions } from '../../../../alerting/server'; import type { JobMessage } from '../../../common/types/audit_message'; type ModelSizeStats = MlJobStats['model_size_stats']; @@ -85,7 +85,7 @@ export const REALTIME_ISSUE_DETECTED: ActionGroup, Record, @@ -101,7 +101,7 @@ export function registerJobsMonitoringRuleType({ alerting.registerType< AnomalyDetectionJobsHealthRuleParams, never, // Only use if defining useSavedObjectReferences hook - AlertTypeState, + RuleTypeState, AlertInstanceState, AnomalyDetectionJobsHealthAlertContext, AnomalyDetectionJobRealtimeIssue diff --git a/x-pack/plugins/monitoring/common/types/alerts.ts b/x-pack/plugins/monitoring/common/types/alerts.ts index 9abca4cbdc948..f4b3101b3eaa9 100644 --- a/x-pack/plugins/monitoring/common/types/alerts.ts +++ b/x-pack/plugins/monitoring/common/types/alerts.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Alert, AlertTypeParams, SanitizedAlert } from '../../../alerting/common'; +import { Rule, RuleTypeParams, SanitizedRule } from '../../../alerting/common'; import { AlertParamType, AlertMessageTokenType, @@ -13,14 +13,14 @@ import { AlertClusterHealthType, } from '../enums'; -export type CommonAlert = Alert | SanitizedAlert; +export type CommonAlert = Rule | SanitizedRule; export interface RulesByType { [type: string]: CommonAlertStatus[]; } export interface CommonAlertStatus { states: CommonAlertState[]; - sanitizedRule: Alert | SanitizedAlert; + sanitizedRule: Rule | SanitizedRule; } export interface CommonAlertState { diff --git a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx index 6f847ab8d693a..55e8d4c5bd245 100644 --- a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import type { AlertTypeParams } from '../../../../alerting/common'; +import type { RuleTypeParams } from '../../../../alerting/common'; import type { RuleTypeModel, ValidationResult } from '../../../../triggers_actions_ui/public'; import { RULE_CCR_READ_EXCEPTIONS, @@ -20,7 +20,7 @@ import { LazyExpressionProps, } from '../components/param_details_form/lazy_expression'; -interface ValidateOptions extends AlertTypeParams { +interface ValidateOptions extends RuleTypeParams { duration: string; } diff --git a/x-pack/plugins/monitoring/public/alerts/components/param_details_form/validation.tsx b/x-pack/plugins/monitoring/public/alerts/components/param_details_form/validation.tsx index c8107e0f063bc..c240810786f63 100644 --- a/x-pack/plugins/monitoring/public/alerts/components/param_details_form/validation.tsx +++ b/x-pack/plugins/monitoring/public/alerts/components/param_details_form/validation.tsx @@ -6,11 +6,11 @@ */ import { i18n } from '@kbn/i18n'; -import { AlertTypeParams } from '../../../../../alerting/common'; +import { RuleTypeParams } from '../../../../../alerting/common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ValidationResult } from '../../../../../triggers_actions_ui/public/types'; -export type MonitoringAlertTypeParams = ValidateOptions & AlertTypeParams; +export type MonitoringAlertTypeParams = ValidateOptions & RuleTypeParams; interface ValidateOptions { duration: string; threshold: number; diff --git a/x-pack/plugins/monitoring/public/alerts/large_shard_size_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/large_shard_size_alert/index.tsx index c80b65452ddb0..3be2d4477fe10 100644 --- a/x-pack/plugins/monitoring/public/alerts/large_shard_size_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/large_shard_size_alert/index.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import type { AlertTypeParams } from '../../../../alerting/common'; +import type { RuleTypeParams } from '../../../../alerting/common'; import type { RuleTypeModel, ValidationResult } from '../../../../triggers_actions_ui/public'; import { RULE_DETAILS, @@ -20,7 +20,7 @@ import { LazyExpressionProps, } from '../components/param_details_form/lazy_expression'; -interface ValidateOptions extends AlertTypeParams { +interface ValidateOptions extends RuleTypeParams { indexPattern: string; } diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.test.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.test.tsx index 0ba35969d8d17..86fa82b7515a2 100644 --- a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.test.tsx +++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.test.tsx @@ -18,7 +18,7 @@ import { RULE_DISK_USAGE, RULE_MEMORY_USAGE, } from '../../../common/constants'; -import { AlertExecutionStatusValues } from '../../../../alerting/common'; +import { RuleExecutionStatusValues } from '../../../../alerting/common'; import { AlertState } from '../../../common/types/alerts'; jest.mock('../../legacy_shims', () => ({ @@ -54,7 +54,7 @@ const mockAlert = { muteAll: false, mutedInstanceIds: [], executionStatus: { - status: AlertExecutionStatusValues[0], + status: RuleExecutionStatusValues[0], lastExecutionDate: new Date('2020-12-08'), }, notifyWhen: null, diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.test.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.test.tsx index 5f9e8ba8b64fc..60cd91f76f31e 100644 --- a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.test.tsx +++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.test.tsx @@ -18,7 +18,7 @@ import { RULE_DISK_USAGE, RULE_MEMORY_USAGE, } from '../../../common/constants'; -import { AlertExecutionStatusValues } from '../../../../alerting/common'; +import { RuleExecutionStatusValues } from '../../../../alerting/common'; import { AlertState } from '../../../common/types/alerts'; jest.mock('../../legacy_shims', () => ({ @@ -53,7 +53,7 @@ const mockAlert = { muteAll: false, mutedInstanceIds: [], executionStatus: { - status: AlertExecutionStatusValues[0], + status: RuleExecutionStatusValues[0], lastExecutionDate: new Date('2020-12-08'), }, notifyWhen: null, diff --git a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts index a276f96df009f..03727cf1b98bd 100644 --- a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts +++ b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts @@ -39,7 +39,7 @@ import { RULE_LARGE_SHARD_SIZE, } from '../../common/constants'; import { RulesClient } from '../../../alerting/server'; -import { Alert } from '../../../alerting/common'; +import { Rule } from '../../../alerting/common'; import { CommonAlertParams } from '../../common/types/alerts'; const BY_TYPE = { @@ -77,7 +77,7 @@ export class AlertsFactory { if (!alertClientAlerts.total || !alertClientAlerts.data?.length) { return []; } - return alertClientAlerts.data.map((alert) => new alertCls(alert as Alert) as BaseRule); + return alertClientAlerts.data.map((alert) => new alertCls(alert as Rule) as BaseRule); } public static getAll() { diff --git a/x-pack/plugins/monitoring/server/alerts/base_rule.ts b/x-pack/plugins/monitoring/server/alerts/base_rule.ts index 0c48fed40ee34..3769fb8e29912 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_rule.ts @@ -9,17 +9,12 @@ import { Logger, ElasticsearchClient } from 'kibana/server'; import { i18n } from '@kbn/i18n'; import { RuleType, - AlertExecutorOptions, + RuleExecutorOptions, Alert, RulesClient, - AlertServices, + RuleExecutorServices, } from '../../../alerting/server'; -import { - Alert as Rule, - AlertTypeParams, - RawAlertInstance, - SanitizedAlert, -} from '../../../alerting/common'; +import { Rule, RuleTypeParams, RawAlertInstance, SanitizedRule } from '../../../alerting/common'; import { ActionsClient } from '../../../actions/server'; import { AlertState, @@ -70,7 +65,7 @@ export class BaseRule { protected scopedLogger: Logger; constructor( - public sanitizedRule?: SanitizedAlert, + public sanitizedRule?: SanitizedRule, public ruleOptions: RuleOptions = defaultRuleOptions() ) { const defaultOptions = defaultRuleOptions(); @@ -99,7 +94,7 @@ export class BaseRule { minimumLicenseRequired: 'basic', isExportable: false, executor: ( - options: AlertExecutorOptions & { + options: RuleExecutorOptions & { state: ExecutedState; } ): Promise => this.execute(options), @@ -118,7 +113,7 @@ export class BaseRule { rulesClient: RulesClient, actionsClient: ActionsClient, actions: AlertEnableAction[] - ): Promise> { + ): Promise> { const existingRuleData = await rulesClient.find({ options: { search: this.ruleOptions.id, @@ -152,7 +147,7 @@ export class BaseRule { throttle = '1d', interval = '1m', } = this.ruleOptions; - return await rulesClient.create({ + return await rulesClient.create({ data: { enabled: true, tags: [], @@ -220,7 +215,7 @@ export class BaseRule { services, params, state, - }: AlertExecutorOptions & { + }: RuleExecutorOptions & { state: ExecutedState; }): Promise { this.scopedLogger.debug( @@ -260,7 +255,7 @@ export class BaseRule { protected async processData( data: AlertData[], clusters: AlertCluster[], - services: AlertServices, + services: RuleExecutorServices, state: ExecutedState ) { const currentUTC = +new Date(); diff --git a/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_rule.ts b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_rule.ts index e072602d6b711..1f90eb588d9c7 100644 --- a/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_rule.ts @@ -26,12 +26,12 @@ import { RULE_CCR_READ_EXCEPTIONS, RULE_DETAILS } from '../../common/constants'; import { fetchCCRReadExceptions } from '../lib/alerts/fetch_ccr_read_exceptions'; import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; import { parseDuration } from '../../../alerting/common/parse_duration'; -import { SanitizedAlert, RawAlertInstance } from '../../../alerting/common'; +import { SanitizedRule, RawAlertInstance } from '../../../alerting/common'; import { AlertingDefaults, createLink } from './alert_helpers'; import { Globals } from '../static_globals'; export class CCRReadExceptionsRule extends BaseRule { - constructor(public sanitizedRule?: SanitizedAlert) { + constructor(public sanitizedRule?: SanitizedRule) { super(sanitizedRule, { id: RULE_CCR_READ_EXCEPTIONS, name: RULE_DETAILS[RULE_CCR_READ_EXCEPTIONS].label, diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_health_rule.ts b/x-pack/plugins/monitoring/server/alerts/cluster_health_rule.ts index a40fafc65d636..256de16821e64 100644 --- a/x-pack/plugins/monitoring/server/alerts/cluster_health_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/cluster_health_rule.ts @@ -22,7 +22,7 @@ import { Alert } from '../../../alerting/server'; import { RULE_CLUSTER_HEALTH, LEGACY_RULE_DETAILS } from '../../common/constants'; import { AlertMessageTokenType, AlertClusterHealthType, AlertSeverity } from '../../common/enums'; import { AlertingDefaults } from './alert_helpers'; -import { SanitizedAlert } from '../../../alerting/common'; +import { SanitizedRule } from '../../../alerting/common'; import { fetchClusterHealth } from '../lib/alerts/fetch_cluster_health'; const RED_STATUS_MESSAGE = i18n.translate('xpack.monitoring.alerts.clusterHealth.redMessage', { @@ -37,7 +37,7 @@ const YELLOW_STATUS_MESSAGE = i18n.translate( ); export class ClusterHealthRule extends BaseRule { - constructor(public sanitizedRule?: SanitizedAlert) { + constructor(public sanitizedRule?: SanitizedRule) { super(sanitizedRule, { id: RULE_CLUSTER_HEALTH, name: LEGACY_RULE_DETAILS[RULE_CLUSTER_HEALTH].label, diff --git a/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.ts b/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.ts index 08a5cdb6c2780..a3f849c4736ea 100644 --- a/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.ts @@ -28,13 +28,13 @@ import { RULE_CPU_USAGE, RULE_DETAILS } from '../../common/constants'; import { ROUNDED_FLOAT } from '../../common/formatting'; import { fetchCpuUsageNodeStats } from '../lib/alerts/fetch_cpu_usage_node_stats'; import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; -import { RawAlertInstance, SanitizedAlert } from '../../../alerting/common'; +import { RawAlertInstance, SanitizedRule } from '../../../alerting/common'; import { parseDuration } from '../../../alerting/common/parse_duration'; import { AlertingDefaults, createLink } from './alert_helpers'; import { Globals } from '../static_globals'; export class CpuUsageRule extends BaseRule { - constructor(public sanitizedRule?: SanitizedAlert) { + constructor(public sanitizedRule?: SanitizedRule) { super(sanitizedRule, { id: RULE_CPU_USAGE, name: RULE_DETAILS[RULE_CPU_USAGE].label, diff --git a/x-pack/plugins/monitoring/server/alerts/disk_usage_rule.ts b/x-pack/plugins/monitoring/server/alerts/disk_usage_rule.ts index a52a2fd79d654..f2494ed918d1c 100644 --- a/x-pack/plugins/monitoring/server/alerts/disk_usage_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/disk_usage_rule.ts @@ -28,12 +28,12 @@ import { RULE_DISK_USAGE, RULE_DETAILS } from '../../common/constants'; import { ROUNDED_FLOAT } from '../../common/formatting'; import { fetchDiskUsageNodeStats } from '../lib/alerts/fetch_disk_usage_node_stats'; import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; -import { RawAlertInstance, SanitizedAlert } from '../../../alerting/common'; +import { RawAlertInstance, SanitizedRule } from '../../../alerting/common'; import { AlertingDefaults, createLink } from './alert_helpers'; import { Globals } from '../static_globals'; export class DiskUsageRule extends BaseRule { - constructor(public sanitizedRule?: SanitizedAlert) { + constructor(public sanitizedRule?: SanitizedRule) { super(sanitizedRule, { id: RULE_DISK_USAGE, name: RULE_DETAILS[RULE_DISK_USAGE].label, diff --git a/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_rule.ts b/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_rule.ts index 43f5be14538b6..14bdc70054bb9 100644 --- a/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_rule.ts @@ -21,12 +21,12 @@ import { Alert } from '../../../alerting/server'; import { RULE_ELASTICSEARCH_VERSION_MISMATCH, LEGACY_RULE_DETAILS } from '../../common/constants'; import { AlertSeverity } from '../../common/enums'; import { AlertingDefaults } from './alert_helpers'; -import { SanitizedAlert } from '../../../alerting/common'; +import { SanitizedRule } from '../../../alerting/common'; import { Globals } from '../static_globals'; import { fetchElasticsearchVersions } from '../lib/alerts/fetch_elasticsearch_versions'; export class ElasticsearchVersionMismatchRule extends BaseRule { - constructor(public sanitizedRule?: SanitizedAlert) { + constructor(public sanitizedRule?: SanitizedRule) { super(sanitizedRule, { id: RULE_ELASTICSEARCH_VERSION_MISMATCH, name: LEGACY_RULE_DETAILS[RULE_ELASTICSEARCH_VERSION_MISMATCH].label, diff --git a/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_rule.ts b/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_rule.ts index 4e7a688b92ca9..bb7522c21dd8c 100644 --- a/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_rule.ts @@ -21,12 +21,12 @@ import { Alert } from '../../../alerting/server'; import { RULE_KIBANA_VERSION_MISMATCH, LEGACY_RULE_DETAILS } from '../../common/constants'; import { AlertSeverity } from '../../common/enums'; import { AlertingDefaults } from './alert_helpers'; -import { SanitizedAlert } from '../../../alerting/common'; +import { SanitizedRule } from '../../../alerting/common'; import { Globals } from '../static_globals'; import { fetchKibanaVersions } from '../lib/alerts/fetch_kibana_versions'; export class KibanaVersionMismatchRule extends BaseRule { - constructor(public sanitizedRule?: SanitizedAlert) { + constructor(public sanitizedRule?: SanitizedRule) { super(sanitizedRule, { id: RULE_KIBANA_VERSION_MISMATCH, name: LEGACY_RULE_DETAILS[RULE_KIBANA_VERSION_MISMATCH].label, diff --git a/x-pack/plugins/monitoring/server/alerts/large_shard_size_rule.ts b/x-pack/plugins/monitoring/server/alerts/large_shard_size_rule.ts index fbcf557a1f6f5..6a47af79a45d5 100644 --- a/x-pack/plugins/monitoring/server/alerts/large_shard_size_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/large_shard_size_rule.ts @@ -25,12 +25,12 @@ import { Alert } from '../../../alerting/server'; import { RULE_LARGE_SHARD_SIZE, RULE_DETAILS } from '../../common/constants'; import { fetchIndexShardSize } from '../lib/alerts/fetch_index_shard_size'; import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; -import { SanitizedAlert, RawAlertInstance } from '../../../alerting/common'; +import { SanitizedRule, RawAlertInstance } from '../../../alerting/common'; import { AlertingDefaults, createLink } from './alert_helpers'; import { Globals } from '../static_globals'; export class LargeShardSizeRule extends BaseRule { - constructor(public sanitizedRule?: SanitizedAlert) { + constructor(public sanitizedRule?: SanitizedRule) { super(sanitizedRule, { id: RULE_LARGE_SHARD_SIZE, name: RULE_DETAILS[RULE_LARGE_SHARD_SIZE].label, diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.ts index ad13ca9c56dfa..460c34ac89f5d 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.ts @@ -20,18 +20,18 @@ import { AlertLicense, AlertLicenseState, } from '../../common/types/alerts'; -import { AlertExecutorOptions, Alert } from '../../../alerting/server'; +import { RuleExecutorOptions, Alert } from '../../../alerting/server'; import { RULE_LICENSE_EXPIRATION, LEGACY_RULE_DETAILS } from '../../common/constants'; import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; import { AlertingDefaults } from './alert_helpers'; -import { SanitizedAlert } from '../../../alerting/common'; +import { SanitizedRule } from '../../../alerting/common'; import { Globals } from '../static_globals'; import { fetchLicenses } from '../lib/alerts/fetch_licenses'; const EXPIRES_DAYS = [60, 30, 14, 7]; export class LicenseExpirationRule extends BaseRule { - constructor(public sanitizedRule?: SanitizedAlert) { + constructor(public sanitizedRule?: SanitizedRule) { super(sanitizedRule, { id: RULE_LICENSE_EXPIRATION, name: LEGACY_RULE_DETAILS[RULE_LICENSE_EXPIRATION].label, @@ -64,7 +64,7 @@ export class LicenseExpirationRule extends BaseRule { }); } - protected async execute(options: AlertExecutorOptions): Promise { + protected async execute(options: RuleExecutorOptions): Promise { if (!Globals.app.config.ui.show_license_expiration) { return; } diff --git a/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_rule.ts b/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_rule.ts index bca82de1a5fae..e5556c03ccdb9 100644 --- a/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_rule.ts @@ -21,12 +21,12 @@ import { Alert } from '../../../alerting/server'; import { RULE_LOGSTASH_VERSION_MISMATCH, LEGACY_RULE_DETAILS } from '../../common/constants'; import { AlertSeverity } from '../../common/enums'; import { AlertingDefaults } from './alert_helpers'; -import { SanitizedAlert } from '../../../alerting/common'; +import { SanitizedRule } from '../../../alerting/common'; import { Globals } from '../static_globals'; import { fetchLogstashVersions } from '../lib/alerts/fetch_logstash_versions'; export class LogstashVersionMismatchRule extends BaseRule { - constructor(public sanitizedRule?: SanitizedAlert) { + constructor(public sanitizedRule?: SanitizedRule) { super(sanitizedRule, { id: RULE_LOGSTASH_VERSION_MISMATCH, name: LEGACY_RULE_DETAILS[RULE_LOGSTASH_VERSION_MISMATCH].label, diff --git a/x-pack/plugins/monitoring/server/alerts/memory_usage_rule.ts b/x-pack/plugins/monitoring/server/alerts/memory_usage_rule.ts index 62f790b1eb6d0..fe8c2067c8c09 100644 --- a/x-pack/plugins/monitoring/server/alerts/memory_usage_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/memory_usage_rule.ts @@ -28,13 +28,13 @@ import { RULE_MEMORY_USAGE, RULE_DETAILS } from '../../common/constants'; import { ROUNDED_FLOAT } from '../../common/formatting'; import { fetchMemoryUsageNodeStats } from '../lib/alerts/fetch_memory_usage_node_stats'; import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; -import { RawAlertInstance, SanitizedAlert } from '../../../alerting/common'; +import { RawAlertInstance, SanitizedRule } from '../../../alerting/common'; import { AlertingDefaults, createLink } from './alert_helpers'; import { parseDuration } from '../../../alerting/common/parse_duration'; import { Globals } from '../static_globals'; export class MemoryUsageRule extends BaseRule { - constructor(public sanitizedRule?: SanitizedAlert) { + constructor(public sanitizedRule?: SanitizedRule) { super(sanitizedRule, { id: RULE_MEMORY_USAGE, name: RULE_DETAILS[RULE_MEMORY_USAGE].label, diff --git a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_rule.ts b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_rule.ts index 9002855e2b67f..e22489c1fabcf 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_rule.ts @@ -22,7 +22,7 @@ import { import { Alert } from '../../../alerting/server'; import { RULE_MISSING_MONITORING_DATA, RULE_DETAILS } from '../../common/constants'; import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; -import { RawAlertInstance, SanitizedAlert } from '../../../alerting/common'; +import { RawAlertInstance, SanitizedRule } from '../../../alerting/common'; import { parseDuration } from '../../../alerting/common/parse_duration'; import { fetchMissingMonitoringData } from '../lib/alerts/fetch_missing_monitoring_data'; import { AlertingDefaults, createLink } from './alert_helpers'; @@ -32,7 +32,7 @@ import { Globals } from '../static_globals'; const LIMIT_BUFFER = 3 * 60 * 1000; export class MissingMonitoringDataRule extends BaseRule { - constructor(public sanitizedRule?: SanitizedAlert) { + constructor(public sanitizedRule?: SanitizedRule) { super(sanitizedRule, { id: RULE_MISSING_MONITORING_DATA, name: RULE_DETAILS[RULE_MISSING_MONITORING_DATA].label, diff --git a/x-pack/plugins/monitoring/server/alerts/nodes_changed_rule.ts b/x-pack/plugins/monitoring/server/alerts/nodes_changed_rule.ts index 3b14cf2428889..11617c8247b9c 100644 --- a/x-pack/plugins/monitoring/server/alerts/nodes_changed_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/nodes_changed_rule.ts @@ -22,7 +22,7 @@ import { import { Alert } from '../../../alerting/server'; import { RULE_NODES_CHANGED, LEGACY_RULE_DETAILS } from '../../common/constants'; import { AlertingDefaults } from './alert_helpers'; -import { SanitizedAlert } from '../../../alerting/common'; +import { SanitizedRule } from '../../../alerting/common'; import { fetchNodesFromClusterStats } from '../lib/alerts/fetch_nodes_from_cluster_stats'; import { AlertSeverity } from '../../common/enums'; interface AlertNodesChangedStates { @@ -56,7 +56,7 @@ function getNodeStates(nodes: AlertClusterStatsNodes): AlertNodesChangedStates { } export class NodesChangedRule extends BaseRule { - constructor(public sanitizedRule?: SanitizedAlert) { + constructor(public sanitizedRule?: SanitizedRule) { super(sanitizedRule, { id: RULE_NODES_CHANGED, name: LEGACY_RULE_DETAILS[RULE_NODES_CHANGED].label, diff --git a/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_rule_base.ts b/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_rule_base.ts index ca1b78a62646a..a31d37c7a6bc2 100644 --- a/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_rule_base.ts +++ b/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_rule_base.ts @@ -23,7 +23,7 @@ import { import { Alert } from '../../../alerting/server'; import { fetchThreadPoolRejectionStats } from '../lib/alerts/fetch_thread_pool_rejections_stats'; import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; -import { Alert as Rule, RawAlertInstance } from '../../../alerting/common'; +import { Rule, RawAlertInstance } from '../../../alerting/common'; import { AlertingDefaults, createLink } from './alert_helpers'; import { Globals } from '../static_globals'; diff --git a/x-pack/plugins/monitoring/server/alerts/thread_pool_search_rejections_rule.ts b/x-pack/plugins/monitoring/server/alerts/thread_pool_search_rejections_rule.ts index 509c50cda70bb..0a13230ff44e5 100644 --- a/x-pack/plugins/monitoring/server/alerts/thread_pool_search_rejections_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/thread_pool_search_rejections_rule.ts @@ -7,13 +7,13 @@ import { ThreadPoolRejectionsRuleBase } from './thread_pool_rejections_rule_base'; import { RULE_THREAD_POOL_SEARCH_REJECTIONS, RULE_DETAILS } from '../../common/constants'; -import { Alert } from '../../../alerting/common'; +import { Rule } from '../../../alerting/common'; export class ThreadPoolSearchRejectionsRule extends ThreadPoolRejectionsRuleBase { private static TYPE = RULE_THREAD_POOL_SEARCH_REJECTIONS; private static THREAD_POOL_TYPE = 'search'; private static readonly LABEL = RULE_DETAILS[RULE_THREAD_POOL_SEARCH_REJECTIONS].label; - constructor(sanitizedRule?: Alert) { + constructor(sanitizedRule?: Rule) { super( sanitizedRule, ThreadPoolSearchRejectionsRule.TYPE, diff --git a/x-pack/plugins/monitoring/server/alerts/thread_pool_write_rejections_rule.ts b/x-pack/plugins/monitoring/server/alerts/thread_pool_write_rejections_rule.ts index ca7e1dc67d5b1..1247100d54345 100644 --- a/x-pack/plugins/monitoring/server/alerts/thread_pool_write_rejections_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/thread_pool_write_rejections_rule.ts @@ -7,13 +7,13 @@ import { ThreadPoolRejectionsRuleBase } from './thread_pool_rejections_rule_base'; import { RULE_THREAD_POOL_WRITE_REJECTIONS, RULE_DETAILS } from '../../common/constants'; -import { Alert } from '../../../alerting/common'; +import { Rule } from '../../../alerting/common'; export class ThreadPoolWriteRejectionsRule extends ThreadPoolRejectionsRuleBase { private static TYPE = RULE_THREAD_POOL_WRITE_REJECTIONS; private static THREAD_POOL_TYPE = 'write'; private static readonly LABEL = RULE_DETAILS[RULE_THREAD_POOL_WRITE_REJECTIONS].label; - constructor(sanitizedRule?: Alert) { + constructor(sanitizedRule?: Rule) { super( sanitizedRule, ThreadPoolWriteRejectionsRule.TYPE, diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts index a441cad27b394..1f4d4e9408428 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts @@ -12,7 +12,7 @@ import { LegacyServer, RouteDependencies } from '../../../../types'; import { ALERT_ACTION_TYPE_LOG } from '../../../../../common/constants'; import { ActionResult } from '../../../../../../actions/common'; import { disableWatcherClusterAlerts } from '../../../../lib/alerts/disable_watcher_cluster_alerts'; -import { AlertTypeParams, SanitizedAlert } from '../../../../../../alerting/common'; +import { RuleTypeParams, SanitizedRule } from '../../../../../../alerting/common'; const DEFAULT_SERVER_LOG_NAME = 'Monitoring: Write to Kibana log'; @@ -79,7 +79,7 @@ export function enableAlertsRoute(server: LegacyServer, npRoute: RouteDependenci }, ]; - let createdAlerts: Array> = []; + let createdAlerts: Array> = []; const disabledWatcherClusterAlerts = await disableWatcherClusterAlerts( npRoute.cluster.asScoped(request).asCurrentUser, npRoute.logger diff --git a/x-pack/plugins/observability/public/pages/rules/components/execution_status.tsx b/x-pack/plugins/observability/public/pages/rules/components/execution_status.tsx index 4cdcabe574396..6bb1e0865fccc 100644 --- a/x-pack/plugins/observability/public/pages/rules/components/execution_status.tsx +++ b/x-pack/plugins/observability/public/pages/rules/components/execution_status.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiHealth, EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { AlertExecutionStatusErrorReasons } from '../../../../../alerting/common'; +import { RuleExecutionStatusErrorReasons } from '../../../../../alerting/common'; import { getHealthColor, rulesStatusesTranslationsMapping } from '../config'; import { RULE_STATUS_LICENSE_ERROR } from '../translations'; import { ExecutionStatusProps } from '../types'; @@ -16,7 +16,7 @@ export function ExecutionStatus({ executionStatus }: ExecutionStatusProps) { const healthColor = getHealthColor(executionStatus.status); const tooltipMessage = executionStatus.status === 'error' ? `Error: ${executionStatus?.error?.message}` : null; - const isLicenseError = executionStatus.error?.reason === AlertExecutionStatusErrorReasons.License; + const isLicenseError = executionStatus.error?.reason === RuleExecutionStatusErrorReasons.License; const statusMessage = isLicenseError ? RULE_STATUS_LICENSE_ERROR : rulesStatusesTranslationsMapping[executionStatus.status]; diff --git a/x-pack/plugins/observability/public/pages/rules/components/last_response_filter.tsx b/x-pack/plugins/observability/public/pages/rules/components/last_response_filter.tsx index 5a9be48252909..1ed2b681bfe2c 100644 --- a/x-pack/plugins/observability/public/pages/rules/components/last_response_filter.tsx +++ b/x-pack/plugins/observability/public/pages/rules/components/last_response_filter.tsx @@ -16,7 +16,7 @@ import { EuiFilterSelectItem, EuiHealth, } from '@elastic/eui'; -import { AlertExecutionStatuses, AlertExecutionStatusValues } from '../../../../../alerting/common'; +import { RuleExecutionStatuses, RuleExecutionStatusValues } from '../../../../../alerting/common'; import { getHealthColor, rulesStatusesTranslationsMapping } from '../config'; import { StatusFilterProps } from '../types'; @@ -59,7 +59,7 @@ export const LastResponseFilter: React.FunctionComponent = ({ } >
- {[...AlertExecutionStatusValues].sort().map((item: AlertExecutionStatuses) => { + {[...RuleExecutionStatusValues].sort().map((item: RuleExecutionStatuses) => { const healthColor = getHealthColor(item); return ( ( + render: (_executionStatus: RuleExecutionStatus, item: RuleTableItem) => ( ), }, diff --git a/x-pack/plugins/observability/public/pages/rules/types.ts b/x-pack/plugins/observability/public/pages/rules/types.ts index 0fba341d7f16d..1854713e6f913 100644 --- a/x-pack/plugins/observability/public/pages/rules/types.ts +++ b/x-pack/plugins/observability/public/pages/rules/types.ts @@ -6,7 +6,7 @@ */ import { Dispatch, SetStateAction } from 'react'; import { EuiTableSortingType, EuiBasicTableColumn } from '@elastic/eui'; -import { AlertExecutionStatus } from '../../../../alerting/common'; +import { RuleExecutionStatus } from '../../../../alerting/common'; import { RuleTableItem, Rule } from '../../../../triggers_actions_ui/public'; export interface StatusProps { type: RuleStatus; @@ -44,7 +44,7 @@ export interface StatusFilterProps { } export interface ExecutionStatusProps { - executionStatus: AlertExecutionStatus; + executionStatus: RuleExecutionStatus; } export interface LastRunProps { diff --git a/x-pack/plugins/observability/public/services/get_observability_alerts.ts b/x-pack/plugins/observability/public/services/get_observability_alerts.ts index 652e480c3daed..bb9f2080b9dad 100644 --- a/x-pack/plugins/observability/public/services/get_observability_alerts.ts +++ b/x-pack/plugins/observability/public/services/get_observability_alerts.ts @@ -6,13 +6,13 @@ */ import type { HttpSetup } from 'kibana/public'; -import { Alert } from '../../../alerting/common'; +import { Rule } from '../../../alerting/common'; const allowedConsumers = ['apm', 'uptime', 'logs', 'infrastructure', 'alerts']; export async function getObservabilityAlerts({ http }: { http: HttpSetup }) { try { - const { data = [] }: { data: Alert[] } = + const { data = [] }: { data: Rule[] } = (await http.get('/api/alerts/_find', { query: { page: 1, diff --git a/x-pack/plugins/rule_registry/docs/alerts_client/classes/alertsclient.md b/x-pack/plugins/rule_registry/docs/alerts_client/classes/alertsclient.md index 75f3fd24cbc19..ace131d5e4b6b 100644 --- a/x-pack/plugins/rule_registry/docs/alerts_client/classes/alertsclient.md +++ b/x-pack/plugins/rule_registry/docs/alerts_client/classes/alertsclient.md @@ -135,7 +135,7 @@ ___ | Name | Type | | :------ | :------ | -| `Params` | `Params`: `AlertTypeParams` = `never` | +| `Params` | `Params`: `RuleTypeParams` = `never` | #### Parameters @@ -184,7 +184,7 @@ ___ | Name | Type | | :------ | :------ | -| `Params` | `Params`: `AlertTypeParams` = `never` | +| `Params` | `Params`: `RuleTypeParams` = `never` | #### Parameters @@ -375,7 +375,7 @@ ___ | Name | Type | | :------ | :------ | -| `Params` | `Params`: `AlertTypeParams` = `never` | +| `Params` | `Params`: `RuleTypeParams` = `never` | #### Parameters diff --git a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/bulkupdateoptions.md b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/bulkupdateoptions.md index e27790aefbe2a..efe28580897d7 100644 --- a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/bulkupdateoptions.md +++ b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/bulkupdateoptions.md @@ -6,7 +6,7 @@ | Name | Type | | :------ | :------ | -| `Params` | `Params`: `AlertTypeParams` | +| `Params` | `Params`: `RuleTypeParams` | ## Table of contents diff --git a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md index b868123345b4a..650c6a3d3d113 100644 --- a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md +++ b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md @@ -6,7 +6,7 @@ | Name | Type | | :------ | :------ | -| `Params` | `Params`: `AlertTypeParams` | +| `Params` | `Params`: `RuleTypeParams` | ## Table of contents diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts index a97b43332e0a9..a0b3d2154d688 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts @@ -21,7 +21,7 @@ import { InlineScript, QueryDslQueryContainer, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { AlertTypeParams } from '../../../alerting/server'; +import { RuleTypeParams } from '../../../alerting/server'; import { ReadOperations, AlertingAuthorization, @@ -69,14 +69,14 @@ export interface ConstructorOptions { ruleDataService: IRuleDataService; } -export interface UpdateOptions { +export interface UpdateOptions { id: string; status: string; _version: string | undefined; index: string; } -export interface BulkUpdateOptions { +export interface BulkUpdateOptions { ids: string[] | undefined | null; status: STATUS_VALUES; index: string; @@ -496,7 +496,7 @@ export class AlertsClient { } } - public async update({ + public async update({ id, status, _version, @@ -540,7 +540,7 @@ export class AlertsClient { } } - public async bulkUpdate({ + public async bulkUpdate({ ids, query, index, @@ -598,7 +598,7 @@ export class AlertsClient { } } - public async find({ + public async find({ query, aggs, _source, diff --git a/x-pack/plugins/rule_registry/server/types.ts b/x-pack/plugins/rule_registry/server/types.ts index 1ea7a66c8fdeb..752a573630af7 100644 --- a/x-pack/plugins/rule_registry/server/types.ts +++ b/x-pack/plugins/rule_registry/server/types.ts @@ -9,21 +9,21 @@ import { RequestHandlerContext } from 'kibana/server'; import { AlertInstanceContext, AlertInstanceState, - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeState, } from '../../alerting/common'; -import { AlertExecutorOptions, AlertServices, RuleType } from '../../alerting/server'; +import { RuleExecutorOptions, RuleExecutorServices, RuleType } from '../../alerting/server'; import { AlertsClient } from './alert_data_client/alerts_client'; type SimpleAlertType< - TState extends AlertTypeState, - TParams extends AlertTypeParams = {}, + TState extends RuleTypeState, + TParams extends RuleTypeParams = {}, TAlertInstanceContext extends AlertInstanceContext = {} > = RuleType; export type AlertTypeExecutor< - TState extends AlertTypeState, - TParams extends AlertTypeParams = {}, + TState extends RuleTypeState, + TParams extends RuleTypeParams = {}, TAlertInstanceContext extends AlertInstanceContext = {}, TServices extends Record = {} > = ( @@ -33,8 +33,8 @@ export type AlertTypeExecutor< ) => Promise; export type AlertTypeWithExecutor< - TState extends AlertTypeState = {}, - TParams extends AlertTypeParams = {}, + TState extends RuleTypeState = {}, + TParams extends RuleTypeParams = {}, TAlertInstanceContext extends AlertInstanceContext = {}, TServices extends Record = {} > = Omit< @@ -45,17 +45,17 @@ export type AlertTypeWithExecutor< }; export type AlertExecutorOptionsWithExtraServices< - Params extends AlertTypeParams = never, - State extends AlertTypeState = never, + Params extends RuleTypeParams = never, + State extends RuleTypeState = never, InstanceState extends AlertInstanceState = never, InstanceContext extends AlertInstanceContext = never, ActionGroupIds extends string = never, TExtraServices extends {} = never > = Omit< - AlertExecutorOptions, + RuleExecutorOptions, 'services' > & { - services: AlertServices & TExtraServices; + services: RuleExecutorServices & TExtraServices; }; /** diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index 9f79768416e27..c31c4f15039cd 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -12,12 +12,12 @@ import * as rt from 'io-ts'; import { v4 } from 'uuid'; import { difference } from 'lodash'; import { - AlertExecutorOptions, + RuleExecutorOptions, Alert, AlertInstanceContext, AlertInstanceState, - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeState, } from '../../../alerting/server'; import { ParsedExperimentalFields } from '../../common/parse_experimental_fields'; import { ParsedTechnicalFields } from '../../common/parse_technical_fields'; @@ -71,8 +71,8 @@ export interface LifecycleAlertServices< } export type LifecycleRuleExecutor< - Params extends AlertTypeParams = never, - State extends AlertTypeState = never, + Params extends RuleTypeParams = never, + State extends RuleTypeState = never, InstanceState extends AlertInstanceState = never, InstanceContext extends AlertInstanceContext = never, ActionGroupIds extends string = never @@ -95,10 +95,10 @@ const trackedAlertStateRt = rt.type({ export type TrackedLifecycleAlertState = rt.TypeOf; -const alertTypeStateRt = () => +const alertTypeStateRt = () => rt.record(rt.string, rt.unknown) as rt.Type; -const wrappedStateRt = () => +const wrappedStateRt = () => rt.type({ wrapped: alertTypeStateRt(), trackedAlerts: rt.record(rt.string, trackedAlertStateRt), @@ -109,7 +109,7 @@ const wrappedStateRt = () => * there's no easy way to instantiate generic values such as the runtime type * factory function. */ -export type WrappedLifecycleRuleState = AlertTypeState & { +export type WrappedLifecycleRuleState = RuleTypeState & { wrapped: State | void; trackedAlerts: Record; }; @@ -117,8 +117,8 @@ export type WrappedLifecycleRuleState = AlertTypeS export const createLifecycleExecutor = (logger: Logger, ruleDataClient: PublicContract) => < - Params extends AlertTypeParams = never, - State extends AlertTypeState = never, + Params extends RuleTypeParams = never, + State extends RuleTypeState = never, InstanceState extends AlertInstanceState = never, InstanceContext extends AlertInstanceContext = never, ActionGroupIds extends string = never @@ -132,7 +132,7 @@ export const createLifecycleExecutor = > ) => async ( - options: AlertExecutorOptions< + options: RuleExecutorOptions< Params, WrappedLifecycleRuleState, InstanceState, diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_executor_mock.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_executor_mock.ts index 9a694b40d3228..0833a1c08d452 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_executor_mock.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_executor_mock.ts @@ -6,8 +6,8 @@ */ import { - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, } from '../../../../plugins/alerting/server'; @@ -17,8 +17,8 @@ import { LifecycleAlertServices, LifecycleRuleExecutor } from './create_lifecycl export const createLifecycleRuleExecutorMock = < - Params extends AlertTypeParams = never, - State extends AlertTypeState = never, + Params extends RuleTypeParams = never, + State extends RuleTypeState = never, InstanceState extends AlertInstanceState = never, InstanceContext extends AlertInstanceContext = never, ActionGroupIds extends string = never diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts index 29e3a1902c868..68f6340dfb513 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts @@ -9,8 +9,8 @@ import { IRuleDataClient } from '../rule_data_client'; import { AlertInstanceContext, AlertInstanceState, - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeState, } from '../../../alerting/common'; import { AlertTypeWithExecutor } from '../types'; import { LifecycleAlertService, createLifecycleExecutor } from './create_lifecycle_executor'; @@ -18,7 +18,7 @@ import { LifecycleAlertService, createLifecycleExecutor } from './create_lifecyc export const createLifecycleRuleTypeFactory = ({ logger, ruleDataClient }: { logger: Logger; ruleDataClient: IRuleDataClient }) => < - TParams extends AlertTypeParams, + TParams extends RuleTypeParams, TAlertInstanceContext extends AlertInstanceContext, TServices extends { alertWithLifecycle: LifecycleAlertService, TAlertInstanceContext, string>; @@ -29,7 +29,7 @@ export const createLifecycleRuleTypeFactory = const createBoundLifecycleExecutor = createLifecycleExecutor(logger, ruleDataClient); const executor = createBoundLifecycleExecutor< TParams, - AlertTypeState, + RuleTypeState, AlertInstanceState, TAlertInstanceContext, string diff --git a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.mock.ts b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.mock.ts index 86ef14e491f4e..256843a2646da 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.mock.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.mock.ts @@ -16,7 +16,7 @@ export const createPersistenceServicesMock = (): jest.Mocked { return { - ...alertsMock.createAlertServices(), + ...alertsMock.createRuleExecutorServices(), ...createPersistenceServicesMock(), }; }; diff --git a/x-pack/plugins/rule_registry/server/utils/get_common_alert_fields.ts b/x-pack/plugins/rule_registry/server/utils/get_common_alert_fields.ts index a4f429c634dc9..a033890746f48 100644 --- a/x-pack/plugins/rule_registry/server/utils/get_common_alert_fields.ts +++ b/x-pack/plugins/rule_registry/server/utils/get_common_alert_fields.ts @@ -18,11 +18,11 @@ import { TIMESTAMP, } from '@kbn/rule-data-utils'; -import { AlertExecutorOptions } from '../../../alerting/server'; +import { RuleExecutorOptions } from '../../../alerting/server'; import { CommonAlertFieldsLatest } from '../../common/schemas'; export const getCommonAlertFields = ( - options: AlertExecutorOptions + options: RuleExecutorOptions ): CommonAlertFieldsLatest => { return { [ALERT_RULE_CATEGORY]: options.rule.ruleTypeName, diff --git a/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services_mock.ts b/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services_mock.ts index dbfc5a9460fe2..f1660628b4080 100644 --- a/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services_mock.ts +++ b/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services_mock.ts @@ -18,7 +18,7 @@ class AlertsMockWrapper< InstanceContext extends AlertInstanceContext = AlertInstanceContext > { createAlertServices() { - return alertsMock.createAlertServices(); + return alertsMock.createRuleExecutorServices(); } } diff --git a/x-pack/plugins/rule_registry/server/utils/persistence_types.ts b/x-pack/plugins/rule_registry/server/utils/persistence_types.ts index 5c80d60ee5118..8bc511e83ac21 100644 --- a/x-pack/plugins/rule_registry/server/utils/persistence_types.ts +++ b/x-pack/plugins/rule_registry/server/utils/persistence_types.ts @@ -7,12 +7,12 @@ import { Logger } from '@kbn/logging'; import { - AlertExecutorOptions, + RuleExecutorOptions, AlertInstanceContext, AlertInstanceState, RuleType, - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeState, } from '../../../alerting/server'; import { WithoutReservedActionGroups } from '../../../alerting/common'; import { IRuleDataClient } from '../rule_data_client'; @@ -37,8 +37,8 @@ export interface PersistenceServices { } export type PersistenceAlertType< - TParams extends AlertTypeParams, - TState extends AlertTypeState, + TParams extends RuleTypeParams, + TState extends RuleTypeState, TInstanceContext extends AlertInstanceContext = {}, TActionGroupIds extends string = never > = Omit< @@ -46,7 +46,7 @@ export type PersistenceAlertType< 'executor' > & { executor: ( - options: AlertExecutorOptions< + options: RuleExecutorOptions< TParams, TState, AlertInstanceState, @@ -62,8 +62,8 @@ export type CreatePersistenceRuleTypeWrapper = (options: { ruleDataClient: IRuleDataClient; logger: Logger; }) => < - TParams extends AlertTypeParams, - TState extends AlertTypeState, + TParams extends RuleTypeParams, + TState extends RuleTypeState, TInstanceContext extends AlertInstanceContext = {}, TActionGroupIds extends string = never >( diff --git a/x-pack/plugins/rule_registry/server/utils/rule_executor_test_utils.ts b/x-pack/plugins/rule_registry/server/utils/rule_executor_test_utils.ts index d5ec6e236e567..c9b7138348c3d 100644 --- a/x-pack/plugins/rule_registry/server/utils/rule_executor_test_utils.ts +++ b/x-pack/plugins/rule_registry/server/utils/rule_executor_test_utils.ts @@ -11,18 +11,18 @@ import { uiSettingsServiceMock, } from '../../../../../src/core/server/mocks'; import { - AlertExecutorOptions, + RuleExecutorOptions, AlertInstanceContext, AlertInstanceState, - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeState, } from '../../../alerting/server'; import { alertsMock } from '../../../alerting/server/mocks'; import { dataPluginMock } from '../../../../../src/plugins/data/server/mocks'; export const createDefaultAlertExecutorOptions = < - Params extends AlertTypeParams = never, - State extends AlertTypeState = never, + Params extends RuleTypeParams = never, + State extends RuleTypeState = never, InstanceState extends AlertInstanceState = {}, InstanceContext extends AlertInstanceContext = {}, ActionGroupIds extends string = '' @@ -44,7 +44,7 @@ export const createDefaultAlertExecutorOptions = < startedAt?: Date; updatedAt?: Date; shouldWriteAlerts?: boolean; -}): AlertExecutorOptions => ({ +}): RuleExecutorOptions => ({ alertId, createdBy: 'CREATED_BY', startedAt, @@ -70,7 +70,8 @@ export const createDefaultAlertExecutorOptions = < params, spaceId: 'SPACE_ID', services: { - alertFactory: alertsMock.createAlertServices().alertFactory, + alertFactory: alertsMock.createRuleExecutorServices() + .alertFactory, savedObjectsClient: savedObjectsClientMock.create(), uiSettingsClient: uiSettingsServiceMock.createClient(), scopedClusterClient: elasticsearchServiceMock.createScopedClusterClient(), diff --git a/x-pack/plugins/rule_registry/server/utils/with_rule_data_client_factory.ts b/x-pack/plugins/rule_registry/server/utils/with_rule_data_client_factory.ts index 365744c8d6842..91bd168257aaa 100644 --- a/x-pack/plugins/rule_registry/server/utils/with_rule_data_client_factory.ts +++ b/x-pack/plugins/rule_registry/server/utils/with_rule_data_client_factory.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { AlertInstanceContext, AlertTypeParams, AlertTypeState } from '../../../alerting/common'; +import { AlertInstanceContext, RuleTypeParams, RuleTypeState } from '../../../alerting/common'; import { IRuleDataClient } from '../rule_data_client'; import { AlertTypeWithExecutor } from '../types'; export const withRuleDataClientFactory = (ruleDataClient: IRuleDataClient) => < - TState extends AlertTypeState, - TParams extends AlertTypeParams, + TState extends RuleTypeState, + TParams extends RuleTypeParams, TAlertInstanceContext extends AlertInstanceContext, TServices extends Record = {} >( diff --git a/x-pack/plugins/security_solution/common/detection_engine/transform_actions.test.ts b/x-pack/plugins/security_solution/common/detection_engine/transform_actions.test.ts index d5a9aee84f7f9..0ecfccea362f5 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/transform_actions.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/transform_actions.test.ts @@ -8,7 +8,7 @@ import { transformRuleToAlertAction, transformAlertToRuleAction } from './transform_actions'; describe('transform_actions', () => { - test('it should transform RuleAlertAction[] to AlertAction[]', () => { + test('it should transform RuleAlertAction[] to RuleAction[]', () => { const ruleAction = { id: 'id', group: 'group', @@ -24,7 +24,7 @@ describe('transform_actions', () => { }); }); - test('it should transform AlertAction[] to RuleAlertAction[]', () => { + test('it should transform RuleAction[] to RuleAlertAction[]', () => { const alertAction = { id: 'id', group: 'group', diff --git a/x-pack/plugins/security_solution/common/detection_engine/transform_actions.ts b/x-pack/plugins/security_solution/common/detection_engine/transform_actions.ts index 5f89f66e9bc0a..acc58a96981a0 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/transform_actions.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/transform_actions.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AlertAction } from '../../../alerting/common'; +import { RuleAction } from '../../../alerting/common'; import { RuleAlertAction } from './types'; export const transformRuleToAlertAction = ({ @@ -13,7 +13,7 @@ export const transformRuleToAlertAction = ({ id, action_type_id, // eslint-disable-line @typescript-eslint/naming-convention params, -}: RuleAlertAction): AlertAction => ({ +}: RuleAlertAction): RuleAction => ({ group, id, params, @@ -25,7 +25,7 @@ export const transformAlertToRuleAction = ({ id, actionTypeId, params, -}: AlertAction): RuleAlertAction => ({ +}: RuleAction): RuleAlertAction => ({ group, id, params, diff --git a/x-pack/plugins/security_solution/common/detection_engine/types.ts b/x-pack/plugins/security_solution/common/detection_engine/types.ts index eca1ee10bc6d8..d563d1333cd04 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/types.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/types.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { AlertAction } from '../../../alerting/common'; +import { RuleAction } from '../../../alerting/common'; -export type RuleAlertAction = Omit & { +export type RuleAlertAction = Omit & { action_type_id: string; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/actions_description.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/actions_description.tsx index 43b703512d6e6..8618575a7a4c9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/actions_description.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/actions_description.tsx @@ -7,9 +7,9 @@ import React from 'react'; import { startCase } from 'lodash/fp'; -import { AlertAction } from '../../../../../../alerting/common'; +import { RuleAction } from '../../../../../../alerting/common'; -const ActionsDescription = ({ actions }: { actions: AlertAction[] }) => { +const ActionsDescription = ({ actions }: { actions: RuleAction[] }) => { if (!actions.length) return null; return ( @@ -21,12 +21,12 @@ const ActionsDescription = ({ actions }: { actions: AlertAction[] }) => { ); }; -export const buildActionsDescription = (actions: AlertAction[], title: string) => ({ +export const buildActionsDescription = (actions: RuleAction[], title: string) => ({ title: actions.length ? title : '', description: , }); -const getActionTypeName = (actionTypeId: AlertAction['actionTypeId']) => { +const getActionTypeName = (actionTypeId: RuleAction['actionTypeId']) => { if (!actionTypeId) return ''; const actionType = actionTypeId.split('.')[1]; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx index 55154def55b50..5fb7266e228a5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx @@ -20,7 +20,7 @@ import { loadActionTypes, ActionVariables, } from '../../../../../../triggers_actions_ui/public'; -import { AlertAction } from '../../../../../../alerting/common'; +import { RuleAction } from '../../../../../../alerting/common'; import { convertArrayToCamelCase, useKibana } from '../../../../common/lib/kibana'; import { FORM_ERRORS_TITLE } from './translations'; @@ -87,8 +87,8 @@ export const RuleActionsField: React.FC = ({ triggersActionsUi: { actionTypeRegistry }, } = useKibana().services; - const actions: AlertAction[] = useMemo( - () => (!isEmpty(field.value) ? (field.value as AlertAction[]) : []), + const actions: RuleAction[] = useMemo( + () => (!isEmpty(field.value) ? (field.value as RuleAction[]) : []), [field.value] ); @@ -105,7 +105,7 @@ export const RuleActionsField: React.FC = ({ const setActionIdByIndex = useCallback( (id: string, index: number) => { - const updatedActions = [...(actions as Array>)]; + const updatedActions = [...(actions as Array>)]; updatedActions[index] = deepMerge(updatedActions[index], { id }); field.setValue(updatedActions); }, @@ -114,7 +114,7 @@ export const RuleActionsField: React.FC = ({ ); const setAlertActionsProperty = useCallback( - (updatedActions: AlertAction[]) => field.setValue(updatedActions), + (updatedActions: RuleAction[]) => field.setValue(updatedActions), [field] ); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts index 797f67e1fbae5..9438d409f0914 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts @@ -43,8 +43,8 @@ import { } from '../../../../../common/detection_engine/schemas/request'; /** - * Params is an "record", since it is a type of AlertActionParams which is action templates. - * @see x-pack/plugins/alerting/common/alert.ts + * Params is an "record", since it is a type of RuleActionParams which is action templates. + * @see x-pack/plugins/alerting/common/rule.ts * @deprecated Use the one from @kbn/security-io-ts-alerting-types */ export const action = t.exact( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts index c1141ba527dbd..567e408089883 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts @@ -17,7 +17,7 @@ import { } from '@kbn/securitysolution-io-ts-alerting-types'; import type { Filter } from '@kbn/es-query'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; -import { AlertAction } from '../../../../../../alerting/common'; +import { RuleAction } from '../../../../../../alerting/common'; import { FieldValueQueryBar } from '../../../components/rules/query_bar'; import { FieldValueTimeline } from '../../../components/rules/pick_timeline'; import { FieldValueThreshold } from '../../../components/rules/threshold_input'; @@ -143,7 +143,7 @@ export interface ScheduleStepRule { } export interface ActionsStepRule { - actions: AlertAction[]; + actions: RuleAction[]; enabled: boolean; kibanaSiemAppUrl?: string; throttle?: string | null; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_create_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_create_notifications.ts index 13e4b405ca26b..f28dd8b45b035 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_create_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_create_notifications.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SanitizedAlert } from '../../../../../alerting/common'; +import { SanitizedRule } from '../../../../../alerting/common'; import { SERVER_APP_ID, LEGACY_NOTIFICATIONS_ID } from '../../../../common/constants'; // eslint-disable-next-line no-restricted-imports import { CreateNotificationParams, LegacyRuleNotificationAlertTypeParams } from './legacy_types'; @@ -22,7 +22,7 @@ export const legacyCreateNotifications = async ({ ruleAlertId, interval, name, -}: CreateNotificationParams): Promise> => +}: CreateNotificationParams): Promise> => rulesClient.create({ data: { name, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_find_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_find_notifications.ts index 51584879a4bd9..a7fa01226f008 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_find_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_find_notifications.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AlertTypeParams, FindResult } from '../../../../../alerting/server'; +import { RuleTypeParams, FindResult } from '../../../../../alerting/server'; import { LEGACY_NOTIFICATIONS_ID } from '../../../../common/constants'; // eslint-disable-next-line no-restricted-imports import { LegacyFindNotificationParams } from './legacy_types'; @@ -32,7 +32,7 @@ export const legacyFindNotifications = async ({ filter, sortField, sortOrder, -}: LegacyFindNotificationParams): Promise> => +}: LegacyFindNotificationParams): Promise> => rulesClient.find({ options: { fields, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_read_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_read_notifications.ts index 9e2fb9515bb82..f6c6f0f982660 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_read_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_read_notifications.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AlertTypeParams, SanitizedAlert } from '../../../../../alerting/common'; +import { RuleTypeParams, SanitizedRule } from '../../../../../alerting/common'; // eslint-disable-next-line no-restricted-imports import { LegacyReadNotificationParams, legacyIsAlertType } from './legacy_types'; // eslint-disable-next-line no-restricted-imports @@ -19,7 +19,7 @@ export const legacyReadNotifications = async ({ rulesClient, id, ruleAlertId, -}: LegacyReadNotificationParams): Promise | null> => { +}: LegacyReadNotificationParams): Promise | null> => { if (id != null) { try { const notification = await rulesClient.get({ id }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_rules_notification_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_rules_notification_alert_type.test.ts index 20aac86a336e0..c49e6d060c7c0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_rules_notification_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_rules_notification_alert_type.test.ts @@ -10,7 +10,7 @@ import { getAlertMock } from '../routes/__mocks__/request_responses'; // eslint-disable-next-line no-restricted-imports import { legacyRulesNotificationAlertType } from './legacy_rules_notification_alert_type'; import { buildSignalsSearchQuery } from './build_signals_query'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, RuleExecutorServicesMock } from '../../../../../alerting/server/mocks'; // eslint-disable-next-line no-restricted-imports import { LegacyNotificationExecutorOptions } from './legacy_types'; import { @@ -30,10 +30,10 @@ describe('legacyRules_notification_alert_type', () => { let payload: LegacyNotificationExecutorOptions; let alert: ReturnType; let logger: ReturnType; - let alertServices: AlertServicesMock; + let alertServices: RuleExecutorServicesMock; beforeEach(() => { - alertServices = alertsMock.createAlertServices(); + alertServices = alertsMock.createRuleExecutorServices(); logger = loggingSystemMock.createLogger(); payload = { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_types.ts index 225ac707b1f2d..44cca599a0468 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_types.ts @@ -9,28 +9,28 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { RulesClient, - PartialAlert, + PartialRule, RuleType, - AlertTypeParams, - AlertTypeState, + RuleTypeParams, + RuleTypeState, AlertInstanceState, AlertInstanceContext, - AlertExecutorOptions, + RuleExecutorOptions, } from '../../../../../alerting/server'; -import { Alert, AlertAction } from '../../../../../alerting/common'; +import { Rule, RuleAction } from '../../../../../alerting/common'; import { LEGACY_NOTIFICATIONS_ID } from '../../../../common/constants'; /** * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function */ -export interface LegacyRuleNotificationAlertTypeParams extends AlertTypeParams { +export interface LegacyRuleNotificationAlertTypeParams extends RuleTypeParams { ruleAlertId: string; } /** * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function */ -export type LegacyRuleNotificationAlertType = Alert; +export type LegacyRuleNotificationAlertType = Rule; /** * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function @@ -56,7 +56,7 @@ export interface LegacyClients { * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function */ export interface LegacyNotificationAlertParams { - actions: AlertAction[]; + actions: RuleAction[]; enabled: boolean; ruleAlertId: string; interval: string; @@ -81,7 +81,7 @@ export interface LegacyReadNotificationParams { * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function */ export const legacyIsAlertType = ( - partialAlert: PartialAlert + partialAlert: PartialRule ): partialAlert is LegacyRuleNotificationAlertType => { return partialAlert.alertTypeId === LEGACY_NOTIFICATIONS_ID; }; @@ -89,9 +89,9 @@ export const legacyIsAlertType = ( /** * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function */ -export type LegacyNotificationExecutorOptions = AlertExecutorOptions< +export type LegacyNotificationExecutorOptions = RuleExecutorOptions< LegacyRuleNotificationAlertTypeParams, - AlertTypeState, + RuleTypeState, AlertInstanceState, AlertInstanceContext >; @@ -106,7 +106,7 @@ export const legacyIsNotificationAlertExecutor = ( ): obj is RuleType< LegacyRuleNotificationAlertTypeParams, LegacyRuleNotificationAlertTypeParams, - AlertTypeState, + RuleTypeState, AlertInstanceState, AlertInstanceContext > => { @@ -120,7 +120,7 @@ export type LegacyNotificationAlertTypeDefinition = Omit< RuleType< LegacyRuleNotificationAlertTypeParams, LegacyRuleNotificationAlertTypeParams, - AlertTypeState, + RuleTypeState, AlertInstanceState, AlertInstanceContext, 'default' @@ -131,7 +131,7 @@ export type LegacyNotificationAlertTypeDefinition = Omit< services, params, state, - }: LegacyNotificationExecutorOptions) => Promise; + }: LegacyNotificationExecutorOptions) => Promise; }; /** diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts index eebda81fd63f0..a10f4a59c7359 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AlertServicesMock, alertsMock } from '../../../../../alerting/server/mocks'; +import { RuleExecutorServicesMock, alertsMock } from '../../../../../alerting/server/mocks'; import { sampleThresholdAlert } from '../rule_types/__mocks__/threshold'; import { NotificationRuleTypeParams, @@ -13,7 +13,7 @@ import { } from './schedule_notification_actions'; describe('schedule_notification_actions', () => { - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + const alertServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); const alertId = 'fb30ddd1-5edc-43e2-9afb-3bcd970b78ee'; const notificationRuleParams: NotificationRuleTypeParams = { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 0c9418657a4eb..09e7dc543019f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -35,7 +35,7 @@ import { getFinalizeSignalsMigrationSchemaMock } from '../../../../../common/det import { EqlSearchResponse } from '../../../../../common/detection_engine/types'; import { getSignalsMigrationStatusSchemaMock } from '../../../../../common/detection_engine/schemas/request/get_signals_migration_status_schema.mock'; import { RuleParams } from '../../schemas/rule_schemas'; -import { SanitizedAlert, ResolvedSanitizedRule } from '../../../../../../alerting/common'; +import { SanitizedRule, ResolvedSanitizedRule } from '../../../../../../alerting/common'; import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; import { getPerformBulkActionSchemaMock, @@ -391,7 +391,7 @@ export const nonRuleAlert = (isRuleRegistryEnabled: boolean) => ({ export const getAlertMock = ( isRuleRegistryEnabled: boolean, params: T -): SanitizedAlert => ({ +): SanitizedRule => ({ id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', name: 'Detect Root/Admin Users', tags: [`${INTERNAL_RULE_ID_KEY}:rule-1`, `${INTERNAL_IMMUTABLE_KEY}:false`], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.ts index 4c4eee366ab84..94294192531f8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.ts @@ -14,7 +14,7 @@ import { SavedObjectsClientContract } from 'kibana/server'; import { RuleAlertType } from '../../rules/types'; import type { RulesClient } from '../../../../../../alerting/server'; -import { SanitizedAlert } from '../../../../../../alerting/common'; +import { SanitizedRule } from '../../../../../../alerting/common'; import { DETECTION_ENGINE_RULES_BULK_ACTION, @@ -211,7 +211,7 @@ export const migrateRuleActions = async ({ rulesClient: RulesClient; savedObjectsClient: SavedObjectsClientContract; rule: RuleAlertType; -}): Promise> => { +}): Promise> => { const migratedRule = await legacyMigrate({ rulesClient, savedObjectsClient, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts index 9b8477bd247b5..5e7e0504c99ed 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts @@ -32,7 +32,7 @@ import { RuleExecutionStatus } from '../../../../../common/detection_engine/sche import { AlertInstanceContext, AlertInstanceState, - AlertTypeState, + RuleTypeState, parseDuration, } from '../../../../../../alerting/common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -129,7 +129,7 @@ export const previewRulesRoute = async ( const runExecutors = async < TParams extends RuleParams, - TState extends AlertTypeState, + TState extends RuleTypeState, TInstanceState extends AlertInstanceState, TInstanceContext extends AlertInstanceContext, TActionGroupIds extends string = '' diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts index 0ca665bb10584..04823606faf3e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts @@ -27,7 +27,7 @@ import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; import { PartialFilter } from '../../types'; import { BulkError, createBulkErrorObject } from '../utils'; import { getOutputRuleAlertForRest } from '../__mocks__/utils'; -import { PartialAlert } from '../../../../../../alerting/server'; +import { PartialRule } from '../../../../../../alerting/server'; import { createRulesAndExceptionsStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; import { RuleAlertType } from '../../rules/types'; import { ImportRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; @@ -383,7 +383,7 @@ describe.each([ }); test('returns 500 if the data is not of type siem alert', () => { - const unsafeCast = { data: [{ random: 1 }] } as unknown as PartialAlert; + const unsafeCast = { data: [{ random: 1 }] } as unknown as PartialRule; const output = transform(unsafeCast, undefined, isRuleRegistryEnabled); expect(output).toBeNull(); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts index da07f5ae1a23a..bd74e83f5a664 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts @@ -15,7 +15,7 @@ import { RuleExecutionSummary } from '../../../../../common/detection_engine/sch import { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; import { ImportRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; import { CreateRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/create_rules_bulk_schema'; -import { PartialAlert, FindResult } from '../../../../../../alerting/server'; +import { PartialRule, FindResult } from '../../../../../../alerting/server'; import { ActionsClient, FindActionResult } from '../../../../../../actions/server'; import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; import { RuleAlertType, isAlertType } from '../../rules/types'; @@ -121,7 +121,7 @@ export const transformFindAlerts = ( }; export const transform = ( - rule: PartialAlert, + rule: PartialRule, ruleExecutionSummary?: RuleExecutionSummary | null, isRuleRegistryEnabled?: boolean, legacyRuleActions?: LegacyRulesActionsSavedObject | null diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts index e743e26e8da3f..293f73b9dcd05 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts @@ -16,7 +16,7 @@ import { RulesSchema, rulesSchema, } from '../../../../../common/detection_engine/schemas/response/rules_schema'; -import { PartialAlert } from '../../../../../../alerting/server'; +import { PartialRule } from '../../../../../../alerting/server'; import { isAlertType } from '../../rules/types'; import { createBulkErrorObject, BulkError } from '../utils'; import { transform } from './utils'; @@ -26,7 +26,7 @@ import { LegacyRulesActionsSavedObject } from '../../rule_actions/legacy_get_rul import { internalRuleToAPIResponse } from '../../schemas/rule_converters'; export const transformValidate = ( - rule: PartialAlert, + rule: PartialRule, ruleExecutionSummary: RuleExecutionSummary | null, isRuleRegistryEnabled?: boolean, legacyRuleActions?: LegacyRulesActionsSavedObject | null @@ -45,7 +45,7 @@ export const transformValidate = ( }; export const newTransformValidate = ( - rule: PartialAlert, + rule: PartialRule, ruleExecutionSummary: RuleExecutionSummary | null, isRuleRegistryEnabled?: boolean, legacyRuleActions?: LegacyRulesActionsSavedObject | null @@ -65,7 +65,7 @@ export const newTransformValidate = ( export const transformValidateBulkError = ( ruleId: string, - rule: PartialAlert, + rule: PartialRule, ruleExecutionSummary: RuleExecutionSummary | null, isRuleRegistryEnabled?: boolean ): RulesSchema | BulkError => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_create_rule_actions_saved_object.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_create_rule_actions_saved_object.ts index df27a5bcc280d..55e3ae3b346bc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_create_rule_actions_saved_object.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_create_rule_actions_saved_object.ts @@ -6,7 +6,7 @@ */ import { SavedObjectReference } from 'kibana/server'; -import { AlertServices } from '../../../../../alerting/server'; +import { RuleExecutorServices } from '../../../../../alerting/server'; // eslint-disable-next-line no-restricted-imports import { legacyRuleActionsSavedObjectType } from './legacy_saved_object_mappings'; // eslint-disable-next-line no-restricted-imports @@ -18,15 +18,15 @@ import { legacyGetThrottleOptions, legacyTransformActionToReference, } from './legacy_utils'; -import { AlertAction } from '../../../../../alerting/common'; +import { RuleAction } from '../../../../../alerting/common'; /** * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function */ interface LegacyCreateRuleActionsSavedObject { ruleAlertId: string; - savedObjectsClient: AlertServices['savedObjectsClient']; - actions: AlertAction[] | undefined; + savedObjectsClient: RuleExecutorServices['savedObjectsClient']; + actions: RuleAction[] | undefined; throttle: string | null | undefined; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts index feaaa7f3e6c08..df135e9c1c8ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts @@ -9,7 +9,7 @@ import { chunk } from 'lodash'; import { SavedObjectsFindOptionsReference } from 'kibana/server'; import { Logger } from 'src/core/server'; -import { AlertServices } from '../../../../../alerting/server'; +import { RuleExecutorServices } from '../../../../../alerting/server'; // eslint-disable-next-line no-restricted-imports import { legacyRuleActionsSavedObjectType } from './legacy_saved_object_mappings'; // eslint-disable-next-line no-restricted-imports @@ -25,7 +25,7 @@ import { initPromisePool } from '../../../utils/promise_pool'; */ interface LegacyGetBulkRuleActionsSavedObject { alertIds: string[]; - savedObjectsClient: AlertServices['savedObjectsClient']; + savedObjectsClient: RuleExecutorServices['savedObjectsClient']; logger: Logger; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_rule_actions_saved_object.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_rule_actions_saved_object.ts index d972c6535b3b6..de47e127daa73 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_rule_actions_saved_object.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_rule_actions_saved_object.ts @@ -7,7 +7,7 @@ import { SavedObjectsFindOptionsReference } from 'kibana/server'; import { Logger } from 'src/core/server'; -import { AlertServices } from '../../../../../alerting/server'; +import { RuleExecutorServices } from '../../../../../alerting/server'; // eslint-disable-next-line no-restricted-imports import { legacyRuleActionsSavedObjectType } from './legacy_saved_object_mappings'; @@ -24,7 +24,7 @@ import { legacyGetRuleActionsFromSavedObject } from './legacy_utils'; */ interface LegacyGetRuleActionsSavedObject { ruleAlertId: string; - savedObjectsClient: AlertServices['savedObjectsClient']; + savedObjectsClient: RuleExecutorServices['savedObjectsClient']; logger: Logger; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts index 36f81709b293f..475f17b8b58a3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts @@ -6,7 +6,7 @@ */ import { SavedObjectAttributes } from 'kibana/server'; -import { AlertActionParams } from '../../../../../alerting/common'; +import { RuleActionParams } from '../../../../../alerting/common'; /** * This was the pre-7.16 version of LegacyRuleAlertAction and how it was stored on disk pre-7.16. @@ -16,7 +16,7 @@ import { AlertActionParams } from '../../../../../alerting/common'; export interface LegacyRuleAlertAction { group: string; id: string; - params: AlertActionParams; + params: RuleActionParams; action_type_id: string; } @@ -26,7 +26,7 @@ export interface LegacyRuleAlertAction { */ export interface LegacyRuleAlertSavedObjectAction { group: string; - params: AlertActionParams; + params: RuleActionParams; action_type_id: string; actionRef: string; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_update_or_create_rule_actions_saved_object.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_update_or_create_rule_actions_saved_object.ts index d56d4ff921bd3..f700f78fd41b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_update_or_create_rule_actions_saved_object.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_update_or_create_rule_actions_saved_object.ts @@ -6,8 +6,8 @@ */ import { Logger } from 'src/core/server'; -import { AlertAction } from '../../../../../alerting/common'; -import { AlertServices } from '../../../../../alerting/server'; +import { RuleAction } from '../../../../../alerting/common'; +import { RuleExecutorServices } from '../../../../../alerting/server'; // eslint-disable-next-line no-restricted-imports import { legacyGetRuleActionsSavedObject } from './legacy_get_rule_actions_saved_object'; @@ -21,8 +21,8 @@ import { legacyUpdateRuleActionsSavedObject } from './legacy_update_rule_actions */ interface LegacyUpdateOrCreateRuleActionsSavedObject { ruleAlertId: string; - savedObjectsClient: AlertServices['savedObjectsClient']; - actions: AlertAction[] | undefined; + savedObjectsClient: RuleExecutorServices['savedObjectsClient']; + actions: RuleAction[] | undefined; throttle: string | null | undefined; logger: Logger; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_update_rule_actions_saved_object.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_update_rule_actions_saved_object.ts index fbbbda24e48be..feb6895b87a2f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_update_rule_actions_saved_object.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_update_rule_actions_saved_object.ts @@ -6,7 +6,7 @@ */ import { SavedObjectReference } from 'kibana/server'; -import { AlertServices } from '../../../../../alerting/server'; +import { RuleExecutorServices } from '../../../../../alerting/server'; // eslint-disable-next-line no-restricted-imports import { legacyRuleActionsSavedObjectType } from './legacy_saved_object_mappings'; // eslint-disable-next-line no-restricted-imports @@ -21,15 +21,15 @@ import { } from './legacy_utils'; // eslint-disable-next-line no-restricted-imports import { LegacyIRuleActionsAttributesSavedObjectAttributes } from './legacy_types'; -import { AlertAction } from '../../../../../alerting/common'; +import { RuleAction } from '../../../../../alerting/common'; /** * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function */ interface LegacyUpdateRuleActionsSavedObject { ruleAlertId: string; - savedObjectsClient: AlertServices['savedObjectsClient']; - actions: AlertAction[] | undefined; + savedObjectsClient: RuleExecutorServices['savedObjectsClient']; + actions: RuleAction[] | undefined; throttle: string | null | undefined; ruleActions: LegacyRulesActionsSavedObject; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_utils.test.ts index 448548e96884b..0d5e4e704d7ff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_utils.test.ts @@ -8,7 +8,7 @@ import { SavedObjectsUpdateResponse } from 'kibana/server'; import { loggingSystemMock } from 'src/core/server/mocks'; -import { AlertAction } from '../../../../../alerting/common'; +import { RuleAction } from '../../../../../alerting/common'; // eslint-disable-next-line no-restricted-imports import { legacyRuleActionsSavedObjectType } from './legacy_saved_object_mappings'; @@ -343,7 +343,7 @@ describe('legacy_utils', () => { describe('legacyTransformActionToReference', () => { type FuncReturn = ReturnType; - const alertAction: AlertAction = { + const alertAction: RuleAction = { id: '123', group: 'group_1', params: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_utils.ts index 78f6c7419ae66..b84550d56dbd5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_utils.ts @@ -8,7 +8,7 @@ import { SavedObjectsUpdateResponse } from 'kibana/server'; import { Logger } from 'src/core/server'; -import { AlertAction } from '../../../../../alerting/common'; +import { RuleAction } from '../../../../../alerting/common'; // eslint-disable-next-line no-restricted-imports import { @@ -112,7 +112,7 @@ export const legacyGetActionReference = (id: string, index: number) => ({ * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function */ export const legacyTransformActionToReference = ( - alertAction: AlertAction, + alertAction: RuleAction, index: number ): LegacyRuleAlertSavedObjectAction => ({ actionRef: `action_${index}`, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts index 2537f7eeeaf72..fdcddd52d5c66 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts @@ -10,11 +10,11 @@ import { Moment } from 'moment'; import { Logger } from '@kbn/logging'; import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; -import { AlertExecutorOptions, RuleType } from '../../../../../alerting/server'; +import { RuleExecutorOptions, RuleType } from '../../../../../alerting/server'; import { AlertInstanceContext, AlertInstanceState, - AlertTypeState, + RuleTypeState, WithoutReservedActionGroups, } from '../../../../../alerting/common'; import { ListClient } from '../../../../../lists/server'; @@ -34,7 +34,7 @@ import { IEventLogService } from '../../../../../event_log/server'; import { ITelemetryEventsSender } from '../../telemetry/sender'; import { RuleExecutionLogForExecutorsFactory } from '../rule_execution_log'; -export interface SecurityAlertTypeReturnValue { +export interface SecurityAlertTypeReturnValue { bulkCreateTimes: string[]; createdSignalsCount: number; createdSignals: unknown[]; @@ -65,7 +65,7 @@ export interface RunOpts { export type SecurityAlertType< TParams extends RuleParams, - TState extends AlertTypeState, + TState extends RuleTypeState, TInstanceContext extends AlertInstanceContext = {}, TActionGroupIds extends string = never > = Omit< @@ -73,7 +73,7 @@ export type SecurityAlertType< 'executor' > & { executor: ( - options: AlertExecutorOptions< + options: RuleExecutorOptions< TParams, TState, AlertInstanceState, @@ -99,7 +99,7 @@ export type CreateSecurityRuleTypeWrapper = ( options: CreateSecurityRuleTypeWrapperProps ) => < TParams extends RuleParams, - TState extends AlertTypeState, + TState extends RuleTypeState, TInstanceContext extends AlertInstanceContext = {} >( type: SecurityAlertType diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts index ac2f6495b8b46..59e2b9f2f597b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { AlertTypeState } from '../../../../../../alerting/server'; +import { RuleTypeState } from '../../../../../../alerting/server'; import { SecurityAlertTypeReturnValue } from '../types'; -export const createResultObject = (state: TState) => { +export const createResultObject = (state: TState) => { const result: SecurityAlertTypeReturnValue = { bulkCreateTimes: [], createdSignalsCount: 0, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts index ef9d198d2040f..863d1104ded36 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts @@ -12,7 +12,7 @@ import { normalizeThresholdObject, } from '../../../../common/detection_engine/utils'; import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; -import { AlertTypeParams, SanitizedAlert } from '../../../../../alerting/common'; +import { RuleTypeParams, SanitizedRule } from '../../../../../alerting/common'; import { DEFAULT_INDICATOR_SOURCE_PATH, NOTIFICATION_THROTTLE_NO_ACTIONS, @@ -77,8 +77,8 @@ export const createRules = async ({ actions, isRuleRegistryEnabled, id, -}: CreateRulesOptions): Promise> => { - const rule = await rulesClient.create({ +}: CreateRulesOptions): Promise> => { + const rule = await rulesClient.create({ options: { id, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.ts index 2ccd5f21366ee..1b15f71a1827c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.ts @@ -10,7 +10,7 @@ import uuid from 'uuid'; import { i18n } from '@kbn/i18n'; import { ruleTypeMappings, SIGNALS_ID } from '@kbn/securitysolution-rules'; -import { SanitizedAlert } from '../../../../../alerting/common'; +import { SanitizedRule } from '../../../../../alerting/common'; import { SERVER_APP_ID } from '../../../../common/constants'; import { InternalRuleCreate, RuleParams } from '../schemas/rule_schemas'; import { addTags } from './add_tags'; @@ -23,7 +23,7 @@ const DUPLICATE_TITLE = i18n.translate( ); export const duplicateRule = ( - rule: SanitizedAlert, + rule: SanitizedRule, isRuleRegistryEnabled: boolean ): InternalRuleCreate => { const newRuleId = uuid.v4(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.ts index c0389de766ea5..81db46bb9fee2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.ts @@ -9,7 +9,7 @@ import { transformDataToNdjson } from '@kbn/securitysolution-utils'; import { Logger } from 'src/core/server'; import { ExceptionListClient } from '../../../../../lists/server'; -import { RulesClient, AlertServices } from '../../../../../alerting/server'; +import { RulesClient, RuleExecutorServices } from '../../../../../alerting/server'; import { getNonPackagedRules } from './get_existing_prepackaged_rules'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; import { transformAlertsToRules } from '../routes/rules/utils'; @@ -21,7 +21,7 @@ import { legacyGetBulkRuleActionsSavedObject } from '../rule_actions/legacy_get_ export const getExportAll = async ( rulesClient: RulesClient, exceptionsClient: ExceptionListClient | undefined, - savedObjectsClient: AlertServices['savedObjectsClient'], + savedObjectsClient: RuleExecutorServices['savedObjectsClient'], logger: Logger, isRuleRegistryEnabled: boolean ): Promise<{ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts index 1181995e0ae4a..9da3cc3cfdde3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts @@ -11,7 +11,7 @@ import { transformDataToNdjson } from '@kbn/securitysolution-utils'; import { Logger } from 'src/core/server'; import { ExceptionListClient } from '../../../../../lists/server'; import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; -import { RulesClient, AlertServices } from '../../../../../alerting/server'; +import { RulesClient, RuleExecutorServices } from '../../../../../alerting/server'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; @@ -43,7 +43,7 @@ export interface RulesErrors { export const getExportByObjectIds = async ( rulesClient: RulesClient, exceptionsClient: ExceptionListClient | undefined, - savedObjectsClient: AlertServices['savedObjectsClient'], + savedObjectsClient: RuleExecutorServices['savedObjectsClient'], objects: Array<{ rule_id: string }>, logger: Logger, isRuleRegistryEnabled: boolean @@ -81,7 +81,7 @@ export const getExportByObjectIds = async ( export const getRulesFromObjects = async ( rulesClient: RulesClient, - savedObjectsClient: AlertServices['savedObjectsClient'], + savedObjectsClient: RuleExecutorServices['savedObjectsClient'], objects: Array<{ rule_id: string }>, logger: Logger, isRuleRegistryEnabled: boolean diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts index 3f7191a970020..59fcca766f1b9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -6,7 +6,7 @@ */ import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; -import { SanitizedAlert, AlertTypeParams } from '../../../../../alerting/common'; +import { SanitizedRule, RuleTypeParams } from '../../../../../alerting/common'; import { RulesClient } from '../../../../../alerting/server'; import { createRules } from './create_rules'; import { PartialFilter } from '../types'; @@ -16,8 +16,8 @@ export const installPrepackagedRules = ( rules: AddPrepackagedRulesSchemaDecoded[], outputIndex: string, isRuleRegistryEnabled: boolean -): Array>> => - rules.reduce>>>((acc, rule) => { +): Array>> => + rules.reduce>>>((acc, rule) => { const { anomaly_threshold: anomalyThreshold, author, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts index b862dca6a022a..8b86f139cd742 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts @@ -7,7 +7,7 @@ import { validate } from '@kbn/securitysolution-io-ts-utils'; import { defaults } from 'lodash/fp'; -import { PartialAlert } from '../../../../../alerting/server'; +import { PartialRule } from '../../../../../alerting/server'; import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; import { normalizeMachineLearningJobIds, @@ -85,7 +85,7 @@ export const patchRules = async ({ anomalyThreshold, machineLearningJobId, actions, -}: PatchRulesOptions): Promise | null> => { +}: PatchRulesOptions): Promise | null> => { if (rule == null) { return null; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.ts index 2571791164b6b..b866755e00371 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ResolvedSanitizedRule, SanitizedAlert } from '../../../../../alerting/common'; +import { ResolvedSanitizedRule, SanitizedRule } from '../../../../../alerting/common'; import { INTERNAL_RULE_ID_KEY } from '../../../../common/constants'; import { RuleParams } from '../schemas/rule_schemas'; import { findRules } from './find_rules'; @@ -25,7 +25,7 @@ export const readRules = async ({ id, ruleId, }: ReadRuleOptions): Promise< - SanitizedAlert | ResolvedSanitizedRule | null + SanitizedRule | ResolvedSanitizedRule | null > => { if (id != null) { try { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts index 7e66f1d0aa7a2..6c98ba56b9c88 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts @@ -93,13 +93,13 @@ import { NamespaceOrUndefined, } from '../../../../common/detection_engine/schemas/common'; -import { RulesClient, PartialAlert } from '../../../../../alerting/server'; -import { SanitizedAlert } from '../../../../../alerting/common'; +import { RulesClient, PartialRule } from '../../../../../alerting/server'; +import { SanitizedRule } from '../../../../../alerting/common'; import { PartialFilter } from '../types'; import { RuleParams } from '../schemas/rule_schemas'; import { IRuleExecutionLogForRoutes } from '../rule_execution_log'; -export type RuleAlertType = SanitizedAlert; +export type RuleAlertType = SanitizedRule; // eslint-disable-next-line @typescript-eslint/no-explicit-any export interface IRuleAssetSOAttributes extends Record { @@ -126,14 +126,14 @@ export interface Clients { export const isAlertTypes = ( isRuleRegistryEnabled: boolean, - partialAlert: Array> + partialAlert: Array> ): partialAlert is RuleAlertType[] => { return partialAlert.every((rule) => isAlertType(isRuleRegistryEnabled, rule)); }; export const isAlertType = ( isRuleRegistryEnabled: boolean, - partialAlert: PartialAlert + partialAlert: PartialRule ): partialAlert is RuleAlertType => { const ruleTypeValues = Object.values(ruleTypeMappings) as unknown as string[]; return isRuleRegistryEnabled @@ -287,5 +287,5 @@ export interface FindRuleOptions { export interface LegacyMigrateParams { rulesClient: RulesClient; savedObjectsClient: SavedObjectsClientContract; - rule: SanitizedAlert | null | undefined; + rule: SanitizedRule | null | undefined; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts index ceb6a3739bd6c..93b038fbb58ce 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts @@ -9,7 +9,7 @@ import { chunk } from 'lodash/fp'; import { SavedObjectsClientContract } from 'kibana/server'; import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; import { MAX_RULES_TO_UPDATE_IN_PARALLEL } from '../../../../common/constants'; -import { RulesClient, PartialAlert } from '../../../../../alerting/server'; +import { RulesClient, PartialRule } from '../../../../../alerting/server'; import { patchRules } from './patch_rules'; import { readRules } from './read_rules'; import { PartialFilter } from '../types'; @@ -67,7 +67,7 @@ export const createPromises = ( outputIndex: string, isRuleRegistryEnabled: boolean, ruleExecutionLog: IRuleExecutionLogForRoutes -): Array | null>> => { +): Array | null>> => { return rules.map(async (rule) => { const { author, @@ -202,7 +202,7 @@ export const createPromises = ( // the existing rule exceptionsList, actions: migratedRule.actions.map(transformAlertToRuleAction), // Actions come from the existing rule - })) as PartialAlert; // TODO: Replace AddPrepackagedRulesSchema with type specific rules schema so we can clean up these types + })) as PartialRule; // TODO: Replace AddPrepackagedRulesSchema with type specific rules schema so we can clean up these types } else { // Note: we do not pass down enabled as we do not want to suddenly disable // or enable rules on the user when they were not expecting it if a rule updates diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts index 6c13955aaab58..23038df541ff2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts @@ -9,7 +9,7 @@ import { validate } from '@kbn/securitysolution-io-ts-utils'; import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants'; import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; -import { PartialAlert } from '../../../../../alerting/server'; +import { PartialRule } from '../../../../../alerting/server'; import { UpdateRulesOptions } from './types'; import { addTags } from './add_tags'; @@ -30,7 +30,7 @@ export const updateRules = async ({ defaultOutputIndex, existingRule, ruleUpdate, -}: UpdateRulesOptions): Promise | null> => { +}: UpdateRulesOptions): Promise | null> => { if (existingRule == null) { return null; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts index 448d1b1a1db63..06324f4827b84 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts @@ -14,7 +14,7 @@ import { transformFromAlertThrottle, transformActions, } from './utils'; -import { AlertAction, SanitizedAlert } from '../../../../../alerting/common'; +import { RuleAction, SanitizedRule } from '../../../../../alerting/common'; import { RuleParams } from '../schemas/rule_schemas'; import { NOTIFICATION_THROTTLE_NO_ACTIONS, @@ -297,7 +297,7 @@ describe('utils', () => { params: {}, }, ], - } as SanitizedAlert, + } as SanitizedRule, undefined ) ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); @@ -310,7 +310,7 @@ describe('utils', () => { muteAll: false, notifyWhen: 'onActiveAlert', actions: [], - } as unknown as SanitizedAlert, + } as unknown as SanitizedRule, undefined ) ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); @@ -324,7 +324,7 @@ describe('utils', () => { notifyWhen: 'onThrottleInterval', actions: [], throttle: '1d', - } as unknown as SanitizedAlert, + } as unknown as SanitizedRule, undefined ) ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); @@ -344,7 +344,7 @@ describe('utils', () => { params: {}, }, ], - } as SanitizedAlert, + } as SanitizedRule, undefined ) ).toEqual(NOTIFICATION_THROTTLE_RULE); @@ -363,7 +363,7 @@ describe('utils', () => { params: {}, }, ], - } as SanitizedAlert, + } as SanitizedRule, undefined ) ).toEqual(NOTIFICATION_THROTTLE_RULE); @@ -397,7 +397,7 @@ describe('utils', () => { params: {}, }, ], - } as SanitizedAlert, + } as SanitizedRule, legacyRuleActions ) ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); @@ -424,7 +424,7 @@ describe('utils', () => { muteAll: true, notifyWhen: 'onActiveAlert', actions: [], - } as unknown as SanitizedAlert, + } as unknown as SanitizedRule, legacyRuleActions ) ).toEqual(NOTIFICATION_THROTTLE_RULE); @@ -451,7 +451,7 @@ describe('utils', () => { muteAll: true, notifyWhen: 'onActiveAlert', actions: null, - } as unknown as SanitizedAlert, + } as unknown as SanitizedRule, legacyRuleActions ) ).toEqual(NOTIFICATION_THROTTLE_RULE); @@ -460,7 +460,7 @@ describe('utils', () => { describe('#transformActions', () => { test('It transforms two alert actions', () => { - const alertAction: AlertAction[] = [ + const alertAction: RuleAction[] = [ { id: 'id_1', group: 'group', @@ -493,7 +493,7 @@ describe('utils', () => { }); test('It transforms two alert actions but not a legacyRuleActions if this is also passed in', () => { - const alertAction: AlertAction[] = [ + const alertAction: RuleAction[] = [ { id: 'id_1', group: 'group', @@ -538,7 +538,7 @@ describe('utils', () => { }); test('It will transform the legacyRuleActions if the alertAction is an empty array', () => { - const alertAction: AlertAction[] = []; + const alertAction: RuleAction[] = []; const legacyRuleActions: LegacyRuleActions = { id: 'id_1', ruleThrottle: '', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts index 73039697268e6..fe5785d841b8f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts @@ -28,7 +28,7 @@ import type { } from '@kbn/securitysolution-io-ts-alerting-types'; import type { ListArrayOrUndefined } from '@kbn/securitysolution-io-ts-list-types'; import type { VersionOrUndefined } from '@kbn/securitysolution-io-ts-types'; -import { AlertAction, AlertNotifyWhenType, SanitizedAlert } from '../../../../../alerting/common'; +import { RuleAction, RuleNotifyWhenType, SanitizedRule } from '../../../../../alerting/common'; import { DescriptionOrUndefined, AnomalyThresholdOrUndefined, @@ -194,7 +194,7 @@ export const calculateName = ({ */ export const transformToNotifyWhen = ( throttle: string | null | undefined -): AlertNotifyWhenType | null => { +): RuleNotifyWhenType | null => { if (throttle == null || throttle === NOTIFICATION_THROTTLE_NO_ACTIONS) { return null; // Although I return null, this does not change the value of the "notifyWhen" and it keeps the current value of "notifyWhen" } else if (throttle === NOTIFICATION_THROTTLE_RULE) { @@ -232,7 +232,7 @@ export const transformToAlertThrottle = (throttle: string | null | undefined): s * @returns The actions of the FullResponseSchema */ export const transformActions = ( - alertAction: AlertAction[] | undefined, + alertAction: RuleAction[] | undefined, legacyRuleActions: LegacyRuleActions | null | undefined ): FullResponseSchema['actions'] => { if (alertAction != null && alertAction.length !== 0) { @@ -254,7 +254,7 @@ export const transformActions = ( * @returns The "security_solution" throttle */ export const transformFromAlertThrottle = ( - rule: SanitizedAlert, + rule: SanitizedRule, legacyRuleActions: LegacyRuleActions | null | undefined ): string => { if (legacyRuleActions == null || (rule.actions != null && rule.actions.length > 0)) { @@ -288,9 +288,9 @@ export const maybeMute = async ({ muteAll, throttle, }: { - id: SanitizedAlert['id']; + id: SanitizedRule['id']; rulesClient: RulesClient; - muteAll: SanitizedAlert['muteAll']; + muteAll: SanitizedRule['muteAll']; throttle: string | null | undefined; }): Promise => { if (muteAll && throttle !== NOTIFICATION_THROTTLE_NO_ACTIONS) { @@ -310,7 +310,7 @@ export const legacyMigrate = async ({ rulesClient, savedObjectsClient, rule, -}: LegacyMigrateParams): Promise | null | undefined> => { +}: LegacyMigrateParams): Promise | null | undefined> => { if (rule == null || rule.id == null) { return rule; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts index fbfab6304a4aa..d72cdc560543f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts @@ -31,7 +31,7 @@ import { AppClient } from '../../../types'; import { addTags } from '../rules/add_tags'; import { DEFAULT_MAX_SIGNALS, SERVER_APP_ID } from '../../../../common/constants'; import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; -import { ResolvedSanitizedRule, SanitizedAlert } from '../../../../../alerting/common'; +import { ResolvedSanitizedRule, SanitizedRule } from '../../../../../alerting/common'; import { transformTags } from '../routes/rules/utils'; import { transformFromAlertThrottle, @@ -283,7 +283,7 @@ export const commonParamsCamelToSnake = (params: BaseRuleParams) => { }; export const internalRuleToAPIResponse = ( - rule: SanitizedAlert | ResolvedSanitizedRule, + rule: SanitizedRule | ResolvedSanitizedRule, ruleExecutionSummary?: RuleExecutionSummary | null, legacyRuleActions?: LegacyRuleActions | null ): FullResponseSchema => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts index 1a35d9f0771fe..fdd6dcf0f4da2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts @@ -12,7 +12,7 @@ import { Logger } from '../../../../../../../src/core/server'; import { AlertInstanceContext, AlertInstanceState, - AlertServices, + RuleExecutorServices, } from '../../../../../alerting/server'; import { GenericBulkCreateResponse } from '../rule_types/factories'; import { AnomalyResults, Anomaly } from '../../machine_learning'; @@ -25,7 +25,7 @@ import { BaseFieldsLatest } from '../../../../common/detection_engine/schemas/al interface BulkCreateMlSignalsParams { someResult: AnomalyResults; completeRule: CompleteRule; - services: AlertServices; + services: RuleExecutorServices; logger: Logger; id: string; signalsIndex: string; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts index 604a3a79e7152..e7810e6fe0078 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts @@ -7,7 +7,7 @@ import dateMath from '@elastic/datemath'; import { loggingSystemMock } from 'src/core/server/mocks'; -import { alertsMock, AlertServicesMock } from '../../../../../../alerting/server/mocks'; +import { alertsMock, RuleExecutorServicesMock } from '../../../../../../alerting/server/mocks'; import { eqlExecutor } from './eql'; import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { getEntryListMock } from '../../../../../../lists/common/schemas/types/entry_list.mock'; @@ -22,7 +22,7 @@ jest.mock('../../routes/index/get_index_version'); describe('eql_executor', () => { const version = '8.0.0'; let logger: ReturnType; - let alertServices: AlertServicesMock; + let alertServices: RuleExecutorServicesMock; (getIndexVersion as jest.Mock).mockReturnValue(SIGNALS_TEMPLATE_VERSION); const params = getEqlRuleParams(); const eqlCompleteRule = getCompleteRuleMock(params); @@ -33,7 +33,7 @@ describe('eql_executor', () => { }; beforeEach(() => { - alertServices = alertsMock.createAlertServices(); + alertServices = alertsMock.createRuleExecutorServices(); logger = loggingSystemMock.createLogger(); alertServices.scopedClusterClient.asCurrentUser.eql.search.mockResolvedValue({ hits: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts index dd9d5e2938e67..6975445f5d04f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts @@ -11,7 +11,7 @@ import { Logger } from 'src/core/server'; import { AlertInstanceContext, AlertInstanceState, - AlertServices, + RuleExecutorServices, } from '../../../../../../alerting/server'; import { buildEqlSearchRequest } from '../build_events_query'; import { hasLargeValueItem } from '../../../../../common/detection_engine/utils'; @@ -54,7 +54,7 @@ export const eqlExecutor = async ({ tuple: RuleRangeTuple; exceptionItems: ExceptionListItemSchema[]; experimentalFeatures: ExperimentalFeatures; - services: AlertServices; + services: RuleExecutorServices; version: string; logger: Logger; bulkCreate: BulkCreate; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts index 9b93ba182785f..417382b0bd05a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts @@ -7,7 +7,7 @@ import dateMath from '@elastic/datemath'; import { loggingSystemMock } from 'src/core/server/mocks'; -import { alertsMock, AlertServicesMock } from '../../../../../../alerting/server/mocks'; +import { alertsMock, RuleExecutorServicesMock } from '../../../../../../alerting/server/mocks'; import { mlExecutor } from './ml'; import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { getCompleteRuleMock, getMlRuleParams } from '../../schemas/rule_schemas.mock'; @@ -26,7 +26,7 @@ describe('ml_executor', () => { let mlMock: ReturnType; const exceptionItems = [getExceptionListItemSchemaMock()]; let logger: ReturnType; - let alertServices: AlertServicesMock; + let alertServices: RuleExecutorServicesMock; const params = getMlRuleParams(); const mlCompleteRule = getCompleteRuleMock(params); @@ -44,7 +44,7 @@ describe('ml_executor', () => { beforeEach(() => { jobsSummaryMock = jest.fn(); - alertServices = alertsMock.createAlertServices(); + alertServices = alertsMock.createRuleExecutorServices(); logger = loggingSystemMock.createLogger(); mlMock = mlPluginServerMock.createSetupContract(); mlMock.jobServiceProvider.mockReturnValue({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts index 3610c45017019..d20885ebb9292 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts @@ -10,7 +10,7 @@ import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-t import { AlertInstanceContext, AlertInstanceState, - AlertServices, + RuleExecutorServices, } from '../../../../../../alerting/server'; import { ListClient } from '../../../../../../lists/server'; import { isJobStarted } from '../../../../../common/machine_learning/helpers'; @@ -41,7 +41,7 @@ export const mlExecutor = async ({ ml: SetupPlugins['ml']; listClient: ListClient; exceptionItems: ExceptionListItemSchema[]; - services: AlertServices; + services: RuleExecutorServices; logger: Logger; buildRuleMessage: BuildRuleMessage; bulkCreate: BulkCreate; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts index 5f1ab1c2dd5ff..da1e93bf76b54 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts @@ -10,7 +10,7 @@ import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-t import { AlertInstanceContext, AlertInstanceState, - AlertServices, + RuleExecutorServices, } from '../../../../../../alerting/server'; import { ListClient } from '../../../../../../lists/server'; import { getFilter } from '../get_filter'; @@ -44,7 +44,7 @@ export const queryExecutor = async ({ listClient: ListClient; exceptionItems: ExceptionListItemSchema[]; experimentalFeatures: ExperimentalFeatures; - services: AlertServices; + services: RuleExecutorServices; version: string; searchAfterSize: number; logger: Logger; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts index 00edf2fffc8e0..1aedf7111e078 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts @@ -10,7 +10,7 @@ import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-t import { AlertInstanceContext, AlertInstanceState, - AlertServices, + RuleExecutorServices, } from '../../../../../../alerting/server'; import { ListClient } from '../../../../../../lists/server'; import { getInputIndex } from '../get_input_output_index'; @@ -42,7 +42,7 @@ export const threatMatchExecutor = async ({ tuple: RuleRangeTuple; listClient: ListClient; exceptionItems: ExceptionListItemSchema[]; - services: AlertServices; + services: RuleExecutorServices; version: string; searchAfterSize: number; logger: Logger; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts index e01e3498c2c7a..14c56aa3bc9be 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts @@ -9,7 +9,7 @@ import dateMath from '@elastic/datemath'; import { loggingSystemMock } from 'src/core/server/mocks'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; -import { alertsMock, AlertServicesMock } from '../../../../../../alerting/server/mocks'; +import { alertsMock, RuleExecutorServicesMock } from '../../../../../../alerting/server/mocks'; import { thresholdExecutor } from './threshold'; import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { getEntryListMock } from '../../../../../../lists/common/schemas/types/entry_list.mock'; @@ -22,7 +22,7 @@ import { ThresholdRuleParams } from '../../schemas/rule_schemas'; describe('threshold_executor', () => { const version = '8.0.0'; let logger: ReturnType; - let alertServices: AlertServicesMock; + let alertServices: RuleExecutorServicesMock; const params = getThresholdRuleParams(); const thresholdCompleteRule = getCompleteRuleMock(params); @@ -40,7 +40,7 @@ describe('threshold_executor', () => { }); beforeEach(() => { - alertServices = alertsMock.createAlertServices(); + alertServices = alertsMock.createRuleExecutorServices(); alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise(sampleEmptyDocSearchResults()) ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts index 441baa7ee94fd..7055269abacda 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts @@ -13,7 +13,7 @@ import { Logger } from 'src/core/server'; import { AlertInstanceContext, AlertInstanceState, - AlertServices, + RuleExecutorServices, } from '../../../../../../alerting/server'; import { hasLargeValueItem } from '../../../../../common/detection_engine/utils'; import { CompleteRule, ThresholdRuleParams } from '../../schemas/rule_schemas'; @@ -60,7 +60,7 @@ export const thresholdExecutor = async ({ tuple: RuleRangeTuple; exceptionItems: ExceptionListItemSchema[]; experimentalFeatures: ExperimentalFeatures; - services: AlertServices; + services: RuleExecutorServices; version: string; logger: Logger; buildRuleMessage: BuildRuleMessage; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.test.ts index 49f70eafd7d3a..6e824a9a639a3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.test.ts @@ -6,18 +6,18 @@ */ import { getFilter } from './get_filter'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, RuleExecutorServicesMock } from '../../../../../alerting/server/mocks'; import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; describe('get_filter', () => { - let servicesMock: AlertServicesMock; + let servicesMock: RuleExecutorServicesMock; beforeAll(() => { jest.resetAllMocks(); }); beforeEach(() => { - servicesMock = alertsMock.createAlertServices(); + servicesMock = alertsMock.createRuleExecutorServices(); servicesMock.savedObjectsClient.get.mockImplementation(async (type: string, id: string) => ({ id, type, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts index f113e84c88ba8..d674cc9a3e5a1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts @@ -18,7 +18,7 @@ import { import { AlertInstanceContext, AlertInstanceState, - AlertServices, + RuleExecutorServices, } from '../../../../../alerting/server'; import { PartialFilter } from '../types'; import { withSecuritySpan } from '../../../utils/with_security_span'; @@ -30,7 +30,7 @@ interface GetFilterArgs { language: LanguageOrUndefined; query: QueryOrUndefined; savedId: SavedIdOrUndefined; - services: AlertServices; + services: RuleExecutorServices; index: IndexOrUndefined; lists: ExceptionListItemSchema[]; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.test.ts index 2aaa56493df60..fea6ac75677bf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.test.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, RuleExecutorServicesMock } from '../../../../../alerting/server/mocks'; import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; import { getInputIndex, GetInputIndex } from './get_input_output_index'; import { allowedExperimentalValues } from '../../../../common/experimental_features'; describe('get_input_output_index', () => { - let servicesMock: AlertServicesMock; + let servicesMock: RuleExecutorServicesMock; beforeAll(() => { jest.resetAllMocks(); @@ -22,7 +22,7 @@ describe('get_input_output_index', () => { }); let defaultProps: GetInputIndex; beforeEach(() => { - servicesMock = alertsMock.createAlertServices(); + servicesMock = alertsMock.createRuleExecutorServices(); servicesMock.savedObjectsClient.get.mockImplementation(async (type: string, id: string) => ({ id, type, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.ts index ba1b9344aa8a1..c2a77cb608303 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.ts @@ -9,7 +9,7 @@ import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../common/con import { AlertInstanceContext, AlertInstanceState, - AlertServices, + RuleExecutorServices, } from '../../../../../alerting/server'; import { ExperimentalFeatures } from '../../../../common/experimental_features'; import { withSecuritySpan } from '../../../utils/with_security_span'; @@ -17,7 +17,7 @@ import { withSecuritySpan } from '../../../utils/with_security_span'; export interface GetInputIndex { experimentalFeatures: ExperimentalFeatures; index: string[] | null | undefined; - services: AlertServices; + services: RuleExecutorServices; version: string; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts index 88d6114387aa3..3958cb2a81a7f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts @@ -9,14 +9,14 @@ import { RuleParams } from '../../schemas/rule_schemas'; import { AlertInstanceContext, AlertInstanceState, - AlertTypeState, + RuleTypeState, } from '../../../../../../alerting/common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { Alert } from '../../../../../../alerting/server/alert'; export const alertInstanceFactoryStub = < TParams extends RuleParams, - TState extends AlertTypeState, + TState extends RuleTypeState, TInstanceState extends AlertInstanceState, TInstanceContext extends AlertInstanceContext, TActionGroupIds extends string = '' diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index d6332af195fcf..58e472c75870c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -15,7 +15,7 @@ import { sampleDocWithSortId, } from './__mocks__/es_results'; import { searchAfterAndBulkCreate } from './search_after_bulk_create'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, RuleExecutorServicesMock } from '../../../../../alerting/server/mocks'; import uuid from 'uuid'; import { listMock } from '../../../../../lists/server/mocks'; import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; @@ -51,7 +51,7 @@ import { CommonAlertFieldsLatest } from '../../../../../rule_registry/common/sch const buildRuleMessage = mockBuildRuleMessage; describe('searchAfterAndBulkCreate', () => { - let mockService: AlertServicesMock; + let mockService: RuleExecutorServicesMock; let mockPersistenceServices: jest.Mocked; let buildReasonMessage: BuildReasonMessage; let bulkCreate: BulkCreate; @@ -84,7 +84,7 @@ describe('searchAfterAndBulkCreate', () => { listClient = listMock.getListClient(); listClient.searchListItemByValues = jest.fn().mockResolvedValue([]); inputIndexPattern = ['auditbeat-*']; - mockService = alertsMock.createAlertServices(); + mockService = alertsMock.createRuleExecutorServices(); tuple = getRuleRangeTuples({ logger: mockLogger, previousStartedAt: new Date(), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts index d00925af74316..5bbef98b48335 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts @@ -11,7 +11,7 @@ import { sampleDocSearchResultsWithSortId, } from './__mocks__/es_results'; import { singleSearchAfter } from './single_search_after'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, RuleExecutorServicesMock } from '../../../../../alerting/server/mocks'; import { buildRuleMessageFactory } from './rule_messages'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; @@ -23,7 +23,7 @@ const buildRuleMessage = buildRuleMessageFactory({ name: 'fake name', }); describe('singleSearchAfter', () => { - const mockService: AlertServicesMock = alertsMock.createAlertServices(); + const mockService: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); beforeEach(() => { jest.clearAllMocks(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts index 62b0097c17e75..c247cf00b141f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts @@ -9,7 +9,7 @@ import { performance } from 'perf_hooks'; import { AlertInstanceContext, AlertInstanceState, - AlertServices, + RuleExecutorServices, } from '../../../../../alerting/server'; import { Logger } from '../../../../../../../src/core/server'; import type { SignalSearchResponse, SignalSource } from './types'; @@ -25,7 +25,7 @@ interface SingleSearchAfterParams { index: string[]; from: string; to: string; - services: AlertServices; + services: RuleExecutorServices; logger: Logger; pageSize: number; sortOrder?: estypes.SortOrder; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts index f0f2d7e1af3b5..08915efd4f372 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts @@ -23,7 +23,7 @@ import { ListClient } from '../../../../../../lists/server'; import { AlertInstanceContext, AlertInstanceState, - AlertServices, + RuleExecutorServices, } from '../../../../../../alerting/server'; import { ElasticsearchClient, Logger } from '../../../../../../../../src/core/server'; import { ITelemetryEventsSender } from '../../../telemetry/sender'; @@ -57,7 +57,7 @@ export interface CreateThreatSignalsOptions { query: string; savedId: string | undefined; searchAfterSize: number; - services: AlertServices; + services: RuleExecutorServices; threatFilters: unknown[]; threatIndex: ThreatIndex; threatIndicatorPath: ThreatIndicatorPath; @@ -87,7 +87,7 @@ export interface CreateThreatSignalOptions { query: string; savedId: string | undefined; searchAfterSize: number; - services: AlertServices; + services: RuleExecutorServices; threatEnrichment: SignalsEnrichment; threatMapping: ThreatMapping; tuple: RuleRangeTuple; @@ -113,7 +113,7 @@ export interface CreateEventSignalOptions { query: string; savedId: string | undefined; searchAfterSize: number; - services: AlertServices; + services: RuleExecutorServices; threatEnrichment: SignalsEnrichment; tuple: RuleRangeTuple; type: Type; @@ -234,7 +234,7 @@ export interface BuildThreatEnrichmentOptions { buildRuleMessage: BuildRuleMessage; exceptionItems: ExceptionListItemSchema[]; logger: Logger; - services: AlertServices; + services: RuleExecutorServices; threatFilters: unknown[]; threatIndex: ThreatIndex; threatIndicatorPath: ThreatIndicatorPath; @@ -245,7 +245,7 @@ export interface BuildThreatEnrichmentOptions { } export interface EventsOptions { - services: AlertServices; + services: RuleExecutorServices; query: string; buildRuleMessage: BuildRuleMessage; language: ThreatLanguageOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts index 49a81a47502d2..f7d0418f63b2c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts @@ -14,7 +14,7 @@ import { Logger } from '../../../../../../../../src/core/server'; import { AlertInstanceContext, AlertInstanceState, - AlertServices, + RuleExecutorServices, } from '../../../../../../alerting/server'; import { BaseHit } from '../../../../../common/detection_engine/types'; import { TermAggregationBucket } from '../../../types'; @@ -35,7 +35,7 @@ import { BaseFieldsLatest } from '../../../../../common/detection_engine/schemas interface BulkCreateThresholdSignalsParams { someResult: SignalSearchResponse; completeRule: CompleteRule; - services: AlertServices; + services: RuleExecutorServices; inputIndexPattern: string[]; logger: Logger; filter: unknown; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_previous_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_previous_threshold_signals.ts index 1a2bfbf3a962d..3cfa2be0202f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_previous_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_previous_threshold_signals.ts @@ -9,7 +9,7 @@ import { TimestampOverrideOrUndefined } from '../../../../../common/detection_en import { AlertInstanceContext, AlertInstanceState, - AlertServices, + RuleExecutorServices, } from '../../../../../../alerting/server'; import { Logger } from '../../../../../../../../src/core/server'; import { BuildRuleMessage } from '../rule_messages'; @@ -20,7 +20,7 @@ interface FindPreviousThresholdSignalsParams { from: string; to: string; indexPattern: string[]; - services: AlertServices; + services: RuleExecutorServices; logger: Logger; ruleId: string; bucketByFields: string[]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.test.ts index 3a1149e8c8e99..5b0ee22d87093 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { alertsMock, AlertServicesMock } from '../../../../../../alerting/server/mocks'; +import { alertsMock, RuleExecutorServicesMock } from '../../../../../../alerting/server/mocks'; import { getQueryFilter } from '../../../../../common/detection_engine/get_query_filter'; import { mockLogger } from '../__mocks__/es_results'; import { buildRuleMessageFactory } from '../rule_messages'; @@ -24,12 +24,12 @@ const mockSingleSearchAfter = jest.fn(); // Failing with rule registry enabled describe('findThresholdSignals', () => { - let mockService: AlertServicesMock; + let mockService: RuleExecutorServicesMock; beforeEach(() => { jest.clearAllMocks(); jest.spyOn(single_search_after, 'singleSearchAfter').mockImplementation(mockSingleSearchAfter); - mockService = alertsMock.createAlertServices(); + mockService = alertsMock.createRuleExecutorServices(); }); it('should generate a threshold signal query when only a value is provided', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts index 52aa429dd64d6..45b56dce7e69e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts @@ -15,7 +15,7 @@ import { import { AlertInstanceContext, AlertInstanceState, - AlertServices, + RuleExecutorServices, } from '../../../../../../alerting/server'; import { Logger } from '../../../../../../../../src/core/server'; import { BuildRuleMessage } from '../rule_messages'; @@ -26,7 +26,7 @@ interface FindThresholdSignalsParams { from: string; to: string; inputIndexPattern: string[]; - services: AlertServices; + services: RuleExecutorServices; logger: Logger; filter: unknown; threshold: ThresholdNormalized; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.ts index 276431c3bc929..097bdfe8d6034 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.ts @@ -9,7 +9,7 @@ import { TimestampOverrideOrUndefined } from '../../../../../common/detection_en import { AlertInstanceContext, AlertInstanceState, - AlertServices, + RuleExecutorServices, } from '../../../../../../alerting/server'; import { Logger } from '../../../../../../../../src/core/server'; import { ThresholdSignalHistory } from '../types'; @@ -21,7 +21,7 @@ interface GetThresholdSignalHistoryParams { from: string; to: string; indexPattern: string[]; - services: AlertServices; + services: RuleExecutorServices; logger: Logger; ruleId: string; bucketByFields: string[]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index bbe6adc5c729f..541d2cd4c19b8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -12,11 +12,11 @@ import { Status } from '../../../../common/detection_engine/schemas/common/schem import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; import { RuleType, - AlertTypeState, + RuleTypeState, AlertInstanceState, AlertInstanceContext, - AlertExecutorOptions, - AlertServices, + RuleExecutorOptions as AlertingRuleExecutorOptions, + RuleExecutorServices, } from '../../../../../alerting/server'; import { TermAggregationBucket } from '../../types'; import { @@ -189,9 +189,9 @@ export type AlertSourceHit = estypes.SearchHit; export type WrappedSignalHit = BaseHit; export type BaseSignalHit = estypes.SearchHit; -export type RuleExecutorOptions = AlertExecutorOptions< +export type RuleExecutorOptions = AlertingRuleExecutorOptions< RuleParams, - AlertTypeState, + RuleTypeState, AlertInstanceState, AlertInstanceContext >; @@ -203,7 +203,7 @@ export const isAlertExecutor = ( ): obj is RuleType< RuleParams, RuleParams, // This type is used for useSavedObjectReferences, use an Omit here if you want to remove any values. - AlertTypeState, + RuleTypeState, AlertInstanceState, AlertInstanceContext, 'default' @@ -214,7 +214,7 @@ export const isAlertExecutor = ( export type SignalRuleAlertTypeDefinition = RuleType< RuleParams, RuleParams, // This type is used for useSavedObjectReferences, use an Omit here if you want to remove any values. - AlertTypeState, + RuleTypeState, AlertInstanceState, AlertInstanceContext, 'default' @@ -302,7 +302,7 @@ export interface SearchAfterAndBulkCreateParams { maxSignals: number; }; completeRule: CompleteRule; - services: AlertServices; + services: RuleExecutorServices; listClient: ListClient; exceptionsList: ExceptionListItemSchema[]; logger: Logger; @@ -361,7 +361,7 @@ export interface ThresholdQueryBucket extends TermAggregationBucket { }; } -export interface ThresholdAlertState extends AlertTypeState { +export interface ThresholdAlertState extends RuleTypeState { initialized: boolean; signalHistory: ThresholdSignalHistory; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index 7c020a476c41b..02ed86b65e878 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -10,7 +10,7 @@ import sinon from 'sinon'; import type { TransportResult } from '@elastic/elasticsearch'; import { ALERT_REASON, ALERT_RULE_PARAMETERS, ALERT_UUID } from '@kbn/rule-data-utils'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, RuleExecutorServicesMock } from '../../../../../alerting/server/mocks'; import { listMock } from '../../../../../lists/server/mocks'; import { buildRuleMessageFactory } from './rule_messages'; import { ExceptionListClient } from '../../../../../lists/server'; @@ -426,10 +426,10 @@ describe('utils', () => { }); describe('#getListsClient', () => { - let alertServices: AlertServicesMock; + let alertServices: RuleExecutorServicesMock; beforeEach(() => { - alertServices = alertsMock.createAlertServices(); + alertServices = alertsMock.createRuleExecutorServices(); }); test('it successfully returns list and exceptions list client', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 0a4618d81081b..a0d73707466ce 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -34,7 +34,7 @@ import type { import { AlertInstanceContext, AlertInstanceState, - AlertServices, + RuleExecutorServices, parseDuration, } from '../../../../../alerting/server'; import type { ExceptionListClient, ListClient, ListPluginSetup } from '../../../../../lists/server'; @@ -193,7 +193,7 @@ export const hasTimestampFields = async (args: { }; export const checkPrivileges = async ( - services: AlertServices, + services: RuleExecutorServices, indices: string[] ): Promise => checkPrivilegesFromEsClient(services.scopedClusterClient.asCurrentUser, indices); @@ -247,7 +247,7 @@ export const getListsClient = ({ lists: ListPluginSetup | undefined; spaceId: string; updatedByUser: string | null; - services: AlertServices; + services: RuleExecutorServices; savedObjectClient: SavedObjectsClientContract; }): { listClient: ListClient; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts index f25c23d2d5ed3..d81832d9bbe35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts @@ -53,11 +53,11 @@ import { EventCategoryOverrideOrUndefined, } from '../../../common/detection_engine/schemas/common/schemas'; -import { AlertTypeParams } from '../../../../alerting/common'; +import { RuleTypeParams as AlertingRuleTypeParams } from '../../../../alerting/common'; export type PartialFilter = Partial; -export interface RuleTypeParams extends AlertTypeParams { +export interface RuleTypeParams extends AlertingRuleTypeParams { anomalyThreshold?: AnomalyThresholdOrUndefined; author: AuthorOrUndefined; buildingBlockType: BuildingBlockTypeOrUndefined; diff --git a/x-pack/plugins/security_solution/server/usage/types.ts b/x-pack/plugins/security_solution/server/usage/types.ts index 3bc2235f9c624..9844d9721af75 100644 --- a/x-pack/plugins/security_solution/server/usage/types.ts +++ b/x-pack/plugins/security_solution/server/usage/types.ts @@ -6,7 +6,7 @@ */ import type { CoreSetup, Logger } from 'src/core/server'; -import type { SanitizedAlert } from '../../../alerting/common/alert'; +import type { SanitizedRule } from '../../../alerting/common/'; import type { RuleParams } from '../lib/detection_engine/schemas/rule_schemas'; import type { SetupPlugins } from '../plugin'; @@ -44,7 +44,7 @@ export interface AlertAggs { * {@see RawRule} */ export type RuleSearchResult = Omit< - SanitizedAlert, + SanitizedRule, 'createdBy' | 'updatedBy' | 'createdAt' | 'updatedAt' > & { createdBy: string | null; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/index.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/index.ts index 218cbff3bb1ec..740bf10c04432 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/index.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/index.ts @@ -11,7 +11,7 @@ import { validateExpression } from './validation'; import { EsQueryAlertParams, SearchType } from './types'; import { RuleTypeModel } from '../../../../triggers_actions_ui/public'; import { PluginSetupContract as AlertingSetup } from '../../../../alerting/public'; -import { SanitizedAlert } from '../../../../alerting/common'; +import { SanitizedRule } from '../../../../alerting/common'; const PLUGIN_ID = 'discover'; const ES_QUERY_ALERT_TYPE = '.es-query'; @@ -47,7 +47,7 @@ function registerNavigation(alerting: AlertingSetup) { alerting.registerNavigation( PLUGIN_ID, ES_QUERY_ALERT_TYPE, - (alert: SanitizedAlert>) => { + (alert: SanitizedRule>) => { return `#/viewAlert/${alert.id}`; } ); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts index b60183d7ae2fb..8b46dd6d57337 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AlertTypeParams } from '../../../../alerting/common'; +import { RuleTypeParams } from '../../../../alerting/common'; import { SerializedSearchSourceFields } from '../../../../../../src/plugins/data/common'; export interface Comparator { @@ -19,7 +19,7 @@ export enum SearchType { searchSource = 'searchSource', } -export interface CommonAlertParams extends AlertTypeParams { +export interface CommonAlertParams extends RuleTypeParams { size: number; thresholdComparator?: string; threshold: number[]; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/types.ts b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/types.ts index fec011223f65e..ccc94c8ac00f9 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/types.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/types.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { AlertTypeParams } from '../../../../alerting/common'; +import { RuleTypeParams } from '../../../../alerting/common'; import { Query } from '../../../../../../src/plugins/data/common'; -export interface GeoContainmentAlertParams extends AlertTypeParams { +export interface GeoContainmentAlertParams extends RuleTypeParams { index: string; indexId: string; geoField: string; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/types.ts b/x-pack/plugins/stack_alerts/public/alert_types/threshold/types.ts index a6257c1b5b7fa..ba6ca421173f5 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/types.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AlertTypeParams } from '../../../../alerting/common'; +import { RuleTypeParams } from '../../../../alerting/common'; export interface Comparator { text: string; @@ -27,7 +27,7 @@ export interface GroupByType { validNormalizedTypes: string[]; } -export interface IndexThresholdAlertParams extends AlertTypeParams { +export interface IndexThresholdAlertParams extends RuleTypeParams { index: string | string[]; timeField?: string; aggType: string; diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.ts index e340ba6505506..7e4e8bf831493 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/action_context.ts @@ -7,12 +7,12 @@ import { i18n } from '@kbn/i18n'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { AlertExecutorOptions, AlertInstanceContext } from '../../../../alerting/server'; +import { RuleExecutorOptions, AlertInstanceContext } from '../../../../alerting/server'; import { OnlyEsQueryAlertParams, OnlySearchSourceAlertParams } from './types'; // alert type context provided to actions -type AlertInfo = Pick; +type AlertInfo = Pick; export interface ActionContext extends EsQueryAlertActionContext { // a short pre-constructed message which may be used in an action field diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts index 18f182d32deb2..c59b24ddb6210 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts @@ -7,9 +7,9 @@ import uuid from 'uuid'; import type { Writable } from '@kbn/utility-types'; -import { AlertServices } from '../../../../alerting/server'; +import { RuleExecutorServices } from '../../../../alerting/server'; import { - AlertServicesMock, + RuleExecutorServicesMock, alertsMock, AlertInstanceMock, } from '../../../../alerting/server/mocks'; @@ -146,7 +146,7 @@ describe('alertType', () => { thresholdComparator: Comparator.BETWEEN, threshold: [0], }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + const alertServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); const searchResult: ESSearchResponse = generateResults([]); alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValueOnce( @@ -175,7 +175,7 @@ describe('alertType', () => { thresholdComparator: Comparator.GT, threshold: [0], }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + const alertServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); const newestDocumentTimestamp = Date.now(); @@ -220,7 +220,7 @@ describe('alertType', () => { thresholdComparator: Comparator.GT, threshold: [0], }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + const alertServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); const previousTimestamp = Date.now(); const newestDocumentTimestamp = previousTimestamp + 1000; @@ -268,7 +268,7 @@ describe('alertType', () => { thresholdComparator: Comparator.GT, threshold: [0], }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + const alertServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); const oldestDocumentTimestamp = Date.now(); @@ -310,7 +310,7 @@ describe('alertType', () => { thresholdComparator: Comparator.GT, threshold: [0], }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + const alertServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); const oldestDocumentTimestamp = Date.now(); @@ -381,7 +381,7 @@ describe('alertType', () => { thresholdComparator: Comparator.GT, threshold: [0], }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + const alertServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); const oldestDocumentTimestamp = Date.now(); @@ -426,7 +426,7 @@ describe('alertType', () => { thresholdComparator: Comparator.GT, threshold: [0], }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + const alertServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); const oldestDocumentTimestamp = Date.now(); @@ -524,7 +524,7 @@ describe('alertType', () => { it('alert executor handles no documents returned by ES', async () => { const params = defaultParams; const searchResult: ESSearchResponse = generateResults([]); - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + const alertServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); (searchSourceInstanceMock.getField as jest.Mock).mockImplementationOnce((name: string) => { if (name === 'index') { @@ -540,7 +540,7 @@ describe('alertType', () => { it('alert executor throws an error when index does not have time field', async () => { const params = defaultParams; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + const alertServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); (searchSourceInstanceMock.getField as jest.Mock).mockImplementationOnce((name: string) => { if (name === 'index') { @@ -555,7 +555,7 @@ describe('alertType', () => { it('alert executor schedule actions when condition met', async () => { const params = { ...defaultParams, thresholdComparator: Comparator.GT_OR_EQ, threshold: [3] }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + const alertServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); (searchSourceInstanceMock.getField as jest.Mock).mockImplementationOnce((name: string) => { if (name === 'index') { @@ -620,7 +620,7 @@ async function invokeExecutor({ state, }: { params: OnlySearchSourceAlertParams | OnlyEsQueryAlertParams; - alertServices: AlertServicesMock; + alertServices: RuleExecutorServicesMock; state?: EsQueryAlertState; }) { return await alertType.executor({ @@ -628,7 +628,7 @@ async function invokeExecutor({ executionId: uuid.v4(), startedAt: new Date(), previousStartedAt: new Date(), - services: alertServices as unknown as AlertServices< + services: alertServices as unknown as RuleExecutorServices< EsQueryAlertState, ActionContext, typeof ActionGroupId diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.ts index 79618976d3aa0..3ba9fc2686940 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { schema, Type, TypeOf } from '@kbn/config-schema'; import { validateTimeWindowUnits } from '../../../../triggers_actions_ui/server'; -import { AlertTypeState } from '../../../../alerting/server'; +import { RuleTypeState } from '../../../../alerting/server'; import { Comparator } from '../../../common/comparator_types'; import { ComparatorFnNames } from '../lib'; @@ -16,7 +16,7 @@ export const ES_QUERY_MAX_HITS_PER_EXECUTION = 10000; // alert type parameters export type EsQueryAlertParams = TypeOf; -export interface EsQueryAlertState extends AlertTypeState { +export interface EsQueryAlertState extends RuleTypeState { latestTimestamp: string | undefined; } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/types.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/types.ts index 3bcfbc1aef8f9..12b2ee02af171 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/types.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AlertExecutorOptions, AlertTypeParams } from '../../types'; +import { RuleExecutorOptions, RuleTypeParams } from '../../types'; import { ActionContext } from './action_context'; import { EsQueryAlertParams, EsQueryAlertState } from './alert_type_params'; import { ActionGroupId } from './constants'; @@ -19,7 +19,7 @@ export type OnlySearchSourceAlertParams = Omit< searchType: 'searchSource'; }; -export type ExecutorOptions

= AlertExecutorOptions< +export type ExecutorOptions

= RuleExecutorOptions< P, EsQueryAlertState, {}, diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts index a25e9e8d7bf69..c65dc4d5745ea 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts @@ -12,11 +12,11 @@ import { STACK_ALERTS_FEATURE_ID } from '../../../common'; import { getGeoContainmentExecutor } from './geo_containment'; import { RuleType, - AlertTypeState, + RuleTypeState, AlertInstanceState, AlertInstanceContext, RuleParamsAndRefs, - AlertTypeParams, + RuleTypeParams, } from '../../../../alerting/server'; import { Query } from '../../../../../../src/plugins/data/common/query'; @@ -24,7 +24,7 @@ export const ActionGroupId = 'Tracked entity contained'; export const RecoveryActionGroupId = 'notGeoContained'; export const GEO_CONTAINMENT_ID = '.geo-containment'; -export interface GeoContainmentParams extends AlertTypeParams { +export interface GeoContainmentParams extends RuleTypeParams { index: string; indexId: string; geoField: string; @@ -126,7 +126,7 @@ export const ParamsSchema = schema.object({ boundaryIndexQuery: schema.maybe(schema.any({})), }); -export interface GeoContainmentState extends AlertTypeState { +export interface GeoContainmentState extends RuleTypeState { shapesFilters: Record; shapesIdsNamesMap: Record; prevLocationMap: Record; diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts index aca79e29cd3e5..5d59c3cab0746 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts @@ -9,7 +9,7 @@ import _ from 'lodash'; import { Logger } from 'src/core/server'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { executeEsQueryFactory, getShapesFilters, OTHER_CATEGORY } from './es_query_builder'; -import { AlertServices } from '../../../../alerting/server'; +import { RuleExecutorServices } from '../../../../alerting/server'; import { ActionGroupId, GeoContainmentInstanceState, @@ -87,7 +87,7 @@ export function transformResults( export function getActiveEntriesAndGenerateAlerts( prevLocationMap: Map, currLocationMap: Map, - alertFactory: AlertServices< + alertFactory: RuleExecutorServices< GeoContainmentInstanceState, GeoContainmentInstanceContext, typeof ActionGroupId diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts index bf9489e12777c..a5f8d0e064874 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts @@ -7,7 +7,7 @@ import _ from 'lodash'; import { loggingSystemMock, elasticsearchServiceMock } from 'src/core/server/mocks'; -import { AlertServicesMock, alertsMock } from '../../../../../alerting/server/mocks'; +import { RuleExecutorServicesMock, alertsMock } from '../../../../../alerting/server/mocks'; import sampleAggsJsonResponse from './es_sample_response.json'; import sampleShapesJsonResponse from './es_sample_response_shapes.json'; import sampleAggsJsonResponseWithNesting from './es_sample_response_with_nesting.json'; @@ -525,8 +525,8 @@ describe('geo_containment', () => { } }); - const alertServicesWithSearchMock: AlertServicesMock = { - ...alertsMock.createAlertServices(), + const alertServicesWithSearchMock: RuleExecutorServicesMock = { + ...alertsMock.createRuleExecutorServices(), // @ts-ignore alertFactory: alertFactory(contextKeys, testAlertActionArr), // @ts-ignore diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts index 02450da5bbdf7..f75869d8e0db2 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts @@ -7,11 +7,11 @@ import { i18n } from '@kbn/i18n'; import { Params } from './alert_type_params'; -import { AlertExecutorOptions, AlertInstanceContext } from '../../../../alerting/server'; +import { RuleExecutorOptions, AlertInstanceContext } from '../../../../alerting/server'; // alert type context provided to actions -type RuleInfo = Pick; +type RuleInfo = Pick; export interface ActionContext extends BaseActionContext { // a short pre-constructed message which may be used in an action field diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts index 060730fb668e3..16117ed6fd507 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts @@ -8,11 +8,11 @@ import uuid from 'uuid'; import type { Writable } from '@kbn/utility-types'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; -import { AlertServices } from '../../../../alerting/server'; +import { RuleExecutorServices } from '../../../../alerting/server'; import { getAlertType, ActionGroupId } from './alert_type'; import { ActionContext } from './action_context'; import { Params } from './alert_type_params'; -import { AlertServicesMock, alertsMock } from '../../../../alerting/server/mocks'; +import { RuleExecutorServicesMock, alertsMock } from '../../../../alerting/server/mocks'; import { Comparator } from '../../../common/comparator_types'; describe('alertType', () => { @@ -20,7 +20,7 @@ describe('alertType', () => { const data = { timeSeriesQuery: jest.fn(), }; - const alertServices: AlertServicesMock = alertsMock.createAlertServices(); + const alertServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); const alertType = getAlertType(logger, Promise.resolve(data)); @@ -173,7 +173,11 @@ describe('alertType', () => { executionId: uuid.v4(), startedAt: new Date(), previousStartedAt: new Date(), - services: alertServices as unknown as AlertServices<{}, ActionContext, typeof ActionGroupId>, + services: alertServices as unknown as RuleExecutorServices< + {}, + ActionContext, + typeof ActionGroupId + >, params, state: { latestTimestamp: undefined, @@ -208,7 +212,7 @@ describe('alertType', () => { }); it('should ensure a null result does not fire actions', async () => { - const customAlertServices: AlertServicesMock = alertsMock.createAlertServices(); + const customAlertServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); data.timeSeriesQuery.mockImplementation((...args) => { return { results: [ @@ -235,7 +239,7 @@ describe('alertType', () => { executionId: uuid.v4(), startedAt: new Date(), previousStartedAt: new Date(), - services: customAlertServices as unknown as AlertServices< + services: customAlertServices as unknown as RuleExecutorServices< {}, ActionContext, typeof ActionGroupId @@ -274,7 +278,7 @@ describe('alertType', () => { }); it('should ensure an undefined result does not fire actions', async () => { - const customAlertServices: AlertServicesMock = alertsMock.createAlertServices(); + const customAlertServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); data.timeSeriesQuery.mockImplementation((...args) => { return { results: [ @@ -301,7 +305,7 @@ describe('alertType', () => { executionId: uuid.v4(), startedAt: new Date(), previousStartedAt: new Date(), - services: customAlertServices as unknown as AlertServices< + services: customAlertServices as unknown as RuleExecutorServices< {}, ActionContext, typeof ActionGroupId diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts index 1d838fda0e8e0..9da47aa76e0c0 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { Logger } from 'src/core/server'; -import { RuleType, AlertExecutorOptions, StackAlertsStartDeps } from '../../types'; +import { RuleType, RuleExecutorOptions, StackAlertsStartDeps } from '../../types'; import { Params, ParamsSchema } from './alert_type_params'; import { ActionContext, BaseActionContext, addMessages } from './action_context'; import { STACK_ALERTS_FEATURE_ID } from '../../../common'; @@ -132,7 +132,7 @@ export function getAlertType( }; async function executor( - options: AlertExecutorOptions + options: RuleExecutorOptions ) { const { alertId: ruleId, name, services, params } = options; const { alertFactory, scopedClusterClient } = services; diff --git a/x-pack/plugins/stack_alerts/server/types.ts b/x-pack/plugins/stack_alerts/server/types.ts index 719373d21f18a..3690cbd92e459 100644 --- a/x-pack/plugins/stack_alerts/server/types.ts +++ b/x-pack/plugins/stack_alerts/server/types.ts @@ -12,8 +12,8 @@ export type { PluginSetupContract as AlertingSetup, RuleType, RuleParamsAndRefs, - AlertExecutorOptions, - AlertTypeParams, + RuleExecutorOptions, + RuleTypeParams, } from '../../alerting/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; diff --git a/x-pack/plugins/transform/common/types/alerting.ts b/x-pack/plugins/transform/common/types/alerting.ts index 07d6bbdbe4152..cc031d36f0483 100644 --- a/x-pack/plugins/transform/common/types/alerting.ts +++ b/x-pack/plugins/transform/common/types/alerting.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { Alert, AlertTypeParams } from '../../../alerting/common'; +import type { Rule, RuleTypeParams } from '../../../alerting/common'; export type TransformHealthRuleParams = { includeTransforms?: string[]; @@ -18,10 +18,10 @@ export type TransformHealthRuleParams = { enabled: boolean; } | null; } | null; -} & AlertTypeParams; +} & RuleTypeParams; export type TransformHealthRuleTestsConfig = TransformHealthRuleParams['testsConfig']; export type TransformHealthTests = keyof Exclude; -export type TransformHealthAlertRule = Omit, 'apiKey'>; +export type TransformHealthAlertRule = Omit, 'apiKey'>; diff --git a/x-pack/plugins/transform/server/lib/alerting/transform_health_rule_type/register_transform_health_rule_type.ts b/x-pack/plugins/transform/server/lib/alerting/transform_health_rule_type/register_transform_health_rule_type.ts index ad85649c0f2c4..04a4685c45892 100644 --- a/x-pack/plugins/transform/server/lib/alerting/transform_health_rule_type/register_transform_health_rule_type.ts +++ b/x-pack/plugins/transform/server/lib/alerting/transform_health_rule_type/register_transform_health_rule_type.ts @@ -11,7 +11,7 @@ import type { ActionGroup, AlertInstanceContext, AlertInstanceState, - AlertTypeState, + RuleTypeState, } from '../../../../../alerting/common'; import { PLUGIN, TRANSFORM_RULE_TYPE } from '../../../../common/constants'; import { transformHealthRuleParams, TransformHealthRuleParams } from './schema'; @@ -64,7 +64,7 @@ export function registerTransformHealthRuleType(params: RegisterParams) { export function getTransformHealthRuleType(): RuleType< TransformHealthRuleParams, never, - AlertTypeState, + RuleTypeState, AlertInstanceState, TransformHealthAlertContext, TransformIssue diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md index 14bf6164979e7..f4a1f79da1546 100644 --- a/x-pack/plugins/triggers_actions_ui/README.md +++ b/x-pack/plugins/triggers_actions_ui/README.md @@ -266,7 +266,7 @@ export interface RuleType { params?: { validate: (object: any) => any }; }; actionGroups: string[]; - executor: ({ services, params, state }: AlertExecutorOptions) => Promise; + executor: ({ services, params, state }: RuleExecutorOptions) => Promise; requiresAppContext: boolean; } ``` @@ -1385,7 +1385,7 @@ Then this dependencies will be used to embed Actions form or register your own a ``` import React, { useCallback } from 'react'; import { ActionForm } from '../../../../../../../../../plugins/triggers_actions_ui/public'; - import { AlertAction } from '../../../../../../../../../plugins/triggers_actions_ui/public/types'; + import { RuleAction } from '../../../../../../../../../plugins/triggers_actions_ui/public/types'; const ALOWED_BY_PLUGIN_ACTION_TYPES = [ { id: '.email', name: 'Email', enabled: true }, @@ -1428,7 +1428,7 @@ Then this dependencies will be used to embed Actions form or register your own a setActionIdByIndex={(id: string, index: number) => { initialAlert.actions[index].id = id; }} - setRuleProperty={(_updatedActions: AlertAction[]) => {}} + setRuleProperty={(_updatedActions: RuleAction[]) => {}} setActionParamsProperty={(key: string, value: any, index: number) => (initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value }) } @@ -1446,12 +1446,12 @@ Then this dependencies will be used to embed Actions form or register your own a ActionForm Props definition: ``` interface ActionAccordionFormProps { - actions: AlertAction[]; + actions: RuleAction[]; defaultActionGroupId: string; actionGroups?: ActionGroup[]; setActionIdByIndex: (id: string, index: number) => void; setActionGroupIdByIndex?: (group: string, index: number) => void; - setRuleProperty: (actions: AlertAction[]) => void; + setRuleProperty: (actions: RuleAction[]) => void; setActionParamsProperty: (key: string, value: any, index: number) => void; http: HttpSetup; actionTypeRegistry: ActionTypeRegistryContract; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.ts index 7936e5843de7f..a06eb360a70c3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { AlertActionParam } from '../../../../alerting/common'; +import { RuleActionParam } from '../../../../alerting/common'; import { EventActionOptions } from '../components/builtin_action_types/types'; import { AlertProvidedActionVariables } from './action_variables'; -export type DefaultActionParams = Record | undefined; +export type DefaultActionParams = Record | undefined; export type DefaultActionParamsGetter = ( actionTypeId: string, actionGroupId: string diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts index 69b7b494431fc..10a16313e87a4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { AlertExecutionStatus } from '../../../../../alerting/common'; +import { RuleExecutionStatus } from '../../../../../alerting/common'; import { AsApiContract, RewriteRequestCase } from '../../../../../actions/common'; import { Rule, RuleAction, ResolvedRule } from '../../../types'; @@ -20,7 +20,7 @@ const transformAction: RewriteRequestCase = ({ actionTypeId, }); -const transformExecutionStatus: RewriteRequestCase = ({ +const transformExecutionStatus: RewriteRequestCase = ({ last_execution_date: lastExecutionDate, last_duration: lastDuration, ...rest diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 7c1af79c05626..34e2094d51a6d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -35,7 +35,7 @@ import { AddConnectorInline } from './connector_add_inline'; import { actionTypeCompare } from '../../lib/action_type_compare'; import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled'; import { VIEW_LICENSE_OPTIONS_LINK, DEFAULT_HIDDEN_ACTION_TYPES } from '../../../common/constants'; -import { ActionGroup, AlertActionParam } from '../../../../../alerting/common'; +import { ActionGroup, RuleActionParam } from '../../../../../alerting/common'; import { useKibana } from '../../../common/lib/kibana'; import { DefaultActionParamsGetter } from '../../lib/get_defaults_for_action_params'; import { ConnectorAddModal } from '.'; @@ -55,7 +55,7 @@ export interface ActionAccordionFormProps { setActionIdByIndex: (id: string, index: number) => void; setActionGroupIdByIndex?: (group: string, index: number) => void; setActions: (actions: RuleAction[]) => void; - setActionParamsProperty: (key: string, value: AlertActionParam, index: number) => void; + setActionParamsProperty: (key: string, value: RuleActionParam, index: number) => void; actionTypes?: ActionType[]; messageVariables?: ActionVariables; setHasActionsDisabled?: (value: boolean) => void; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx index c817f5895a816..5c336d37ebb51 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx @@ -25,7 +25,7 @@ import { EuiErrorBoundary, } from '@elastic/eui'; import { partition } from 'lodash'; -import { ActionVariable, AlertActionParam } from '../../../../../alerting/common'; +import { ActionVariable, RuleActionParam } from '../../../../../alerting/common'; import { IErrorObject, RuleAction, @@ -49,7 +49,7 @@ export type ActionTypeFormProps = { onAddConnector: () => void; onConnectorSelected: (id: string) => void; onDeleteAction: () => void; - setActionParamsProperty: (key: string, value: AlertActionParam, index: number) => void; + setActionParamsProperty: (key: string, value: RuleActionParam, index: number) => void; actionTypesIndex: ActionTypeIndex; connectors: ActionConnector[]; actionTypeRegistry: ActionTypeRegistryContract; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx index dca16e5acbf1b..106567a3c363d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx @@ -24,7 +24,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import moment from 'moment'; import { ActionGroup, - AlertExecutionStatusErrorReasons, + RuleExecutionStatusErrorReasons, AlertStatusValues, } from '../../../../../../alerting/common'; import { Rule, RuleSummary, AlertStatus, RuleType } from '../../../../types'; @@ -102,7 +102,7 @@ export function RuleComponent({ const healthColor = getHealthColor(rule.executionStatus.status); const isLicenseError = - rule.executionStatus.error?.reason === AlertExecutionStatusErrorReasons.License; + rule.executionStatus.error?.reason === RuleExecutionStatusErrorReasons.License; const statusMessage = isLicenseError ? ALERT_STATUS_LICENSE_ERROR : rulesStatusesTranslationsMapping[rule.executionStatus.status]; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx index a38c9c5d1975a..a11bea88a9d3a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx @@ -15,8 +15,8 @@ import { Rule, ActionType, RuleTypeModel, RuleType } from '../../../../types'; import { EuiBadge, EuiFlexItem, EuiButtonEmpty, EuiPageHeaderProps } from '@elastic/eui'; import { ActionGroup, - AlertExecutionStatusErrorReasons, - AlertExecutionStatusWarningReasons, + RuleExecutionStatusErrorReasons, + RuleExecutionStatusWarningReasons, ALERTS_FEATURE_ID, } from '../../../../../../alerting/common'; import { useKibana } from '../../../../common/lib/kibana'; @@ -107,7 +107,7 @@ describe('rule_details', () => { status: 'error', lastExecutionDate: new Date('2020-08-20T19:23:38Z'), error: { - reason: AlertExecutionStatusErrorReasons.License, + reason: RuleExecutionStatusErrorReasons.License, message: 'test', }, }, @@ -127,7 +127,7 @@ describe('rule_details', () => { status: 'warning', lastExecutionDate: new Date('2020-08-20T19:23:38Z'), warning: { - reason: AlertExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, + reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, message: 'warning message', }, }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx index da2e3feb528c3..e8d4c0b089485 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx @@ -26,7 +26,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; -import { AlertExecutionStatusErrorReasons, parseDuration } from '../../../../../../alerting/common'; +import { RuleExecutionStatusErrorReasons, parseDuration } from '../../../../../../alerting/common'; import { hasAllPrivilege, hasExecuteActionsCapability } from '../../../lib/capabilities'; import { getAlertingSectionBreadcrumb, getRuleDetailsBreadcrumb } from '../../../lib/breadcrumb'; import { getCurrentDocTitle } from '../../../lib/doc_title'; @@ -373,7 +373,7 @@ export const RuleDetails: React.FunctionComponent = ({ {rule.enabled && - rule.executionStatus.error?.reason === AlertExecutionStatusErrorReasons.License ? ( + rule.executionStatus.error?.reason === RuleExecutionStatusErrorReasons.License ? ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/view_in_app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/view_in_app.tsx index 85b9bcd79806f..096e3bcf111b9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/view_in_app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/view_in_app.tsx @@ -13,9 +13,9 @@ import { fromNullable, fold } from 'fp-ts/lib/Option'; import { pipe } from 'fp-ts/lib/pipeable'; import { - AlertNavigation as RuleNavigation, - AlertStateNavigation as RuleStateNavigation, - AlertUrlNavigation as RuleUrlNavigation, + RuleNavigation, + RuleStateNavigation, + RuleUrlNavigation, } from '../../../../../../alerting/common'; import { Rule } from '../../../../types'; import { useKibana } from '../../../../common/lib/kibana'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx index d73a0a9dd9e36..14dd7829fe49e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx @@ -57,7 +57,7 @@ import { import { getTimeOptions } from '../../../common/lib/get_time_options'; import { ActionForm } from '../action_connector_form'; import { - AlertActionParam as RuleActionParam, + RuleActionParam, ALERTS_FEATURE_ID, RecoveredActionGroup, isActionGroupDisabledForActionTypeId, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts index c9186ca1d01c8..1e1a1a5b64462 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts @@ -8,10 +8,7 @@ import { SavedObjectAttribute } from 'kibana/public'; import { isEqual } from 'lodash'; import { Reducer } from 'react'; -import { - AlertActionParam as RuleActionParam, - IntervalSchedule, -} from '../../../../../alerting/common'; +import { RuleActionParam, IntervalSchedule } from '../../../../../alerting/common'; import { Rule, RuleAction } from '../../../types'; export type InitialRule = Partial & diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_filter.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_filter.tsx index 185d18f605d42..65a4654b6ac29 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_filter.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_filter.tsx @@ -15,8 +15,8 @@ import { EuiHealth, } from '@elastic/eui'; import { - AlertExecutionStatuses, - AlertExecutionStatusValues, + RuleExecutionStatuses, + RuleExecutionStatusValues, } from '../../../../../../alerting/common'; import { rulesStatusesTranslationsMapping } from '../translations'; @@ -65,7 +65,7 @@ export const RuleStatusFilter: React.FunctionComponent = } >

- {[...AlertExecutionStatusValues].sort().map((item: AlertExecutionStatuses) => { + {[...RuleExecutionStatusValues].sort().map((item: RuleExecutionStatuses) => { const healthColor = getHealthColor(item); return ( = ); }; -export function getHealthColor(status: AlertExecutionStatuses) { +export function getHealthColor(status: RuleExecutionStatuses) { switch (status) { case 'active': return 'success'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx index a018b73eeeed9..7a73919b4dfec 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx @@ -14,8 +14,8 @@ import { ruleTypeRegistryMock } from '../../../rule_type_registry.mock'; import { RulesList, percentileFields } from './rules_list'; import { RuleTypeModel, ValidationResult, Percentiles } from '../../../../types'; import { - AlertExecutionStatusErrorReasons, - AlertExecutionStatusWarningReasons, + RuleExecutionStatusErrorReasons, + RuleExecutionStatusWarningReasons, ALERTS_FEATURE_ID, parseDuration, } from '../../../../../../alerting/common'; @@ -305,7 +305,7 @@ describe('rules_list component with items', () => { lastDuration: 122000, lastExecutionDate: new Date('2020-08-20T19:23:38Z'), error: { - reason: AlertExecutionStatusErrorReasons.Unknown, + reason: RuleExecutionStatusErrorReasons.Unknown, message: 'test', }, }, @@ -331,7 +331,7 @@ describe('rules_list component with items', () => { lastDuration: 500, lastExecutionDate: new Date('2020-08-20T19:23:38Z'), error: { - reason: AlertExecutionStatusErrorReasons.License, + reason: RuleExecutionStatusErrorReasons.License, message: 'test', }, }, @@ -357,7 +357,7 @@ describe('rules_list component with items', () => { lastDuration: 500, lastExecutionDate: new Date('2020-08-20T19:23:38Z'), warning: { - reason: AlertExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, + reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, message: 'test', }, }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx index c8638015a2942..fa74c957d35e4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx @@ -79,10 +79,10 @@ import { routeToRuleDetails, DEFAULT_SEARCH_PAGE_SIZE } from '../../../constants import { DeleteModalConfirmation } from '../../../components/delete_modal_confirmation'; import { EmptyPrompt } from '../../../components/prompts/empty_prompt'; import { - AlertExecutionStatus, - AlertExecutionStatusValues, + RuleExecutionStatus, + RuleExecutionStatusValues, ALERTS_FEATURE_ID, - AlertExecutionStatusErrorReasons, + RuleExecutionStatusErrorReasons, formatDuration, parseDuration, MONITORING_HISTORY_LIMIT, @@ -191,7 +191,7 @@ export const RulesList: React.FunctionComponent = () => { ruleTypeId: string; } | null>(null); const [rulesStatusesTotal, setRulesStatusesTotal] = useState>( - AlertExecutionStatusValues.reduce( + RuleExecutionStatusValues.reduce( (prev: Record, status: string) => ({ ...prev, @@ -366,15 +366,12 @@ export const RulesList: React.FunctionComponent = () => { ); }; - const renderAlertExecutionStatus = ( - executionStatus: AlertExecutionStatus, - item: RuleTableItem - ) => { + const renderRuleExecutionStatus = (executionStatus: RuleExecutionStatus, item: RuleTableItem) => { const healthColor = getHealthColor(executionStatus.status); const tooltipMessage = executionStatus.status === 'error' ? `Error: ${executionStatus?.error?.message}` : null; const isLicenseError = - executionStatus.error?.reason === AlertExecutionStatusErrorReasons.License; + executionStatus.error?.reason === RuleExecutionStatusErrorReasons.License; const statusMessage = isLicenseError ? ALERT_STATUS_LICENSE_ERROR : rulesStatusesTranslationsMapping[executionStatus.status]; @@ -468,11 +465,11 @@ export const RulesList: React.FunctionComponent = () => { }; }; - const buildErrorListItems = (_executionStatus: AlertExecutionStatus) => { + const buildErrorListItems = (_executionStatus: RuleExecutionStatus) => { const hasErrorMessage = _executionStatus.status === 'error'; const errorMessage = _executionStatus?.error?.message; const isLicenseError = - _executionStatus.error?.reason === AlertExecutionStatusErrorReasons.License; + _executionStatus.error?.reason === RuleExecutionStatusErrorReasons.License; const statusMessage = isLicenseError ? ALERT_STATUS_LICENSE_ERROR : null; return [ @@ -494,7 +491,7 @@ export const RulesList: React.FunctionComponent = () => { ]; }; - const toggleErrorMessage = (_executionStatus: AlertExecutionStatus, ruleItem: RuleTableItem) => { + const toggleErrorMessage = (_executionStatus: RuleExecutionStatus, ruleItem: RuleTableItem) => { setItemIdToExpandedRowMap((itemToExpand) => { const _itemToExpand = { ...itemToExpand }; if (_itemToExpand[ruleItem.id]) { @@ -828,8 +825,8 @@ export const RulesList: React.FunctionComponent = () => { truncateText: false, width: '120px', 'data-test-subj': 'rulesTableCell-lastResponse', - render: (_executionStatus: AlertExecutionStatus, item: RuleTableItem) => { - return renderAlertExecutionStatus(item.executionStatus, item); + render: (_executionStatus: RuleExecutionStatus, item: RuleTableItem) => { + return renderRuleExecutionStatus(item.executionStatus, item); }, }, { @@ -920,7 +917,7 @@ export const RulesList: React.FunctionComponent = () => { const _executionStatus = item.executionStatus; const hasErrorMessage = _executionStatus.status === 'error'; const isLicenseError = - _executionStatus.error?.reason === AlertExecutionStatusErrorReasons.License; + _executionStatus.error?.reason === RuleExecutionStatusErrorReasons.License; return isLicenseError || hasErrorMessage ? ( ` // so the `Params` is a black-box of Record type SanitizedRule = Omit< - SanitizedAlert, + AlertingSanitizedRule, 'alertTypeId' > & { - ruleTypeId: SanitizedAlert['alertTypeId']; + ruleTypeId: AlertingSanitizedRule['alertTypeId']; }; type Rule = SanitizedRule; type ResolvedRule = Omit, 'alertTypeId'> & { ruleTypeId: ResolvedSanitizedRule['alertTypeId']; }; -type RuleAggregations = Omit & { - ruleExecutionStatus: AlertAggregations['alertExecutionStatus']; +type RuleAggregations = Omit & { + ruleExecutionStatus: AlertingRuleAggregations['alertExecutionStatus']; }; export type { @@ -120,7 +120,7 @@ export enum RuleFlyoutCloseReason { export interface ActionParamsProps { actionParams: Partial; index: number; - editAction: (key: string, value: AlertActionParam, index: number) => void; + editAction: (key: string, value: RuleActionParam, index: number) => void; errors: IErrorObject; messageVariables?: ActionVariable[]; defaultMessage?: string; diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts index fb4879fa9ba39..c27d14b70c33f 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts @@ -29,7 +29,7 @@ const DefaultQueryParams: TimeSeriesQuery = { }; describe('timeSeriesQuery', () => { - const esClient = alertsMock.createAlertServices().scopedClusterClient.asCurrentUser; + const esClient = alertsMock.createRuleExecutorServices().scopedClusterClient.asCurrentUser; const logger = loggingSystemMock.create().get() as jest.Mocked; const params = { logger, diff --git a/x-pack/plugins/uptime/public/state/api/alerts.ts b/x-pack/plugins/uptime/public/state/api/alerts.ts index d19b9688b21d0..49df4f390611b 100644 --- a/x-pack/plugins/uptime/public/state/api/alerts.ts +++ b/x-pack/plugins/uptime/public/state/api/alerts.ts @@ -12,7 +12,7 @@ import { ActionConnector } from '../alerts/alerts'; import { AlertsResult, MonitorIdParam } from '../actions/types'; import type { ActionType, AsApiContract, Rule } from '../../../../triggers_actions_ui/public'; import { API_URLS } from '../../../common/constants'; -import { AlertTypeParams } from '../../../../alerting/common'; +import { RuleTypeParams } from '../../../../alerting/common'; import { AtomicStatusCheckParams } from '../../../common/runtime_types/alerts'; import { populateAlertActions, RuleAction } from './alert_actions'; @@ -42,7 +42,7 @@ export const fetchConnectors = async (): Promise => { ); }; -export interface NewAlertParams extends AlertTypeParams { +export interface NewAlertParams extends RuleTypeParams { selectedMonitor: Ping; defaultActions: ActionConnector[]; defaultEmail?: DefaultEmail; diff --git a/x-pack/plugins/uptime/server/lib/alerts/test_utils/index.ts b/x-pack/plugins/uptime/server/lib/alerts/test_utils/index.ts index 374719172405f..e1a5da32c5fe4 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/test_utils/index.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/test_utils/index.ts @@ -61,7 +61,7 @@ export const createRuleTypeMocks = ( const services = { ...getUptimeESMockClient(), - ...alertsMock.createAlertServices(), + ...alertsMock.createRuleExecutorServices(), alertWithLifecycle: jest.fn().mockReturnValue({ scheduleActions, replaceState }), getAlertStartedDate: jest.fn().mockReturnValue('2022-03-17T13:13:33.755Z'), logger: loggerMock, diff --git a/x-pack/plugins/uptime/server/lib/alerts/types.ts b/x-pack/plugins/uptime/server/lib/alerts/types.ts index 5275cddae9d24..b100965db7a73 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/types.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/types.ts @@ -7,7 +7,7 @@ import { UptimeCorePluginsSetup, UptimeServerSetup } from '../adapters'; import { UMServerLibs } from '../lib'; import { AlertTypeWithExecutor } from '../../../../rule_registry/server'; -import { AlertInstanceContext, AlertTypeState } from '../../../../alerting/common'; +import { AlertInstanceContext, RuleTypeState } from '../../../../alerting/common'; import { LifecycleAlertService } from '../../../../rule_registry/server'; /** @@ -21,11 +21,7 @@ export type DefaultUptimeAlertInstance = AlertTy Record, AlertInstanceContext, { - alertWithLifecycle: LifecycleAlertService< - AlertTypeState, - AlertInstanceContext, - TActionGroupIds - >; + alertWithLifecycle: LifecycleAlertService; getAlertStartedDate: (alertId: string) => string | null; } >; diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts index 7a4506dc53a31..9acc0d791b886 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts @@ -14,8 +14,8 @@ import { RuleType, AlertInstanceState, AlertInstanceContext, - AlertTypeState, - AlertTypeParams, + RuleTypeState, + RuleTypeParams, } from '../../../../../../../plugins/alerting/server'; export const EscapableStrings = { @@ -53,7 +53,7 @@ function getAlwaysFiringAlertType() { groupsToScheduleActionsInSeries: schema.maybe(schema.arrayOf(schema.nullable(schema.string()))), }); type ParamsType = TypeOf; - interface State extends AlertTypeState { + interface State extends RuleTypeState { groupInSeriesIndex?: number; } interface InstanceState extends AlertInstanceState { @@ -63,7 +63,7 @@ function getAlwaysFiringAlertType() { instanceContextValue: boolean; } const result: RuleType< - ParamsType & AlertTypeParams, + ParamsType & RuleTypeParams, never, // Only use if defining useSavedObjectReferences hook State, InstanceState, @@ -153,7 +153,7 @@ async function alwaysFiringExecutor(alertExecutorOptions: any) { } function getCumulativeFiringAlertType() { - interface State extends AlertTypeState { + interface State extends RuleTypeState { runCount?: number; } interface InstanceState extends AlertInstanceState { @@ -197,7 +197,7 @@ function getNeverFiringAlertType() { reference: schema.string(), }); type ParamsType = TypeOf; - interface State extends AlertTypeState { + interface State extends RuleTypeState { globalStateValue: boolean; } const result: RuleType = { @@ -401,7 +401,7 @@ function getPatternFiringAlertType() { reference: schema.maybe(schema.string()), }); type ParamsType = TypeOf; - interface State extends AlertTypeState { + interface State extends RuleTypeState { patternIndex?: number; } const result: RuleType = { @@ -470,7 +470,7 @@ function getPatternSuccessOrFailureAlertType() { pattern: schema.arrayOf(schema.oneOf([schema.boolean(), schema.string()])), }); type ParamsType = TypeOf; - interface State extends AlertTypeState { + interface State extends RuleTypeState { patternIndex?: number; } const result: RuleType = { @@ -510,7 +510,7 @@ function getLongRunningPatternRuleType(cancelAlertsOnRuleTimeout: boolean = true pattern: schema.arrayOf(schema.boolean()), }); type ParamsType = TypeOf; - interface State extends AlertTypeState { + interface State extends RuleTypeState { patternIndex?: number; } const result: RuleType = { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts index dba73cba184dd..2eb33d95fa4ea 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { AlertExecutionStatusErrorReasons } from '../../../../../plugins/alerting/common'; +import { RuleExecutionStatusErrorReasons } from '../../../../../plugins/alerting/common'; import { Spaces } from '../../scenarios'; import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../common/lib'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; @@ -54,7 +54,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon executionStatus = await waitForStatus(alertId, new Set(['error'])); expect(executionStatus.error).to.be.ok(); - expect(executionStatus.error.reason).to.be(AlertExecutionStatusErrorReasons.Decrypt); + expect(executionStatus.error.reason).to.be(RuleExecutionStatusErrorReasons.Decrypt); expect(executionStatus.error.message).to.be('Unable to decrypt attribute "apiKey"'); }); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts index 9bcce86b57fe6..23200cdab110a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { getUrlPrefix } from '../../../common/lib'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import type { RawRule, RawAlertAction } from '../../../../../plugins/alerting/server/types'; +import type { RawRule, RawRuleAction } from '../../../../../plugins/alerting/server/types'; import { FILEBEAT_7X_INDICATOR_PATH } from '../../../../../plugins/alerting/server/saved_objects/migrations'; // eslint-disable-next-line import/no-default-export @@ -243,7 +243,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { expect(searchResult.statusCode).to.equal(200); expect((searchResult.body.hits.total as estypes.SearchTotalHits).value).to.equal(1); const hit = searchResult.body.hits.hits[0]; - expect((hit!._source!.alert! as RawRule).actions! as RawAlertAction[]).to.eql([ + expect((hit!._source!.alert! as RawRule).actions! as RawRuleAction[]).to.eql([ { actionRef: 'action_0', actionTypeId: 'test.noop', diff --git a/x-pack/test/functional/services/ml/alerting.ts b/x-pack/test/functional/services/ml/alerting.ts index 45c4c9ba39ed1..0acda99622443 100644 --- a/x-pack/test/functional/services/ml/alerting.ts +++ b/x-pack/test/functional/services/ml/alerting.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { MlApi } from './api'; import { MlCommonUI } from './common_ui'; import { ML_ALERT_TYPES } from '../../../../plugins/ml/common/constants/alerts'; -import { Alert } from '../../../../plugins/alerting/common'; +import { Rule } from '../../../../plugins/alerting/common'; import { MlAnomalyDetectionAlertParams } from '../../../../plugins/ml/common/types/alerts'; export function MachineLearningAlertingProvider( @@ -161,9 +161,7 @@ export function MachineLearningAlertingProvider( .set('kbn-xsrf', 'foo'); mlApi.assertResponseStatusCode(200, findResponseStatus, anomalyDetectionRules); - for (const rule of anomalyDetectionRules.data as Array< - Alert - >) { + for (const rule of anomalyDetectionRules.data as Array>) { const { body, status } = await supertest .delete(`/api/alerting/rule/${rule.id}`) .set('kbn-xsrf', 'foo'); diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts index a5fa20fca25ef..d3eff0b60955e 100644 --- a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts @@ -8,7 +8,7 @@ import React from 'react'; import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public'; import { PluginSetupContract as AlertingSetup } from '../../../../../../plugins/alerting/public'; -import { SanitizedAlert } from '../../../../../../plugins/alerting/common'; +import { SanitizedRule } from '../../../../../../plugins/alerting/common'; import { TriggersAndActionsUIPublicPluginSetup } from '../../../../../../plugins/triggers_actions_ui/public'; export type Setup = void; @@ -24,7 +24,7 @@ export class AlertingFixturePlugin implements Plugin `/rule/${alert.id}` + (alert: SanitizedRule) => `/rule/${alert.id}` ); triggersActionsUi.ruleTypeRegistry.register({ diff --git a/x-pack/test/rule_registry/common/lib/helpers/wait_until_next_execution.ts b/x-pack/test/rule_registry/common/lib/helpers/wait_until_next_execution.ts index 8bc325c4a6bb7..64999aafd2f60 100644 --- a/x-pack/test/rule_registry/common/lib/helpers/wait_until_next_execution.ts +++ b/x-pack/test/rule_registry/common/lib/helpers/wait_until_next_execution.ts @@ -7,18 +7,18 @@ import { GetService } from '../../types'; import { getAlertsTargetIndices } from './get_alerts_target_indices'; import { BULK_INDEX_DELAY, MAX_POLLS } from '../../constants'; -import { Alert } from '../../../../../plugins/alerting/common'; +import { Rule } from '../../../../../plugins/alerting/common'; import { getSpaceUrlPrefix } from '../authentication/spaces'; import { User } from '../authentication/types'; export async function waitUntilNextExecution( getService: GetService, user: User, - alert: Alert, + alert: Rule, spaceId: string, intervalInSeconds: number = 1, count: number = 0 -): Promise { +): Promise { const supertest = getService('supertestWithoutAuth'); const es = getService('es'); @@ -48,7 +48,7 @@ export async function waitUntilNextExecution( throw error; } - const nextAlert = body as Alert; + const nextAlert = body as Rule; if (nextAlert.executionStatus.lastExecutionDate !== alert.executionStatus.lastExecutionDate) { await new Promise((resolve) => { diff --git a/x-pack/test/rule_registry/common/types.ts b/x-pack/test/rule_registry/common/types.ts index c773a19b6d95d..bd457d17a92df 100644 --- a/x-pack/test/rule_registry/common/types.ts +++ b/x-pack/test/rule_registry/common/types.ts @@ -6,13 +6,13 @@ */ import { GenericFtrProviderContext } from '@kbn/test'; import { - Alert as Rule, - AlertTypeParams as RuleTypeParams, + Rule, + RuleTypeParams, ActionGroupIdsOf, AlertInstanceState as AlertState, AlertInstanceContext as AlertContext, } from '../../../plugins/alerting/common'; -import { AlertTypeState as RuleTypeState } from '../../../plugins/alerting/server'; +import { RuleTypeState } from '../../../plugins/alerting/server'; import { services } from './services'; export type GetService = GenericFtrProviderContext['getService']; diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/create_rule.ts b/x-pack/test/rule_registry/spaces_only/tests/trial/create_rule.ts index 4df3ff6b20649..4e9c9c1475388 100644 --- a/x-pack/test/rule_registry/spaces_only/tests/trial/create_rule.ts +++ b/x-pack/test/rule_registry/spaces_only/tests/trial/create_rule.ts @@ -29,7 +29,7 @@ import { deleteAlert, } from '../../../common/lib/helpers'; import { AlertDef, AlertParams } from '../../../common/types'; -import { Alert } from '../../../../../plugins/alerting/common'; +import { Rule } from '../../../../../plugins/alerting/common'; import { APM_METRIC_INDEX_NAME } from '../../../common/constants'; import { obsOnly } from '../../../common/lib/authentication/users'; @@ -58,7 +58,7 @@ export default function registryRulesApiTest({ getService }: FtrProviderContext) describe('when creating a rule', () => { let createResponse: { - alert: Alert; + alert: Rule; status: number; }; before(async () => { diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts b/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts index 8fabaf9151d53..689144791b048 100644 --- a/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts +++ b/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts @@ -31,7 +31,7 @@ import { MockAlertState, MockAllowedActionGroups, } from '../../../common/types'; -import { AlertExecutorOptions as RuleExecutorOptions } from '../../../../../plugins/alerting/server'; +import { RuleExecutorOptions } from '../../../../../plugins/alerting/server'; import { cleanupRegistryIndices } from '../../../common/lib/helpers/cleanup_registry_indices'; // eslint-disable-next-line import/no-default-export From 32599aa5bda29ea3557e60fb20a49998f6e394ab Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 4 Apr 2022 14:52:19 +0300 Subject: [PATCH 58/65] [Visualize] Fixes the unclickable panel menu for all the pie charts (#129290) --- .../expression_renderers/partition_vis_renderer.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx index 53e729466c1d2..b0fe277cebfd3 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx +++ b/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx @@ -9,6 +9,7 @@ import React, { lazy } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { I18nProvider } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { ExpressionRenderDefinition } from '../../../../expressions/public'; import type { PersistedState } from '../../../../visualizations/public'; @@ -32,6 +33,12 @@ export const strings = { const LazyPartitionVisComponent = lazy(() => import('../components/partition_vis_component')); const PartitionVisComponent = withSuspense(LazyPartitionVisComponent); +const partitionVisRenderer = css({ + position: 'relative', + width: '100%', + height: '100%', +}); + export const getPartitionVisRenderer: ( deps: VisTypePieDependencies ) => ExpressionRenderDefinition = ({ theme, palettes, getStartDeps }) => ({ @@ -50,7 +57,7 @@ export const getPartitionVisRenderer: ( render( -
+
Date: Mon, 4 Apr 2022 14:08:05 +0200 Subject: [PATCH 59/65] [Fleet] Update fleet_server bundled package 1.1.1 (#129311) --- fleet_packages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fleet_packages.json b/fleet_packages.json index c620d438d4f8d..3a12e605316a7 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -27,7 +27,7 @@ }, { "name": "fleet_server", - "version": "1.1.0" + "version": "1.1.1" }, { "name": "synthetics", From 68a03cc90398b0d64ea7cfabfa975150dcedec80 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 4 Apr 2022 13:14:35 +0100 Subject: [PATCH 60/65] skip flaky suite (#129224) --- .../apm_api_integration/tests/alerts/anomaly_alert.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts index bd4be58e2bb1c..5c1efcf5311b8 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts @@ -20,7 +20,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const log = getService('log'); - registry.when( + // FLAKY: https://github.com/elastic/kibana/issues/129224 + registry.when.skip( 'fetching service anomalies with a trial license', { config: 'trial', archives: ['apm_mappings_only_8.0.0'] }, () => { From e1c25c9ddbe49eae4e62bdcae7b6cf0d0f5f0d67 Mon Sep 17 00:00:00 2001 From: Hrant Muradyan <69071631+hro-maker@users.noreply.github.com> Date: Mon, 4 Apr 2022 16:49:17 +0400 Subject: [PATCH 61/65] [Console] Fixes Elasticsearch doc VIEW IN CONSOLE will clean local Kibana console form history. (#127430) * release_note:fix issue #12053 * removed empty spaces * [Console] fix pretier errors * [Console] fixing ci Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../containers/editor/legacy/console_editor/editor.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx index 4ad86e495831e..b9732aaa183d0 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -105,7 +105,6 @@ function EditorUI({ initialTextValue }: EditorProps) { const loadBufferFromRemote = (url: string) => { const coreEditor = editor.getCoreEditor(); - if (/^https?:\/\//.test(url)) { const loadFrom: Record = { url, @@ -121,7 +120,8 @@ function EditorUI({ initialTextValue }: EditorProps) { // Fire and forget. $.ajax(loadFrom).done(async (data) => { - await editor.update(data, true); + // when we load data from another Api we also must pass history + await editor.update(`${initialTextValue}\n ${data}`, true); editor.moveToNextRequestEdge(false); coreEditor.clearSelection(); editor.highlightCurrentRequestsAndUpdateActionBar(); From 881a8ec8c7233bab008beef36e954f17b0d1fed6 Mon Sep 17 00:00:00 2001 From: Boris Kirov Date: Mon, 4 Apr 2022 15:16:53 +0200 Subject: [PATCH 62/65] [APM] Convert DB statement code box to EuiCode (#129193) * [APM] Convert DB statement code box to EuiCode Closes #76849 * added a target_blank attribute to the link * added target attribute and removed more code from the db statement --- .../waterfall/span_flyout/span_db.tsx | 53 ++----------------- .../shared/metadata_table/index.tsx | 22 ++++---- 2 files changed, 15 insertions(+), 60 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/span_db.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/span_db.tsx index ee2c09de2a1a4..ff67baea99095 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/span_db.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/span_db.tsx @@ -5,65 +5,20 @@ * 2.0. */ -import { EuiSpacer, EuiTitle } from '@elastic/eui'; +import { EuiSpacer, EuiTitle, EuiCodeBlock } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { tint } from 'polished'; import React, { Fragment } from 'react'; -import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'; -import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql'; -import xcode from 'react-syntax-highlighter/dist/cjs/styles/hljs/xcode'; -import { euiStyled } from '../../../../../../../../../../../src/plugins/kibana_react/common'; import { Span } from '../../../../../../../../typings/es_schemas/ui/span'; -import { useTheme } from '../../../../../../../hooks/use_theme'; -import { TruncateHeightSection } from './truncate_height_section'; - -SyntaxHighlighter.registerLanguage('sql', sql); - -const DatabaseStatement = euiStyled.div` - padding: ${({ theme }) => - `${theme.eui.paddingSizes.s} ${theme.eui.paddingSizes.m}`}; - background: ${({ theme }) => tint(0.9, theme.eui.euiColorWarning)}; - border-radius: ${({ theme }) => theme.eui.euiBorderRadiusSmall}; - border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; - font-family: ${({ theme }) => theme.eui.euiCodeFontFamily}; - font-size: ${({ theme }) => theme.eui.euiFontSizeS}; -`; interface Props { spanDb?: NonNullable['db']; } export function SpanDatabase({ spanDb }: Props) { - const theme = useTheme(); - const dbSyntaxLineHeight = theme.eui.euiSizeL; - const previewHeight = 240; // 10 * dbSyntaxLineHeight - if (!spanDb || !spanDb.statement) { return null; } - const statementItem = - spanDb.type !== 'sql' ? ( - spanDb.statement - ) : ( - - - {spanDb.statement} - - - ); - return ( @@ -76,9 +31,11 @@ export function SpanDatabase({ spanDb }: Props) { )}

+ + + {spanDb.statement} + - {statementItem} - ); } diff --git a/x-pack/plugins/apm/public/components/shared/metadata_table/index.tsx b/x-pack/plugins/apm/public/components/shared/metadata_table/index.tsx index ab6c132c61e13..c7e5cf77dd220 100644 --- a/x-pack/plugins/apm/public/components/shared/metadata_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/metadata_table/index.tsx @@ -58,28 +58,26 @@ export function MetadataTable({ sections, isLoading }: Props) { const noResultFound = Boolean(searchTerm) && isEmpty(filteredSections); return ( - - - - - How to add labels and other data - - - - + + + + + + How to add labels and other data + + + {isLoading && ( From e2e8dbb08f6e866095ca5c8f2e6d128748c276a8 Mon Sep 17 00:00:00 2001 From: Bhavya RM Date: Mon, 4 Apr 2022 09:20:47 -0400 Subject: [PATCH 63/65] Rendering imported dashboards in import saved objects between version tests and refactor of multi-space imports test (#129065) --- .../functional/page_objects/dashboard_page.ts | 5 ++ .../management/saved_objects_page.ts | 34 +++++++++ .../import_saved_objects_between_versions.ts | 69 +++++++++++-------- .../multi_space_import.ts | 68 +++++++----------- 4 files changed, 102 insertions(+), 74 deletions(-) diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index d2376d84bdccb..4a77b5673e9fe 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -668,6 +668,11 @@ export class DashboardPageObject extends FtrService { await this.renderable.waitForRender(parseInt(count)); } + public async verifyNoRenderErrors() { + const errorEmbeddables = await this.testSubjects.findAll('embeddableStackError'); + expect(errorEmbeddables.length).to.be(0); + } + public async getSharedContainerData() { this.log.debug('getSharedContainerData'); const sharedContainer = await this.find.byCssSelector('[data-shared-items-container]'); diff --git a/test/functional/page_objects/management/saved_objects_page.ts b/test/functional/page_objects/management/saved_objects_page.ts index 655b84f2cf8e7..0ce5396f1472a 100644 --- a/test/functional/page_objects/management/saved_objects_page.ts +++ b/test/functional/page_objects/management/saved_objects_page.ts @@ -32,6 +32,40 @@ export class SavedObjectsPageObject extends FtrService { return await searchBox.getAttribute('value'); } + async getExportCount() { + return await this.retry.tryForTime(10000, async () => { + const exportText = await this.testSubjects.getVisibleText('exportAllObjects'); + const parts = exportText.trim().split(' '); + if (parts.length !== 3) { + throw new Error('text not loaded yet'); + } + const count = Number.parseInt(parts[1], 10); + if (count === 0) { + throw new Error('text not loaded yet'); + } + return count; + }); + } + + getSpacePrefix(spaceId: string) { + return spaceId && spaceId !== 'default' ? `/s/${spaceId}` : ``; + } + + async importIntoSpace(path: string, spaceId = 'default') { + await this.common.navigateToUrl('settings', 'kibana/objects', { + basePath: this.getSpacePrefix(spaceId), + shouldUseHashForSubUrl: false, + }); + await this.waitTableIsLoaded(); + + await this.importFile(path); + + await this.checkImportSucceeded(); + await this.clickImportDone(); + await this.waitTableIsLoaded(); + return await this.getExportCount(); + } + async importFile(path: string, overwriteAll = true) { this.log.debug(`importFile(${path})`); diff --git a/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts b/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts index 2b20964965cdc..168a2fd8d8aec 100644 --- a/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts +++ b/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts @@ -16,66 +16,77 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); - const PageObjects = getPageObjects(['common', 'settings', 'header', 'savedObjects']); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - - const getExportCount = async () => { - return await retry.tryForTime(10000, async () => { - const exportText = await testSubjects.getVisibleText('exportAllObjects'); - const parts = exportText.trim().split(' '); - if (parts.length !== 3) { - throw new Error('text not loaded yet'); - } - const count = Number.parseInt(parts[1], 10); - if (count === 0) { - throw new Error('text not loaded yet'); - } - return count; - }); - }; + const PageObjects = getPageObjects([ + 'common', + 'settings', + 'savedObjects', + 'dashboard', + 'timePicker', + ]); + const renderService = getService('renderable'); describe('Export import saved objects between versions', function () { before(async function () { await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); + await esArchiver.loadIfNeeded( + 'test/functional/fixtures/es_archiver/getting_started/shakespeare' + ); await kibanaServer.uiSettings.replace({}); - await PageObjects.settings.navigateTo(); - await PageObjects.settings.clickKibanaSavedObjects(); }); beforeEach(async () => { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSavedObjects(); await PageObjects.savedObjects.waitTableIsLoaded(); }); after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await esArchiver.unload('test/functional/fixtures/es_archiver/getting_started/shakespeare'); await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); }); - it('should be able to import 7.13 saved objects into 8.0.0', async function () { - const initialObjectCount = await getExportCount(); - + it('should be able to import 7.13 saved objects into 8.0.0 and verfiy the rendering of two dashboards', async function () { + const initialObjectCount = await PageObjects.savedObjects.getExportCount(); await PageObjects.savedObjects.importFile( path.join(__dirname, 'exports', '_7.13_import_saved_objects.ndjson') ); await PageObjects.savedObjects.checkImportSucceeded(); await PageObjects.savedObjects.clickImportDone(); await PageObjects.savedObjects.waitTableIsLoaded(); - - const newObjectCount = await getExportCount(); + const newObjectCount = await PageObjects.savedObjects.getExportCount(); expect(newObjectCount - initialObjectCount).to.eql(86); + + // logstash by reference dashboard with drilldowns + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.loadSavedDashboard('by_reference_drilldown'); + // dashboard should load properly + await PageObjects.dashboard.expectOnDashboard('by_reference_drilldown'); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + // count of panels rendered completely + await renderService.waitForRender(4); + // There should be 0 error embeddables on the dashboard + await PageObjects.dashboard.verifyNoRenderErrors(); + + // combined shakespeare and logstash dashboard + await PageObjects.dashboard.loadSavedDashboard('lens_combined_dashboard'); + await PageObjects.dashboard.expectOnDashboard('lens_combined_dashboard'); + // count of panels rendered completely + await renderService.waitForRender(2); + // There should be 0 error embeddables on the dashboard + await PageObjects.dashboard.verifyNoRenderErrors(); }); it('should be able to import alerts and actions saved objects from 7.14 into 8.0.0', async function () { - const initialObjectCount = await getExportCount(); - + const initialObjectCount = await PageObjects.savedObjects.getExportCount(); await PageObjects.savedObjects.importFile( path.join(__dirname, 'exports', '_7.14_import_alerts_actions.ndjson') ); await PageObjects.savedObjects.checkImportSucceeded(); await PageObjects.savedObjects.clickImportDone(); await PageObjects.savedObjects.waitTableIsLoaded(); - - const newObjectCount = await getExportCount(); + const newObjectCount = await PageObjects.savedObjects.getExportCount(); expect(newObjectCount - initialObjectCount).to.eql(23); }); }); diff --git a/x-pack/test/functional/apps/saved_objects_management/multi_space_import.ts b/x-pack/test/functional/apps/saved_objects_management/multi_space_import.ts index 30c460c20ff10..9e7c9e3b001f5 100644 --- a/x-pack/test/functional/apps/saved_objects_management/multi_space_import.ts +++ b/x-pack/test/functional/apps/saved_objects_management/multi_space_import.ts @@ -24,50 +24,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'dashboard', ]); const testSubjects = getService('testSubjects'); - const retry = getService('retry'); const spacesService = getService('spaces'); const renderService = getService('renderable'); const kibanaServer = getService('kibanaServer'); - - const getExportCount = async () => { - return await retry.tryForTime(10000, async () => { - const exportText = await testSubjects.getVisibleText('exportAllObjects'); - const parts = exportText.trim().split(' '); - if (parts.length !== 3) { - throw new Error('text not loaded yet'); - } - const count = Number.parseInt(parts[1], 10); - if (count === 0) { - throw new Error('text not loaded yet'); - } - return count; - }); - }; - const getSpacePrefix = (spaceId: string) => { return spaceId && spaceId !== 'default' ? `/s/${spaceId}` : ``; }; - const importIntoSpace = async (spaceId: string) => { - await PageObjects.common.navigateToUrl('settings', 'kibana/objects', { - basePath: getSpacePrefix(spaceId), - shouldUseHashForSubUrl: false, - }); - await PageObjects.savedObjects.waitTableIsLoaded(); - const initialObjectCount = await getExportCount(); - - await PageObjects.savedObjects.importFile( - path.join(__dirname, 'exports', '_8.0.0_multispace_import.ndjson') - ); - await PageObjects.savedObjects.checkImportSucceeded(); - await PageObjects.savedObjects.clickImportDone(); - await PageObjects.savedObjects.waitTableIsLoaded(); - - const newObjectCount = await getExportCount(); - - expect(newObjectCount - initialObjectCount).to.eql(6); - }; - const checkIfDashboardRendered = async (spaceId: string) => { await PageObjects.common.navigateToUrl('dashboard', undefined, { basePath: getSpacePrefix(spaceId), @@ -76,13 +39,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.loadSavedDashboard('multi_space_import_8.0.0_export'); // dashboard should load properly await PageObjects.dashboard.expectOnDashboard('multi_space_import_8.0.0_export'); - // count of panels rendered completely await renderService.waitForRender(8); - // There should be 0 error embeddables on the dashboard - const errorEmbeddables = await testSubjects.findAll('embeddableStackError'); - expect(errorEmbeddables.length).to.be(0); + await PageObjects.dashboard.verifyNoRenderErrors(); }; describe('should be able to handle multi-space imports correctly', function () { @@ -109,14 +69,34 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('imported dashboard into default space should render correctly', async () => { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSavedObjects(); + const initialObjectCount = await PageObjects.savedObjects.getExportCount(); const spaceId = 'default'; - await importIntoSpace(spaceId); + const importFileName = '_8.0.0_multispace_import.ndjson'; + const importFilePath = path.join(__dirname, 'exports', importFileName); + const newObjectCount = await PageObjects.savedObjects.importIntoSpace( + importFilePath, + spaceId + ); + expect(newObjectCount - initialObjectCount).to.eql(6); await checkIfDashboardRendered(spaceId); }); it('imported dashboard into another space should render correctly', async () => { const spaceId = 'another_space'; - await importIntoSpace(spaceId); + await PageObjects.common.navigateToUrl('settings', 'kibana/objects', { + basePath: getSpacePrefix(spaceId), + shouldUseHashForSubUrl: false, + }); + const initialObjectCount = await PageObjects.savedObjects.getExportCount(); + const importFileName = '_8.0.0_multispace_import.ndjson'; + const importFilePath = path.join(__dirname, 'exports', importFileName); + const newObjectCount = await PageObjects.savedObjects.importIntoSpace( + importFilePath, + spaceId + ); + expect(newObjectCount - initialObjectCount).to.eql(6); await checkIfDashboardRendered(spaceId); }); @@ -140,9 +120,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Wait for successful copy await testSubjects.waitForDeleted(`cts-summary-indicator-loading-${destinationSpaceId}`); await testSubjects.existOrFail(`cts-summary-indicator-success-${destinationSpaceId}`); - const summaryCounts = await PageObjects.copySavedObjectsToSpace.getSummaryCounts(); - expect(summaryCounts).to.eql({ success: 6, pending: 0, From d89ddadc17d495df06d54b19c1db4928340f29c7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 07:05:12 -0700 Subject: [PATCH 64/65] Update typescript (main) (#129106) Co-authored-by: Renovate Bot Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Spencer --- package.json | 12 +- .../public/state_management/url_templates.ts | 5 +- .../public/common/store/app/actions.ts | 5 +- .../public/common/store/inputs/actions.ts | 5 +- .../public/common/store/sourcerer/actions.ts | 5 +- .../timelines/store/timeline/actions.ts | 35 +++--- .../timelines/public/store/t_grid/actions.ts | 15 ++- yarn.lock | 112 +++++++++--------- 8 files changed, 104 insertions(+), 90 deletions(-) diff --git a/package.json b/package.json index 6e7886823fca1..58a7f9137e57c 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "**/react-syntax-highlighter/**/highlight.js": "^10.4.1", "**/refractor/prismjs": "~1.27.0", "**/trim": "1.0.1", - "**/typescript": "4.6.2", + "**/typescript": "4.6.3", "**/underscore": "^1.13.1", "globby/fast-glob": "3.2.7", "puppeteer/node-fetch": "^2.6.7" @@ -730,9 +730,9 @@ "@types/yargs": "^15.0.0", "@types/yauzl": "^2.9.1", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^5.14.0", - "@typescript-eslint/parser": "^5.14.0", - "@typescript-eslint/typescript-estree": "^5.14.0", + "@typescript-eslint/eslint-plugin": "^5.17.0", + "@typescript-eslint/parser": "^5.17.0", + "@typescript-eslint/typescript-estree": "^5.17.0", "@yarnpkg/lockfile": "^1.1.0", "abab": "^2.0.4", "aggregate-error": "^3.1.0", @@ -871,7 +871,7 @@ "postcss": "^7.0.32", "postcss-loader": "^3.0.0", "postcss-prefix-selector": "^1.7.2", - "prettier": "^2.5.1", + "prettier": "^2.6.1", "q": "^1.5.1", "react-test-renderer": "^16.12.0", "read-pkg": "^5.2.0", @@ -905,7 +905,7 @@ "ts-loader": "^7.0.5", "ts-morph": "^13.0.2", "tsd": "^0.13.1", - "typescript": "4.6.2", + "typescript": "4.6.3", "unlazy-loader": "^0.1.3", "url-loader": "^2.2.0", "val-loader": "^1.1.1", diff --git a/x-pack/plugins/graph/public/state_management/url_templates.ts b/x-pack/plugins/graph/public/state_management/url_templates.ts index 736270e53a797..01b1a9296b0b6 100644 --- a/x-pack/plugins/graph/public/state_management/url_templates.ts +++ b/x-pack/plugins/graph/public/state_management/url_templates.ts @@ -23,8 +23,9 @@ import { matchesOne } from './helpers'; const actionCreator = actionCreatorFactory('x-pack/graph/urlTemplates'); export const loadTemplates = actionCreator('LOAD_TEMPLATES'); -export const saveTemplate = - actionCreator<{ index: number; template: UrlTemplate }>('SAVE_TEMPLATE'); +export const saveTemplate = actionCreator<{ index: number; template: UrlTemplate }>( + 'SAVE_TEMPLATE' +); export const removeTemplate = actionCreator('REMOVE_TEMPLATE'); export type UrlTemplatesState = UrlTemplate[]; diff --git a/x-pack/plugins/security_solution/public/common/store/app/actions.ts b/x-pack/plugins/security_solution/public/common/store/app/actions.ts index 7e2ebed62ae16..a262b053d706c 100644 --- a/x-pack/plugins/security_solution/public/common/store/app/actions.ts +++ b/x-pack/plugins/security_solution/public/common/store/app/actions.ts @@ -15,8 +15,9 @@ export const updateNote = actionCreator<{ note: Note }>('UPDATE_NOTE'); export const addNotes = actionCreator<{ notes: Note[] }>('ADD_NOTE'); -export const addError = - actionCreator<{ id: string; title: string; message: string[] }>('ADD_ERRORS'); +export const addError = actionCreator<{ id: string; title: string; message: string[] }>( + 'ADD_ERRORS' +); export const removeError = actionCreator<{ id: string }>('REMOVE_ERRORS'); diff --git a/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts b/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts index 3da18090eb443..3e3643fe6dbe0 100644 --- a/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts +++ b/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts @@ -69,8 +69,9 @@ export const setInspectionParameter = actionCreator<{ export const deleteAllQuery = actionCreator<{ id: InputsModelId }>('DELETE_ALL_QUERY'); -export const toggleTimelineLinkTo = - actionCreator<{ linkToId: InputsModelId }>('TOGGLE_TIMELINE_LINK_TO'); +export const toggleTimelineLinkTo = actionCreator<{ linkToId: InputsModelId }>( + 'TOGGLE_TIMELINE_LINK_TO' +); export const removeTimelineLinkTo = actionCreator('REMOVE_TIMELINE_LINK_TO'); export const addTimelineLinkTo = actionCreator<{ linkToId: InputsModelId }>('ADD_TIMELINE_LINK_TO'); diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts index 0033596d8cd1d..7517e345c0c9a 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts @@ -26,8 +26,9 @@ export const setDataViewLoading = actionCreator<{ loading: boolean; }>('SET_DATA_VIEW_LOADING'); -export const setSignalIndexName = - actionCreator<{ signalIndexName: string }>('SET_SIGNAL_INDEX_NAME'); +export const setSignalIndexName = actionCreator<{ signalIndexName: string }>( + 'SET_SIGNAL_INDEX_NAME' +); export const setSourcererDataViews = actionCreator('SET_SOURCERER_DATA_VIEWS'); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts index 10403d5404816..1fe56de596ce0 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts @@ -53,8 +53,9 @@ export const addHistory = actionCreator<{ id: string; historyId: string }>('ADD_ export const addNote = actionCreator<{ id: string; noteId: string }>('ADD_NOTE'); -export const addNoteToEvent = - actionCreator<{ id: string; noteId: string; eventId: string }>('ADD_NOTE_TO_EVENT'); +export const addNoteToEvent = actionCreator<{ id: string; noteId: string; eventId: string }>( + 'ADD_NOTE_TO_EVENT' +); export const showTimeline = actionCreator<{ id: string; show: boolean }>('SHOW_TIMELINE'); @@ -68,8 +69,9 @@ export const createTimeline = actionCreator('CREATE_TIMELI export const pinEvent = actionCreator<{ id: string; eventId: string }>('PIN_EVENT'); -export const setTimelineUpdatedAt = - actionCreator<{ id: string; updated: number }>('SET_TIMELINE_UPDATED_AT'); +export const setTimelineUpdatedAt = actionCreator<{ id: string; updated: number }>( + 'SET_TIMELINE_UPDATED_AT' +); export const removeProvider = actionCreator<{ id: string; @@ -152,8 +154,9 @@ export const applyKqlFilterQuery = actionCreator<{ filterQuery: SerializedFilterQuery; }>('APPLY_KQL_FILTER_QUERY'); -export const updateIsFavorite = - actionCreator<{ id: string; isFavorite: boolean }>('UPDATE_IS_FAVORITE'); +export const updateIsFavorite = actionCreator<{ id: string; isFavorite: boolean }>( + 'UPDATE_IS_FAVORITE' +); export const updateIsLive = actionCreator<{ id: string; isLive: boolean }>('UPDATE_IS_LIVE'); @@ -163,14 +166,17 @@ export const updateTitleAndDescription = actionCreator<{ title: string; }>('UPDATE_TITLE_AND_DESCRIPTION'); -export const updatePageIndex = - actionCreator<{ id: string; activePage: number }>('UPDATE_PAGE_INDEX'); +export const updatePageIndex = actionCreator<{ id: string; activePage: number }>( + 'UPDATE_PAGE_INDEX' +); -export const updateProviders = - actionCreator<{ id: string; providers: DataProvider[] }>('UPDATE_PROVIDERS'); +export const updateProviders = actionCreator<{ id: string; providers: DataProvider[] }>( + 'UPDATE_PROVIDERS' +); -export const updateRange = - actionCreator<{ id: string; start: string; end: string }>('UPDATE_RANGE'); +export const updateRange = actionCreator<{ id: string; start: string; end: string }>( + 'UPDATE_RANGE' +); export const updateAutoSaveMsg = actionCreator<{ timelineId: string | null; @@ -189,8 +195,9 @@ export const setFilters = actionCreator<{ filters: Filter[]; }>('SET_TIMELINE_FILTERS'); -export const updateEventType = - actionCreator<{ id: string; eventType: TimelineEventsType }>('UPDATE_EVENT_TYPE'); +export const updateEventType = actionCreator<{ id: string; eventType: TimelineEventsType }>( + 'UPDATE_EVENT_TYPE' +); export const setExcludedRowRendererIds = actionCreator<{ id: string; diff --git a/x-pack/plugins/timelines/public/store/t_grid/actions.ts b/x-pack/plugins/timelines/public/store/t_grid/actions.ts index 00e207180b134..45ec64dc64e07 100644 --- a/x-pack/plugins/timelines/public/store/t_grid/actions.ts +++ b/x-pack/plugins/timelines/public/store/t_grid/actions.ts @@ -65,8 +65,9 @@ export const updateColumns = actionCreator<{ columns: ColumnHeaderOptions[]; }>('UPDATE_COLUMNS'); -export const updateItemsPerPage = - actionCreator<{ id: string; itemsPerPage: number }>('UPDATE_ITEMS_PER_PAGE'); +export const updateItemsPerPage = actionCreator<{ id: string; itemsPerPage: number }>( + 'UPDATE_ITEMS_PER_PAGE' +); export const updateItemsPerPageOptions = actionCreator<{ id: string; @@ -108,11 +109,13 @@ export const clearEventsDeleted = actionCreator<{ export const initializeTGridSettings = actionCreator('INITIALIZE_TGRID'); -export const setTGridSelectAll = - actionCreator<{ id: string; selectAll: boolean }>('SET_TGRID_SELECT_ALL'); +export const setTGridSelectAll = actionCreator<{ id: string; selectAll: boolean }>( + 'SET_TGRID_SELECT_ALL' +); -export const setTimelineUpdatedAt = - actionCreator<{ id: string; updated: number }>('SET_TIMELINE_UPDATED_AT'); +export const setTimelineUpdatedAt = actionCreator<{ id: string; updated: number }>( + 'SET_TIMELINE_UPDATED_AT' +); export const addProviderToTimeline = actionCreator<{ id: string; dataProvider: DataProvider }>( 'ADD_PROVIDER_TO_TIMELINE' diff --git a/yarn.lock b/yarn.lock index eca2c6c07f4ec..b2cbef0917afb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7120,14 +7120,14 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.14.0.tgz#5119b67152356231a0e24b998035288a9cd21335" - integrity sha512-ir0wYI4FfFUDfLcuwKzIH7sMVA+db7WYen47iRSaCGl+HMAZI9fpBwfDo45ZALD3A45ZGyHWDNLhbg8tZrMX4w== - dependencies: - "@typescript-eslint/scope-manager" "5.14.0" - "@typescript-eslint/type-utils" "5.14.0" - "@typescript-eslint/utils" "5.14.0" +"@typescript-eslint/eslint-plugin@^5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.17.0.tgz#704eb4e75039000531255672bf1c85ee85cf1d67" + integrity sha512-qVstvQilEd89HJk3qcbKt/zZrfBZ+9h2ynpAGlWjWiizA7m/MtLT9RoX6gjtpE500vfIg8jogAkDzdCxbsFASQ== + dependencies: + "@typescript-eslint/scope-manager" "5.17.0" + "@typescript-eslint/type-utils" "5.17.0" + "@typescript-eslint/utils" "5.17.0" debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" @@ -7147,14 +7147,14 @@ eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/parser@^5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.14.0.tgz#7c79f898aa3cff0ceee6f1d34eeed0f034fb9ef3" - integrity sha512-aHJN8/FuIy1Zvqk4U/gcO/fxeMKyoSv/rS46UXMXOJKVsLQ+iYPuXNbpbH7cBLcpSbmyyFbwrniLx5+kutu1pw== +"@typescript-eslint/parser@^5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.17.0.tgz#7def77d5bcd8458d12d52909118cf3f0a45f89d5" + integrity sha512-aRzW9Jg5Rlj2t2/crzhA2f23SIYFlF9mchGudyP0uiD6SenIxzKoLjwzHbafgHn39dNV/TV7xwQkLfFTZlJ4ig== dependencies: - "@typescript-eslint/scope-manager" "5.14.0" - "@typescript-eslint/types" "5.14.0" - "@typescript-eslint/typescript-estree" "5.14.0" + "@typescript-eslint/scope-manager" "5.17.0" + "@typescript-eslint/types" "5.17.0" + "@typescript-eslint/typescript-estree" "5.17.0" debug "^4.3.2" "@typescript-eslint/scope-manager@4.31.2": @@ -7165,20 +7165,20 @@ "@typescript-eslint/types" "4.31.2" "@typescript-eslint/visitor-keys" "4.31.2" -"@typescript-eslint/scope-manager@5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.14.0.tgz#ea518962b42db8ed0a55152ea959c218cb53ca7b" - integrity sha512-LazdcMlGnv+xUc5R4qIlqH0OWARyl2kaP8pVCS39qSL3Pd1F7mI10DbdXeARcE62sVQE4fHNvEqMWsypWO+yEw== +"@typescript-eslint/scope-manager@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.17.0.tgz#4cea7d0e0bc0e79eb60cad431c89120987c3f952" + integrity sha512-062iCYQF/doQ9T2WWfJohQKKN1zmmXVfAcS3xaiialiw8ZUGy05Em6QVNYJGO34/sU1a7a+90U3dUNfqUDHr3w== dependencies: - "@typescript-eslint/types" "5.14.0" - "@typescript-eslint/visitor-keys" "5.14.0" + "@typescript-eslint/types" "5.17.0" + "@typescript-eslint/visitor-keys" "5.17.0" -"@typescript-eslint/type-utils@5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.14.0.tgz#711f08105860b12988454e91df433567205a8f0b" - integrity sha512-d4PTJxsqaUpv8iERTDSQBKUCV7Q5yyXjqXUl3XF7Sd9ogNLuKLkxz82qxokqQ4jXdTPZudWpmNtr/JjbbvUixw== +"@typescript-eslint/type-utils@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.17.0.tgz#1c4549d68c89877662224aabb29fbbebf5fc9672" + integrity sha512-3hU0RynUIlEuqMJA7dragb0/75gZmwNwFf/QJokWzPehTZousP/MNifVSgjxNcDCkM5HI2K22TjQWUmmHUINSg== dependencies: - "@typescript-eslint/utils" "5.14.0" + "@typescript-eslint/utils" "5.17.0" debug "^4.3.2" tsutils "^3.21.0" @@ -7187,10 +7187,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.31.2.tgz#2aea7177d6d744521a168ed4668eddbd912dfadf" integrity sha512-kWiTTBCTKEdBGrZKwFvOlGNcAsKGJSBc8xLvSjSppFO88AqGxGNYtF36EuEYG6XZ9vT0xX8RNiHbQUKglbSi1w== -"@typescript-eslint/types@5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.14.0.tgz#96317cf116cea4befabc0defef371a1013f8ab11" - integrity sha512-BR6Y9eE9360LNnW3eEUqAg6HxS9Q35kSIs4rp4vNHRdfg0s+/PgHgskvu5DFTM7G5VKAVjuyaN476LCPrdA7Mw== +"@typescript-eslint/types@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.17.0.tgz#861ec9e669ffa2aa9b873dd4d28d9b1ce26d216f" + integrity sha512-AgQ4rWzmCxOZLioFEjlzOI3Ch8giDWx8aUDxyNw9iOeCvD3GEYAB7dxWGQy4T/rPVe8iPmu73jPHuaSqcjKvxw== "@typescript-eslint/typescript-estree@4.31.2": version "4.31.2" @@ -7205,28 +7205,28 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@5.14.0", "@typescript-eslint/typescript-estree@^5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.14.0.tgz#78b7f7385d5b6f2748aacea5c9b7f6ae62058314" - integrity sha512-QGnxvROrCVtLQ1724GLTHBTR0lZVu13izOp9njRvMkCBgWX26PKvmMP8k82nmXBRD3DQcFFq2oj3cKDwr0FaUA== +"@typescript-eslint/typescript-estree@5.17.0", "@typescript-eslint/typescript-estree@^5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.17.0.tgz#a7cba7dfc8f9cc2ac78c18584e684507df4f2488" + integrity sha512-X1gtjEcmM7Je+qJRhq7ZAAaNXYhTgqMkR10euC4Si6PIjb+kwEQHSxGazXUQXFyqfEXdkGf6JijUu5R0uceQzg== dependencies: - "@typescript-eslint/types" "5.14.0" - "@typescript-eslint/visitor-keys" "5.14.0" + "@typescript-eslint/types" "5.17.0" + "@typescript-eslint/visitor-keys" "5.17.0" debug "^4.3.2" globby "^11.0.4" is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/utils@5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.14.0.tgz#6c8bc4f384298cbbb32b3629ba7415f9f80dc8c4" - integrity sha512-EHwlII5mvUA0UsKYnVzySb/5EE/t03duUTweVy8Zqt3UQXBrpEVY144OTceFKaOe4xQXZJrkptCf7PjEBeGK4w== +"@typescript-eslint/utils@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.17.0.tgz#549a9e1d491c6ccd3624bc3c1b098f5cfb45f306" + integrity sha512-DVvndq1QoxQH+hFv+MUQHrrWZ7gQ5KcJzyjhzcqB1Y2Xes1UQQkTRPUfRpqhS8mhTWsSb2+iyvDW1Lef5DD7vA== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.14.0" - "@typescript-eslint/types" "5.14.0" - "@typescript-eslint/typescript-estree" "5.14.0" + "@typescript-eslint/scope-manager" "5.17.0" + "@typescript-eslint/types" "5.17.0" + "@typescript-eslint/typescript-estree" "5.17.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" @@ -7238,12 +7238,12 @@ "@typescript-eslint/types" "4.31.2" eslint-visitor-keys "^2.0.0" -"@typescript-eslint/visitor-keys@5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.14.0.tgz#1927005b3434ccd0d3ae1b2ecf60e65943c36986" - integrity sha512-yL0XxfzR94UEkjBqyymMLgCBdojzEuy/eim7N9/RIcTNxpJudAcqsU8eRyfzBbcEzGoPWfdM3AGak3cN08WOIw== +"@typescript-eslint/visitor-keys@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.17.0.tgz#52daae45c61b0211b4c81b53a71841911e479128" + integrity sha512-6K/zlc4OfCagUu7Am/BD5k8PSWQOgh34Nrv9Rxe2tBzlJ7uOeJ/h7ugCGDCeEZHT6k2CJBhbk9IsbkPI0uvUkA== dependencies: - "@typescript-eslint/types" "5.14.0" + "@typescript-eslint/types" "5.17.0" eslint-visitor-keys "^3.0.0" "@ungap/promise-all-settled@1.1.2": @@ -22949,10 +22949,10 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" - integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== +prettier@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.1.tgz#d472797e0d7461605c1609808e27b80c0f9cfe17" + integrity sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A== prettier@~2.2.1: version "2.2.1" @@ -28338,10 +28338,10 @@ typescript-tuple@^2.2.1: dependencies: typescript-compare "^0.0.2" -typescript@4.6.2, typescript@^3.3.3333, typescript@^3.5.3, typescript@^4.5.5, typescript@~4.4.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4" - integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg== +typescript@4.6.3, typescript@^3.3.3333, typescript@^3.5.3, typescript@^4.5.5, typescript@~4.4.2: + version "4.6.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" + integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== ua-parser-js@^0.7.18: version "0.7.24" From ce83b0a5d9e04ad0629d2609c73ad01832d92b15 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Mon, 4 Apr 2022 16:08:16 +0200 Subject: [PATCH 65/65] Remove docs reference to ephemeral encryption key. (#129331) Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> --- docs/user/security/secure-saved-objects.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/security/secure-saved-objects.asciidoc b/docs/user/security/secure-saved-objects.asciidoc index 3b15a576500f1..1093aa1de0d05 100644 --- a/docs/user/security/secure-saved-objects.asciidoc +++ b/docs/user/security/secure-saved-objects.asciidoc @@ -16,7 +16,7 @@ xpack.encryptedSavedObjects: [IMPORTANT] ============================================================================ -If you don't specify an encryption key, {kib} automatically generates a random key at startup. Every time you restart {kib}, it uses a new ephemeral encryption key and is unable to decrypt saved objects encrypted with another key. To prevent data loss, {kib} might disable features that rely on this encryption until you explicitly set an encryption key. +If you don't specify an encryption key, {kib} might disable features that rely on encrypted saved objects. ============================================================================ [[encryption-key-rotation]]