From a52829d0404ab915192b0ce1073995245cd9db1d Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 26 Apr 2022 10:33:47 +0200 Subject: [PATCH 01/27] [Exploratory view] Move refresh button to header and also fix it's working (#130665) --- .../configurations/lens_attributes.ts | 4 +- .../exploratory_view.test.tsx | 1 - .../exploratory_view/exploratory_view.tsx | 42 ++++++------------- .../header/refresh_button.tsx | 36 ++++++++++++++++ .../hooks/use_lens_attributes.ts | 2 +- .../hooks/use_series_storage.tsx | 7 ++++ .../shared/exploratory_view/index.tsx | 18 +++++--- .../exploratory_view/lens_embeddable.tsx | 2 +- .../shared/exploratory_view/rtl_helpers.tsx | 1 + 9 files changed, 72 insertions(+), 41 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/header/refresh_button.tsx diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index 6ff6e8ed3f586..cce0a0a3bc5f6 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -785,7 +785,7 @@ export class LensAttributes { }; } - getJSON(): TypedLensByValueInput['attributes'] { + getJSON(lastRefresh?: number): TypedLensByValueInput['attributes'] { const uniqueIndexPatternsIds = Array.from( new Set([...this.layerConfigs.map(({ indexPattern }) => indexPattern.id)]) ); @@ -794,7 +794,7 @@ export class LensAttributes { return { title: 'Prefilled from exploratory view app', - description: '', + description: lastRefresh ? `Last refreshed at ${new Date(lastRefresh).toISOString()}` : '', visualizationType: 'lnsXY', references: [ ...uniqueIndexPatternsIds.map((patternId) => ({ diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx index f6a41fb6bef67..2bd79116dc45b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx @@ -63,7 +63,6 @@ describe('ExploratoryView', () => { it('shows/hides the chart', async () => { render(); - expect(screen.queryByText('Refresh')).toBeInTheDocument(); const toggleButton = await screen.findByText('Hide chart'); expect(toggleButton).toBeInTheDocument(); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx index 905f6f20a2e6b..ac4e094e5abbc 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx @@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n'; import React, { useEffect, useRef, useState } from 'react'; import styled from 'styled-components'; import { - EuiButton, EuiButtonEmpty, EuiResizableContainer, EuiTitle, @@ -26,7 +25,6 @@ import { useAppDataViewContext } from './hooks/use_app_data_view'; import { SeriesViews } from './views/series_views'; import { LensEmbeddable } from './lens_embeddable'; import { EmptyView } from './components/empty_view'; -import { ChartTimeRange, LastUpdated } from './header/last_updated'; import { useExpViewTimeRange } from './hooks/use_time_range'; import { ExpViewActionMenu } from './components/action_menu'; import { useExploratoryView } from './contexts/exploratory_view_config'; @@ -49,15 +47,14 @@ export function ExploratoryView({ const { isEditMode } = useExploratoryView(); - const [chartTimeRangeContext, setChartTimeRangeContext] = useState(); - const [lensAttributes, setLensAttributes] = useState( null ); const { loadDataView, loading } = useAppDataViewContext(); - const { firstSeries, allSeries, lastRefresh, reportType, setLastRefresh } = useSeriesStorage(); + const { firstSeries, allSeries, lastRefresh, reportType, setChartTimeRangeContext } = + useSeriesStorage(); const lensAttributesT = useLensAttributes(); const timeRange = useExpViewTimeRange(); @@ -115,7 +112,7 @@ export function ExploratoryView({ return ( <> - + - {hiddenPanel === 'chartPanel' ? null : ( - <> - - - - - setLastRefresh(Date.now())} - size="s" - > - {REFRESH_LABEL} - - - - )} .euiPanel { padding-bottom: 0; + padding-top: 0; + } + .expExpressionRenderer__expression { + padding-bottom: 0 !important; + padding-top: 0 !important; } } `; @@ -218,6 +204,10 @@ const ShowPreview = styled(EuiButtonEmpty)` bottom: 34px; `; +const PREVIEW_LABEL = i18n.translate('xpack.observability.overview.exploratoryView.preview', { + defaultMessage: 'Preview', +}); + const HIDE_CHART_LABEL = i18n.translate('xpack.observability.overview.exploratoryView.hideChart', { defaultMessage: 'Hide chart', }); @@ -226,14 +216,6 @@ const SHOW_CHART_LABEL = i18n.translate('xpack.observability.overview.explorator defaultMessage: 'Show chart', }); -const PREVIEW_LABEL = i18n.translate('xpack.observability.overview.exploratoryView.preview', { - defaultMessage: 'Preview', -}); - -const REFRESH_LABEL = i18n.translate('xpack.observability.overview.exploratoryView.refresh', { - defaultMessage: 'Refresh', -}); - const LENS_NOT_AVAILABLE = i18n.translate( 'xpack.observability.overview.exploratoryView.lensDisabled', { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/refresh_button.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/refresh_button.tsx new file mode 100644 index 0000000000000..eb31669453dbb --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/refresh_button.tsx @@ -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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { LastUpdated } from './last_updated'; +import { useSeriesStorage } from '../hooks/use_series_storage'; + +export function RefreshButton() { + const { setLastRefresh, chartTimeRangeContext } = useSeriesStorage(); + + return ( + + + + + + setLastRefresh(Date.now())}> + {REFRESH_LABEL} + + + + ); +} + +export const REFRESH_LABEL = i18n.translate( + 'xpack.observability.overview.exploratoryView.refresh', + { + defaultMessage: 'Refresh', + } +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts index 8d47d42e2007e..0e4620f1d520b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts @@ -121,7 +121,7 @@ export const useLensAttributes = (): TypedLensByValueInput['attributes'] | null const lensAttributes = new LensAttributes(layerConfigs); - return lensAttributes.getJSON(); + return lensAttributes.getJSON(lastRefresh); // we also want to check the state on allSeries changes // eslint-disable-next-line react-hooks/exhaustive-deps }, [dataViews, reportType, storage, theme, lastRefresh, allSeries]); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx index 24d5fc1d20615..8da1a4591a73d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx @@ -8,6 +8,7 @@ import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'; import { IKbnUrlStateStorage, ISessionStorageStateStorage } from '@kbn/kibana-utils-plugin/public'; import { OperationType, SeriesType } from '@kbn/lens-plugin/public'; +import { ChartTimeRange } from '../header/last_updated'; import { useUiTracker } from '../../../../hooks/use_track_metric'; import type { AppDataType, @@ -32,6 +33,8 @@ export interface SeriesContextValue { setReportType: (reportType: ReportViewType) => void; storage: IKbnUrlStateStorage | ISessionStorageStateStorage; reportType: ReportViewType; + chartTimeRangeContext?: ChartTimeRange; + setChartTimeRangeContext: React.Dispatch>; } export const UrlStorageContext = createContext({} as SeriesContextValue); @@ -56,6 +59,8 @@ export function UrlStorageContextProvider({ const [lastRefresh, setLastRefresh] = useState(() => Date.now()); + const [chartTimeRangeContext, setChartTimeRangeContext] = useState(); + const [reportType, setReportType] = useState( () => ((storage as IKbnUrlStateStorage).get(reportTypeKey) ?? '') as ReportViewType ); @@ -135,6 +140,8 @@ export function UrlStorageContextProvider({ setLastRefresh, setReportType, reportType, + chartTimeRangeContext, + setChartTimeRangeContext, firstSeries: firstSeries!, }; return {children}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx index f9a58a8ff7433..82713f152aa4a 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx @@ -22,6 +22,7 @@ import { DataViewContextProvider } from './hooks/use_app_data_view'; import { UrlStorageContextProvider } from './hooks/use_series_storage'; import { useTrackPageview } from '../../..'; import { usePluginContext } from '../../../hooks/use_plugin_context'; +import { RefreshButton } from './header/refresh_button'; const PAGE_TITLE = i18n.translate('xpack.observability.expView.heading.label', { defaultMessage: 'Explore data', @@ -72,13 +73,18 @@ export function ExploratoryViewPage({ }); return ( - - - + + ], + }} + > + - - - + + + ); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx index cd1fd4cc014bc..3f7583f874bee 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx @@ -46,7 +46,7 @@ export function LensEmbeddable(props: Props) { (isLoading) => { const timeLoaded = Date.now(); - setChartTimeRangeContext({ + setChartTimeRangeContext?.({ lastUpdated: timeLoaded, to: parseRelativeDate(timeRange?.to || '')?.valueOf(), from: parseRelativeDate(timeRange?.from || '')?.valueOf(), diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx index 23c635be3ba81..fe7fe29ee4637 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx @@ -319,6 +319,7 @@ function mockSeriesStorageContext({ firstSeries: mockDataSeries[0], allSeries: mockDataSeries, setReportType: jest.fn(), + setChartTimeRangeContext: jest.fn(), storage: { get: jest .fn() From eb043f77028be5f63b822b2412e6b401fa4d3fa1 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Tue, 26 Apr 2022 11:04:34 +0100 Subject: [PATCH 02/27] [Connectors] moves the connector deprecation check to the server side (#130541) This PR fixes a UI glitch where preconfigured connectors couldn't show a deprecation warning on SN connectors as the check on the client side couldn't support them. --- .../actions-and-connectors/create.asciidoc | 1 + docs/api/actions-and-connectors/get.asciidoc | 1 + .../actions-and-connectors/get_all.asciidoc | 2 + .../legacy/create.asciidoc | 1 + .../legacy/get.asciidoc | 1 + .../legacy/get_all.asciidoc | 4 +- .../legacy/update.asciidoc | 1 + .../actions-and-connectors/update.asciidoc | 1 + .../cases/cases-api-find-connectors.asciidoc | 1 + .../testing-connectors.asciidoc | 1 + .../server/action_type_registry.test.ts | 1 + .../actions/server/actions_client.test.ts | 20 +++++++ .../plugins/actions/server/actions_client.ts | 18 +++++- .../server/create_execute_function.test.ts | 3 + .../server/lib/action_executor.test.ts | 9 +++ .../server/lib/is_conector_deprecated.test.ts | 51 ++++++++++++++++ .../server/lib/is_conector_deprecated.ts | 29 +++++++++ x-pack/plugins/actions/server/plugin.ts | 10 +++- .../alert_history_es_index.ts | 1 + .../actions/server/routes/create.test.ts | 11 +++- .../plugins/actions/server/routes/create.ts | 2 + .../plugins/actions/server/routes/get.test.ts | 5 ++ x-pack/plugins/actions/server/routes/get.ts | 2 + .../plugins/actions/server/routes/get_all.ts | 10 +++- .../server/routes/legacy/create.test.ts | 3 + .../actions/server/routes/legacy/get.test.ts | 4 ++ .../server/routes/legacy/update.test.ts | 3 + .../actions/server/routes/update.test.ts | 4 ++ .../plugins/actions/server/routes/update.ts | 2 + .../action_task_params_migrations.test.ts | 1 + x-pack/plugins/actions/server/types.ts | 1 + .../server/usage/actions_telemetry.test.ts | 5 ++ .../server/rules_client/tests/create.test.ts | 7 +++ .../alerting/server/rules_client/tests/lib.ts | 3 + .../server/rules_client/tests/update.test.ts | 12 ++++ .../server/client/configure/client.test.ts | 4 ++ .../routes/__mocks__/request_responses.ts | 2 + .../routes/rules/utils.test.ts | 11 ++++ .../synthetics/public/state/api/alerts.ts | 2 + .../builtin_action_types/email/email.test.tsx | 6 ++ .../es_index/es_index_params.test.tsx | 2 + .../builtin_action_types/jira/jira.test.tsx | 1 + .../jira/jira_connectors.test.tsx | 5 ++ .../jira/jira_params.test.tsx | 1 + .../resilient/resilient.test.tsx | 1 + .../resilient/resilient_connectors.test.tsx | 5 ++ .../resilient/resilient_params.test.tsx | 1 + .../server_log/server_log.test.tsx | 1 + .../servicenow/helpers.test.ts | 15 +++-- .../servicenow/helpers.ts | 9 +-- .../servicenow/servicenow.test.tsx | 1 + .../servicenow/servicenow_connectors.test.tsx | 4 ++ .../servicenow/servicenow_connectors.tsx | 3 +- .../servicenow_itom_params.test.tsx | 1 + .../servicenow_itsm_params.test.tsx | 1 + .../servicenow/servicenow_itsm_params.tsx | 3 +- .../servicenow/servicenow_sir_params.test.tsx | 1 + .../servicenow/servicenow_sir_params.tsx | 3 +- .../servicenow/update_connector.test.tsx | 1 + .../servicenow/use_choices.test.tsx | 1 + .../servicenow/use_get_app_info.test.tsx | 1 + .../servicenow/use_get_choices.test.tsx | 1 + .../swimlane/swimlane_params.test.tsx | 1 + .../swimlane/use_get_application.test.tsx | 1 + .../webhook/webhook.test.tsx | 2 + .../webhook/webhook_connectors.test.tsx | 4 ++ .../xmatters/xmatters.test.tsx | 6 ++ .../xmatters/xmatters_connectors.test.tsx | 7 +++ .../lib/action_connector_api/connectors.ts | 2 + .../lib/action_connector_api/create.test.ts | 4 +- .../lib/action_connector_api/create.ts | 5 +- .../lib/action_connector_api/update.test.ts | 2 + .../lib/action_connector_api/update.ts | 2 + .../lib/check_action_type_enabled.test.tsx | 2 + .../application/lib/value_validators.test.ts | 3 + .../action_connector_form.test.tsx | 1 + .../action_form.test.tsx | 7 +++ .../action_type_form.test.tsx | 3 + .../connector_edit_flyout.test.tsx | 2 + .../connector_reducer.test.ts | 1 + .../connectors_selection.test.tsx | 1 + .../actions_connectors_list.test.tsx | 7 +++ .../components/actions_connectors_list.tsx | 5 +- .../components/rule_details.test.tsx | 2 + .../common/connectors_seleciton.test.tsx | 59 ------------------- .../public/common/connectors_selection.tsx | 43 -------------- .../triggers_actions_ui/public/types.ts | 1 + .../alerting_api_integration/common/config.ts | 12 ++++ .../actions/builtin_action_types/email.ts | 8 +++ .../actions/builtin_action_types/es_index.ts | 4 ++ .../actions/builtin_action_types/jira.ts | 2 + .../actions/builtin_action_types/pagerduty.ts | 2 + .../actions/builtin_action_types/resilient.ts | 2 + .../builtin_action_types/server_log.ts | 2 + .../builtin_action_types/servicenow_itom.ts | 2 + .../builtin_action_types/servicenow_itsm.ts | 2 + .../builtin_action_types/servicenow_sir.ts | 2 + .../actions/builtin_action_types/slack.ts | 2 + .../actions/builtin_action_types/swimlane.ts | 2 + .../actions/builtin_action_types/webhook.ts | 4 ++ .../actions/builtin_action_types/xmatters.ts | 2 + .../tests/actions/create.ts | 1 + .../security_and_spaces/tests/actions/get.ts | 2 + .../tests/actions/get_all.ts | 38 ++++++++++++ .../tests/actions/update.ts | 1 + .../tests/telemetry/actions_telemetry.ts | 2 +- .../actions/builtin_action_types/es_index.ts | 4 ++ .../spaces_only/tests/actions/create.ts | 2 + .../spaces_only/tests/actions/get.ts | 16 +++++ .../spaces_only/tests/actions/get_all.ts | 41 +++++++++++++ .../tests/actions/type_not_enabled.ts | 2 + .../spaces_only/tests/actions/update.ts | 2 + .../tests/trial/configure/get_connectors.ts | 4 ++ .../tests/trial/configure/get_connectors.ts | 4 ++ 114 files changed, 526 insertions(+), 132 deletions(-) create mode 100644 x-pack/plugins/actions/server/lib/is_conector_deprecated.test.ts create mode 100644 x-pack/plugins/actions/server/lib/is_conector_deprecated.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/common/connectors_seleciton.test.tsx diff --git a/docs/api/actions-and-connectors/create.asciidoc b/docs/api/actions-and-connectors/create.asciidoc index c9ea31c98cf19..401f4c5372688 100644 --- a/docs/api/actions-and-connectors/create.asciidoc +++ b/docs/api/actions-and-connectors/create.asciidoc @@ -74,6 +74,7 @@ The API returns the following: "executionTimeField": null }, "is_preconfigured": false, + "is_deprecated": false, "is_missing_secrets": false } -------------------------------------------------- diff --git a/docs/api/actions-and-connectors/get.asciidoc b/docs/api/actions-and-connectors/get.asciidoc index 95336e7f55d30..bc6b5fa8f364c 100644 --- a/docs/api/actions-and-connectors/get.asciidoc +++ b/docs/api/actions-and-connectors/get.asciidoc @@ -51,6 +51,7 @@ The API returns the following: "executionTimeField": null }, "is_preconfigured": false, + "is_deprecated": false, "is_missing_secrets": false } -------------------------------------------------- diff --git a/docs/api/actions-and-connectors/get_all.asciidoc b/docs/api/actions-and-connectors/get_all.asciidoc index 943c7d123775f..26bb7247e2ce1 100644 --- a/docs/api/actions-and-connectors/get_all.asciidoc +++ b/docs/api/actions-and-connectors/get_all.asciidoc @@ -44,6 +44,7 @@ The API returns the following: "connector_type_id": ".email", "name": "email: preconfigured-mail-connector", "is_preconfigured": true, + "is_deprecated": false, "referenced_by_count": 0 <1> }, { @@ -56,6 +57,7 @@ The API returns the following: "executionTimeField": null }, "is_preconfigured": false, + "is_deprecated": false, "is_missing_secrets": false, "referenced_by_count": 3 } diff --git a/docs/api/actions-and-connectors/legacy/create.asciidoc b/docs/api/actions-and-connectors/legacy/create.asciidoc index e0d531a2befb9..5b5b71b1d6daa 100644 --- a/docs/api/actions-and-connectors/legacy/create.asciidoc +++ b/docs/api/actions-and-connectors/legacy/create.asciidoc @@ -76,6 +76,7 @@ The API returns the following: "executionTimeField": null }, "isPreconfigured": false, + "isDeprecated": false, "isMissingSecrets": false } -------------------------------------------------- diff --git a/docs/api/actions-and-connectors/legacy/get.asciidoc b/docs/api/actions-and-connectors/legacy/get.asciidoc index dab462e3ae4fb..d6554d606a286 100644 --- a/docs/api/actions-and-connectors/legacy/get.asciidoc +++ b/docs/api/actions-and-connectors/legacy/get.asciidoc @@ -53,6 +53,7 @@ The API returns the following: "executionTimeField": null }, "isPreconfigured": false, + "isDeprecated": false, "isMissingSecrets": false } -------------------------------------------------- diff --git a/docs/api/actions-and-connectors/legacy/get_all.asciidoc b/docs/api/actions-and-connectors/legacy/get_all.asciidoc index 2180720ce6542..9e52b6883ec11 100644 --- a/docs/api/actions-and-connectors/legacy/get_all.asciidoc +++ b/docs/api/actions-and-connectors/legacy/get_all.asciidoc @@ -45,7 +45,8 @@ The API returns the following: "id": "preconfigured-mail-action", "actionTypeId": ".email", "name": "email: preconfigured-mail-action", - "isPreconfigured": true + "isPreconfigured": true, + "isDeprecated": false }, { "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", @@ -57,6 +58,7 @@ The API returns the following: "executionTimeField": null }, "isPreconfigured": false, + "isDeprecated": false, "isMissingSecrets": false } ] diff --git a/docs/api/actions-and-connectors/legacy/update.asciidoc b/docs/api/actions-and-connectors/legacy/update.asciidoc index 5202f8124e6a8..1e6a4e71c8b81 100644 --- a/docs/api/actions-and-connectors/legacy/update.asciidoc +++ b/docs/api/actions-and-connectors/legacy/update.asciidoc @@ -71,6 +71,7 @@ The API returns the following: "executionTimeField": null }, "isPreconfigured": false, + "isDeprecated": false, "isMissingSecrets": false } -------------------------------------------------- diff --git a/docs/api/actions-and-connectors/update.asciidoc b/docs/api/actions-and-connectors/update.asciidoc index 0b7dcc898a122..7ccb10714f474 100644 --- a/docs/api/actions-and-connectors/update.asciidoc +++ b/docs/api/actions-and-connectors/update.asciidoc @@ -69,6 +69,7 @@ The API returns the following: "executionTimeField": null }, "is_preconfigured": false, + "is_deprecated": false, "is_missing_secrets": false } -------------------------------------------------- diff --git a/docs/api/cases/cases-api-find-connectors.asciidoc b/docs/api/cases/cases-api-find-connectors.asciidoc index 0a1554cfde4f8..b12be5621e991 100644 --- a/docs/api/cases/cases-api-find-connectors.asciidoc +++ b/docs/api/cases/cases-api-find-connectors.asciidoc @@ -55,6 +55,7 @@ The API returns a JSON object describing the connectors and their settings: "projectKey":"ES" }, "isPreconfigured":false, + "isDeprecated": false, "referencedByCount":0 }] -------------------------------------------------- \ No newline at end of file diff --git a/docs/user/alerting/troubleshooting/testing-connectors.asciidoc b/docs/user/alerting/troubleshooting/testing-connectors.asciidoc index f90a7ebc35614..64ba106655321 100644 --- a/docs/user/alerting/troubleshooting/testing-connectors.asciidoc +++ b/docs/user/alerting/troubleshooting/testing-connectors.asciidoc @@ -39,6 +39,7 @@ $ kbn-action ls "service": null }, "isPreconfigured": false, + "isDeprecated": false, "referencedByCount": 0 } ] diff --git a/x-pack/plugins/actions/server/action_type_registry.test.ts b/x-pack/plugins/actions/server/action_type_registry.test.ts index 760aafbefbba8..ec88ba5fb12a7 100644 --- a/x-pack/plugins/actions/server/action_type_registry.test.ts +++ b/x-pack/plugins/actions/server/action_type_registry.test.ts @@ -42,6 +42,7 @@ beforeEach(() => { name: 'Slack #xyz', secrets: {}, isPreconfigured: true, + isDeprecated: false, }, ], }; diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts index 0b20e7078dd3b..afee13b8c9bca 100644 --- a/x-pack/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -311,6 +311,7 @@ describe('create()', () => { expect(result).toEqual({ id: '1', isPreconfigured: false, + isDeprecated: false, name: 'my name', actionTypeId: 'my-action-type', isMissingSecrets: false, @@ -443,6 +444,7 @@ describe('create()', () => { expect(result).toEqual({ id: '1', isPreconfigured: false, + isDeprecated: false, name: 'my name', actionTypeId: 'my-action-type', isMissingSecrets: false, @@ -632,6 +634,7 @@ describe('get()', () => { test: 'test1', }, isPreconfigured: true, + isDeprecated: false, name: 'test', config: { foo: 'bar', @@ -689,6 +692,7 @@ describe('get()', () => { test: 'test1', }, isPreconfigured: true, + isDeprecated: false, name: 'test', config: { foo: 'bar', @@ -778,6 +782,7 @@ describe('get()', () => { expect(result).toEqual({ id: '1', isPreconfigured: false, + isDeprecated: false, }); expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledTimes(1); expect(unsecuredSavedObjectsClient.get.mock.calls[0]).toMatchInlineSnapshot(` @@ -807,6 +812,7 @@ describe('get()', () => { test: 'test1', }, isPreconfigured: true, + isDeprecated: false, name: 'test', config: { foo: 'bar', @@ -821,6 +827,7 @@ describe('get()', () => { id: 'testPreconfigured', actionTypeId: '.slack', isPreconfigured: true, + isDeprecated: false, name: 'test', }); expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled(); @@ -876,6 +883,7 @@ describe('getAll()', () => { actionTypeId: '.slack', secrets: {}, isPreconfigured: true, + isDeprecated: false, name: 'test', config: { foo: 'bar', @@ -1015,6 +1023,7 @@ describe('getAll()', () => { actionTypeId: '.slack', secrets: {}, isPreconfigured: true, + isDeprecated: false, name: 'test', config: { foo: 'bar', @@ -1028,6 +1037,7 @@ describe('getAll()', () => { { id: '1', isPreconfigured: false, + isDeprecated: false, name: 'test', config: { foo: 'bar', @@ -1039,6 +1049,7 @@ describe('getAll()', () => { id: 'testPreconfigured', actionTypeId: '.slack', isPreconfigured: true, + isDeprecated: false, name: 'test', referencedByCount: 2, }, @@ -1092,6 +1103,7 @@ describe('getBulk()', () => { actionTypeId: '.slack', secrets: {}, isPreconfigured: true, + isDeprecated: false, name: 'test', config: { foo: 'bar', @@ -1225,6 +1237,7 @@ describe('getBulk()', () => { actionTypeId: '.slack', secrets: {}, isPreconfigured: true, + isDeprecated: false, name: 'test', config: { foo: 'bar', @@ -1242,6 +1255,7 @@ describe('getBulk()', () => { }, id: 'testPreconfigured', isPreconfigured: true, + isDeprecated: false, name: 'test', secrets: {}, }, @@ -1253,6 +1267,7 @@ describe('getBulk()', () => { id: '1', isMissingSecrets: false, isPreconfigured: false, + isDeprecated: false, name: 'test', }, ]); @@ -1458,6 +1473,7 @@ describe('update()', () => { expect(result).toEqual({ id: 'my-action', isPreconfigured: false, + isDeprecated: false, actionTypeId: 'my-action-type', isMissingSecrets: false, name: 'my name', @@ -1529,6 +1545,7 @@ describe('update()', () => { expect(result).toEqual({ id: 'my-action', isPreconfigured: false, + isDeprecated: false, actionTypeId: 'my-action-type', isMissingSecrets: true, name: 'my name', @@ -1668,6 +1685,7 @@ describe('update()', () => { expect(result).toEqual({ id: 'my-action', isPreconfigured: false, + isDeprecated: false, actionTypeId: 'my-action-type', isMissingSecrets: true, name: 'my name', @@ -2000,6 +2018,7 @@ describe('isPreconfigured()', () => { test: 'test1', }, isPreconfigured: true, + isDeprecated: false, name: 'test', config: { foo: 'bar', @@ -2035,6 +2054,7 @@ describe('isPreconfigured()', () => { test: 'test1', }, isPreconfigured: true, + isDeprecated: false, name: 'test', config: { foo: 'bar', diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index cafbc6e6bd999..dacf6de36bd37 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -45,6 +45,7 @@ import { } from './authorization/get_authorization_mode_by_source'; import { connectorAuditEvent, ConnectorAuditAction } from './lib/audit_events'; import { trackLegacyRBACExemption } from './lib/track_legacy_rbac_exemption'; +import { isConnectorDeprecated } from './lib/is_conector_deprecated'; // We are assuming there won't be many actions. This is why we will load // all the actions in advance and assume the total count to not go over 10000. @@ -187,6 +188,7 @@ export class ActionsClient { name: result.attributes.name, config: result.attributes.config, isPreconfigured: false, + isDeprecated: isConnectorDeprecated(result.attributes), }; } @@ -270,6 +272,7 @@ export class ActionsClient { name: result.attributes.name as string, config: result.attributes.config as Record, isPreconfigured: false, + isDeprecated: isConnectorDeprecated(result.attributes), }; } @@ -306,6 +309,7 @@ export class ActionsClient { actionTypeId: preconfiguredActionsList.actionTypeId, name: preconfiguredActionsList.name, isPreconfigured: true, + isDeprecated: isConnectorDeprecated(preconfiguredActionsList), }; } @@ -325,6 +329,7 @@ export class ActionsClient { name: result.attributes.name, config: result.attributes.config, isPreconfigured: false, + isDeprecated: isConnectorDeprecated(result.attributes), }; } @@ -349,7 +354,9 @@ export class ActionsClient { perPage: MAX_ACTIONS_RETURNED, type: 'action', }) - ).saved_objects.map(actionFromSavedObject); + ).saved_objects.map((rawAction) => + actionFromSavedObject(rawAction, isConnectorDeprecated(rawAction.attributes)) + ); savedObjectsActions.forEach(({ id }) => this.auditLogger?.log( @@ -367,6 +374,7 @@ export class ActionsClient { actionTypeId: preconfiguredAction.actionTypeId, name: preconfiguredAction.name, isPreconfigured: true, + isDeprecated: isConnectorDeprecated(preconfiguredAction), })), ].sort((a, b) => a.name.localeCompare(b.name)); return await injectExtraFindData( @@ -435,7 +443,7 @@ export class ActionsClient { `Failed to load action ${action.id} (${action.error.statusCode}): ${action.error.message}` ); } - actionResults.push(actionFromSavedObject(action)); + actionResults.push(actionFromSavedObject(action, isConnectorDeprecated(action.attributes))); } return actionResults; } @@ -559,11 +567,15 @@ export class ActionsClient { } } -function actionFromSavedObject(savedObject: SavedObject): ActionResult { +function actionFromSavedObject( + savedObject: SavedObject, + isDeprecated: boolean +): ActionResult { return { id: savedObject.id, ...savedObject.attributes, isPreconfigured: false, + isDeprecated, }; } diff --git a/x-pack/plugins/actions/server/create_execute_function.test.ts b/x-pack/plugins/actions/server/create_execute_function.test.ts index a503b1adeb40c..f81dcf1ba1404 100644 --- a/x-pack/plugins/actions/server/create_execute_function.test.ts +++ b/x-pack/plugins/actions/server/create_execute_function.test.ts @@ -247,6 +247,7 @@ describe('execute()', () => { actionTypeId: 'mock-action-preconfigured', config: {}, isPreconfigured: true, + isDeprecated: false, name: 'x', secrets: {}, }, @@ -324,6 +325,7 @@ describe('execute()', () => { actionTypeId: 'mock-action-preconfigured', config: {}, isPreconfigured: true, + isDeprecated: false, name: 'x', secrets: {}, }, @@ -506,6 +508,7 @@ describe('execute()', () => { name: 'Slack #xyz', secrets: {}, isPreconfigured: true, + isDeprecated: false, }, ], }); diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts index 881877d74bb9d..093236c939aa1 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts @@ -80,6 +80,7 @@ test('successfully executes', async () => { name: actionSavedObject.id, ...pick(actionSavedObject.attributes, 'actionTypeId', 'config'), isPreconfigured: false, + isDeprecated: false, }; actionsClient.get.mockResolvedValueOnce(actionResult); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); @@ -203,6 +204,7 @@ test('successfully executes as a task', async () => { name: actionSavedObject.id, ...pick(actionSavedObject.attributes, 'actionTypeId', 'config'), isPreconfigured: false, + isDeprecated: false, }; actionsClient.get.mockResolvedValueOnce(actionResult); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); @@ -246,6 +248,7 @@ test('provides empty config when config and / or secrets is empty', async () => name: actionSavedObject.id, actionTypeId: actionSavedObject.attributes.actionTypeId, isPreconfigured: false, + isDeprecated: false, }; actionsClient.get.mockResolvedValueOnce(actionResult); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); @@ -282,6 +285,7 @@ test('throws an error when config is invalid', async () => { name: actionSavedObject.id, actionTypeId: actionSavedObject.attributes.actionTypeId, isPreconfigured: false, + isDeprecated: false, }; actionsClient.get.mockResolvedValueOnce(actionResult); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); @@ -321,6 +325,7 @@ test('throws an error when connector is invalid', async () => { name: actionSavedObject.id, actionTypeId: actionSavedObject.attributes.actionTypeId, isPreconfigured: false, + isDeprecated: false, }; actionsClient.get.mockResolvedValueOnce(actionResult); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); @@ -360,6 +365,7 @@ test('throws an error when params is invalid', async () => { name: actionSavedObject.id, actionTypeId: actionSavedObject.attributes.actionTypeId, isPreconfigured: false, + isDeprecated: false, }; actionsClient.get.mockResolvedValueOnce(actionResult); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); @@ -401,6 +407,7 @@ test('throws an error if actionType is not enabled', async () => { name: actionSavedObject.id, actionTypeId: actionSavedObject.attributes.actionTypeId, isPreconfigured: false, + isDeprecated: false, }; actionsClient.get.mockResolvedValueOnce(actionResult); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); @@ -441,6 +448,7 @@ test('should not throws an error if actionType is preconfigured', async () => { name: actionSavedObject.id, ...pick(actionSavedObject.attributes, 'actionTypeId', 'config', 'secrets'), isPreconfigured: false, + isDeprecated: false, }; actionsClient.get.mockResolvedValueOnce(actionResult); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); @@ -753,6 +761,7 @@ function setupActionExecutorMock() { name: actionSavedObject.name, ...pick(actionSavedObject.attributes, 'actionTypeId', 'config'), isPreconfigured: false, + isDeprecated: false, }; actionsClient.get.mockResolvedValueOnce(actionResult); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); diff --git a/x-pack/plugins/actions/server/lib/is_conector_deprecated.test.ts b/x-pack/plugins/actions/server/lib/is_conector_deprecated.test.ts new file mode 100644 index 0000000000000..f5ace7e055254 --- /dev/null +++ b/x-pack/plugins/actions/server/lib/is_conector_deprecated.test.ts @@ -0,0 +1,51 @@ +/* + * 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 { isConnectorDeprecated } from './is_conector_deprecated'; + +describe('isConnectorDeprecated', () => { + const connector = { + id: 'test', + actionTypeId: '.webhook', + name: 'Test', + config: { apiUrl: 'http://example.com', usesTableApi: false }, + secrets: { username: 'test', password: 'test' }, + isPreconfigured: false as const, + }; + + it('returns false if the connector is not ITSM or SecOps', () => { + expect(isConnectorDeprecated(connector)).toBe(false); + }); + + it('returns false if the connector is .servicenow and the usesTableApi=false', () => { + expect(isConnectorDeprecated({ ...connector, actionTypeId: '.servicenow' })).toBe(false); + }); + + it('returns false if the connector is .servicenow-sir and the usesTableApi=false', () => { + expect(isConnectorDeprecated({ ...connector, actionTypeId: '.servicenow-sir' })).toBe(false); + }); + + it('returns true if the connector is .servicenow and the usesTableApi=true', () => { + expect( + isConnectorDeprecated({ + ...connector, + actionTypeId: '.servicenow', + config: { ...connector.config, usesTableApi: true }, + }) + ).toBe(true); + }); + + it('returns true if the connector is .servicenow-sir and the usesTableApi=true', () => { + expect( + isConnectorDeprecated({ + ...connector, + actionTypeId: '.servicenow-sir', + config: { ...connector.config, usesTableApi: true }, + }) + ).toBe(true); + }); +}); diff --git a/x-pack/plugins/actions/server/lib/is_conector_deprecated.ts b/x-pack/plugins/actions/server/lib/is_conector_deprecated.ts new file mode 100644 index 0000000000000..210631cb532f6 --- /dev/null +++ b/x-pack/plugins/actions/server/lib/is_conector_deprecated.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PreConfiguredAction, RawAction } from '../types'; + +export type ConnectorWithOptionalDeprecation = Omit & + Pick, 'isDeprecated'>; + +export const isConnectorDeprecated = ( + connector: RawAction | ConnectorWithOptionalDeprecation +): boolean => { + if (connector.actionTypeId === '.servicenow' || connector.actionTypeId === '.servicenow-sir') { + /** + * Connectors after the Elastic ServiceNow application use the + * Import Set API (https://developer.servicenow.com/dev.do#!/reference/api/rome/rest/c_ImportSetAPI) + * A ServiceNow connector is considered deprecated if it uses the Table API. + * + * All other connectors do not have the usesTableApi config property + * so the function will always return false for them. + */ + return !!connector.config?.usesTableApi; + } + + return false; +}; diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index ba8757b70b522..3cdf28a8d80f3 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -93,6 +93,10 @@ import { ACTIONS_FEATURE_ID, AlertHistoryEsIndexConnectorId } from '../common'; import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER } from './constants/event_log'; import { ConnectorTokenClient } from './builtin_action_types/lib/connector_token_client'; import { InMemoryMetrics, registerClusterCollector, registerNodeCollector } from './monitoring'; +import { + isConnectorDeprecated, + ConnectorWithOptionalDeprecation, +} from './lib/is_conector_deprecated'; export interface PluginSetupContract { registerType< @@ -221,10 +225,14 @@ export class ActionsPlugin implements Plugin { actionTypeId: EsIndexActionTypeId, id: AlertHistoryEsIndexConnectorId, isPreconfigured: true, + isDeprecated: false, config: { index: AlertHistoryDefaultIndexName, }, diff --git a/x-pack/plugins/actions/server/routes/create.test.ts b/x-pack/plugins/actions/server/routes/create.test.ts index 5089b7f96286f..7ddffd72fe8db 100644 --- a/x-pack/plugins/actions/server/routes/create.test.ts +++ b/x-pack/plugins/actions/server/routes/create.test.ts @@ -39,13 +39,20 @@ describe('createActionRoute', () => { actionTypeId: 'abc', config: { foo: true }, isPreconfigured: false, + isDeprecated: false, isMissingSecrets: false, }; const createApiResult = { - ...omit(createResult, ['actionTypeId', 'isPreconfigured', 'isMissingSecrets']), + ...omit(createResult, [ + 'actionTypeId', + 'isPreconfigured', + 'isDeprecated', + 'isMissingSecrets', + ]), connector_type_id: createResult.actionTypeId, is_preconfigured: createResult.isPreconfigured, + is_deprecated: createResult.isDeprecated, is_missing_secrets: createResult.isMissingSecrets, }; @@ -104,6 +111,7 @@ describe('createActionRoute', () => { isMissingSecrets: false, config: { foo: true }, isPreconfigured: false, + isDeprecated: false, }); const [context, req, res] = mockHandlerArguments( @@ -143,6 +151,7 @@ describe('createActionRoute', () => { config: { foo: true }, isMissingSecrets: false, isPreconfigured: false, + isDeprecated: false, }); const [context, req, res] = mockHandlerArguments( diff --git a/x-pack/plugins/actions/server/routes/create.ts b/x-pack/plugins/actions/server/routes/create.ts index 0dd050c55328e..1f14b030f0676 100644 --- a/x-pack/plugins/actions/server/routes/create.ts +++ b/x-pack/plugins/actions/server/routes/create.ts @@ -29,12 +29,14 @@ const rewriteBodyReq: RewriteRequestCase = ({ const rewriteBodyRes: RewriteResponseCase = ({ actionTypeId, isPreconfigured, + isDeprecated, isMissingSecrets, ...res }) => ({ ...res, connector_type_id: actionTypeId, is_preconfigured: isPreconfigured, + is_deprecated: isDeprecated, is_missing_secrets: isMissingSecrets, }); diff --git a/x-pack/plugins/actions/server/routes/get.test.ts b/x-pack/plugins/actions/server/routes/get.test.ts index bf51ccaa4b0e5..7f9736ad7dfb2 100644 --- a/x-pack/plugins/actions/server/routes/get.test.ts +++ b/x-pack/plugins/actions/server/routes/get.test.ts @@ -38,6 +38,7 @@ describe('getActionRoute', () => { name: 'action name', config: {}, isPreconfigured: false, + isDeprecated: false, isMissingSecrets: false, }; @@ -58,6 +59,7 @@ describe('getActionRoute', () => { "config": Object {}, "connector_type_id": "2", "id": "1", + "is_deprecated": false, "is_missing_secrets": false, "is_preconfigured": false, "name": "action name", @@ -75,6 +77,7 @@ describe('getActionRoute', () => { name: 'action name', config: {}, is_preconfigured: false, + is_deprecated: false, is_missing_secrets: false, }, }); @@ -95,6 +98,7 @@ describe('getActionRoute', () => { name: 'action name', config: {}, isPreconfigured: false, + isDeprecated: false, }); const [context, req, res] = mockHandlerArguments( @@ -129,6 +133,7 @@ describe('getActionRoute', () => { name: 'action name', config: {}, isPreconfigured: false, + isDeprecated: false, }); const [context, req, res] = mockHandlerArguments( diff --git a/x-pack/plugins/actions/server/routes/get.ts b/x-pack/plugins/actions/server/routes/get.ts index 352670bd5b170..207ed694f2985 100644 --- a/x-pack/plugins/actions/server/routes/get.ts +++ b/x-pack/plugins/actions/server/routes/get.ts @@ -20,11 +20,13 @@ const rewriteBodyRes: RewriteResponseCase = ({ actionTypeId, isPreconfigured, isMissingSecrets, + isDeprecated, ...res }) => ({ ...res, connector_type_id: actionTypeId, is_preconfigured: isPreconfigured, + is_deprecated: isDeprecated, is_missing_secrets: isMissingSecrets, }); diff --git a/x-pack/plugins/actions/server/routes/get_all.ts b/x-pack/plugins/actions/server/routes/get_all.ts index 3710b60ce5101..61da031db99d4 100644 --- a/x-pack/plugins/actions/server/routes/get_all.ts +++ b/x-pack/plugins/actions/server/routes/get_all.ts @@ -13,10 +13,18 @@ import { verifyAccessAndContext } from './verify_access_and_context'; const rewriteBodyRes: RewriteResponseCase = (results) => { return results.map( - ({ actionTypeId, isPreconfigured, referencedByCount, isMissingSecrets, ...res }) => ({ + ({ + actionTypeId, + isPreconfigured, + isDeprecated, + referencedByCount, + isMissingSecrets, + ...res + }) => ({ ...res, connector_type_id: actionTypeId, is_preconfigured: isPreconfigured, + is_deprecated: isDeprecated, referenced_by_count: referencedByCount, is_missing_secrets: isMissingSecrets, }) diff --git a/x-pack/plugins/actions/server/routes/legacy/create.test.ts b/x-pack/plugins/actions/server/routes/legacy/create.test.ts index a79720c8f21e4..656c24d118d30 100644 --- a/x-pack/plugins/actions/server/routes/legacy/create.test.ts +++ b/x-pack/plugins/actions/server/routes/legacy/create.test.ts @@ -47,6 +47,7 @@ describe('createActionRoute', () => { actionTypeId: 'abc', config: { foo: true }, isPreconfigured: false, + isDeprecated: false, }; const actionsClient = actionsClientMock.create(); @@ -103,6 +104,7 @@ describe('createActionRoute', () => { actionTypeId: 'abc', config: { foo: true }, isPreconfigured: false, + isDeprecated: false, }); const [context, req, res] = mockHandlerArguments({ actionsClient }, {}); @@ -131,6 +133,7 @@ describe('createActionRoute', () => { actionTypeId: 'abc', config: { foo: true }, isPreconfigured: false, + isDeprecated: false, }); const [context, req, res] = mockHandlerArguments({ actionsClient }, {}); diff --git a/x-pack/plugins/actions/server/routes/legacy/get.test.ts b/x-pack/plugins/actions/server/routes/legacy/get.test.ts index df3c3822ab08c..a79185a4ea882 100644 --- a/x-pack/plugins/actions/server/routes/legacy/get.test.ts +++ b/x-pack/plugins/actions/server/routes/legacy/get.test.ts @@ -46,6 +46,7 @@ describe('getActionRoute', () => { name: 'action name', config: {}, isPreconfigured: false, + isDeprecated: false, }; const actionsClient = actionsClientMock.create(); @@ -65,6 +66,7 @@ describe('getActionRoute', () => { "actionTypeId": "2", "config": Object {}, "id": "1", + "isDeprecated": false, "isPreconfigured": false, "name": "action name", }, @@ -94,6 +96,7 @@ describe('getActionRoute', () => { name: 'action name', config: {}, isPreconfigured: false, + isDeprecated: false, }); const [context, req, res] = mockHandlerArguments( @@ -128,6 +131,7 @@ describe('getActionRoute', () => { name: 'action name', config: {}, isPreconfigured: false, + isDeprecated: false, }); const [context, req, res] = mockHandlerArguments( diff --git a/x-pack/plugins/actions/server/routes/legacy/update.test.ts b/x-pack/plugins/actions/server/routes/legacy/update.test.ts index d9c956d3fd35c..49bb0ff2b86e0 100644 --- a/x-pack/plugins/actions/server/routes/legacy/update.test.ts +++ b/x-pack/plugins/actions/server/routes/legacy/update.test.ts @@ -46,6 +46,7 @@ describe('updateActionRoute', () => { name: 'My name', config: { foo: true }, isPreconfigured: false, + isDeprecated: false, }; const actionsClient = actionsClientMock.create(); @@ -103,6 +104,7 @@ describe('updateActionRoute', () => { name: 'My name', config: { foo: true }, isPreconfigured: false, + isDeprecated: false, }; const actionsClient = actionsClientMock.create(); @@ -146,6 +148,7 @@ describe('updateActionRoute', () => { name: 'My name', config: { foo: true }, isPreconfigured: false, + isDeprecated: false, }; const actionsClient = actionsClientMock.create(); diff --git a/x-pack/plugins/actions/server/routes/update.test.ts b/x-pack/plugins/actions/server/routes/update.test.ts index 2856ea6014fd8..8fd11c59c77ce 100644 --- a/x-pack/plugins/actions/server/routes/update.test.ts +++ b/x-pack/plugins/actions/server/routes/update.test.ts @@ -38,6 +38,7 @@ describe('updateActionRoute', () => { name: 'My name', config: { foo: true }, isPreconfigured: false, + isDeprecated: false, }; const actionsClient = actionsClientMock.create(); @@ -65,6 +66,7 @@ describe('updateActionRoute', () => { name: 'My name', config: { foo: true }, is_preconfigured: false, + is_deprecated: false, }, }); @@ -103,6 +105,7 @@ describe('updateActionRoute', () => { name: 'My name', config: { foo: true }, isPreconfigured: false, + isDeprecated: false, }; const actionsClient = actionsClientMock.create(); @@ -146,6 +149,7 @@ describe('updateActionRoute', () => { name: 'My name', config: { foo: true }, isPreconfigured: false, + isDeprecated: false, }; const actionsClient = actionsClientMock.create(); diff --git a/x-pack/plugins/actions/server/routes/update.ts b/x-pack/plugins/actions/server/routes/update.ts index a8892c814fc66..364a583625b4f 100644 --- a/x-pack/plugins/actions/server/routes/update.ts +++ b/x-pack/plugins/actions/server/routes/update.ts @@ -26,11 +26,13 @@ const rewriteBodyRes: RewriteResponseCase = ({ actionTypeId, isPreconfigured, isMissingSecrets, + isDeprecated, ...res }) => ({ ...res, connector_type_id: actionTypeId, is_preconfigured: isPreconfigured, + is_deprecated: isDeprecated, is_missing_secrets: isMissingSecrets, }); diff --git a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts index 399efe023c3fd..c352b5dfa3a21 100644 --- a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts +++ b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.test.ts @@ -26,6 +26,7 @@ const preconfiguredActions = [ name: 'Slack #xyz', secrets: {}, isPreconfigured: true, + isDeprecated: false, }, ]; diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index 9cb4f1aec63e8..491d7ab5be2e4 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -74,6 +74,7 @@ export interface ActionResult { isMissingSecrets: false, name: 'email connector', isPreconfigured: false, + isDeprecated: false, }, ]); taskManager.schedule.mockResolvedValue({ @@ -687,6 +688,7 @@ describe('create()', () => { isMissingSecrets: false, name: 'email connector', isPreconfigured: false, + isDeprecated: false, }, { id: '2', @@ -702,6 +704,7 @@ describe('create()', () => { isMissingSecrets: false, name: 'email connector', isPreconfigured: false, + isDeprecated: false, }, ]); unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ @@ -857,6 +860,7 @@ describe('create()', () => { isMissingSecrets: false, name: 'email connector', isPreconfigured: false, + isDeprecated: false, }, { id: '2', @@ -872,6 +876,7 @@ describe('create()', () => { isMissingSecrets: false, name: 'another email connector', isPreconfigured: false, + isDeprecated: false, }, { id: 'preconfigured', @@ -887,6 +892,7 @@ describe('create()', () => { isMissingSecrets: false, name: 'preconfigured email connector', isPreconfigured: true, + isDeprecated: false, }, ]); actionsClient.isPreconfigured.mockReset(); @@ -2523,6 +2529,7 @@ describe('create()', () => { isMissingSecrets: true, name: 'email connector', isPreconfigured: false, + isDeprecated: false, }, ]); await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( diff --git a/x-pack/plugins/alerting/server/rules_client/tests/lib.ts b/x-pack/plugins/alerting/server/rules_client/tests/lib.ts index 194ca6c8279a7..d7eaf71038988 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/lib.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/lib.ts @@ -57,6 +57,7 @@ export function getBeforeSetup( { id: '1', isPreconfigured: false, + isDeprecated: false, actionTypeId: 'test', name: 'test', config: { @@ -66,6 +67,7 @@ export function getBeforeSetup( { id: '2', isPreconfigured: false, + isDeprecated: false, actionTypeId: 'test2', name: 'test2', config: { @@ -76,6 +78,7 @@ export function getBeforeSetup( id: 'testPreconfigured', actionTypeId: '.slack', isPreconfigured: true, + isDeprecated: false, name: 'test', }, ]); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts index c2005d8a061b5..573ae98ba49f0 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts @@ -122,6 +122,7 @@ describe('update()', () => { isMissingSecrets: false, name: 'email connector', isPreconfigured: false, + isDeprecated: false, }, ]); rulesClientParams.getActionsClient.mockResolvedValue(actionsClient); @@ -160,6 +161,7 @@ describe('update()', () => { isMissingSecrets: false, name: 'email connector', isPreconfigured: false, + isDeprecated: false, }, { id: '2', @@ -175,6 +177,7 @@ describe('update()', () => { isMissingSecrets: false, name: 'email connector', isPreconfigured: false, + isDeprecated: false, }, ]); unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ @@ -435,6 +438,7 @@ describe('update()', () => { isMissingSecrets: false, name: 'email connector', isPreconfigured: false, + isDeprecated: false, }, { id: '2', @@ -450,6 +454,7 @@ describe('update()', () => { isMissingSecrets: false, name: 'another email connector', isPreconfigured: false, + isDeprecated: false, }, { id: 'preconfigured', @@ -465,6 +470,7 @@ describe('update()', () => { isMissingSecrets: false, name: 'preconfigured email connector', isPreconfigured: true, + isDeprecated: false, }, ]); actionsClient.isPreconfigured.mockReset(); @@ -1362,6 +1368,7 @@ describe('update()', () => { isMissingSecrets: false, name: 'email connector', isPreconfigured: false, + isDeprecated: false, }, { id: '2', @@ -1377,6 +1384,7 @@ describe('update()', () => { isMissingSecrets: false, name: 'email connector', isPreconfigured: false, + isDeprecated: false, }, ]); unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ @@ -1749,6 +1757,7 @@ describe('update()', () => { isMissingSecrets: false, name: 'email connector', isPreconfigured: false, + isDeprecated: false, }, { id: '2', @@ -1764,6 +1773,7 @@ describe('update()', () => { isMissingSecrets: true, name: 'another connector', isPreconfigured: false, + isDeprecated: false, }, ]); @@ -1826,6 +1836,7 @@ describe('update()', () => { isMissingSecrets: false, name: 'email connector', isPreconfigured: false, + isDeprecated: false, }, { id: '2', @@ -1841,6 +1852,7 @@ describe('update()', () => { isMissingSecrets: false, name: 'email connector', isPreconfigured: false, + isDeprecated: false, }, ]); unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ diff --git a/x-pack/plugins/cases/server/client/configure/client.test.ts b/x-pack/plugins/cases/server/client/configure/client.test.ts index fa3f2b3f987f1..f96f55a823aac 100644 --- a/x-pack/plugins/cases/server/client/configure/client.test.ts +++ b/x-pack/plugins/cases/server/client/configure/client.test.ts @@ -40,6 +40,7 @@ describe('client', () => { actionTypeId: '.jira', name: '1', isPreconfigured: false, + isDeprecated: false, referencedByCount: 1, }, ]); @@ -57,6 +58,7 @@ describe('client', () => { name: '1', config: {}, isPreconfigured: true, + isDeprecated: false, referencedByCount: 1, }, ]); @@ -84,6 +86,7 @@ describe('client', () => { name: '1', config: {}, isPreconfigured: false, + isDeprecated: false, referencedByCount: 1, }, { @@ -92,6 +95,7 @@ describe('client', () => { name: '2', config: {}, isPreconfigured: false, + isDeprecated: false, referencedByCount: 1, }, ]; 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 1304a06fa7dc1..10607e352000d 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 @@ -379,6 +379,7 @@ export const createActionResult = (): ActionResult => ({ name: '', config: {}, isPreconfigured: false, + isDeprecated: false, }); export const nonRuleAlert = () => ({ @@ -426,6 +427,7 @@ export const updateActionResult = (): ActionResult => ({ name: '', config: {}, isPreconfigured: false, + isDeprecated: false, }); export const getMockPrivilegesResult = () => ({ 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 450741c5bf70b..6e2a5d08becbc 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 @@ -999,6 +999,7 @@ describe('utils', () => { actionTypeId: 'default', name: 'name', isPreconfigured: false, + isDeprecated: false, }, ]); const [errors, output] = await getInvalidConnectors(rules, clients.actionsClient); @@ -1042,6 +1043,7 @@ describe('utils', () => { actionTypeId: 'default', name: 'name', isPreconfigured: false, + isDeprecated: false, }, { id: '789', @@ -1049,6 +1051,7 @@ describe('utils', () => { actionTypeId: 'default', name: 'name', isPreconfigured: false, + isDeprecated: false, }, ]); const [errors, output] = await getInvalidConnectors(rules, clients.actionsClient); @@ -1098,6 +1101,7 @@ describe('utils', () => { actionTypeId: 'default', name: 'name', isPreconfigured: false, + isDeprecated: false, }, { id: '789', @@ -1105,6 +1109,7 @@ describe('utils', () => { actionTypeId: 'default', name: 'name', isPreconfigured: false, + isDeprecated: false, }, ]); const [errors, output] = await getInvalidConnectors(rules, clients.actionsClient); @@ -1161,6 +1166,7 @@ describe('utils', () => { actionTypeId: 'default', name: 'name', isPreconfigured: false, + isDeprecated: false, }, { id: '789', @@ -1168,6 +1174,7 @@ describe('utils', () => { actionTypeId: 'default', name: 'name', isPreconfigured: false, + isDeprecated: false, }, ]); const [errors, output] = await getInvalidConnectors(rules, clients.actionsClient); @@ -1226,6 +1233,7 @@ describe('utils', () => { actionTypeId: 'default', name: 'name', isPreconfigured: false, + isDeprecated: false, }, { id: '789', @@ -1233,6 +1241,7 @@ describe('utils', () => { actionTypeId: 'default', name: 'name', isPreconfigured: false, + isDeprecated: false, }, ]); const [errors, output] = await getInvalidConnectors(rules, clients.actionsClient); @@ -1332,6 +1341,7 @@ describe('utils', () => { actionTypeId: 'default', name: 'name', isPreconfigured: false, + isDeprecated: false, }, { id: '789', @@ -1339,6 +1349,7 @@ describe('utils', () => { actionTypeId: 'default', name: 'name', isPreconfigured: false, + isDeprecated: false, }, ]); const [errors, output] = await getInvalidConnectors(rules, clients.actionsClient); diff --git a/x-pack/plugins/synthetics/public/state/api/alerts.ts b/x-pack/plugins/synthetics/public/state/api/alerts.ts index 29cef1b08e5fd..1b97422cc67a5 100644 --- a/x-pack/plugins/synthetics/public/state/api/alerts.ts +++ b/x-pack/plugins/synthetics/public/state/api/alerts.ts @@ -30,12 +30,14 @@ export const fetchConnectors = async (): Promise => { connector_type_id: actionTypeId, referenced_by_count: referencedByCount, is_preconfigured: isPreconfigured, + is_deprecated: isDeprecated, is_missing_secrets: isMissingSecrets, ...res }) => ({ ...res, actionTypeId, referencedByCount, + isDeprecated, isPreconfigured, isMissingSecrets, }) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.test.tsx index f91ef64ca28a3..ab2cb5332d452 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.test.tsx @@ -54,6 +54,7 @@ describe('connector validation', () => { actionTypeId: '.email', name: 'email', isPreconfigured: false, + isDeprecated: false, config: { from: 'test@test.com', port: 2323, @@ -95,6 +96,7 @@ describe('connector validation', () => { id: 'test', actionTypeId: '.email', isPreconfigured: false, + isDeprecated: false, name: 'email', config: { from: 'test@test.com', @@ -172,6 +174,7 @@ describe('connector validation', () => { id: 'test', actionTypeId: '.email', isPreconfigured: false, + isDeprecated: false, name: 'email', config: { from: 'test@test.com', @@ -213,6 +216,7 @@ describe('connector validation', () => { id: 'test', actionTypeId: '.email', isPreconfigured: false, + isDeprecated: false, name: 'email', config: { from: 'test@test.com', @@ -253,6 +257,7 @@ describe('connector validation', () => { id: 'test', actionTypeId: '.email', isPreconfigured: false, + isDeprecated: false, name: 'email', config: { from: 'test@test.com', @@ -296,6 +301,7 @@ describe('connector validation', () => { actionTypeId: '.email', name: 'email', isPreconfigured: false, + isDeprecated: false, config: { from: 'test@test.com', hasAuth: true, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.test.tsx index 6125255b3a52a..910627b0c851b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.test.tsx @@ -33,6 +33,7 @@ const actionConnector = { }, id: 'es index connector', isPreconfigured: false, + isDeprecated: false, name: 'test name', secrets: {}, }; @@ -44,6 +45,7 @@ const preconfiguredActionConnector = { }, id: AlertHistoryEsIndexConnectorId, isPreconfigured: true, + isDeprecated: false, name: 'Alert history Elasticsearch index', secrets: {}, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx index 425bcf651f104..4becccec3483d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx @@ -39,6 +39,7 @@ describe('jira connector validation', () => { actionTypeId: '.jira', name: 'jira', isPreconfigured: false, + isDeprecated: false, config: { apiUrl: 'https://siem-kibana.atlassian.net', projectKey: 'CK', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.test.tsx index 1c8c58c7c4a16..22d154373ea66 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.test.tsx @@ -21,6 +21,7 @@ describe('JiraActionConnectorFields renders', () => { id: 'test', actionTypeId: '.jira', isPreconfigured: false, + isDeprecated: false, name: 'jira', config: { apiUrl: 'https://test/', @@ -62,6 +63,7 @@ describe('JiraActionConnectorFields renders', () => { id: 'test', actionTypeId: '.jira', isPreconfigured: false, + isDeprecated: false, name: 'jira', config: { apiUrl: 'https://test/', @@ -98,6 +100,7 @@ describe('JiraActionConnectorFields renders', () => { const actionConnector = { actionTypeId: '.jira', isPreconfigured: false, + isDeprecated: false, secrets: {}, config: {}, } as JiraActionConnector; @@ -120,6 +123,7 @@ describe('JiraActionConnectorFields renders', () => { const actionConnector = { actionTypeId: '.jira', isPreconfigured: false, + isDeprecated: false, isMissingSecrets: true, secrets: {}, config: {}, @@ -147,6 +151,7 @@ describe('JiraActionConnectorFields renders', () => { id: 'test', actionTypeId: '.jira', isPreconfigured: false, + isDeprecated: false, name: 'jira', config: { apiUrl: 'https://test/', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.test.tsx index 6c39236b3ff98..16581008c9e6a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.test.tsx @@ -44,6 +44,7 @@ const connector: ActionConnector = { actionTypeId: '.test', name: 'Test', isPreconfigured: false, + isDeprecated: false, }; const editAction = jest.fn(); const defaultProps = { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx index 89b4b6ee668db..67a51964dcb03 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx @@ -38,6 +38,7 @@ describe('resilient connector validation', () => { id: 'test', actionTypeId: '.resilient', isPreconfigured: false, + isDeprecated: false, name: 'resilient', config: { apiUrl: 'https://test/', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.test.tsx index 13459888ac365..7db5cc113fcb9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.test.tsx @@ -21,6 +21,7 @@ describe('ResilientActionConnectorFields renders', () => { id: 'test', actionTypeId: '.resilient', isPreconfigured: false, + isDeprecated: false, name: 'resilient', config: { apiUrl: 'https://test/', @@ -62,6 +63,7 @@ describe('ResilientActionConnectorFields renders', () => { id: 'test', actionTypeId: '.resilient', isPreconfigured: false, + isDeprecated: false, name: 'resilient', config: { apiUrl: 'https://test/', @@ -99,6 +101,7 @@ describe('ResilientActionConnectorFields renders', () => { const actionConnector = { actionTypeId: '.resilient', isPreconfigured: false, + isDeprecated: false, config: {}, secrets: {}, } as ResilientActionConnector; @@ -121,6 +124,7 @@ describe('ResilientActionConnectorFields renders', () => { const actionConnector = { actionTypeId: '.resilient', isPreconfigured: false, + isDeprecated: false, config: {}, secrets: {}, isMissingSecrets: true, @@ -148,6 +152,7 @@ describe('ResilientActionConnectorFields renders', () => { id: 'test', actionTypeId: '.resilient', isPreconfigured: false, + isDeprecated: false, name: 'resilient', config: { apiUrl: 'https://test/', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.test.tsx index 247e968d08fc6..09ae6fe002d41 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.test.tsx @@ -39,6 +39,7 @@ const connector = { actionTypeId: '.test', name: 'Test', isPreconfigured: false, + isDeprecated: false, }; const editAction = jest.fn(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log.test.tsx index e91f76d94d2fb..69c1c18bd06ea 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log.test.tsx @@ -37,6 +37,7 @@ describe('server-log connector validation', () => { name: 'server-log', config: {}, isPreconfigured: false, + isDeprecated: false, }; expect(await actionTypeModel.validateConnector(actionConnector)).toEqual({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.test.ts index 912b308d8d79c..d76cb5ddb5725 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.test.ts @@ -22,9 +22,14 @@ const deprecatedConnector: ActionConnector = { actionTypeId: '.servicenow', name: 'Test', isPreconfigured: false, + isDeprecated: true, }; -const validConnector = { ...deprecatedConnector, config: { usesTableApi: false } }; +const validConnector = { + ...deprecatedConnector, + config: { usesTableApi: false }, + isDeprecated: false, +}; describe('helpers', () => { describe('isRESTApiError', () => { @@ -69,21 +74,21 @@ describe('helpers', () => { }); describe('getConnectorDescriptiveTitle', () => { - it('adds deprecated to the connector name when the connector usesTableApi', () => { + it('adds deprecated to the connector name when the connector is deprectaed', () => { expect(getConnectorDescriptiveTitle(deprecatedConnector)).toEqual('Test (deprecated)'); }); - it('does not add deprecated when the connector has usesTableApi:false', () => { + it('does not add deprecated when the connector is not deprectaed', () => { expect(getConnectorDescriptiveTitle(validConnector)).toEqual('Test'); }); }); describe('getSelectedConnectorIcon', () => { - it('returns undefined when the connector has usesTableApi:false', () => { + it('returns undefined when the connector is not deprectaed', () => { expect(getSelectedConnectorIcon(validConnector)).toBeUndefined(); }); - it('returns a component when the connector has usesTableApi:true', () => { + it('returns a component when the connector is deprectaed', () => { expect(getSelectedConnectorIcon(deprecatedConnector)).toBeDefined(); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.ts index 610759a569fd3..49ae38c12947f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/helpers.ts @@ -9,10 +9,7 @@ import { lazy, ComponentType } from 'react'; import { EuiSelectOption } from '@elastic/eui'; import { AppInfo, Choice, RESTApiError } from './types'; import { ActionConnector, IErrorObject } from '../../../../types'; -import { - deprecatedMessage, - checkConnectorIsDeprecated, -} from '../../../../common/connectors_selection'; +import { deprecatedMessage } from '../../../../common/connectors_selection'; export const DEFAULT_CORRELATION_ID = '{{rule.id}}:{{alert.id}}'; @@ -30,7 +27,7 @@ export const isFieldInvalid = ( export const getConnectorDescriptiveTitle = (connector: ActionConnector) => { let title = connector.name; - if (checkConnectorIsDeprecated(connector)) { + if (connector.isDeprecated) { title += ` ${deprecatedMessage}`; } @@ -40,7 +37,7 @@ export const getConnectorDescriptiveTitle = (connector: ActionConnector) => { export const getSelectedConnectorIcon = ( actionConnector: ActionConnector ): React.LazyExoticComponent> | undefined => { - if (checkConnectorIsDeprecated(actionConnector)) { + if (actionConnector.isDeprecated) { return lazy(() => import('./servicenow_selection_row')); } }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx index 0e6eb4d8ff0f2..a52c48d4e8e27 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx @@ -50,6 +50,7 @@ describe('servicenow connector validation', () => { actionTypeId: id, name: 'ServiceNow', isPreconfigured: false, + isDeprecated: false, config: { apiUrl: 'https://dev94428.service-now.com/', usesTableApi: false, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.test.tsx index 786c84942e08f..315ac6db4ad92 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.test.tsx @@ -33,6 +33,7 @@ describe('ServiceNowActionConnectorFields renders', () => { id: 'test', actionTypeId: '.servicenow', isPreconfigured: false, + isDeprecated: true, name: 'SN', config: { apiUrl: 'https://test/', @@ -42,6 +43,7 @@ describe('ServiceNowActionConnectorFields renders', () => { const usesImportSetApiConnector = { ...usesTableApiConnector, + isDeprecated: false, config: { ...usesTableApiConnector.config, usesTableApi: false, @@ -94,6 +96,7 @@ describe('ServiceNowActionConnectorFields renders', () => { const actionConnector = { actionTypeId: '.servicenow', isPreconfigured: false, + isDeprecated: false, config: {}, secrets: {}, } as ServiceNowActionConnector; @@ -117,6 +120,7 @@ describe('ServiceNowActionConnectorFields renders', () => { const actionConnector = { actionTypeId: '.servicenow', isPreconfigured: false, + isDeprecated: false, isMissingSecrets: true, config: {}, secrets: {}, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx index a12d43c35369e..a9ff9497bdf19 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx @@ -23,7 +23,6 @@ import { InstallationCallout } from './installation_callout'; import { UpdateConnector } from './update_connector'; import { updateActionConnector } from '../../../lib/action_connector_api'; import { Credentials } from './credentials'; -import { checkConnectorIsDeprecated } from '../../../../common/connectors_selection'; // eslint-disable-next-line import/no-default-export export { ServiceNowConnectorFields as default }; @@ -46,7 +45,7 @@ const ServiceNowConnectorFields: React.FC< } = useKibana().services; const { apiUrl, usesTableApi } = action.config; const { username, password } = action.secrets; - const requiresNewApplication = !checkConnectorIsDeprecated(action); + const requiresNewApplication = !action.isDeprecated; const [showUpdateConnector, setShowUpdateConnector] = useState(false); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itom_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itom_params.test.tsx index ef934d4ebacd7..d17c77da1f820 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itom_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itom_params.test.tsx @@ -41,6 +41,7 @@ const connector: ActionConnector = { actionTypeId: '.test', name: 'Test', isPreconfigured: false, + isDeprecated: false, }; const editAction = jest.fn(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.test.tsx index cdbfad469b68d..f8375a5aaeb6e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.test.tsx @@ -46,6 +46,7 @@ const connector: ActionConnector = { actionTypeId: '.test', name: 'Test', isPreconfigured: false, + isDeprecated: false, }; const editAction = jest.fn(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx index 6c3c9528e5b19..3bae1c3b858d6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx @@ -26,7 +26,6 @@ import { useGetChoices } from './use_get_choices'; import { choicesToEuiOptions, DEFAULT_CORRELATION_ID } from './helpers'; import * as i18n from './translations'; -import { checkConnectorIsDeprecated } from '../../../../common/connectors_selection'; const useGetChoicesFields = ['urgency', 'severity', 'impact', 'category', 'subcategory']; const defaultFields: Fields = { @@ -47,7 +46,7 @@ const ServiceNowParamsFields: React.FunctionComponent< notifications: { toasts }, } = useKibana().services; - const isDeprecatedActionConnector = checkConnectorIsDeprecated(actionConnector); + const isDeprecatedActionConnector = actionConnector?.isDeprecated; const actionConnectorRef = useRef(actionConnector?.id ?? ''); const { incident, comments } = useMemo( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.test.tsx index 0a426aeade4c0..9f15cb07f92e1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.test.tsx @@ -48,6 +48,7 @@ const connector: ActionConnector = { actionTypeId: '.test', name: 'Test', isPreconfigured: false, + isDeprecated: false, }; const editAction = jest.fn(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx index daeaea1c86137..a341dca3f255a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx @@ -27,7 +27,6 @@ import { useGetChoices } from './use_get_choices'; import { ServiceNowSIRActionParams, Fields, Choice } from './types'; import { choicesToEuiOptions, DEFAULT_CORRELATION_ID } from './helpers'; import { DeprecatedCallout } from './deprecated_callout'; -import { checkConnectorIsDeprecated } from '../../../../common/connectors_selection'; const useGetChoicesFields = ['category', 'subcategory', 'priority']; const defaultFields: Fields = { @@ -45,7 +44,7 @@ const ServiceNowSIRParamsFields: React.FunctionComponent< notifications: { toasts }, } = useKibana().services; - const isDeprecatedActionConnector = checkConnectorIsDeprecated(actionConnector); + const isDeprecatedActionConnector = actionConnector?.isDeprecated; const actionConnectorRef = useRef(actionConnector?.id ?? ''); const { incident, comments } = useMemo( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.test.tsx index 00cce14906e82..0c7bc3b938ef8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.test.tsx @@ -19,6 +19,7 @@ const actionConnector: ServiceNowActionConnector = { id: 'test', actionTypeId: '.servicenow', isPreconfigured: false, + isDeprecated: false, name: 'servicenow', config: { apiUrl: 'https://test/', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_choices.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_choices.test.tsx index 175a80c63d4b7..22839c03d3bb7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_choices.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_choices.test.tsx @@ -27,6 +27,7 @@ const actionConnector = { actionTypeId: '.servicenow', name: 'ServiceNow ITSM', isPreconfigured: false, + isDeprecated: false, config: { apiUrl: 'https://dev94428.service-now.com/', }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_app_info.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_app_info.test.tsx index f842f6863676a..1e496e66c373b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_app_info.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_app_info.test.tsx @@ -32,6 +32,7 @@ const actionConnector = { actionTypeId: '.servicenow', name: 'ServiceNow ITSM', isPreconfigured: false, + isDeprecated: false, config: { apiUrl: 'https://test.service-now.com/', usesTableApi: false, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.test.tsx index ecbc9512a4d3a..06956e6402300 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.test.tsx @@ -28,6 +28,7 @@ const actionConnector = { actionTypeId: '.servicenow', name: 'ServiceNow ITSM', isPreconfigured: false, + isDeprecated: false, config: { apiUrl: 'https://dev94428.service-now.com/', }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.test.tsx index 03f20546b1003..302e5c80af15c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.test.tsx @@ -36,6 +36,7 @@ describe('SwimlaneParamsFields renders', () => { actionTypeId: '.test', name: 'Test', isPreconfigured: false, + isDeprecated: false, }; const defaultProps = { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/use_get_application.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/use_get_application.test.tsx index 4744c4d22fdc9..fc0ae22efe377 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/use_get_application.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/use_get_application.test.tsx @@ -24,6 +24,7 @@ const action = { actionTypeId: '.swimlane', name: 'Swimlane', isPreconfigured: false, + isDeprecated: false, config: { apiUrl: 'https://test.swimlane.com/', appId: 'bcq16kdTbz5jlwM6h', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx index 8a17b1c0b206a..7551f647a5b4a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx @@ -40,6 +40,7 @@ describe('webhook connector validation', () => { actionTypeId: '.webhook', name: 'webhook', isPreconfigured: false, + isDeprecated: false, config: { method: 'PUT', url: 'http://test.com', @@ -74,6 +75,7 @@ describe('webhook connector validation', () => { actionTypeId: '.webhook', name: 'webhook', isPreconfigured: false, + isDeprecated: false, config: { method: 'PUT', url: 'http://test.com', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.test.tsx index 2579d8dd1a93b..533d6d9a9b605 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.test.tsx @@ -20,6 +20,7 @@ describe('WebhookActionConnectorFields renders', () => { id: 'test', actionTypeId: '.webhook', isPreconfigured: false, + isDeprecated: false, name: 'webhook', config: { method: 'PUT', @@ -53,6 +54,7 @@ describe('WebhookActionConnectorFields renders', () => { secrets: {}, actionTypeId: '.webhook', isPreconfigured: false, + isDeprecated: false, config: { hasAuth: true, }, @@ -81,6 +83,7 @@ describe('WebhookActionConnectorFields renders', () => { id: 'test', actionTypeId: '.webhook', isPreconfigured: false, + isDeprecated: false, name: 'webhook', config: { method: 'PUT', @@ -113,6 +116,7 @@ describe('WebhookActionConnectorFields renders', () => { id: 'test', actionTypeId: '.webhook', isPreconfigured: false, + isDeprecated: false, isMissingSecrets: true, name: 'webhook', config: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.test.tsx index 93d8c6f5d39db..957302d18a6fc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.test.tsx @@ -40,6 +40,7 @@ describe('xmatters connector validation', () => { actionTypeId: '.xmatters', name: 'xmatters', isPreconfigured: false, + isDeprecated: false, config: { configUrl: 'http://test.com', usesBasic: true, @@ -73,6 +74,7 @@ describe('xmatters connector validation', () => { actionTypeId: '.xmatters', name: 'xmatters', isPreconfigured: false, + isDeprecated: false, config: { usesBasic: false, }, @@ -105,6 +107,8 @@ describe('xmatters connector validation', () => { config: { usesBasic: true, }, + isPreconfigured: false, + isDeprecated: false, } as XmattersActionConnector; expect(await actionTypeModel.validateConnector(actionConnector)).toEqual({ @@ -136,6 +140,8 @@ describe('xmatters connector validation', () => { configUrl: 'invalid.url', usesBasic: true, }, + isPreconfigured: false, + isDeprecated: false, } as XmattersActionConnector; expect(await actionTypeModel.validateConnector(actionConnector)).toEqual({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_connectors.test.tsx index a96e2bf679240..7da6cfe0a4a37 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters_connectors.test.tsx @@ -20,6 +20,7 @@ describe('XmattersActionConnectorFields renders', () => { id: 'test', actionTypeId: '.xmatters', isPreconfigured: false, + isDeprecated: false, name: 'xmatters', config: { configUrl: 'http:\\test', @@ -51,6 +52,7 @@ describe('XmattersActionConnectorFields renders', () => { id: 'test', actionTypeId: '.xmatters', isPreconfigured: false, + isDeprecated: false, name: 'xmatters', config: { configUrl: 'http:\\test', @@ -81,6 +83,7 @@ describe('XmattersActionConnectorFields renders', () => { id: 'test', actionTypeId: '.xmatters', isPreconfigured: false, + isDeprecated: false, name: 'xmatters', config: { usesBasic: false, @@ -107,6 +110,7 @@ describe('XmattersActionConnectorFields renders', () => { secrets: {}, actionTypeId: '.xmatters', isPreconfigured: false, + isDeprecated: false, config: { usesBasic: true, }, @@ -135,6 +139,7 @@ describe('XmattersActionConnectorFields renders', () => { id: 'test', actionTypeId: '.xmatters', isPreconfigured: false, + isDeprecated: false, name: 'xmatters', config: { configUrl: 'http:\\test', @@ -165,6 +170,7 @@ describe('XmattersActionConnectorFields renders', () => { id: 'test', actionTypeId: '.xmatters', isPreconfigured: false, + isDeprecated: false, isMissingSecrets: true, name: 'xmatters', config: { @@ -194,6 +200,7 @@ describe('XmattersActionConnectorFields renders', () => { id: 'test', actionTypeId: '.xmatters', isPreconfigured: false, + isDeprecated: false, name: 'xmatters', config: { usesBasic: false, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/connectors.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/connectors.ts index fbef1521b2024..c08232ca5eea5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/connectors.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/connectors.ts @@ -22,12 +22,14 @@ const transformConnector: RewriteRequestCase< > = ({ connector_type_id: actionTypeId, is_preconfigured: isPreconfigured, + is_deprecated: isDeprecated, referenced_by_count: referencedByCount, is_missing_secrets: isMissingSecrets, ...res }) => ({ actionTypeId, isPreconfigured, + isDeprecated, referencedByCount, isMissingSecrets, ...res, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/create.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/create.test.ts index 42588610e036b..a2ed111daa2be 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/create.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/create.test.ts @@ -18,6 +18,7 @@ describe('createActionConnector', () => { const apiResponse = { connector_type_id: 'test', is_preconfigured: false, + is_deprecated: false, name: 'My test', config: {}, secrets: {}, @@ -28,6 +29,7 @@ describe('createActionConnector', () => { const connector: ActionConnectorWithoutId<{}, {}> = { actionTypeId: 'test', isPreconfigured: false, + isDeprecated: false, name: 'My test', config: {}, secrets: {}, @@ -40,7 +42,7 @@ describe('createActionConnector', () => { Array [ "/api/actions/connector", Object { - "body": "{\\"name\\":\\"My test\\",\\"config\\":{},\\"secrets\\":{},\\"connector_type_id\\":\\"test\\",\\"is_preconfigured\\":false}", + "body": "{\\"name\\":\\"My test\\",\\"config\\":{},\\"secrets\\":{},\\"connector_type_id\\":\\"test\\",\\"is_preconfigured\\":false,\\"is_deprecated\\":false}", }, ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/create.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/create.ts index 2799848909446..9227c4747c84a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/create.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/create.ts @@ -15,10 +15,11 @@ import type { const rewriteBodyRequest: RewriteResponseCase< Omit -> = ({ actionTypeId, isPreconfigured, ...res }) => ({ +> = ({ actionTypeId, isPreconfigured, isDeprecated, ...res }) => ({ ...res, connector_type_id: actionTypeId, is_preconfigured: isPreconfigured, + is_deprecated: isDeprecated, }); const rewriteBodyRes: RewriteRequestCase< @@ -26,12 +27,14 @@ const rewriteBodyRes: RewriteRequestCase< > = ({ connector_type_id: actionTypeId, is_preconfigured: isPreconfigured, + is_deprecated: isDeprecated, is_missing_secrets: isMissingSecrets, ...res }) => ({ ...res, actionTypeId, isPreconfigured, + isDeprecated, isMissingSecrets, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.test.ts index 90c0d3adb9235..957f682d1fd6c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.test.ts @@ -19,6 +19,7 @@ describe('updateActionConnector', () => { const apiResponse = { connector_type_id: 'te/st', is_preconfigured: false, + is_deprecated: false, name: 'My test', config: {}, secrets: {}, @@ -29,6 +30,7 @@ describe('updateActionConnector', () => { const connector: ActionConnectorWithoutId<{}, {}> = { actionTypeId: 'te/st', isPreconfigured: false, + isDeprecated: false, name: 'My test', config: {}, secrets: {}, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts index 18f4bdd60ad27..34aa1e127ad95 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/update.ts @@ -18,12 +18,14 @@ const rewriteBodyRes: RewriteRequestCase< > = ({ connector_type_id: actionTypeId, is_preconfigured: isPreconfigured, + is_deprecated: isDeprecated, is_missing_secrets: isMissingSecrets, ...res }) => ({ ...res, actionTypeId, isPreconfigured, + isDeprecated, isMissingSecrets, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx index 6b115abc590cc..93aade03b22b2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx @@ -99,6 +99,7 @@ describe('checkActionFormActionTypeEnabled', () => { actionTypeId: '1', id: 'test1', isPreconfigured: true, + isDeprecated: true, name: 'test', referencedByCount: 0, }, @@ -106,6 +107,7 @@ describe('checkActionFormActionTypeEnabled', () => { actionTypeId: '2', id: 'test2', isPreconfigured: true, + isDeprecated: true, name: 'test', referencedByCount: 0, }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts index 162cb222b78fe..6587fc669b97e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts @@ -112,6 +112,7 @@ describe('getConnectorWithInvalidatedFields', () => { name: 'slack', config: {}, isPreconfigured: false, + isDeprecated: false, }; const secretsErrors = { webhookUrl: ['Webhook URL is required.'] }; const configErrors = {}; @@ -128,6 +129,7 @@ describe('getConnectorWithInvalidatedFields', () => { name: 'jira', config: {} as any, isPreconfigured: false, + isDeprecated: false, }; const secretsErrors = {}; const configErrors = { apiUrl: ['apiUrl is required'] }; @@ -146,6 +148,7 @@ describe('getConnectorWithInvalidatedFields', () => { name: 'slack', config: {}, isPreconfigured: false, + isDeprecated: false, }; const secretsErrors = { webhookUrl: ['Webhook URL must start with https://.'] }; const configErrors = {}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx index 4addfc3833aab..b86a3952eb0a6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx @@ -41,6 +41,7 @@ describe('action_connector_form', () => { config: {}, secrets: {}, isPreconfigured: false, + isDeprecated: false, }; const wrapper = mountWithIntl( { name: 'Test connector', config: {}, isPreconfigured: false, + isDeprecated: false, }, { secrets: {}, @@ -142,6 +143,7 @@ describe('action_form', () => { name: 'Test connector 2', config: {}, isPreconfigured: true, + isDeprecated: false, }, { secrets: {}, @@ -151,6 +153,7 @@ describe('action_form', () => { name: 'Preconfigured Only', config: {}, isPreconfigured: true, + isDeprecated: false, }, { secrets: {}, @@ -160,6 +163,7 @@ describe('action_form', () => { name: 'Regular connector', config: {}, isPreconfigured: false, + isDeprecated: false, }, { secrets: {}, @@ -169,6 +173,7 @@ describe('action_form', () => { name: 'Non consumer connector', config: {}, isPreconfigured: false, + isDeprecated: false, }, { secrets: {}, @@ -178,6 +183,7 @@ describe('action_form', () => { name: 'Connector with disabled action group', config: {}, isPreconfigured: false, + isDeprecated: false, }, { secrets: null, @@ -187,6 +193,7 @@ describe('action_form', () => { name: 'Connector with disabled action group', config: {}, isPreconfigured: false, + isDeprecated: false, }, ]; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx index 484f6698a8b29..7b89d720eabe3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx @@ -154,6 +154,7 @@ function getActionTypeForm( }, id: 'test', isPreconfigured: false, + isDeprecated: false, name: 'test name', secrets: {}, }; @@ -176,6 +177,7 @@ function getActionTypeForm( }, id: 'test', isPreconfigured: false, + isDeprecated: false, name: 'test name', secrets: {}, }, @@ -184,6 +186,7 @@ function getActionTypeForm( name: 'Server log', actionTypeId: '.server-log', isPreconfigured: false, + isDeprecated: false, config: {}, secrets: {}, }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx index 524b848dbb275..e01d325ba3ef5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx @@ -43,6 +43,7 @@ describe('connector_edit_flyout', () => { actionType: 'test-action-type-name', name: 'action-connector', isPreconfigured: false, + isDeprecated: false, referencedByCount: 0, config: {}, }; @@ -87,6 +88,7 @@ describe('connector_edit_flyout', () => { actionType: 'test-action-type-name', name: 'preconfigured-connector', isPreconfigured: true, + isDeprecated: false, referencedByCount: 0, config: {}, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.test.ts index ab9ff5bfa9ffe..bc23b87176598 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.test.ts @@ -21,6 +21,7 @@ describe('connector reducer', () => { name: 'action-connector', referencedByCount: 0, isPreconfigured: false, + isDeprecated: false, config: {}, }; }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connectors_selection.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connectors_selection.test.tsx index 3bef8e19abff1..b56e245633ece 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connectors_selection.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connectors_selection.test.tsx @@ -66,6 +66,7 @@ describe('connectors_selection', () => { }, id: 'testId', isPreconfigured: false, + isDeprecated: false, name: 'test pagerduty', secrets: {}, }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index 4b51c9b306a75..9d6e464f25dd3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -105,6 +105,7 @@ describe('actions_connectors_list component with items', () => { actionTypeId: 'test', description: 'My test', isPreconfigured: false, + isDeprecated: false, referencedByCount: 1, config: {}, }, @@ -114,6 +115,7 @@ describe('actions_connectors_list component with items', () => { description: 'My test 2', referencedByCount: 1, isPreconfigured: false, + isDeprecated: false, config: {}, }, { @@ -123,6 +125,7 @@ describe('actions_connectors_list component with items', () => { isMissingSecrets: true, referencedByCount: 1, isPreconfigured: true, + isDeprecated: false, config: {}, }, { @@ -131,6 +134,7 @@ describe('actions_connectors_list component with items', () => { description: 'My invalid connector type', referencedByCount: 1, isPreconfigured: false, + isDeprecated: false, config: {}, }, ] @@ -237,6 +241,7 @@ describe('actions_connectors_list component with items', () => { secrets: {}, description: `My test ${index}`, isPreconfigured: false, + isDeprecated: false, referencedByCount: 1, config: {}, })) @@ -472,6 +477,7 @@ describe('actions_connectors_list component with deprecated connectors', () => { description: 'My test', referencedByCount: 1, config: { usesTableApi: true }, + isDeprecated: true, }, { id: '2', @@ -479,6 +485,7 @@ describe('actions_connectors_list component with deprecated connectors', () => { description: 'My test 2', referencedByCount: 1, config: { usesTableApi: true }, + isDeprecated: true, }, ]); loadActionTypes.mockResolvedValueOnce([ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index 80d1839b231df..a1867bb6362b8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -50,7 +50,6 @@ import ConnectorAddFlyout from '../../action_connector_form/connector_add_flyout import { connectorDeprecatedMessage, deprecatedMessage, - checkConnectorIsDeprecated, } from '../../../../common/connectors_selection'; const ConnectorIconTipWithSpacing = withTheme(({ theme }: { theme: EuiTheme }) => { @@ -203,7 +202,7 @@ const ActionsConnectorsList: React.FunctionComponent = () => { * TODO: Remove when connectors can provide their own UX message. * Issue: https://github.com/elastic/kibana/issues/114507 */ - const showDeprecatedTooltip = checkConnectorIsDeprecated(item); + const showDeprecatedTooltip = item.isDeprecated; const name = getConnectorName(value, item); const link = ( @@ -490,7 +489,7 @@ function getActionsCountByActionType(actions: ActionConnector[], actionTypeId: s } function getConnectorName(name: string, connector: ActionConnector): string { - return checkConnectorIsDeprecated(connector) ? `${name} ${deprecatedMessage}` : name; + return connector.isDeprecated ? `${name} ${deprecatedMessage}` : name; } const DeleteOperation: React.FunctionComponent<{ 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 b41af16ccab47..fe17dde8c1282 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 @@ -685,6 +685,7 @@ describe('broken connector indicator', () => { name: 'Test connector', config: {}, isPreconfigured: false, + isDeprecated: false, }, { secrets: {}, @@ -694,6 +695,7 @@ describe('broken connector indicator', () => { name: 'Test connector 2', config: {}, isPreconfigured: false, + isDeprecated: false, }, ]); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/connectors_seleciton.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/connectors_seleciton.test.tsx deleted file mode 100644 index 8e55b71699d65..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/common/connectors_seleciton.test.tsx +++ /dev/null @@ -1,59 +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 { checkConnectorIsDeprecated } from './connectors_selection'; - -describe('Connectors select', () => { - describe('checkConnectorIsDeprecated', () => { - const connector = { - id: 'test', - actionTypeId: '.webhook', - name: 'Test', - config: { apiUrl: 'http://example.com', usesTableApi: false }, - secrets: { username: 'test', password: 'test' }, - isPreconfigured: false as const, - }; - - it('returns false if the connector is not defined', () => { - expect(checkConnectorIsDeprecated()).toBe(false); - }); - - it('returns false if the connector is not ITSM or SecOps', () => { - expect(checkConnectorIsDeprecated(connector)).toBe(false); - }); - - it('returns false if the connector is .servicenow and the usesTableApi=false', () => { - expect(checkConnectorIsDeprecated({ ...connector, actionTypeId: '.servicenow' })).toBe(false); - }); - - it('returns false if the connector is .servicenow-sir and the usesTableApi=false', () => { - expect(checkConnectorIsDeprecated({ ...connector, actionTypeId: '.servicenow-sir' })).toBe( - false - ); - }); - - it('returns true if the connector is .servicenow and the usesTableApi=true', () => { - expect( - checkConnectorIsDeprecated({ - ...connector, - actionTypeId: '.servicenow', - config: { ...connector.config, usesTableApi: true }, - }) - ).toBe(true); - }); - - it('returns true if the connector is .servicenow-sir and the usesTableApi=true', () => { - expect( - checkConnectorIsDeprecated({ - ...connector, - actionTypeId: '.servicenow-sir', - config: { ...connector.config, usesTableApi: true }, - }) - ).toBe(true); - }); - }); -}); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/connectors_selection.tsx b/x-pack/plugins/triggers_actions_ui/public/common/connectors_selection.tsx index 334a10c95e112..555b2d4c6b71d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/connectors_selection.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/connectors_selection.tsx @@ -6,8 +6,6 @@ */ import { i18n } from '@kbn/i18n'; -import { ServiceNowActionConnector } from '../application/components/builtin_action_types/servicenow/types'; -import { ActionConnector, UserConfiguredActionConnector } from '../types'; export const preconfiguredMessage = i18n.translate( 'xpack.triggersActionsUI.sections.actionForm.preconfiguredTitleMessage', @@ -27,44 +25,3 @@ export const connectorDeprecatedMessage = i18n.translate( 'xpack.triggersActionsUI.sections.isDeprecatedDescription', { defaultMessage: 'This connector is deprecated. Update it, or create a new one.' } ); - -export const checkConnectorIsDeprecated = ( - connector?: ActionConnector | ServiceNowActionConnector -): boolean => { - if (connector == null) { - return false; - } - - if ( - isConnectorWithConfig(connector) && - (connector.actionTypeId === '.servicenow' || connector.actionTypeId === '.servicenow-sir') - ) { - /** - * Connectors after the Elastic ServiceNow application use the - * Import Set API (https://developer.servicenow.com/dev.do#!/reference/api/rome/rest/c_ImportSetAPI) - * A ServiceNow connector is considered deprecated if it uses the Table API. - * - * All other connectors do not have the usesTableApi config property - * so the function will always return false for them. - */ - return !!connector.config.usesTableApi; - } - - return false; -}; - -type ConnectorWithUnknownConfig = UserConfiguredActionConnector< - Record, - Record ->; - -const isConnectorWithConfig = ( - connector: ActionConnector | ServiceNowActionConnector -): connector is ConnectorWithUnknownConfig => { - const unsafeConnector = connector as UserConfiguredActionConnector< - Record, - Record - >; - - return unsafeConnector.config != null; -}; diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 2d4268252a353..cb68aab1899be 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -195,6 +195,7 @@ export interface ActionConnectorProps { referencedByCount?: number; config: Config; isPreconfigured: boolean; + isDeprecated: boolean; isMissingSecrets?: boolean; } diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 93b1ba7d76a47..02f1f42af5270 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -187,6 +187,18 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', }, }, + 'my-deprecated-servicenow': { + actionTypeId: '.servicenow', + name: 'ServiceNow#xyz', + config: { + apiUrl: 'https://ven04334.service-now.com', + usesTableApi: true, + }, + secrets: { + username: 'elastic_integration', + password: 'somepassword', + }, + }, 'custom-system-abc-connector': { actionTypeId: 'system-abc-action-type', name: 'SystemABC', diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/email.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/email.ts index d7716c5f30f66..6fb2315956b69 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/email.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/email.ts @@ -45,6 +45,7 @@ export default function emailTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdActionId, is_preconfigured: false, + is_deprecated: false, name: 'An email action', connector_type_id: '.email', is_missing_secrets: false, @@ -70,6 +71,7 @@ export default function emailTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, name: 'An email action', connector_type_id: '.email', is_missing_secrets: false, @@ -354,6 +356,7 @@ export default function emailTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'An email action', connector_type_id: '.email', is_missing_secrets: false, @@ -379,6 +382,7 @@ export default function emailTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, name: 'An email action', connector_type_id: '.email', is_missing_secrets: false, @@ -418,6 +422,7 @@ export default function emailTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'An email action', connector_type_id: '.email', is_missing_secrets: false, @@ -443,6 +448,7 @@ export default function emailTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, name: 'An email action', connector_type_id: '.email', is_missing_secrets: false, @@ -487,6 +493,7 @@ export default function emailTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdMSExchangeActionId, is_preconfigured: false, + is_deprecated: false, name: 'An email action', connector_type_id: '.email', is_missing_secrets: false, @@ -514,6 +521,7 @@ export default function emailTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, name: 'An email action', connector_type_id: '.email', is_missing_secrets: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts index fc40d036f925a..edf352936e979 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts @@ -41,6 +41,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'An index action', connector_type_id: '.index', is_missing_secrets: false, @@ -60,6 +61,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, is_missing_secrets: false, name: 'An index action', connector_type_id: '.index', @@ -84,6 +86,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(createdActionWithIndex).to.eql({ id: createdActionWithIndex.id, is_preconfigured: false, + is_deprecated: false, name: 'An index action with index config', connector_type_id: '.index', is_missing_secrets: false, @@ -103,6 +106,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(fetchedActionWithIndex).to.eql({ id: fetchedActionWithIndex.id, is_preconfigured: false, + is_deprecated: false, name: 'An index action with index config', connector_type_id: '.index', is_missing_secrets: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts index 41a0d65b624b7..33185a20c9249 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts @@ -76,6 +76,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'A jira action', connector_type_id: '.jira', is_missing_secrets: false, @@ -92,6 +93,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, name: 'A jira action', connector_type_id: '.jira', is_missing_secrets: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts index 26b59d93e0073..05dba49236197 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts @@ -62,6 +62,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'A pagerduty action', connector_type_id: '.pagerduty', is_missing_secrets: false, @@ -79,6 +80,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, name: 'A pagerduty action', connector_type_id: '.pagerduty', is_missing_secrets: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts index 8e2036ce688ea..e2a92701b62cb 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts @@ -79,6 +79,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'An IBM Resilient action', connector_type_id: '.resilient', is_missing_secrets: false, @@ -95,6 +96,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, name: 'An IBM Resilient action', connector_type_id: '.resilient', is_missing_secrets: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/server_log.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/server_log.ts index 6dacd19460295..fb7bac7d81e9c 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/server_log.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/server_log.ts @@ -30,6 +30,7 @@ export default function serverLogTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, is_missing_secrets: false, name: 'A server.log action', connector_type_id: '.server-log', @@ -45,6 +46,7 @@ export default function serverLogTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, name: 'A server.log action', connector_type_id: '.server-log', is_missing_secrets: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow_itom.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow_itom.ts index 6f1ddc6ee2748..9dcc3ef05266e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow_itom.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow_itom.ts @@ -93,6 +93,7 @@ export default function serviceNowITOMTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'A servicenow action', connector_type_id: '.servicenow-itom', is_missing_secrets: false, @@ -108,6 +109,7 @@ export default function serviceNowITOMTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, name: 'A servicenow action', connector_type_id: '.servicenow-itom', is_missing_secrets: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow_itsm.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow_itsm.ts index 1308959ebbacf..4cc65d7103a58 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow_itsm.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow_itsm.ts @@ -100,6 +100,7 @@ export default function serviceNowITSMTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'A servicenow action', connector_type_id: '.servicenow', is_missing_secrets: false, @@ -116,6 +117,7 @@ export default function serviceNowITSMTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, name: 'A servicenow action', connector_type_id: '.servicenow', is_missing_secrets: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow_sir.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow_sir.ts index c27634ecf6aca..305bbef7cf70a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow_sir.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow_sir.ts @@ -104,6 +104,7 @@ export default function serviceNowSIRTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'A servicenow action', connector_type_id: '.servicenow-sir', is_missing_secrets: false, @@ -120,6 +121,7 @@ export default function serviceNowSIRTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, name: 'A servicenow action', connector_type_id: '.servicenow-sir', is_missing_secrets: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts index 8945c54c843b9..66b988fb9b4eb 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts @@ -59,6 +59,7 @@ export default function slackTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, is_missing_secrets: false, name: 'A slack action', connector_type_id: '.slack', @@ -74,6 +75,7 @@ export default function slackTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, is_missing_secrets: false, name: 'A slack action', connector_type_id: '.slack', diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/swimlane.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/swimlane.ts index 9647d083460fd..a55e8e30d419a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/swimlane.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/swimlane.ts @@ -151,6 +151,7 @@ export default function swimlaneTest({ getService }: FtrProviderContext) { id: createdAction.id, is_missing_secrets: false, is_preconfigured: false, + is_deprecated: false, name: 'A swimlane action', }); @@ -163,6 +164,7 @@ export default function swimlaneTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, is_missing_secrets: false, name: 'A swimlane action', connector_type_id: '.swimlane', diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts index 8d879633be2ec..44a74a7a31571 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts @@ -117,6 +117,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'A generic Webhook action', connector_type_id: '.webhook', is_missing_secrets: false, @@ -135,6 +136,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, name: 'A generic Webhook action', connector_type_id: '.webhook', is_missing_secrets: false, @@ -168,6 +170,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'A generic Webhook action', connector_type_id: '.webhook', is_missing_secrets: false, @@ -205,6 +208,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, is_preconfigured: false, + is_deprecated: false, name: 'A generic Webhook action', connector_type_id: '.webhook', is_missing_secrets: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/xmatters.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/xmatters.ts index 83edc6f5a2984..7ce357bc62e36 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/xmatters.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/xmatters.ts @@ -62,6 +62,7 @@ export default function xmattersTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'An xmatters action', connector_type_id: '.xmatters', is_missing_secrets: false, @@ -95,6 +96,7 @@ export default function xmattersTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'An xmatters action', connector_type_id: '.xmatters', is_missing_secrets: false, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/create.ts index 87804395fc2b7..06e8017177138 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/create.ts @@ -59,6 +59,7 @@ export default function createActionTests({ getService }: FtrProviderContext) { expect(response.body).to.eql({ id: response.body.id, is_preconfigured: false, + is_deprecated: false, is_missing_secrets: false, name: 'My action', connector_type_id: 'test.index-record', diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts index 5a445a4f5f112..9842b13a9745d 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get.ts @@ -64,6 +64,7 @@ export default function getActionTests({ getService }: FtrProviderContext) { id: createdAction.id, is_preconfigured: false, connector_type_id: 'test.index-record', + is_deprecated: false, is_missing_secrets: false, name: 'My action', config: { @@ -150,6 +151,7 @@ export default function getActionTests({ getService }: FtrProviderContext) { connector_type_id: '.slack', name: 'Slack#xyz', is_preconfigured: true, + is_deprecated: false, }); break; default: diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts index 8ae50b9158487..ab6181369755f 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts @@ -70,6 +70,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'My action', connector_type_id: 'test.index-record', is_missing_secrets: false, @@ -81,13 +82,23 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'preconfigured-es-index-action', is_preconfigured: true, + is_deprecated: false, connector_type_id: '.index', name: 'preconfigured_es_index_action', referenced_by_count: 0, }, + { + connector_type_id: '.servicenow', + id: 'my-deprecated-servicenow', + is_preconfigured: true, + is_deprecated: true, + name: 'ServiceNow#xyz', + referenced_by_count: 0, + }, { id: 'my-slack1', is_preconfigured: true, + is_deprecated: false, connector_type_id: '.slack', name: 'Slack#xyz', referenced_by_count: 0, @@ -95,6 +106,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'custom-system-abc-connector', is_preconfigured: true, + is_deprecated: false, connector_type_id: 'system-abc-action-type', name: 'SystemABC', referenced_by_count: 0, @@ -102,6 +114,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'preconfigured.test.index-record', is_preconfigured: true, + is_deprecated: false, connector_type_id: 'test.index-record', name: 'Test:_Preconfigured_Index_Record', referenced_by_count: 0, @@ -184,6 +197,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'My action', connector_type_id: 'test.index-record', is_missing_secrets: false, @@ -195,13 +209,23 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'preconfigured-es-index-action', is_preconfigured: true, + is_deprecated: false, connector_type_id: '.index', name: 'preconfigured_es_index_action', referenced_by_count: 0, }, + { + connector_type_id: '.servicenow', + id: 'my-deprecated-servicenow', + is_deprecated: true, + is_preconfigured: true, + name: 'ServiceNow#xyz', + referenced_by_count: 0, + }, { id: 'my-slack1', is_preconfigured: true, + is_deprecated: false, connector_type_id: '.slack', name: 'Slack#xyz', referenced_by_count: 0, @@ -209,6 +233,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'custom-system-abc-connector', is_preconfigured: true, + is_deprecated: false, connector_type_id: 'system-abc-action-type', name: 'SystemABC', referenced_by_count: 0, @@ -216,6 +241,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'preconfigured.test.index-record', is_preconfigured: true, + is_deprecated: false, connector_type_id: 'test.index-record', name: 'Test:_Preconfigured_Index_Record', referenced_by_count: 0, @@ -274,13 +300,23 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'preconfigured-es-index-action', is_preconfigured: true, + is_deprecated: false, connector_type_id: '.index', name: 'preconfigured_es_index_action', referenced_by_count: 0, }, + { + connector_type_id: '.servicenow', + id: 'my-deprecated-servicenow', + is_preconfigured: true, + is_deprecated: true, + name: 'ServiceNow#xyz', + referenced_by_count: 0, + }, { id: 'my-slack1', is_preconfigured: true, + is_deprecated: false, connector_type_id: '.slack', name: 'Slack#xyz', referenced_by_count: 0, @@ -288,6 +324,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'custom-system-abc-connector', is_preconfigured: true, + is_deprecated: false, connector_type_id: 'system-abc-action-type', name: 'SystemABC', referenced_by_count: 0, @@ -295,6 +332,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'preconfigured.test.index-record', is_preconfigured: true, + is_deprecated: false, connector_type_id: 'test.index-record', name: 'Test:_Preconfigured_Index_Record', referenced_by_count: 0, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts index 31cb9ac03b91b..92bb7084ec534 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/update.ts @@ -73,6 +73,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { expect(response.body).to.eql({ id: createdAction.id, is_preconfigured: false, + is_deprecated: false, connector_type_id: 'test.index-record', is_missing_secrets: false, name: 'My action updated', diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/telemetry/actions_telemetry.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/telemetry/actions_telemetry.ts index 350f0019641b8..1d8d0091b67de 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/telemetry/actions_telemetry.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/telemetry/actions_telemetry.ts @@ -188,7 +188,7 @@ export default function createActionsTelemetryTests({ getService }: FtrProviderC const telemetry = JSON.parse(taskState!); // total number of connectors - expect(telemetry.count_total).to.equal(17); + expect(telemetry.count_total).to.equal(18); // total number of active connectors (used by a rule) expect(telemetry.count_active_total).to.equal(7); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts index 3bc8cec9bf163..5424b18599f64 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts @@ -39,6 +39,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(createdAction).to.eql({ id: createdAction.id, isPreconfigured: false, + isDeprecated: false, name: 'An index action', actionTypeId: '.index', isMissingSecrets: false, @@ -58,6 +59,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(fetchedAction).to.eql({ id: fetchedAction.id, isPreconfigured: false, + isDeprecated: false, isMissingSecrets: false, name: 'An index action', actionTypeId: '.index', @@ -82,6 +84,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(createdActionWithIndex).to.eql({ id: createdActionWithIndex.id, isPreconfigured: false, + isDeprecated: false, name: 'An index action with index config', actionTypeId: '.index', isMissingSecrets: false, @@ -101,6 +104,7 @@ export default function indexTest({ getService }: FtrProviderContext) { expect(fetchedActionWithIndex).to.eql({ id: fetchedActionWithIndex.id, isPreconfigured: false, + isDeprecated: false, name: 'An index action with index config', actionTypeId: '.index', isMissingSecrets: false, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/create.ts index 95b465e08647f..9312ad8a90335 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/create.ts @@ -39,6 +39,7 @@ export default function createActionTests({ getService }: FtrProviderContext) { expect(response.body).to.eql({ id: response.body.id, is_preconfigured: false, + is_deprecated: false, name: 'My action', connector_type_id: 'test.index-record', is_missing_secrets: false, @@ -78,6 +79,7 @@ export default function createActionTests({ getService }: FtrProviderContext) { expect(response.body).to.eql({ id: response.body.id, isPreconfigured: false, + isDeprecated: false, name: 'My action', actionTypeId: 'test.index-record', isMissingSecrets: false, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts index e08d99a694399..d5d5109b6e738 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get.ts @@ -40,6 +40,7 @@ export default function getActionTests({ getService }: FtrProviderContext) { .expect(200, { id: createdAction.id, is_preconfigured: false, + is_deprecated: false, is_missing_secrets: false, connector_type_id: 'test.index-record', name: 'My action', @@ -81,11 +82,24 @@ export default function getActionTests({ getService }: FtrProviderContext) { .expect(200, { id: 'my-slack1', is_preconfigured: true, + is_deprecated: false, connector_type_id: '.slack', name: 'Slack#xyz', }); }); + it('should handle get action request for deprecated connectors from preconfigured list', async () => { + await supertest + .get(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/my-deprecated-servicenow`) + .expect(200, { + id: 'my-deprecated-servicenow', + is_preconfigured: true, + is_deprecated: true, + connector_type_id: '.servicenow', + name: 'ServiceNow#xyz', + }); + }); + describe('legacy', () => { it('should handle get action request appropriately', async () => { const { body: createdAction } = await supertest @@ -109,6 +123,7 @@ export default function getActionTests({ getService }: FtrProviderContext) { .expect(200, { id: createdAction.id, isPreconfigured: false, + isDeprecated: false, actionTypeId: 'test.index-record', isMissingSecrets: false, name: 'My action', @@ -150,6 +165,7 @@ export default function getActionTests({ getService }: FtrProviderContext) { .expect(200, { id: 'my-slack1', isPreconfigured: true, + isDeprecated: false, actionTypeId: '.slack', name: 'Slack#xyz', }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts index a965b1716a671..54a0e6e10a198 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts @@ -52,11 +52,13 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { name: 'Alert history Elasticsearch index', connector_type_id: '.index', is_preconfigured: true, + is_deprecated: false, referenced_by_count: 0, }, { id: createdAction.id, is_preconfigured: false, + is_deprecated: false, name: 'My action', connector_type_id: 'test.index-record', is_missing_secrets: false, @@ -68,13 +70,23 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'preconfigured-es-index-action', is_preconfigured: true, + is_deprecated: false, connector_type_id: '.index', name: 'preconfigured_es_index_action', referenced_by_count: 0, }, + { + connector_type_id: '.servicenow', + id: 'my-deprecated-servicenow', + is_deprecated: true, + is_preconfigured: true, + name: 'ServiceNow#xyz', + referenced_by_count: 0, + }, { id: 'my-slack1', is_preconfigured: true, + is_deprecated: false, connector_type_id: '.slack', name: 'Slack#xyz', referenced_by_count: 0, @@ -82,6 +94,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'custom-system-abc-connector', is_preconfigured: true, + is_deprecated: false, connector_type_id: 'system-abc-action-type', name: 'SystemABC', referenced_by_count: 0, @@ -89,6 +102,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'preconfigured.test.index-record', is_preconfigured: true, + is_deprecated: false, connector_type_id: 'test.index-record', name: 'Test:_Preconfigured_Index_Record', referenced_by_count: 0, @@ -129,18 +143,29 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { name: 'Alert history Elasticsearch index', connector_type_id: '.index', is_preconfigured: true, + is_deprecated: false, referenced_by_count: 0, }, { id: 'preconfigured-es-index-action', is_preconfigured: true, + is_deprecated: false, connector_type_id: '.index', name: 'preconfigured_es_index_action', referenced_by_count: 0, }, + { + connector_type_id: '.servicenow', + id: 'my-deprecated-servicenow', + is_deprecated: true, + is_preconfigured: true, + name: 'ServiceNow#xyz', + referenced_by_count: 0, + }, { id: 'my-slack1', is_preconfigured: true, + is_deprecated: false, connector_type_id: '.slack', name: 'Slack#xyz', referenced_by_count: 0, @@ -148,6 +173,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'custom-system-abc-connector', is_preconfigured: true, + is_deprecated: false, connector_type_id: 'system-abc-action-type', name: 'SystemABC', referenced_by_count: 0, @@ -155,6 +181,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'preconfigured.test.index-record', is_preconfigured: true, + is_deprecated: false, connector_type_id: 'test.index-record', name: 'Test:_Preconfigured_Index_Record', referenced_by_count: 0, @@ -196,11 +223,13 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { name: 'Alert history Elasticsearch index', actionTypeId: '.index', isPreconfigured: true, + isDeprecated: false, referencedByCount: 0, }, { id: createdAction.id, isPreconfigured: false, + isDeprecated: false, name: 'My action', actionTypeId: 'test.index-record', isMissingSecrets: false, @@ -212,13 +241,23 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'preconfigured-es-index-action', isPreconfigured: true, + isDeprecated: false, actionTypeId: '.index', name: 'preconfigured_es_index_action', referencedByCount: 0, }, + { + actionTypeId: '.servicenow', + id: 'my-deprecated-servicenow', + isDeprecated: true, + isPreconfigured: true, + name: 'ServiceNow#xyz', + referencedByCount: 0, + }, { id: 'my-slack1', isPreconfigured: true, + isDeprecated: false, actionTypeId: '.slack', name: 'Slack#xyz', referencedByCount: 0, @@ -226,6 +265,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'custom-system-abc-connector', isPreconfigured: true, + isDeprecated: false, actionTypeId: 'system-abc-action-type', name: 'SystemABC', referencedByCount: 0, @@ -233,6 +273,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { { id: 'preconfigured.test.index-record', isPreconfigured: true, + isDeprecated: false, actionTypeId: 'test.index-record', name: 'Test:_Preconfigured_Index_Record', referencedByCount: 0, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/type_not_enabled.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/type_not_enabled.ts index eafe4f487773f..0ab9baad0b554 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/type_not_enabled.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/type_not_enabled.ts @@ -62,6 +62,7 @@ export default function typeNotEnabledTests({ getService }: FtrProviderContext) config: {}, id: 'uuid-actionId', isPreconfigured: false, + isDeprecated: false, isMissingSecrets: false, name: 'an action created before test.not-enabled was disabled', }); @@ -90,6 +91,7 @@ export default function typeNotEnabledTests({ getService }: FtrProviderContext) config: {}, id: 'uuid-actionId', isPreconfigured: false, + isDeprecated: false, isMissingSecrets: false, name: 'an action created before test.not-enabled was disabled', }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts index f779a97eafe2e..fad31d0bd3a12 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/update.ts @@ -51,6 +51,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { .expect(200, { id: createdAction.id, is_preconfigured: false, + is_deprecated: false, connector_type_id: 'test.index-record', is_missing_secrets: false, name: 'My action updated', @@ -193,6 +194,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) { .expect(200, { id: createdAction.id, isPreconfigured: false, + isDeprecated: false, actionTypeId: 'test.index-record', isMissingSecrets: false, name: 'My action updated', diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/configure/get_connectors.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/configure/get_connectors.ts index b5a58d3af5086..020159253d086 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/configure/get_connectors.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/configure/get_connectors.ts @@ -88,6 +88,7 @@ export default ({ getService }: FtrProviderContext): void => { projectKey: 'pkey', }, isPreconfigured: false, + isDeprecated: false, isMissingSecrets: false, referencedByCount: 0, }, @@ -100,6 +101,7 @@ export default ({ getService }: FtrProviderContext): void => { orgId: 'pkey', }, isPreconfigured: false, + isDeprecated: false, isMissingSecrets: false, referencedByCount: 0, }, @@ -112,6 +114,7 @@ export default ({ getService }: FtrProviderContext): void => { usesTableApi: false, }, isPreconfigured: false, + isDeprecated: false, isMissingSecrets: false, referencedByCount: 0, }, @@ -124,6 +127,7 @@ export default ({ getService }: FtrProviderContext): void => { usesTableApi: false, }, isPreconfigured: false, + isDeprecated: false, isMissingSecrets: false, referencedByCount: 0, }, diff --git a/x-pack/test/cases_api_integration/spaces_only/tests/trial/configure/get_connectors.ts b/x-pack/test/cases_api_integration/spaces_only/tests/trial/configure/get_connectors.ts index 02b91c9f0b918..2abe2996d5e21 100644 --- a/x-pack/test/cases_api_integration/spaces_only/tests/trial/configure/get_connectors.ts +++ b/x-pack/test/cases_api_integration/spaces_only/tests/trial/configure/get_connectors.ts @@ -88,6 +88,7 @@ export default ({ getService }: FtrProviderContext): void => { projectKey: 'pkey', }, isPreconfigured: false, + isDeprecated: false, isMissingSecrets: false, referencedByCount: 0, }, @@ -100,6 +101,7 @@ export default ({ getService }: FtrProviderContext): void => { orgId: 'pkey', }, isPreconfigured: false, + isDeprecated: false, isMissingSecrets: false, referencedByCount: 0, }, @@ -112,6 +114,7 @@ export default ({ getService }: FtrProviderContext): void => { usesTableApi: false, }, isPreconfigured: false, + isDeprecated: false, isMissingSecrets: false, referencedByCount: 0, }, @@ -124,6 +127,7 @@ export default ({ getService }: FtrProviderContext): void => { usesTableApi: false, }, isPreconfigured: false, + isDeprecated: false, isMissingSecrets: false, referencedByCount: 0, }, From b013b9695e9c7f97f66363aeba70d923b3df22d8 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 26 Apr 2022 12:40:23 +0200 Subject: [PATCH 03/27] [Synthetics] Remove deprecated api (index pattern) usage (#130949) --- x-pack/plugins/synthetics/kibana.json | 1 + .../plugins/synthetics/public/apps/plugin.ts | 2 ++ .../synthetics/public/apps/uptime_app.tsx | 6 +++--- .../alerts/alert_query_bar/query_bar.tsx | 6 +++--- .../alerts/alerts_containers/use_snap_shot.ts | 6 +++--- .../filters_expression_select.tsx | 8 +++---- .../monitor_status_alert/add_filter_btn.tsx | 6 +++--- .../overview/filter_group/filter_group.tsx | 8 +++---- .../filter_group/selected_filters.tsx | 10 ++++----- .../overview/query_bar/query_bar.tsx | 6 +++--- .../overview/query_bar/use_query_bar.ts | 6 +++--- ...ntext.tsx => uptime_data_view_context.tsx} | 21 +++++++++---------- .../plugins/synthetics/public/hooks/index.ts | 2 +- .../public/hooks/update_kuery_string.ts | 8 +++---- .../lazy_wrapper/monitor_status.tsx | 6 +++--- 15 files changed, 52 insertions(+), 50 deletions(-) rename x-pack/plugins/synthetics/public/contexts/{uptime_index_pattern_context.tsx => uptime_data_view_context.tsx} (50%) diff --git a/x-pack/plugins/synthetics/kibana.json b/x-pack/plugins/synthetics/kibana.json index d65a89a16161f..bb827019fc70a 100644 --- a/x-pack/plugins/synthetics/kibana.json +++ b/x-pack/plugins/synthetics/kibana.json @@ -8,6 +8,7 @@ "cases", "embeddable", "discover", + "dataViews", "encryptedSavedObjects", "features", "inspector", diff --git a/x-pack/plugins/synthetics/public/apps/plugin.ts b/x-pack/plugins/synthetics/public/apps/plugin.ts index 4a3b3668dab0a..7f1a773376688 100644 --- a/x-pack/plugins/synthetics/public/apps/plugin.ts +++ b/x-pack/plugins/synthetics/public/apps/plugin.ts @@ -38,6 +38,7 @@ import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { Start as InspectorPluginStart } from '@kbn/inspector-plugin/public'; import { CasesUiStart } from '@kbn/cases-plugin/public'; import { CloudSetup } from '@kbn/cloud-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { PLUGIN } from '../../common/constants/plugin'; import { LazySyntheticsPolicyCreateExtension, @@ -67,6 +68,7 @@ export interface ClientPluginsStart { share: SharePluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; cases: CasesUiStart; + dataViews: DataViewsPublicPluginStart; } export interface UptimePluginServices extends Partial { diff --git a/x-pack/plugins/synthetics/public/apps/uptime_app.tsx b/x-pack/plugins/synthetics/public/apps/uptime_app.tsx index 1ca9573308dbc..9887fa81393bc 100644 --- a/x-pack/plugins/synthetics/public/apps/uptime_app.tsx +++ b/x-pack/plugins/synthetics/public/apps/uptime_app.tsx @@ -33,7 +33,7 @@ import { UptimeAlertsFlyoutWrapper } from '../components/overview'; import { store, storage } from '../state'; import { kibanaService } from '../state/kibana_service'; import { ActionMenu } from '../components/common/header/action_menu'; -import { UptimeIndexPatternContextProvider } from '../contexts/uptime_index_pattern_context'; +import { UptimeDataViewContextProvider } from '../contexts/uptime_data_view_context'; export interface UptimeAppColors { danger: string; @@ -124,7 +124,7 @@ const Application = (props: UptimeAppProps) => { - +
{
-
+
diff --git a/x-pack/plugins/synthetics/public/components/overview/alerts/alert_query_bar/query_bar.tsx b/x-pack/plugins/synthetics/public/components/overview/alerts/alert_query_bar/query_bar.tsx index b990fb70a224c..288cd7232a3d5 100644 --- a/x-pack/plugins/synthetics/public/components/overview/alerts/alert_query_bar/query_bar.tsx +++ b/x-pack/plugins/synthetics/public/components/overview/alerts/alert_query_bar/query_bar.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import { isValidKuery } from '../../query_bar/query_bar'; import * as labels from '../translations'; -import { useIndexPattern } from '../../../../hooks'; +import { useUptimeDataView } from '../../../../hooks'; interface Props { query: string; @@ -19,7 +19,7 @@ interface Props { } export const AlertQueryBar = ({ query = '', onChange }: Props) => { - const indexPattern = useIndexPattern(); + const dataView = useUptimeDataView(); const [inputVal, setInputVal] = useState(query); @@ -31,7 +31,7 @@ export const AlertQueryBar = ({ query = '', onChange }: Props) => { return ( { diff --git a/x-pack/plugins/synthetics/public/components/overview/alerts/alerts_containers/use_snap_shot.ts b/x-pack/plugins/synthetics/public/components/overview/alerts/alerts_containers/use_snap_shot.ts index e6aee6207093e..7fd0f0b3a410b 100644 --- a/x-pack/plugins/synthetics/public/components/overview/alerts/alerts_containers/use_snap_shot.ts +++ b/x-pack/plugins/synthetics/public/components/overview/alerts/alerts_containers/use_snap_shot.ts @@ -6,7 +6,7 @@ */ import { useFetcher } from '@kbn/observability-plugin/public'; -import { useIndexPattern, generateUpdatedKueryString } from '../../../../hooks'; +import { useUptimeDataView, generateUpdatedKueryString } from '../../../../hooks'; import { fetchSnapshotCount } from '../../../../state/api'; export const useSnapShotCount = ({ query, filters }: { query: string; filters: [] | string }) => { @@ -15,9 +15,9 @@ export const useSnapShotCount = ({ query, filters }: { query: string; filters: [ ? '' : JSON.stringify(Array.from(Object.entries(filters))); - const indexPattern = useIndexPattern(); + const dataView = useUptimeDataView(); - const [esKuery, error] = generateUpdatedKueryString(indexPattern, query, parsedFilters); + const [esKuery, error] = generateUpdatedKueryString(dataView, query, parsedFilters); const { data, loading } = useFetcher( () => diff --git a/x-pack/plugins/synthetics/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx b/x-pack/plugins/synthetics/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx index e34caf1b95b8b..3720179838899 100644 --- a/x-pack/plugins/synthetics/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx +++ b/x-pack/plugins/synthetics/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx @@ -10,7 +10,7 @@ import { EuiButtonIcon, EuiExpression, EuiFlexGroup, EuiFlexItem, EuiSpacer } fr import { FieldValueSuggestions } from '@kbn/observability-plugin/public'; import { filterLabels } from '../../filter_group/translations'; import { alertFilterLabels, filterAriaLabels } from './translations'; -import { useIndexPattern } from '../../../../contexts/uptime_index_pattern_context'; +import { useUptimeDataView } from '../../../../contexts/uptime_data_view_context'; import { FILTER_FIELDS } from '../../../../../common/constants'; import { useGetUrlParams } from '../../../../hooks'; @@ -122,7 +122,7 @@ export const FiltersExpressionsSelect: React.FC = (curr) => curr.selectedItems.length > 0 || newFilters?.includes(curr.fieldName) ); - const indexPattern = useIndexPattern(); + const dataView = useUptimeDataView(); return ( <> @@ -130,11 +130,11 @@ export const FiltersExpressionsSelect: React.FC = ({ description, id, title, value, fieldName, ariaLabel, selectedItems }) => ( - {indexPattern && ( + {dataView && ( { diff --git a/x-pack/plugins/synthetics/public/components/overview/alerts/monitor_status_alert/add_filter_btn.tsx b/x-pack/plugins/synthetics/public/components/overview/alerts/monitor_status_alert/add_filter_btn.tsx index 58b8e7bb085da..81e782696d58f 100644 --- a/x-pack/plugins/synthetics/public/components/overview/alerts/monitor_status_alert/add_filter_btn.tsx +++ b/x-pack/plugins/synthetics/public/components/overview/alerts/monitor_status_alert/add_filter_btn.tsx @@ -8,7 +8,7 @@ import React, { useState } from 'react'; import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; import * as labels from '../translations'; -import { useIndexPattern } from '../../../../contexts/uptime_index_pattern_context'; +import { useUptimeDataView } from '../../../../contexts/uptime_data_view_context'; interface Props { newFilters: string[]; @@ -21,7 +21,7 @@ export const AddFilterButton: React.FC = ({ newFilters, onNewFilter, aler const getSelectedItems = (fieldName: string) => alertFilters?.[fieldName] ?? []; - const indexPattern = useIndexPattern(); + const dataView = useUptimeDataView(); const onButtonClick = () => { setPopover(!isPopoverOpen); @@ -65,7 +65,7 @@ export const AddFilterButton: React.FC = ({ newFilters, onNewFilter, aler onClick={onButtonClick} size="s" flush="left" - isLoading={!indexPattern} + isLoading={!dataView} > {labels.ADD_FILTER} diff --git a/x-pack/plugins/synthetics/public/components/overview/filter_group/filter_group.tsx b/x-pack/plugins/synthetics/public/components/overview/filter_group/filter_group.tsx index d73c2df73919b..8a0ac4cbe81b1 100644 --- a/x-pack/plugins/synthetics/public/components/overview/filter_group/filter_group.tsx +++ b/x-pack/plugins/synthetics/public/components/overview/filter_group/filter_group.tsx @@ -13,7 +13,7 @@ import { FieldValueSuggestions, useInspectorContext } from '@kbn/observability-p import { useFilterUpdate } from '../../../hooks/use_filter_update'; import { useSelectedFilters } from '../../../hooks/use_selected_filters'; import { SelectedFilters } from './selected_filters'; -import { useIndexPattern } from '../../../contexts/uptime_index_pattern_context'; +import { useUptimeDataView } from '../../../contexts/uptime_data_view_context'; import { useGetUrlParams } from '../../../hooks'; import { EXCLUDE_RUN_ONCE_FILTER } from '../../../../common/constants/client_defaults'; @@ -40,7 +40,7 @@ export const FilterGroup = () => { const { filtersList } = useSelectedFilters(); - const indexPattern = useIndexPattern(); + const dataView = useUptimeDataView(); const onFilterFieldChange = useCallback( (fieldName: string, values: string[], notValues: string[]) => { @@ -52,12 +52,12 @@ export const FilterGroup = () => { return ( <> - {indexPattern && + {dataView && filtersList.map(({ field, label, selectedItems, excludedItems }) => ( void; } export const SelectedFilters = ({ onChange }: Props) => { - const indexPattern = useIndexPattern(); + const dataView = useUptimeDataView(); const { filtersList } = useSelectedFilters(); - if (!indexPattern) return null; + if (!dataView) return null; return ( @@ -26,7 +26,7 @@ export const SelectedFilters = ({ onChange }: Props) => { ...selectedItems.map((value) => ( { onChange( field, @@ -51,7 +51,7 @@ export const SelectedFilters = ({ onChange }: Props) => { ...excludedItems.map((value) => ( { onChange( field, diff --git a/x-pack/plugins/synthetics/public/components/overview/query_bar/query_bar.tsx b/x-pack/plugins/synthetics/public/components/overview/query_bar/query_bar.tsx index c2ecc5a299178..3784bc58b76b2 100644 --- a/x-pack/plugins/synthetics/public/components/overview/query_bar/query_bar.tsx +++ b/x-pack/plugins/synthetics/public/components/overview/query_bar/query_bar.tsx @@ -11,7 +11,7 @@ import { EuiFlexItem } from '@elastic/eui'; import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import { SyntaxType, useQueryBar } from './use_query_bar'; import { KQL_PLACE_HOLDER, SIMPLE_SEARCH_PLACEHOLDER } from './translations'; -import { useGetUrlParams, useIndexPattern } from '../../../hooks'; +import { useGetUrlParams, useUptimeDataView } from '../../../hooks'; const SYNTAX_STORAGE = 'uptime:queryBarSyntax'; @@ -35,7 +35,7 @@ export const QueryBar = () => { const { query, setQuery, submitImmediately } = useQueryBar(); - const indexPattern = useIndexPattern(); + const dataView = useUptimeDataView(); const [inputVal, setInputVal] = useState(query.query as string); @@ -49,7 +49,7 @@ export const QueryBar = () => { return ( { } ); - const indexPattern = useIndexPattern(); + const dataView = useUptimeDataView(); const [, updateUrlParams] = useUrlParams(); const [esFilters, error] = generateUpdatedKueryString( - indexPattern, + dataView, query.language === SyntaxType.kuery ? (query.query as string) : undefined, paramFilters, excludedFilters diff --git a/x-pack/plugins/synthetics/public/contexts/uptime_index_pattern_context.tsx b/x-pack/plugins/synthetics/public/contexts/uptime_data_view_context.tsx similarity index 50% rename from x-pack/plugins/synthetics/public/contexts/uptime_index_pattern_context.tsx rename to x-pack/plugins/synthetics/public/contexts/uptime_data_view_context.tsx index 765d9f8527c88..518d2061d87ae 100644 --- a/x-pack/plugins/synthetics/public/contexts/uptime_index_pattern_context.tsx +++ b/x-pack/plugins/synthetics/public/contexts/uptime_data_view_context.tsx @@ -7,27 +7,26 @@ import React, { createContext, useContext } from 'react'; import { useFetcher } from '@kbn/observability-plugin/public'; -import { DataPublicPluginStart, IndexPattern } from '@kbn/data-plugin/public'; +import { DataViewsPublicPluginStart, DataView } from '@kbn/data-views-plugin/public'; import { useHasData } from '../components/overview/empty_state/use_has_data'; -export const UptimeIndexPatternContext = createContext({} as IndexPattern); +export const UptimeDataViewContext = createContext({} as DataView); -export const UptimeIndexPatternContextProvider: React.FC<{ data: DataPublicPluginStart }> = ({ - children, - data: { indexPatterns }, -}) => { +export const UptimeDataViewContextProvider: React.FC<{ + dataViews: DataViewsPublicPluginStart; +}> = ({ children, dataViews }) => { const { settings, data: indexStatus } = useHasData(); const heartbeatIndices = settings?.heartbeatIndices || ''; - const { data } = useFetcher>(async () => { + const { data } = useFetcher>(async () => { if (heartbeatIndices && indexStatus?.indexExists) { - // this only creates an index pattern in memory, not as saved object - return indexPatterns.create({ title: heartbeatIndices }); + // this only creates an dateView in memory, not as saved object + return dataViews.create({ title: heartbeatIndices }); } }, [heartbeatIndices, indexStatus?.indexExists]); - return ; + return ; }; -export const useIndexPattern = () => useContext(UptimeIndexPatternContext); +export const useUptimeDataView = () => useContext(UptimeDataViewContext); diff --git a/x-pack/plugins/synthetics/public/hooks/index.ts b/x-pack/plugins/synthetics/public/hooks/index.ts index e96d746a05514..b93373481f9f3 100644 --- a/x-pack/plugins/synthetics/public/hooks/index.ts +++ b/x-pack/plugins/synthetics/public/hooks/index.ts @@ -13,4 +13,4 @@ export * from './use_cert_status'; export * from './use_telemetry'; export * from './use_url_params'; export * from './use_breakpoints'; -export { useIndexPattern } from '../contexts/uptime_index_pattern_context'; +export { useUptimeDataView } from '../contexts/uptime_data_view_context'; diff --git a/x-pack/plugins/synthetics/public/hooks/update_kuery_string.ts b/x-pack/plugins/synthetics/public/hooks/update_kuery_string.ts index 8d31ef0abbd91..a8884279f49ba 100644 --- a/x-pack/plugins/synthetics/public/hooks/update_kuery_string.ts +++ b/x-pack/plugins/synthetics/public/hooks/update_kuery_string.ts @@ -6,7 +6,7 @@ */ import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; -import type { IndexPattern } from '@kbn/data-plugin/public'; +import { DataView } from '@kbn/data-views-plugin/public'; import { combineFiltersAndUserSearch, stringifyKueries } from '../../common/lib'; const getKueryString = (urlFilters: string, excludedFilters?: string): string => { @@ -42,7 +42,7 @@ const getKueryString = (urlFilters: string, excludedFilters?: string): string => }; export const generateUpdatedKueryString = ( - indexPattern: IndexPattern | null, + dataView: DataView | null, filterQueryString = '', urlFilters: string, excludedFilters?: string @@ -55,10 +55,10 @@ export const generateUpdatedKueryString = ( // this try catch is necessary to evaluate user input in kuery bar, // this error will be actually shown in UI for user to see try { - if ((filterQueryString || urlFilters || excludedFilters) && indexPattern) { + if ((filterQueryString || urlFilters || excludedFilters) && dataView) { const ast = fromKueryExpression(combinedFilterString); - const elasticsearchQuery = toElasticsearchQuery(ast, indexPattern); + const elasticsearchQuery = toElasticsearchQuery(ast, dataView); esFilters = JSON.stringify(elasticsearchQuery); } diff --git a/x-pack/plugins/synthetics/public/lib/alert_types/lazy_wrapper/monitor_status.tsx b/x-pack/plugins/synthetics/public/lib/alert_types/lazy_wrapper/monitor_status.tsx index 362263fa006ab..10aa71fa533e3 100644 --- a/x-pack/plugins/synthetics/public/lib/alert_types/lazy_wrapper/monitor_status.tsx +++ b/x-pack/plugins/synthetics/public/lib/alert_types/lazy_wrapper/monitor_status.tsx @@ -13,7 +13,7 @@ import { store } from '../../../state'; import { ClientPluginsStart } from '../../../apps/plugin'; import { kibanaService } from '../../../state/kibana_service'; import { AlertMonitorStatus } from '../../../components/overview/alerts/alerts_containers/alert_monitor_status'; -import { UptimeIndexPatternContextProvider } from '../../../contexts/uptime_index_pattern_context'; +import { UptimeDataViewContextProvider } from '../../../contexts/uptime_data_view_context'; interface Props { core: CoreStart; @@ -27,9 +27,9 @@ export default function MonitorStatusAlert({ core, plugins, params }: Props) { return ( - + - + ); From f8d0a2f0e0cf617b2b199fa95fcffb2b6f765486 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 26 Apr 2022 13:17:05 +0200 Subject: [PATCH 04/27] ux app, remove deprecated api usage (#130952) * ux app, remove deprecated api usage * update --- x-pack/plugins/ux/kibana.json | 1 + x-pack/plugins/ux/public/application/ux_app.tsx | 3 ++- .../app/rum_dashboard/local_uifilters/use_data_view.ts | 10 ++++------ x-pack/plugins/ux/public/plugin.ts | 2 ++ 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/ux/kibana.json b/x-pack/plugins/ux/kibana.json index bc57b115b7299..5b403e8d3a331 100644 --- a/x-pack/plugins/ux/kibana.json +++ b/x-pack/plugins/ux/kibana.json @@ -5,6 +5,7 @@ "requiredPlugins": [ "features", "data", + "dataViews", "licensing", "triggersActionsUi", "embeddable", diff --git a/x-pack/plugins/ux/public/application/ux_app.tsx b/x-pack/plugins/ux/public/application/ux_app.tsx index d2879d4fa2797..0ea4f9ce83893 100644 --- a/x-pack/plugins/ux/public/application/ux_app.tsx +++ b/x-pack/plugins/ux/public/application/ux_app.tsx @@ -106,7 +106,7 @@ export function UXAppRoot({ appMountParameters, core, deps, - corePlugins: { embeddable, inspector, maps, observability, data }, + corePlugins: { embeddable, inspector, maps, observability, data, dataViews }, }: { appMountParameters: AppMountParameters; core: CoreStart; @@ -130,6 +130,7 @@ export function UXAppRoot({ observability, embeddable, data, + dataViews, }} > diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/use_data_view.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/use_data_view.ts index bb80fd68ff283..f5c446908a801 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/use_data_view.ts +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/use_data_view.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { DataView } from '@kbn/data-plugin/common'; +import { DataView } from '@kbn/data-views-plugin/common'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { useFetcher } from '../../../../hooks/use_fetcher'; import { useDynamicDataViewFetcher } from '../../../../hooks/use_dynamic_data_view'; @@ -15,10 +15,8 @@ export function useDataView() { const { dataView } = useDynamicDataViewFetcher(); const { - services: { - data: { dataViews }, - }, - } = useKibana<{ data: DataPublicPluginStart }>(); + services: { dataViews }, + } = useKibana<{ dataViews: DataViewsPublicPluginStart }>(); const { data } = useFetcher>(async () => { if (dataView?.title) { diff --git a/x-pack/plugins/ux/public/plugin.ts b/x-pack/plugins/ux/public/plugin.ts index f56c7a140ade5..fe98a490b5b80 100644 --- a/x-pack/plugins/ux/public/plugin.ts +++ b/x-pack/plugins/ux/public/plugin.ts @@ -32,6 +32,7 @@ import { LicensingPluginSetup } from '@kbn/licensing-plugin/public'; import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; import { MapsStartApi } from '@kbn/maps-plugin/public'; import { Start as InspectorPluginStart } from '@kbn/inspector-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; export type UxPluginSetup = void; export type UxPluginStart = void; @@ -52,6 +53,7 @@ export interface ApmPluginStartDeps { maps?: MapsStartApi; inspector: InspectorPluginStart; observability: ObservabilityPublicStart; + dataViews: DataViewsPublicPluginStart; } export class UxPlugin implements Plugin { From 2d4ee2ed85fba49953651c58eb44b8b1e31b5bcc Mon Sep 17 00:00:00 2001 From: Dmitry Tomashevich <39378793+Dmitriynj@users.noreply.github.com> Date: Tue, 26 Apr 2022 16:27:33 +0500 Subject: [PATCH 05/27] [Discover] Fix links in helper callouts (#130873) * [Discover] fix doc explorer link in callout * [Discover] improve the fix Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../document_explorer_callout/document_explorer_callout.tsx | 2 +- .../document_explorer_update_callout.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/discover/public/application/main/components/document_explorer_callout/document_explorer_callout.tsx b/src/plugins/discover/public/application/main/components/document_explorer_callout/document_explorer_callout.tsx index 69dac6b6ce30a..9087d380d8bf2 100644 --- a/src/plugins/discover/public/application/main/components/document_explorer_callout/document_explorer_callout.tsx +++ b/src/plugins/discover/public/application/main/components/document_explorer_callout/document_explorer_callout.tsx @@ -99,7 +99,7 @@ export const DocumentExplorerCallout = () => { - + { Learn more about the structure of your data with {fieldStatistics}." values={{ fieldStatistics: ( - + { ), documentExplorer: ( - + Date: Tue, 26 Apr 2022 13:37:10 +0200 Subject: [PATCH 06/27] Add SecuritySolutionLinkButton and withSecuritySolutionLink (#130879) --- .../common/components/links/index.test.tsx | 29 ++++++++ .../public/common/components/links/index.tsx | 70 +++++++++++++++++-- 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx index adab4db904d6a..e7c82420ef9e0 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx @@ -24,12 +24,15 @@ import { PortOrServiceNameLink, DEFAULT_NUMBER_OF_LINK, ExternalLink, + SecuritySolutionLinkButton, } from '.'; +import { SecurityPageName } from '../../../app/types'; jest.mock('../link_to'); jest.mock('../../../overview/components/events_by_dataset'); +const mockNavigateTo = jest.fn(); jest.mock('../../lib/kibana', () => { return { useUiSetting$: jest.fn(), @@ -40,6 +43,9 @@ jest.mock('../../lib/kibana', () => { }, }, }), + useNavigation: () => ({ + navigateTo: mockNavigateTo, + }), }; }); @@ -580,4 +586,27 @@ describe('Custom Links', () => { ); }); }); + + describe('SecuritySolutionLinkButton', () => { + it('injects href prop with hosts page path', () => { + const path = 'testTabPath'; + + const wrapper = mount( + + ); + + expect(wrapper.find('LinkButton').prop('href')).toEqual(path); + }); + + it('injects onClick prop that calls navigateTo', () => { + const path = 'testTabPath'; + + const wrapper = mount( + + ); + wrapper.find('LinkButton').simulate('click'); + + expect(mockNavigateTo).toHaveBeenLastCalledWith({ url: path }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.tsx index 6020b0def0aed..fb244c40d6e3d 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links/index.tsx @@ -13,9 +13,8 @@ import { EuiLink, EuiToolTip, } from '@elastic/eui'; -import React, { useMemo, useCallback, SyntheticEvent } from 'react'; +import React, { useMemo, useCallback, SyntheticEvent, MouseEventHandler, MouseEvent } from 'react'; import { isArray, isNil } from 'lodash/fp'; - import { IP_REPUTATION_LINKS_SETTING, APP_UI_ID } from '../../../../common/constants'; import { DefaultFieldRendererOverflow, @@ -34,13 +33,13 @@ import { FlowTarget, FlowTargetSourceDest, } from '../../../../common/search_strategy/security_solution/network'; -import { useUiSetting$, useKibana } from '../../lib/kibana'; +import { useUiSetting$, useKibana, useNavigation } from '../../lib/kibana'; import { isUrlInvalid } from '../../utils/validators'; import * as i18n from './translations'; import { SecurityPageName } from '../../../app/types'; import { getUsersDetailsUrl } from '../link_to/redirect_to_users'; -import { LinkAnchor, GenericLinkButton, PortContainer, Comma } from './helpers'; +import { LinkAnchor, GenericLinkButton, PortContainer, Comma, LinkButton } from './helpers'; import { HostsTableType } from '../../../hosts/store/model'; export { LinkButton, LinkAnchor } from './helpers'; @@ -514,3 +513,66 @@ export const WhoIsLink = React.memo<{ children?: React.ReactNode; domain: string ); WhoIsLink.displayName = 'WhoIsLink'; + +interface SecuritySolutionLinkProps { + deepLinkId: SecurityPageName; + path?: string; +} + +type LinkClickEvent = MouseEvent; +type LinkClickEventHandler = MouseEventHandler; + +interface SecuritySolutionInjectedLinkProps { + onClick?: LinkClickEventHandler; + href?: string; +} + +/** + * HOC that wraps any Link component and makes it a Security solutions internal navigation Link. + * + * It injects `onClick` and 'href' into the Link component calculated based on the` deepLinkId` and `path` parameters. + */ +export const withSecuritySolutionLink = ( + WrappedComponent: React.FC +) => { + const SecuritySolutionLink: React.FC> = ({ + deepLinkId, + path, + onClick: onClickProps, + ...rest + }) => { + const { formatUrl } = useFormatUrl(deepLinkId); + const { navigateTo } = useNavigation(); + const url = useMemo(() => formatUrl(path ?? ''), [formatUrl, path]); + + const onClick = useCallback( + (ev: LinkClickEvent) => { + ev.preventDefault(); + + if (onClickProps) { + onClickProps(ev); + } + + navigateTo({ url }); + }, + [navigateTo, url, onClickProps] + ); + + return ; + }; + return SecuritySolutionLink; +}; + +/** + * Security Solutions internal link. + * + * `const example = () => ;` + */ +export const SecuritySolutionLinkButton = withSecuritySolutionLink(LinkButton); + +/** + * Security Solutions internal link. + * + * `const example = () => ;` + */ +export const SecuritySolutionLinkAnchor = withSecuritySolutionLink(LinkAnchor); From bdf8f2c52736020b6b37281355daa986d68cec1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Tue, 26 Apr 2022 14:41:16 +0200 Subject: [PATCH 07/27] [Stack Monitoring] Convert beats routes to TypeScript (#130725) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/monitoring/common/ccs_utils.ts | 4 +-- .../common/http_api/apm/post_apm_instance.ts | 12 +++---- .../common/http_api/apm/post_apm_instances.ts | 12 +++---- .../common/http_api/apm/post_apm_overview.ts | 12 +++---- .../monitoring/common/http_api/beats/index.ts | 10 ++++++ .../common/http_api/beats/post_beat_detail.ts | 29 +++++++++++++++ .../http_api/beats/post_beats_listing.ts | 28 +++++++++++++++ .../http_api/beats/post_beats_overview.ts | 28 +++++++++++++++ .../monitoring/common/http_api/shared/ccs.ts | 10 ++++++ .../common/http_api/shared/cluster.ts | 10 ++++++ .../common/http_api/shared/index.ts | 10 ++++++ .../common/http_api/shared/time_range.ts | 13 +++++++ .../server/lib/details/get_metrics.ts | 13 +++++-- .../routes/api/v1/apm/metric_set_instance.ts | 4 ++- .../routes/api/v1/apm/metric_set_overview.ts | 4 ++- .../beats/{beat_detail.js => beat_detail.ts} | 35 ++++++++---------- .../api/v1/beats/{beats.js => beats.ts} | 36 +++++++++---------- .../api/v1/beats/{index.js => index.ts} | 0 ...ric_set_detail.js => metric_set_detail.ts} | 4 ++- ...set_overview.js => metric_set_overview.ts} | 4 ++- .../api/v1/beats/{overview.js => overview.ts} | 36 +++++++++---------- 21 files changed, 224 insertions(+), 90 deletions(-) create mode 100644 x-pack/plugins/monitoring/common/http_api/beats/index.ts create mode 100644 x-pack/plugins/monitoring/common/http_api/beats/post_beat_detail.ts create mode 100644 x-pack/plugins/monitoring/common/http_api/beats/post_beats_listing.ts create mode 100644 x-pack/plugins/monitoring/common/http_api/beats/post_beats_overview.ts create mode 100644 x-pack/plugins/monitoring/common/http_api/shared/ccs.ts create mode 100644 x-pack/plugins/monitoring/common/http_api/shared/cluster.ts create mode 100644 x-pack/plugins/monitoring/common/http_api/shared/index.ts create mode 100644 x-pack/plugins/monitoring/common/http_api/shared/time_range.ts rename x-pack/plugins/monitoring/server/routes/api/v1/beats/{beat_detail.js => beat_detail.ts} (71%) rename x-pack/plugins/monitoring/server/routes/api/v1/beats/{beats.js => beats.ts} (61%) rename x-pack/plugins/monitoring/server/routes/api/v1/beats/{index.js => index.ts} (100%) rename x-pack/plugins/monitoring/server/routes/api/v1/beats/{metric_set_detail.js => metric_set_detail.ts} (91%) rename x-pack/plugins/monitoring/server/routes/api/v1/beats/{metric_set_overview.js => metric_set_overview.ts} (89%) rename x-pack/plugins/monitoring/server/routes/api/v1/beats/{overview.js => overview.ts} (68%) diff --git a/x-pack/plugins/monitoring/common/ccs_utils.ts b/x-pack/plugins/monitoring/common/ccs_utils.ts index 69cc1c25c372a..fea8f79bbfbbf 100644 --- a/x-pack/plugins/monitoring/common/ccs_utils.ts +++ b/x-pack/plugins/monitoring/common/ccs_utils.ts @@ -23,7 +23,7 @@ export function prefixIndexPatternWithCcs( config: MonitoringConfig, indexPattern: string, ccs?: string -) { +): string { const ccsEnabled = config.ui.ccs.enabled; if (!ccsEnabled || !ccs) { return indexPattern; @@ -67,7 +67,7 @@ export function prefixIndexPatternWithCcs( * @param {String} indexName The index's name, possibly including the cross-cluster prefix * @return {String} {@code null} if none. Otherwise the cluster prefix. */ -export function parseCrossClusterPrefix(indexName: string) { +export function parseCrossClusterPrefix(indexName: string): string | null { const colonIndex = indexName.indexOf(':'); if (colonIndex === -1) { diff --git a/x-pack/plugins/monitoring/common/http_api/apm/post_apm_instance.ts b/x-pack/plugins/monitoring/common/http_api/apm/post_apm_instance.ts index 537b68dabd888..c0bd4688ce0c0 100644 --- a/x-pack/plugins/monitoring/common/http_api/apm/post_apm_instance.ts +++ b/x-pack/plugins/monitoring/common/http_api/apm/post_apm_instance.ts @@ -6,26 +6,24 @@ */ import * as rt from 'io-ts'; +import { ccsRT, clusterUuidRT, timeRangeRT } from '../shared'; export const postApmInstanceRequestParamsRT = rt.type({ - clusterUuid: rt.string, + clusterUuid: clusterUuidRT, apmUuid: rt.string, }); export const postApmInstanceRequestPayloadRT = rt.intersection([ rt.partial({ - ccs: rt.string, + ccs: ccsRT, }), rt.type({ - timeRange: rt.type({ - min: rt.string, - max: rt.string, - }), + timeRange: timeRangeRT, }), ]); export type PostApmInstanceRequestPayload = rt.TypeOf; export const postApmInstanceResponsePayloadRT = rt.type({ - data: rt.string, + // TODO: add payload entries }); diff --git a/x-pack/plugins/monitoring/common/http_api/apm/post_apm_instances.ts b/x-pack/plugins/monitoring/common/http_api/apm/post_apm_instances.ts index 9e5510ed7a8f9..f6b10eccbea91 100644 --- a/x-pack/plugins/monitoring/common/http_api/apm/post_apm_instances.ts +++ b/x-pack/plugins/monitoring/common/http_api/apm/post_apm_instances.ts @@ -6,25 +6,23 @@ */ import * as rt from 'io-ts'; +import { ccsRT, clusterUuidRT, timeRangeRT } from '../shared'; export const postApmInstancesRequestParamsRT = rt.type({ - clusterUuid: rt.string, + clusterUuid: clusterUuidRT, }); export const postApmInstancesRequestPayloadRT = rt.intersection([ rt.partial({ - ccs: rt.string, + ccs: ccsRT, }), rt.type({ - timeRange: rt.type({ - min: rt.string, - max: rt.string, - }), + timeRange: timeRangeRT, }), ]); export type PostApmInstancesRequestPayload = rt.TypeOf; export const postApmInstancesResponsePayloadRT = rt.type({ - data: rt.string, + // TODO: add payload entries }); diff --git a/x-pack/plugins/monitoring/common/http_api/apm/post_apm_overview.ts b/x-pack/plugins/monitoring/common/http_api/apm/post_apm_overview.ts index e7da157984b1d..951d8103249d8 100644 --- a/x-pack/plugins/monitoring/common/http_api/apm/post_apm_overview.ts +++ b/x-pack/plugins/monitoring/common/http_api/apm/post_apm_overview.ts @@ -6,25 +6,23 @@ */ import * as rt from 'io-ts'; +import { ccsRT, clusterUuidRT, timeRangeRT } from '../shared'; export const postApmOverviewRequestParamsRT = rt.type({ - clusterUuid: rt.string, + clusterUuid: clusterUuidRT, }); export const postApmOverviewRequestPayloadRT = rt.intersection([ rt.partial({ - ccs: rt.string, + ccs: ccsRT, }), rt.type({ - timeRange: rt.type({ - min: rt.string, - max: rt.string, - }), + timeRange: timeRangeRT, }), ]); export type PostApmOverviewRequestPayload = rt.TypeOf; export const postApmOverviewResponsePayloadRT = rt.type({ - data: rt.string, + // TODO: add payload entries }); diff --git a/x-pack/plugins/monitoring/common/http_api/beats/index.ts b/x-pack/plugins/monitoring/common/http_api/beats/index.ts new file mode 100644 index 0000000000000..c10908392b84b --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/beats/index.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 * from './post_beats_overview'; +export * from './post_beats_listing'; +export * from './post_beat_detail'; diff --git a/x-pack/plugins/monitoring/common/http_api/beats/post_beat_detail.ts b/x-pack/plugins/monitoring/common/http_api/beats/post_beat_detail.ts new file mode 100644 index 0000000000000..6db962395a42d --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/beats/post_beat_detail.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; +import { ccsRT, clusterUuidRT, timeRangeRT } from '../shared'; + +export const postBeatDetailRequestParamsRT = rt.type({ + clusterUuid: clusterUuidRT, + beatUuid: rt.string, +}); + +export const postBeatDetailRequestPayloadRT = rt.intersection([ + rt.partial({ + ccs: ccsRT, + }), + rt.type({ + timeRange: timeRangeRT, + }), +]); + +export type PostBeatDetailRequestPayload = rt.TypeOf; + +export const postBeatDetailResponsePayloadRT = rt.type({ + // TODO: add payload entries +}); diff --git a/x-pack/plugins/monitoring/common/http_api/beats/post_beats_listing.ts b/x-pack/plugins/monitoring/common/http_api/beats/post_beats_listing.ts new file mode 100644 index 0000000000000..007982849506b --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/beats/post_beats_listing.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 * as rt from 'io-ts'; +import { ccsRT, clusterUuidRT, timeRangeRT } from '../shared'; + +export const postBeatsListingRequestParamsRT = rt.type({ + clusterUuid: clusterUuidRT, +}); + +export const postBeatsListingRequestPayloadRT = rt.intersection([ + rt.partial({ + ccs: ccsRT, + }), + rt.type({ + timeRange: timeRangeRT, + }), +]); + +export type PostBeatsListingRequestPayload = rt.TypeOf; + +export const postBeatsListingResponsePayloadRT = rt.type({ + // TODO: add payload entries +}); diff --git a/x-pack/plugins/monitoring/common/http_api/beats/post_beats_overview.ts b/x-pack/plugins/monitoring/common/http_api/beats/post_beats_overview.ts new file mode 100644 index 0000000000000..b7d73dd0d2eab --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/beats/post_beats_overview.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 * as rt from 'io-ts'; +import { clusterUuidRT, ccsRT, timeRangeRT } from '../shared'; + +export const postBeatsOverviewRequestParamsRT = rt.type({ + clusterUuid: clusterUuidRT, +}); + +export const postBeatsOverviewRequestPayloadRT = rt.intersection([ + rt.partial({ + ccs: ccsRT, + }), + rt.type({ + timeRange: timeRangeRT, + }), +]); + +export type PostBeatsOverviewRequestPayload = rt.TypeOf; + +export const postBeatsOverviewResponsePayloadRT = rt.type({ + // TODO: add payload entries +}); diff --git a/x-pack/plugins/monitoring/common/http_api/shared/ccs.ts b/x-pack/plugins/monitoring/common/http_api/shared/ccs.ts new file mode 100644 index 0000000000000..041b14fa531e3 --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/shared/ccs.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. + */ + +import * as rt from 'io-ts'; + +export const ccsRT = rt.string; diff --git a/x-pack/plugins/monitoring/common/http_api/shared/cluster.ts b/x-pack/plugins/monitoring/common/http_api/shared/cluster.ts new file mode 100644 index 0000000000000..43a3c587c1db7 --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/shared/cluster.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. + */ + +import * as rt from 'io-ts'; + +export const clusterUuidRT = rt.string; diff --git a/x-pack/plugins/monitoring/common/http_api/shared/index.ts b/x-pack/plugins/monitoring/common/http_api/shared/index.ts new file mode 100644 index 0000000000000..db7d3dce6c463 --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/shared/index.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 * from './cluster'; +export * from './ccs'; +export * from './time_range'; diff --git a/x-pack/plugins/monitoring/common/http_api/shared/time_range.ts b/x-pack/plugins/monitoring/common/http_api/shared/time_range.ts new file mode 100644 index 0000000000000..edeb56d1e2ea1 --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/shared/time_range.ts @@ -0,0 +1,13 @@ +/* + * 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 * as rt from 'io-ts'; + +export const timeRangeRT = rt.type({ + min: rt.string, + max: rt.string, +}); diff --git a/x-pack/plugins/monitoring/server/lib/details/get_metrics.ts b/x-pack/plugins/monitoring/server/lib/details/get_metrics.ts index 83db64add1c69..475d2c681596e 100644 --- a/x-pack/plugins/monitoring/server/lib/details/get_metrics.ts +++ b/x-pack/plugins/monitoring/server/lib/details/get_metrics.ts @@ -13,13 +13,20 @@ import { getTimezone } from '../get_timezone'; import { LegacyRequest } from '../../types'; import { INDEX_PATTERN_TYPES } from '../../../common/constants'; -type Metric = string | { keys: string | string[]; name: string }; +export interface NamedMetricDescriptor { + keys: string | string[]; + name: string; +} + +export type SimpleMetricDescriptor = string; + +export type MetricDescriptor = SimpleMetricDescriptor | NamedMetricDescriptor; // TODO: Switch to an options object argument here export async function getMetrics( req: LegacyRequest, moduleType: INDEX_PATTERN_TYPES, - metricSet: Metric[] = [], + metricSet: MetricDescriptor[] = [], filters: Array> = [], metricOptions: Record = {}, numOfBuckets: number = 0, @@ -42,7 +49,7 @@ export async function getMetrics( } return Promise.all( - metricSet.map((metric: Metric) => { + metricSet.map((metric: MetricDescriptor) => { // metric names match the literal metric name, but they can be supplied in groups or individually let metricNames; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/apm/metric_set_instance.ts b/x-pack/plugins/monitoring/server/routes/api/v1/apm/metric_set_instance.ts index d6fc7cbd2c076..427c0610832d3 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/apm/metric_set_instance.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/apm/metric_set_instance.ts @@ -5,7 +5,9 @@ * 2.0. */ -export const metricSet = [ +import { NamedMetricDescriptor } from '../../../../lib/details/get_metrics'; + +export const metricSet: NamedMetricDescriptor[] = [ { name: 'apm_cpu', keys: ['apm_cpu_total'], diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/apm/metric_set_overview.ts b/x-pack/plugins/monitoring/server/routes/api/v1/apm/metric_set_overview.ts index b0dccb8dd34df..c6f126ca2f6b9 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/apm/metric_set_overview.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/apm/metric_set_overview.ts @@ -5,7 +5,9 @@ * 2.0. */ -export const metricSet = [ +import { NamedMetricDescriptor } from '../../../../lib/details/get_metrics'; + +export const metricSet: NamedMetricDescriptor[] = [ { name: 'apm_cpu', keys: ['apm_cpu_total'], diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.js b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.ts similarity index 71% rename from x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.js rename to x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.ts index 8c28dd420675a..18f6de905dbfe 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.ts @@ -5,32 +5,27 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; import { prefixIndexPatternWithCcs } from '../../../../../common/ccs_utils'; +import { INDEX_PATTERN_BEATS } from '../../../../../common/constants'; +import { + postBeatDetailRequestParamsRT, + postBeatDetailRequestPayloadRT, + postBeatDetailResponsePayloadRT, +} from '../../../../../common/http_api/beats'; import { getBeatSummary } from '../../../../lib/beats'; +import { createValidationFunction } from '../../../../lib/create_route_validation_function'; import { getMetrics } from '../../../../lib/details/get_metrics'; import { handleError } from '../../../../lib/errors'; +import { MonitoringCore } from '../../../../types'; import { metricSet } from './metric_set_detail'; -import { INDEX_PATTERN_BEATS } from '../../../../../common/constants'; -export function beatsDetailRoute(server) { +export function beatsDetailRoute(server: MonitoringCore) { server.route({ - method: 'POST', + method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/beats/beat/{beatUuid}', - config: { - validate: { - params: schema.object({ - clusterUuid: schema.string(), - beatUuid: schema.string(), - }), - body: schema.object({ - ccs: schema.maybe(schema.string()), - timeRange: schema.object({ - min: schema.string(), - max: schema.string(), - }), - }), - }, + validate: { + params: createValidationFunction(postBeatDetailRequestParamsRT), + body: createValidationFunction(postBeatDetailRequestPayloadRT), }, async handler(req) { const clusterUuid = req.params.clusterUuid; @@ -52,10 +47,10 @@ export function beatsDetailRoute(server) { getMetrics(req, 'beats', metricSet, [{ term: { 'beats_stats.beat.uuid': beatUuid } }]), ]); - return { + return postBeatDetailResponsePayloadRT.encode({ summary, metrics, - }; + }); } catch (err) { throw handleError(err, req); } diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.js b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.ts similarity index 61% rename from x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.js rename to x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.ts index 83403eaa355bd..8bf73bb115f74 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.ts @@ -5,29 +5,25 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; import { prefixIndexPatternWithCcs } from '../../../../../common/ccs_utils'; -import { getStats, getBeats } from '../../../../lib/beats'; -import { handleError } from '../../../../lib/errors'; import { INDEX_PATTERN_BEATS } from '../../../../../common/constants'; +import { + postBeatsListingRequestParamsRT, + postBeatsListingRequestPayloadRT, + postBeatsListingResponsePayloadRT, +} from '../../../../../common/http_api/beats'; +import { getBeats, getStats } from '../../../../lib/beats'; +import { createValidationFunction } from '../../../../lib/create_route_validation_function'; +import { handleError } from '../../../../lib/errors'; +import { MonitoringCore } from '../../../../types'; -export function beatsListingRoute(server) { +export function beatsListingRoute(server: MonitoringCore) { server.route({ - method: 'POST', + method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/beats/beats', - config: { - validate: { - params: schema.object({ - clusterUuid: schema.string(), - }), - body: schema.object({ - ccs: schema.maybe(schema.string()), - timeRange: schema.object({ - min: schema.string(), - max: schema.string(), - }), - }), - }, + validate: { + params: createValidationFunction(postBeatsListingRequestParamsRT), + body: createValidationFunction(postBeatsListingRequestPayloadRT), }, async handler(req) { const config = server.config; @@ -41,10 +37,10 @@ export function beatsListingRoute(server) { getBeats(req, beatsIndexPattern, clusterUuid), ]); - return { + return postBeatsListingResponsePayloadRT.encode({ stats, listing, - }; + }); } catch (err) { throw handleError(err, req); } diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/beats/index.js b/x-pack/plugins/monitoring/server/routes/api/v1/beats/index.ts similarity index 100% rename from x-pack/plugins/monitoring/server/routes/api/v1/beats/index.js rename to x-pack/plugins/monitoring/server/routes/api/v1/beats/index.ts diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/beats/metric_set_detail.js b/x-pack/plugins/monitoring/server/routes/api/v1/beats/metric_set_detail.ts similarity index 91% rename from x-pack/plugins/monitoring/server/routes/api/v1/beats/metric_set_detail.js rename to x-pack/plugins/monitoring/server/routes/api/v1/beats/metric_set_detail.ts index c6c1e7598658a..a3cc7f409a7f7 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/beats/metric_set_detail.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/beats/metric_set_detail.ts @@ -5,7 +5,9 @@ * 2.0. */ -export const metricSet = [ +import { MetricDescriptor } from '../../../../lib/details/get_metrics'; + +export const metricSet: MetricDescriptor[] = [ { keys: [ 'beat_pipeline_events_total_rate', diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/beats/metric_set_overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/beats/metric_set_overview.ts similarity index 89% rename from x-pack/plugins/monitoring/server/routes/api/v1/beats/metric_set_overview.js rename to x-pack/plugins/monitoring/server/routes/api/v1/beats/metric_set_overview.ts index c5f0c73dd082d..64f066799c193 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/beats/metric_set_overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/beats/metric_set_overview.ts @@ -5,7 +5,9 @@ * 2.0. */ -export const metricSet = [ +import { MetricDescriptor } from '../../../../lib/details/get_metrics'; + +export const metricSet: MetricDescriptor[] = [ { keys: [ 'beat_cluster_pipeline_events_total_rate', diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.ts similarity index 68% rename from x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.js rename to x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.ts index 9cd9bcd9787e2..6497c5568a1a8 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.ts @@ -5,31 +5,27 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; import { prefixIndexPatternWithCcs } from '../../../../../common/ccs_utils'; -import { getMetrics } from '../../../../lib/details/get_metrics'; +import { INDEX_PATTERN_BEATS } from '../../../../../common/constants'; +import { + postBeatsOverviewRequestParamsRT, + postBeatsOverviewRequestPayloadRT, + postBeatsOverviewResponsePayloadRT, +} from '../../../../../common/http_api/beats'; import { getLatestStats, getStats } from '../../../../lib/beats'; +import { createValidationFunction } from '../../../../lib/create_route_validation_function'; +import { getMetrics } from '../../../../lib/details/get_metrics'; import { handleError } from '../../../../lib/errors'; +import { MonitoringCore } from '../../../../types'; import { metricSet } from './metric_set_overview'; -import { INDEX_PATTERN_BEATS } from '../../../../../common/constants'; -export function beatsOverviewRoute(server) { +export function beatsOverviewRoute(server: MonitoringCore) { server.route({ - method: 'POST', + method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/beats', - config: { - validate: { - params: schema.object({ - clusterUuid: schema.string(), - }), - body: schema.object({ - ccs: schema.maybe(schema.string()), - timeRange: schema.object({ - min: schema.string(), - max: schema.string(), - }), - }), - }, + validate: { + params: createValidationFunction(postBeatsOverviewRequestParamsRT), + body: createValidationFunction(postBeatsOverviewRequestPayloadRT), }, async handler(req) { const config = server.config; @@ -44,11 +40,11 @@ export function beatsOverviewRoute(server) { getMetrics(req, 'beats', metricSet), ]); - return { + return postBeatsOverviewResponsePayloadRT.encode({ ...latest, stats, metrics, - }; + }); } catch (err) { throw handleError(err, req); } From dad4963bf0cadce113c51de6a066337fdcc51006 Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Tue, 26 Apr 2022 08:42:50 -0400 Subject: [PATCH 08/27] Update synthetics.sh (#130940) --- .buildkite/scripts/steps/functional/synthetics.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/scripts/steps/functional/synthetics.sh b/.buildkite/scripts/steps/functional/synthetics.sh index ecb2922f89c8d..b72e85c7b6a4c 100644 --- a/.buildkite/scripts/steps/functional/synthetics.sh +++ b/.buildkite/scripts/steps/functional/synthetics.sh @@ -14,4 +14,4 @@ echo "--- Uptime @elastic/synthetics Tests" cd "$XPACK_DIR" checks-reporter-with-killswitch "Uptime @elastic/synthetics Tests" \ - node plugins/uptime/scripts/e2e.js --kibana-install-dir "$KIBANA_BUILD_LOCATION" --grep "MonitorManagement-monitor*" \ No newline at end of file + node plugins/synthetics/scripts/e2e.js --kibana-install-dir "$KIBANA_BUILD_LOCATION" --grep "MonitorManagement-monitor*" From 6ad418b275da3f57b26c0b3e62781a5bc7ad0f9a Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Tue, 26 Apr 2022 10:05:16 -0400 Subject: [PATCH 09/27] [ResponseOps][actions] add config for allow-listing email address domains (#129001) resolves https://github.com/elastic/kibana/issues/126944 Adds a new configuration setting for the actions plugin, xpack.actions.email.domain_allowlist, which is an array of domain name strings which are allowed to be sent emails by the email connector. --- .../connectors/action-types/email.asciidoc | 2 + docs/settings/alert-action-settings.asciidoc | 5 + package.json | 1 + packages/kbn-optimizer/limits.yml | 1 + .../resources/base/bin/kibana-docker | 1 + .../test_suites/core_plugins/rendering.ts | 1 + x-pack/plugins/actions/common/index.ts | 3 + .../actions/common/mustache_template.test.ts | 33 ++ .../actions/common/mustache_template.ts | 18 ++ .../actions/common/servicenow_config.ts | 56 ++++ x-pack/plugins/actions/common/types.ts | 15 + .../common/validate_email_addresses.test.ts | 284 ++++++++++++++++++ .../common/validate_email_addresses.ts | 114 +++++++ x-pack/plugins/actions/kibana.json | 3 +- x-pack/plugins/actions/public/index.ts | 15 + x-pack/plugins/actions/public/plugin.test.ts | 65 ++++ x-pack/plugins/actions/public/plugin.ts | 44 +++ .../actions/server/actions_config.mock.ts | 1 + .../actions/server/actions_config.test.ts | 44 +++ .../plugins/actions/server/actions_config.ts | 26 +- .../server/builtin_action_types/email.test.ts | 134 ++++++--- .../server/builtin_action_types/email.ts | 74 +++-- .../lib/send_email.test.ts | 264 +--------------- .../builtin_action_types/servicenow/types.ts | 14 +- .../server/builtin_action_types/teams.test.ts | 28 +- .../builtin_action_types/webhook.test.ts | 28 +- x-pack/plugins/actions/server/config.test.ts | 19 ++ x-pack/plugins/actions/server/config.ts | 5 + x-pack/plugins/actions/server/index.ts | 3 + x-pack/plugins/actions/server/plugin.ts | 1 + x-pack/plugins/actions/server/routes/index.ts | 2 + x-pack/plugins/actions/tsconfig.json | 3 +- .../plugins/triggers_actions_ui/kibana.json | 6 +- .../builtin_action_types/email/email.test.tsx | 54 +++- .../builtin_action_types/email/email.tsx | 82 ++++- .../email/translations.ts | 17 ++ .../es_index/es_index.test.tsx | 3 +- .../components/builtin_action_types/index.ts | 12 +- .../builtin_action_types/jira/jira.test.tsx | 3 +- .../pagerduty/pagerduty.test.tsx | 3 +- .../resilient/resilient.test.tsx | 3 +- .../server_log/server_log.test.tsx | 3 +- .../builtin_action_types/servicenow/api.ts | 4 +- .../servicenow/servicenow.test.tsx | 3 +- .../servicenow/servicenow_connectors.tsx | 3 +- .../servicenow/update_connector.tsx | 3 +- .../builtin_action_types/slack/slack.test.tsx | 3 +- .../swimlane/swimlane.test.tsx | 3 +- .../builtin_action_types/teams/teams.test.tsx | 3 +- .../webhook/webhook.test.tsx | 3 +- .../xmatters/xmatters.test.tsx | 3 +- .../triggers_actions_ui/public/mocks.ts | 9 +- .../triggers_actions_ui/public/plugin.ts | 5 + .../alerting_api_integration/common/config.ts | 7 + .../spaces_only/config.ts | 3 + .../actions/builtin_action_types/email.ts | 175 +++++++++++ .../spaces_only/tests/actions/index.ts | 1 + yarn.lock | 5 + 58 files changed, 1299 insertions(+), 427 deletions(-) create mode 100644 x-pack/plugins/actions/common/mustache_template.test.ts create mode 100644 x-pack/plugins/actions/common/mustache_template.ts create mode 100644 x-pack/plugins/actions/common/servicenow_config.ts create mode 100644 x-pack/plugins/actions/common/validate_email_addresses.test.ts create mode 100644 x-pack/plugins/actions/common/validate_email_addresses.ts create mode 100644 x-pack/plugins/actions/public/index.ts create mode 100644 x-pack/plugins/actions/public/plugin.test.ts create mode 100644 x-pack/plugins/actions/public/plugin.ts create mode 100644 x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/email.ts diff --git a/docs/management/connectors/action-types/email.asciidoc b/docs/management/connectors/action-types/email.asciidoc index c080c412f0f6b..af408fd36a23c 100644 --- a/docs/management/connectors/action-types/email.asciidoc +++ b/docs/management/connectors/action-types/email.asciidoc @@ -9,6 +9,8 @@ The email connector uses the SMTP protocol to send mail messages, using an integ NOTE: For emails to have a footer with a link back to {kib}, set the <> configuration setting. +NOTE: When the <> configuration setting is used, the email addresses used for all of the Sender (from), To, CC, and BCC properties must have email domains specified in the configuration setting. + [float] [[email-connector-configuration]] ==== Connector configuration diff --git a/docs/settings/alert-action-settings.asciidoc b/docs/settings/alert-action-settings.asciidoc index 384c4b696521d..7579ec207c835 100644 --- a/docs/settings/alert-action-settings.asciidoc +++ b/docs/settings/alert-action-settings.asciidoc @@ -125,6 +125,11 @@ The contents of a PEM-encoded certificate file, or multiple files appended into a single string. This configuration can be used for environments where the files cannot be made available. +[[action-config-email-domain-allowlist]] `xpack.actions.email.domain_allowlist` {ess-icon}:: +A list of allowed email domains which can be used with the email connector. When this setting is not used, all email domains are allowed. When this setting is used, if any email is attempted to be sent that includes an addressee with an email domain that is not in the allowlist, or the from address domain is not in the allowlist, the run of the connector will fail with a message indicating the emails not allowed. + +WARNING: This feature is available in {kib} 7.17.4 and 8.3.0 onwards but is not supported in {kib} 8.0, 8.1 or 8.2. As such this settings should be removed before upgrading from 7.17 to 8.0, 8.1 or 8.2. It is possible to configure the settings in 7.17.4 and then upgrade to 8.3.0 directly. + `xpack.actions.enabledActionTypes` {ess-icon}:: A list of action types that are enabled. It defaults to `[*]`, enabling all types. The names for built-in {kib} action types are prefixed with a `.` and include: `.email`, `.index`, `.jira`, `.pagerduty`, `.resilient`, `.server-log`, `.servicenow`, .`servicenow-itom`, `.servicenow-sir`, `.slack`, `.swimlane`, `.teams`, `.xmatters`, and `.webhook`. An empty list `[]` will disable all action types. + diff --git a/package.json b/package.json index 8f30166439b8b..4c7db193368a6 100644 --- a/package.json +++ b/package.json @@ -250,6 +250,7 @@ "deepmerge": "^4.2.2", "del": "^5.1.0", "elastic-apm-node": "^3.31.0", + "email-addresses": "^5.0.0", "execa": "^4.0.2", "exit-hook": "^2.2.0", "expiry-js": "0.1.7", diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 98089f1f1c5fb..dc16c080306ad 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -1,5 +1,6 @@ pageLoadAssetSize: advancedSettings: 27596 + actions: 20000 alerting: 106936 apm: 64385 canvas: 1066647 diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 6ec12aab3c002..7b569f8d02068 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -188,6 +188,7 @@ kibana_vars=( vis_type_vega.enableExternalUrls xpack.actions.allowedHosts xpack.actions.customHostSettings + xpack.actions.email.domain_allowlist xpack.actions.enabledActionTypes xpack.actions.maxResponseContentLength xpack.actions.preconfigured diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 5f124adc92960..b7adbfa17301c 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -136,6 +136,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'usageCollection.uiCounters.debug (boolean)', 'usageCollection.uiCounters.enabled (boolean)', 'vis_type_vega.enableExternalUrls (boolean)', + 'xpack.actions.email.domain_allowlist (array)', 'xpack.apm.profilingEnabled (boolean)', 'xpack.apm.serviceMapEnabled (boolean)', 'xpack.apm.ui.enabled (boolean)', diff --git a/x-pack/plugins/actions/common/index.ts b/x-pack/plugins/actions/common/index.ts index 1e51adf3e9d09..1b26f04e72265 100644 --- a/x-pack/plugins/actions/common/index.ts +++ b/x-pack/plugins/actions/common/index.ts @@ -11,6 +11,9 @@ export * from './types'; export * from './alert_history_schema'; export * from './rewrite_request_case'; +export * from './mustache_template'; +export * from './validate_email_addresses'; +export * from './servicenow_config'; export const BASE_ACTION_API_PATH = '/api/actions'; export const INTERNAL_BASE_ACTION_API_PATH = '/internal/actions'; diff --git a/x-pack/plugins/actions/common/mustache_template.test.ts b/x-pack/plugins/actions/common/mustache_template.test.ts new file mode 100644 index 0000000000000..2d35c00a8efa1 --- /dev/null +++ b/x-pack/plugins/actions/common/mustache_template.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { hasMustacheTemplate, withoutMustacheTemplate } from './mustache_template'; + +const nonMustacheEmails = ['', 'zero@a.b.c', '}}{{']; +const mustacheEmails = ['{{}}', '"bob" {{}}@elastic.co', 'sneaky{{\n}}pete']; + +describe('mustache_template', () => { + it('hasMustacheTemplate', () => { + for (const email of nonMustacheEmails) { + expect(hasMustacheTemplate(email)).toBe(false); + } + for (const email of mustacheEmails) { + expect(hasMustacheTemplate(email)).toBe(true); + } + }); + + it('withoutMustacheTemplate', () => { + let result = withoutMustacheTemplate(nonMustacheEmails); + expect(result).toEqual(nonMustacheEmails); + + result = withoutMustacheTemplate(mustacheEmails); + expect(result).toEqual([]); + + result = withoutMustacheTemplate(mustacheEmails.concat(nonMustacheEmails)); + expect(result).toEqual(nonMustacheEmails); + }); +}); diff --git a/x-pack/plugins/actions/common/mustache_template.ts b/x-pack/plugins/actions/common/mustache_template.ts new file mode 100644 index 0000000000000..45e4b6d86ce66 --- /dev/null +++ b/x-pack/plugins/actions/common/mustache_template.ts @@ -0,0 +1,18 @@ +/* + * 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 MustacheInEmailRegExp = /\{\{((.|\n)*)\}\}/; + +/** does the string contain `{{.*}}`? */ +export function hasMustacheTemplate(string: string): boolean { + return !!string.match(MustacheInEmailRegExp); +} + +/** filter strings that do not contain `{{.*}}` */ +export function withoutMustacheTemplate(strings: string[]): string[] { + return strings.filter((string) => !hasMustacheTemplate(string)); +} diff --git a/x-pack/plugins/actions/common/servicenow_config.ts b/x-pack/plugins/actions/common/servicenow_config.ts new file mode 100644 index 0000000000000..994f6cb33524f --- /dev/null +++ b/x-pack/plugins/actions/common/servicenow_config.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. + */ + +export const serviceNowITSMTable = 'incident'; +export const serviceNowSIRTable = 'sn_si_incident'; + +export const ServiceNowITSMActionTypeId = '.servicenow'; +export const ServiceNowSIRActionTypeId = '.servicenow-sir'; +export const ServiceNowITOMActionTypeId = '.servicenow-itom'; + +const SN_ITSM_APP_ID = '7148dbc91bf1f450ced060a7234bcb88'; +const SN_SIR_APP_ID = '2f0746801baeb01019ae54e4604bcb0f'; + +export interface SNProductsConfigValue { + table: string; + appScope: string; + useImportAPI: boolean; + importSetTable: string; + commentFieldKey: string; + appId?: string; +} + +export type SNProductsConfig = Record; + +export const snExternalServiceConfig: SNProductsConfig = { + '.servicenow': { + importSetTable: 'x_elas2_inc_int_elastic_incident', + appScope: 'x_elas2_inc_int', + table: 'incident', + useImportAPI: true, + commentFieldKey: 'work_notes', + appId: SN_ITSM_APP_ID, + }, + '.servicenow-sir': { + importSetTable: 'x_elas2_sir_int_elastic_si_incident', + appScope: 'x_elas2_sir_int', + table: 'sn_si_incident', + useImportAPI: true, + commentFieldKey: 'work_notes', + appId: SN_SIR_APP_ID, + }, + '.servicenow-itom': { + importSetTable: 'x_elas2_inc_int_elastic_incident', + appScope: 'x_elas2_inc_int', + table: 'em_event', + useImportAPI: false, + commentFieldKey: 'work_notes', + }, +}; + +export const FIELD_PREFIX = 'u_'; +export const DEFAULT_ALERTS_GROUPING_KEY = '{{rule.id}}:{{alert.id}}'; diff --git a/x-pack/plugins/actions/common/types.ts b/x-pack/plugins/actions/common/types.ts index 20de64a7cc3b5..751b403780080 100644 --- a/x-pack/plugins/actions/common/types.ts +++ b/x-pack/plugins/actions/common/types.ts @@ -16,6 +16,17 @@ export interface ActionType { minimumLicenseRequired: LicenseType; } +export enum InvalidEmailReason { + invalid = 'invalid', + notAllowed = 'notAllowed', +} + +export interface ValidatedEmail { + address: string; + valid: boolean; + reason?: InvalidEmailReason; +} + export interface ActionResult { id: string; actionTypeId: string; @@ -49,3 +60,7 @@ export function isActionTypeExecutorResult( ActionTypeExecutorResultStatusValues.includes(unsafeResult?.status) ); } + +export interface ActionsPublicConfigType { + allowedEmailDomains: string[]; +} diff --git a/x-pack/plugins/actions/common/validate_email_addresses.test.ts b/x-pack/plugins/actions/common/validate_email_addresses.test.ts new file mode 100644 index 0000000000000..af76cdef72966 --- /dev/null +++ b/x-pack/plugins/actions/common/validate_email_addresses.test.ts @@ -0,0 +1,284 @@ +/* + * 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 { ValidatedEmail, InvalidEmailReason } from './types'; +import { + validateEmailAddressesAsAlwaysValid, + validateEmailAddresses, + invalidEmailsAsMessage, +} from './validate_email_addresses'; + +const AllowedDomains = ['elastic.co', 'dev.elastic.co', 'found.no']; +const Emails = [ + 'bob@elastic.co', + '"Dr Tom" ', + 'jim@dev.elastic.co', + 'rex@found.no', + 'sal@alerting.dev.elastic.co', + 'nancy@example.com', + '"Dr RFC 5322" ', + 'totally invalid', + '{{sneaky}}', +]; + +describe('validate_email_address', () => { + test('validateEmailAddressesAsAlwaysValid()', () => { + const emails = ['bob@example.com', 'invalid-email', '']; + const validatedEmails = validateEmailAddressesAsAlwaysValid(emails); + + expect(validatedEmails).toMatchInlineSnapshot(` + Array [ + Object { + "address": "bob@example.com", + "valid": true, + }, + Object { + "address": "invalid-email", + "valid": true, + }, + Object { + "address": "", + "valid": true, + }, + ] + `); + }); + + describe('validateEmailAddresses()', () => { + test('with configured allowlist and no mustache filtering', () => { + const result = validateEmailAddresses(AllowedDomains, Emails); + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "address": "bob@elastic.co", + "valid": true, + }, + Object { + "address": "\\"Dr Tom\\" ", + "valid": true, + }, + Object { + "address": "jim@dev.elastic.co", + "valid": true, + }, + Object { + "address": "rex@found.no", + "valid": true, + }, + Object { + "address": "sal@alerting.dev.elastic.co", + "reason": "notAllowed", + "valid": false, + }, + Object { + "address": "nancy@example.com", + "reason": "notAllowed", + "valid": false, + }, + Object { + "address": "\\"Dr RFC 5322\\" ", + "reason": "notAllowed", + "valid": false, + }, + Object { + "address": "totally invalid", + "reason": "invalid", + "valid": false, + }, + Object { + "address": "{{sneaky}}", + "reason": "invalid", + "valid": false, + }, + ] + `); + }); + + test('with configured allowlist and mustache filtering', () => { + const result = validateEmailAddresses(AllowedDomains, Emails, { + treatMustacheTemplatesAsValid: true, + }); + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "address": "bob@elastic.co", + "valid": true, + }, + Object { + "address": "\\"Dr Tom\\" ", + "valid": true, + }, + Object { + "address": "jim@dev.elastic.co", + "valid": true, + }, + Object { + "address": "rex@found.no", + "valid": true, + }, + Object { + "address": "sal@alerting.dev.elastic.co", + "reason": "notAllowed", + "valid": false, + }, + Object { + "address": "nancy@example.com", + "reason": "notAllowed", + "valid": false, + }, + Object { + "address": "\\"Dr RFC 5322\\" ", + "reason": "notAllowed", + "valid": false, + }, + Object { + "address": "totally invalid", + "reason": "invalid", + "valid": false, + }, + Object { + "address": "{{sneaky}}", + "valid": true, + }, + ] + `); + }); + + test('with no configured allowlist and no mustache filtering', () => { + const result = validateEmailAddresses(null, Emails); + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "address": "bob@elastic.co", + "valid": true, + }, + Object { + "address": "\\"Dr Tom\\" ", + "valid": true, + }, + Object { + "address": "jim@dev.elastic.co", + "valid": true, + }, + Object { + "address": "rex@found.no", + "valid": true, + }, + Object { + "address": "sal@alerting.dev.elastic.co", + "valid": true, + }, + Object { + "address": "nancy@example.com", + "valid": true, + }, + Object { + "address": "\\"Dr RFC 5322\\" ", + "valid": true, + }, + Object { + "address": "totally invalid", + "valid": true, + }, + Object { + "address": "{{sneaky}}", + "valid": true, + }, + ] + `); + }); + + test('with no configured allowlist and mustache filtering', () => { + const result = validateEmailAddresses(null, Emails, { treatMustacheTemplatesAsValid: true }); + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "address": "bob@elastic.co", + "valid": true, + }, + Object { + "address": "\\"Dr Tom\\" ", + "valid": true, + }, + Object { + "address": "jim@dev.elastic.co", + "valid": true, + }, + Object { + "address": "rex@found.no", + "valid": true, + }, + Object { + "address": "sal@alerting.dev.elastic.co", + "valid": true, + }, + Object { + "address": "nancy@example.com", + "valid": true, + }, + Object { + "address": "\\"Dr RFC 5322\\" ", + "valid": true, + }, + Object { + "address": "totally invalid", + "valid": true, + }, + Object { + "address": "{{sneaky}}", + "valid": true, + }, + ] + `); + }); + }); + + const entriesGood: ValidatedEmail[] = [ + { address: 'a', valid: true }, + { address: 'b', valid: true }, + ]; + + const entriesInvalid: ValidatedEmail[] = [ + { address: 'c', valid: false, reason: InvalidEmailReason.invalid }, + { address: 'd', valid: false, reason: InvalidEmailReason.invalid }, + ]; + + const entriesNotAllowed: ValidatedEmail[] = [ + { address: 'e', valid: false, reason: InvalidEmailReason.notAllowed }, + { address: 'f', valid: false, reason: InvalidEmailReason.notAllowed }, + ]; + + describe('invalidEmailsAsMessage()', () => { + test('with all valid entries', () => { + expect(invalidEmailsAsMessage(entriesGood)).toMatchInlineSnapshot(`undefined`); + expect(invalidEmailsAsMessage([entriesGood[0]])).toMatchInlineSnapshot(`undefined`); + }); + + test('with some invalid entries', () => { + let entries = entriesGood.concat(entriesInvalid); + expect(invalidEmailsAsMessage(entries)).toMatchInlineSnapshot(`"not valid emails: c, d"`); + + entries = entriesGood.concat(entriesInvalid[0]); + expect(invalidEmailsAsMessage(entries)).toMatchInlineSnapshot(`"not valid emails: c"`); + }); + + test('with some not allowed entries', () => { + let entries = entriesGood.concat(entriesNotAllowed); + expect(invalidEmailsAsMessage(entries)).toMatchInlineSnapshot(`"not allowed emails: e, f"`); + + entries = entriesGood.concat(entriesNotAllowed[0]); + expect(invalidEmailsAsMessage(entries)).toMatchInlineSnapshot(`"not allowed emails: e"`); + }); + + test('with some invalid and not allowed entries', () => { + const entries = entriesGood.concat(entriesInvalid).concat(entriesNotAllowed); + expect(invalidEmailsAsMessage(entries)).toMatchInlineSnapshot( + `"not valid emails: c, d; not allowed emails: e, f"` + ); + }); + }); +}); diff --git a/x-pack/plugins/actions/common/validate_email_addresses.ts b/x-pack/plugins/actions/common/validate_email_addresses.ts new file mode 100644 index 0000000000000..9a1d6f0e1405a --- /dev/null +++ b/x-pack/plugins/actions/common/validate_email_addresses.ts @@ -0,0 +1,114 @@ +/* + * 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 { parseAddressList } from 'email-addresses'; +import { ValidatedEmail, InvalidEmailReason } from './types'; +import { hasMustacheTemplate } from './mustache_template'; + +/** Options that can be used when validating email addresses */ +export interface ValidateEmailAddressesOptions { + /** treat any address which contains a mustache template as valid */ + treatMustacheTemplatesAsValid?: boolean; +} + +// this can be useful for cases where a plugin needs this function, +// but the actions plugin may not be available. This could be used +// as a stub for the real implementation. +export function validateEmailAddressesAsAlwaysValid(addresses: string[]): ValidatedEmail[] { + return addresses.map((address) => ({ address, valid: true })); +} + +export function validateEmailAddresses( + allowedDomains: string[] | null, + addresses: string[], + options: ValidateEmailAddressesOptions = {} +): ValidatedEmail[] { + // note: this is the legacy default, which would in theory allow + // mustache strings, so options.allowMustache is ignored in this + // case - everything is valid! + if (allowedDomains == null) { + return validateEmailAddressesAsAlwaysValid(addresses); + } + + return addresses.map((address) => validateEmailAddress(allowedDomains, address, options)); +} + +export function invalidEmailsAsMessage(validatedEmails: ValidatedEmail[]): string | undefined { + const invalid = validatedEmails.filter( + (validated) => !validated.valid && validated.reason === InvalidEmailReason.invalid + ); + const notAllowed = validatedEmails.filter( + (validated) => !validated.valid && validated.reason === InvalidEmailReason.notAllowed + ); + + const messages: string[] = []; + if (invalid.length !== 0) { + messages.push(`not valid emails: ${addressesFromValidatedEmails(invalid).join(', ')}`); + } + if (notAllowed.length !== 0) { + messages.push(`not allowed emails: ${addressesFromValidatedEmails(notAllowed).join(', ')}`); + } + + if (messages.length === 0) return; + + return messages.join('; '); +} + +// in case the npm email-addresses returns unexpected things ... +function validateEmailAddress( + allowedDomains: string[], + address: string, + options: ValidateEmailAddressesOptions +): ValidatedEmail { + // The reason we bypass the validation in this case, is that email addresses + // used in an alerting action could contain mustache templates which render + // as the actual values. So we can't really validate them. Fear not! + // We always do a final validation in the executor where we do NOT + // have this flag on. + if (options.treatMustacheTemplatesAsValid && hasMustacheTemplate(address)) { + return { address, valid: true }; + } + + try { + return validateEmailAddress_(allowedDomains, address); + } catch (err) { + return { address, valid: false, reason: InvalidEmailReason.invalid }; + } +} + +function validateEmailAddress_(allowedDomains: string[], address: string): ValidatedEmail { + const emailAddresses = parseAddressList(address); + if (emailAddresses == null) { + return { address, valid: false, reason: InvalidEmailReason.invalid }; + } + + const allowedDomainsSet = new Set(allowedDomains); + + for (const emailAddress of emailAddresses) { + let domains: string[] = []; + + if (emailAddress.type === 'group') { + domains = emailAddress.addresses.map((groupAddress) => groupAddress.domain); + } else if (emailAddress.type === 'mailbox') { + domains = [emailAddress.domain]; + } else { + return { address, valid: false, reason: InvalidEmailReason.invalid }; + } + + for (const domain of domains) { + if (!allowedDomainsSet.has(domain)) { + return { address, valid: false, reason: InvalidEmailReason.notAllowed }; + } + } + } + + return { address, valid: true }; +} + +function addressesFromValidatedEmails(validatedEmails: ValidatedEmail[]) { + return validatedEmails.map((validatedEmail) => validatedEmail.address); +} diff --git a/x-pack/plugins/actions/kibana.json b/x-pack/plugins/actions/kibana.json index 4970d2b0870c8..ad5ca87949848 100644 --- a/x-pack/plugins/actions/kibana.json +++ b/x-pack/plugins/actions/kibana.json @@ -10,5 +10,6 @@ "configPath": ["xpack", "actions"], "requiredPlugins": ["licensing", "taskManager", "encryptedSavedObjects", "eventLog", "features"], "optionalPlugins": ["usageCollection", "spaces", "security", "monitoringCollection"], - "ui": false + "extraPublicDirs": ["common"], + "ui": true } diff --git a/x-pack/plugins/actions/public/index.ts b/x-pack/plugins/actions/public/index.ts new file mode 100644 index 0000000000000..31d4d08b0dbe2 --- /dev/null +++ b/x-pack/plugins/actions/public/index.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. + */ + +import { PluginInitializerContext } from '@kbn/core/public'; +import { Plugin, ActionsPublicPluginSetup } from './plugin'; + +export type { ActionsPublicPluginSetup }; +export { Plugin }; +export function plugin(context: PluginInitializerContext) { + return new Plugin(context); +} diff --git a/x-pack/plugins/actions/public/plugin.test.ts b/x-pack/plugins/actions/public/plugin.test.ts new file mode 100644 index 0000000000000..903fe917f32fa --- /dev/null +++ b/x-pack/plugins/actions/public/plugin.test.ts @@ -0,0 +1,65 @@ +/* + * 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 { coreMock } from '@kbn/core/public/mocks'; +import { Plugin } from './plugin'; + +describe('Actions Plugin', () => { + describe('setup()', () => { + const emails = ['bob@elastic.co', 'jim@somewhere.org', 'not an email']; + + it('should allow all emails when not using email allowlist config', async () => { + const context = coreMock.createPluginInitializerContext({}); + const plugin = new Plugin(context); + const pluginSetup = plugin.setup(); + const validated = pluginSetup.validateEmailAddresses(emails); + expect(validated).toMatchInlineSnapshot(` + Array [ + Object { + "address": "bob@elastic.co", + "valid": true, + }, + Object { + "address": "jim@somewhere.org", + "valid": true, + }, + Object { + "address": "not an email", + "valid": true, + }, + ] + `); + }); + + it('should validate correctly when using email allowlist config', async () => { + const context = coreMock.createPluginInitializerContext({ + email: { domain_allowlist: ['elastic.co'] }, + }); + const plugin = new Plugin(context); + const pluginSetup = plugin.setup(); + const validated = pluginSetup.validateEmailAddresses(emails); + expect(validated).toMatchInlineSnapshot(` + Array [ + Object { + "address": "bob@elastic.co", + "valid": true, + }, + Object { + "address": "jim@somewhere.org", + "reason": "notAllowed", + "valid": false, + }, + Object { + "address": "not an email", + "reason": "invalid", + "valid": false, + }, + ] + `); + }); + }); +}); diff --git a/x-pack/plugins/actions/public/plugin.ts b/x-pack/plugins/actions/public/plugin.ts new file mode 100644 index 0000000000000..7a64d1796fbec --- /dev/null +++ b/x-pack/plugins/actions/public/plugin.ts @@ -0,0 +1,44 @@ +/* + * 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 { Plugin as CorePlugin, PluginInitializerContext } from '@kbn/core/public'; +import { + ValidatedEmail, + validateEmailAddresses as validateEmails, + ValidateEmailAddressesOptions, +} from '../common'; + +export interface ActionsPublicPluginSetup { + validateEmailAddresses( + emails: string[], + options?: ValidateEmailAddressesOptions + ): ValidatedEmail[]; +} + +export interface Config { + email: { + domain_allowlist: string[]; + }; +} + +export class Plugin implements CorePlugin { + private readonly allowedEmailDomains: string[] | null = null; + + constructor(ctx: PluginInitializerContext) { + const config = ctx.config.get(); + this.allowedEmailDomains = config.email?.domain_allowlist || null; + } + + public setup(): ActionsPublicPluginSetup { + return { + validateEmailAddresses: (emails: string[], options: ValidateEmailAddressesOptions) => + validateEmails(this.allowedEmailDomains, emails, options), + }; + } + + public start(): void {} +} diff --git a/x-pack/plugins/actions/server/actions_config.mock.ts b/x-pack/plugins/actions/server/actions_config.mock.ts index f6110a48f785a..bf0ebb4e4791d 100644 --- a/x-pack/plugins/actions/server/actions_config.mock.ts +++ b/x-pack/plugins/actions/server/actions_config.mock.ts @@ -25,6 +25,7 @@ const createActionsConfigMock = () => { }), getCustomHostSettings: jest.fn().mockReturnValue(undefined), getMicrosoftGraphApiUrl: jest.fn().mockReturnValue(undefined), + validateEmailAddresses: jest.fn().mockReturnValue(undefined), }; return mocked; }; diff --git a/x-pack/plugins/actions/server/actions_config.test.ts b/x-pack/plugins/actions/server/actions_config.test.ts index 15261da6fd952..470e6ce8cdc8e 100644 --- a/x-pack/plugins/actions/server/actions_config.test.ts +++ b/x-pack/plugins/actions/server/actions_config.test.ts @@ -470,3 +470,47 @@ describe('getSSLSettings', () => { expect(sslSettings.verificationMode).toBe('none'); }); }); + +const testEmailsOk = ['bob@elastic.co', 'jim@elastic.co']; +const testEmailsNotAllowed = ['hal@bad.com', 'lou@notgood.org']; +const testEmailsInvalid = ['invalid-email-address', '(garbage)']; +const testEmailsAll = testEmailsOk.concat(testEmailsNotAllowed).concat(testEmailsInvalid); + +describe('validateEmailAddresses()', () => { + test('all domains allowed if config not set', () => { + const acu = getActionsConfigurationUtilities(defaultActionsConfig); + const message = acu.validateEmailAddresses(testEmailsAll); + expect(message).toEqual(undefined); + }); + + test('only filtered domains allowed if config set', () => { + const acu = getActionsConfigurationUtilities({ + ...defaultActionsConfig, + email: { + domain_allowlist: ['elastic.co'], + }, + }); + + let message = acu.validateEmailAddresses(testEmailsOk); + expect(message).toBe(undefined); + + message = acu.validateEmailAddresses(testEmailsAll); + expect(message).toMatchInlineSnapshot( + `"not valid emails: invalid-email-address, (garbage); not allowed emails: hal@bad.com, lou@notgood.org"` + ); + }); + + test('no domains allowed if config set to empty array', () => { + const acu = getActionsConfigurationUtilities({ + ...defaultActionsConfig, + email: { + domain_allowlist: [], + }, + }); + + const message = acu.validateEmailAddresses(testEmailsAll); + expect(message).toMatchInlineSnapshot( + `"not valid emails: invalid-email-address, (garbage); not allowed emails: bob@elastic.co, jim@elastic.co, hal@bad.com, lou@notgood.org"` + ); + }); +}); diff --git a/x-pack/plugins/actions/server/actions_config.ts b/x-pack/plugins/actions/server/actions_config.ts index f3f5216c2c3b5..35e08bb5cfe66 100644 --- a/x-pack/plugins/actions/server/actions_config.ts +++ b/x-pack/plugins/actions/server/actions_config.ts @@ -16,7 +16,11 @@ import { getCanonicalCustomHostUrl } from './lib/custom_host_settings'; import { ActionTypeDisabledError } from './lib'; import { ProxySettings, ResponseSettings, SSLSettings } from './types'; import { getSSLSettingsFromConfig } from './builtin_action_types/lib/get_node_ssl_options'; - +import { + ValidateEmailAddressesOptions, + validateEmailAddresses, + invalidEmailsAsMessage, +} from '../common'; export { AllowedHosts, EnabledActionTypes } from './config'; enum AllowListingField { @@ -36,6 +40,10 @@ export interface ActionsConfigurationUtilities { getResponseSettings: () => ResponseSettings; getCustomHostSettings: (targetUrl: string) => CustomHostSettings | undefined; getMicrosoftGraphApiUrl: () => undefined | string; + validateEmailAddresses( + addresses: string[], + options?: ValidateEmailAddressesOptions + ): string | undefined; } function allowListErrorMessage(field: AllowListingField, value: string) { @@ -139,12 +147,26 @@ function getCustomHostSettings( return customHostSettings.find((settings) => settings.url === canonicalUrl); } +function validateEmails( + config: ActionsConfig, + addresses: string[], + options: ValidateEmailAddressesOptions +): string | undefined { + if (config.email == null) { + return; + } + + const validated = validateEmailAddresses(config.email.domain_allowlist, addresses, options); + return invalidEmailsAsMessage(validated); +} + export function getActionsConfigurationUtilities( config: ActionsConfig ): ActionsConfigurationUtilities { const isHostnameAllowed = curry(isAllowed)(config); const isUriAllowed = curry(isHostnameAllowedInUri)(config); const isActionTypeEnabled = curry(isActionTypeEnabledInConfig)(config); + const validatedEmailCurried = curry(validateEmails)(config); return { isHostnameAllowed, isUriAllowed, @@ -170,5 +192,7 @@ export function getActionsConfigurationUtilities( }, getCustomHostSettings: (targetUrl: string) => getCustomHostSettings(config, targetUrl), getMicrosoftGraphApiUrl: () => getMicrosoftGraphApiUrlFromConfig(config), + validateEmailAddresses: (addresses: string[], options: ValidateEmailAddressesOptions) => + validatedEmailCurried(addresses, options), }; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts index 729e1e8c9ac2f..55f0fc0cb5f70 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts @@ -24,6 +24,7 @@ import { EmailActionType, EmailActionTypeExecutorOptions, } from './email'; +import { ValidateEmailAddressesOptions } from '../../common'; const sendEmailMock = sendEmail as jest.Mock; @@ -269,6 +270,26 @@ describe('config validation', () => { `"error validating action type config: [host] value 'smtp.gmail.com' is not in the allowedHosts configuration"` ); }); + + test('config validation for emails calls validateEmailAddresses', async () => { + const configurationUtilities = actionsConfigMock.create(); + configurationUtilities.validateEmailAddresses.mockImplementation(validateEmailAddressesImpl); + + const basicActionType = getActionType({ + logger: mockedLogger, + configurationUtilities, + }); + + expect(() => { + validateConfig(basicActionType, { + from: 'badmail', + service: 'gmail', + }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type config: [from]: stub for actual message"` + ); + expect(configurationUtilities.validateEmailAddresses).toHaveBeenNthCalledWith(1, ['badmail']); + }); }); describe('secrets validation', () => { @@ -404,6 +425,33 @@ describe('params validation', () => { `"error validating action params: [subject]: expected value of type [string] but got [undefined]"` ); }); + + test('params validation for emails calls validateEmailAddresses', async () => { + const configurationUtilities = actionsConfigMock.create(); + configurationUtilities.validateEmailAddresses.mockImplementation(validateEmailAddressesImpl); + + const basicActionType = getActionType({ + logger: mockedLogger, + configurationUtilities, + }); + + expect(() => { + validateParams(basicActionType, { + to: ['to@example.com'], + cc: ['cc@example.com'], + bcc: ['bcc@example.com'], + subject: 'this is a test', + message: 'this is the message', + }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action params: [to/cc/bcc]: stub for actual message"` + ); + + const allEmails = ['to@example.com', 'cc@example.com', 'bcc@example.com']; + expect(configurationUtilities.validateEmailAddresses).toHaveBeenNthCalledWith(1, allEmails, { + treatMustacheTemplatesAsValid: true, + }); + }); }); describe('execute()', () => { @@ -454,21 +502,9 @@ describe('execute()', () => { "status": "ok", } `); + delete sendEmailMock.mock.calls[0][1].configurationUtilities; expect(sendEmailMock.mock.calls[0][1]).toMatchInlineSnapshot(` Object { - "configurationUtilities": Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction], - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, "connectorId": "some-id", "content": Object { "message": "a message to you @@ -517,21 +553,9 @@ describe('execute()', () => { sendEmailMock.mockReset(); await actionType.executor(customExecutorOptions); + delete sendEmailMock.mock.calls[0][1].configurationUtilities; expect(sendEmailMock.mock.calls[0][1]).toMatchInlineSnapshot(` Object { - "configurationUtilities": Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction], - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, "connectorId": "some-id", "content": Object { "message": "a message to you @@ -580,21 +604,9 @@ describe('execute()', () => { sendEmailMock.mockReset(); await actionType.executor(customExecutorOptions); + delete sendEmailMock.mock.calls[0][1].configurationUtilities; expect(sendEmailMock.mock.calls[0][1]).toMatchInlineSnapshot(` Object { - "configurationUtilities": Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction], - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, "connectorId": "some-id", "content": Object { "message": "a message to you @@ -745,4 +757,48 @@ describe('execute()', () => { This message was sent by Kibana. [View this in Kibana](https://localhost:1234/foo/bar/my/app)." `); }); + + test('ensure execution runs validator with allowMustache false', async () => { + const configurationUtilities = actionsConfigMock.create(); + configurationUtilities.validateEmailAddresses.mockImplementation(validateEmailAddressesImpl); + + const testActionType = getActionType({ + logger: mockedLogger, + configurationUtilities, + }); + + const customExecutorOptions: EmailActionTypeExecutorOptions = { + ...executorOptions, + params: { + ...params, + }, + }; + + const result = await testActionType.executor(customExecutorOptions); + expect(result).toMatchInlineSnapshot(` + Object { + "actionId": "some-id", + "message": "[to/cc/bcc]: stub for actual message", + "status": "error", + } + `); + expect(configurationUtilities.validateEmailAddresses.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Array [ + "jim@example.com", + "james@example.com", + "jimmy@example.com", + ], + ], + ] + `); + }); }); + +function validateEmailAddressesImpl( + addresses: string[], + options?: ValidateEmailAddressesOptions +): string | undefined { + return 'stub for actual message'; +} diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.ts b/x-pack/plugins/actions/server/builtin_action_types/email.ts index 29c4f9fb595bd..b2c0549bd7ea4 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/email.ts @@ -10,8 +10,9 @@ import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; import nodemailerGetService from 'nodemailer/lib/well-known'; import SMTPConnection from 'nodemailer/lib/smtp-connection'; - import { Logger } from '@kbn/core/server'; +import { withoutMustacheTemplate } from '../../common'; + import { sendEmail, JSON_TRANSPORT_SERVICE, SendEmailOptions, Transport } from './lib/send_email'; import { portSchema } from './lib/schemas'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; @@ -65,6 +66,12 @@ function validateConfig( ): string | void { const config = configObject; + const emails = [config.from]; + const invalidEmailsMessage = configurationUtilities.validateEmailAddresses(emails); + if (!!invalidEmailsMessage) { + return `[from]: ${invalidEmailsMessage}`; + } + // If service is set as JSON_TRANSPORT_SERVICE or EXCHANGE, host/port are ignored, when the email is sent. // Note, not currently making these message translated, as will be // emitted alongside messages from @kbn/config-schema, which does not @@ -128,30 +135,30 @@ const SecretsSchema = schema.object(SecretsSchemaProps); export type ActionParamsType = TypeOf; -const ParamsSchema = schema.object( - { - to: schema.arrayOf(schema.string(), { defaultValue: [] }), - cc: schema.arrayOf(schema.string(), { defaultValue: [] }), - bcc: schema.arrayOf(schema.string(), { defaultValue: [] }), - subject: schema.string(), - message: schema.string(), - // kibanaFooterLink isn't inteded for users to set, this is here to be able to programatically - // provide a more contextual URL in the footer (ex: URL to the alert details page) - kibanaFooterLink: schema.object({ - path: schema.string({ defaultValue: '/' }), - text: schema.string({ - defaultValue: i18n.translate('xpack.actions.builtin.email.kibanaFooterLinkText', { - defaultMessage: 'Go to Kibana', - }), +const ParamsSchemaProps = { + to: schema.arrayOf(schema.string(), { defaultValue: [] }), + cc: schema.arrayOf(schema.string(), { defaultValue: [] }), + bcc: schema.arrayOf(schema.string(), { defaultValue: [] }), + subject: schema.string(), + message: schema.string(), + // kibanaFooterLink isn't inteded for users to set, this is here to be able to programatically + // provide a more contextual URL in the footer (ex: URL to the alert details page) + kibanaFooterLink: schema.object({ + path: schema.string({ defaultValue: '/' }), + text: schema.string({ + defaultValue: i18n.translate('xpack.actions.builtin.email.kibanaFooterLinkText', { + defaultMessage: 'Go to Kibana', }), }), - }, - { - validate: validateParams, - } -); + }), +}; + +const ParamsSchema = schema.object(ParamsSchemaProps); -function validateParams(paramsObject: unknown): string | void { +function validateParams( + configurationUtilities: ActionsConfigurationUtilities, + paramsObject: unknown +): string | void { // avoids circular reference ... const params = paramsObject as ActionParamsType; @@ -161,6 +168,14 @@ function validateParams(paramsObject: unknown): string | void { if (addrs === 0) { return 'no [to], [cc], or [bcc] entries'; } + + const emails = withoutMustacheTemplate(to.concat(cc).concat(bcc)); + const invalidEmailsMessage = configurationUtilities.validateEmailAddresses(emails, { + treatMustacheTemplatesAsValid: true, + }); + if (invalidEmailsMessage) { + return `[to/cc/bcc]: ${invalidEmailsMessage}`; + } } interface GetActionTypeParams { @@ -203,7 +218,9 @@ export function getActionType(params: GetActionTypeParams): EmailActionType { validate: curry(validateConfig)(configurationUtilities), }), secrets: SecretsSchema, - params: ParamsSchema, + params: schema.object(ParamsSchemaProps, { + validate: curry(validateParams)(configurationUtilities), + }), connector: validateConnector, }, renderParameterTemplates, @@ -243,6 +260,17 @@ async function executor( const params = execOptions.params; const connectorTokenClient = execOptions.services.connectorTokenClient; + const emails = params.to.concat(params.cc).concat(params.bcc); + let invalidEmailsMessage = configurationUtilities.validateEmailAddresses(emails); + if (invalidEmailsMessage) { + return { status: 'error', actionId, message: `[to/cc/bcc]: ${invalidEmailsMessage}` }; + } + + invalidEmailsMessage = configurationUtilities.validateEmailAddresses([config.from]); + if (invalidEmailsMessage) { + return { status: 'error', actionId, message: `[from]: ${invalidEmailsMessage}` }; + } + const transport: Transport = {}; if (secrets.user != null) { diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts index 65aad21db447e..8c36113032461 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts @@ -131,6 +131,7 @@ describe('send_email module', () => { page: 1, }); await sendEmail(mockLogger, sendEmailOptions, connectorTokenClient); + requestOAuthClientCredentialsTokenMock.mock.calls[0].pop(); expect(requestOAuthClientCredentialsTokenMock.mock.calls[0]).toMatchInlineSnapshot(` Array [ "https://login.microsoftonline.com/undefined/oauth2/v2.0/token", @@ -150,32 +151,11 @@ describe('send_email module', () => { "clientSecret": "sdfhkdsjhfksdjfh", "scope": "https://graph.microsoft.com/.default", }, - Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, ] `); + delete sendEmailGraphApiMock.mock.calls[0][0].options.configurationUtilities; + sendEmailGraphApiMock.mock.calls[0].pop(); expect(sendEmailGraphApiMock.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -187,29 +167,6 @@ describe('send_email module', () => { "messageHTML": "

a message

", "options": Object { - "configurationUtilities": Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, "connectorId": "1", "content": Object { "message": "a message", @@ -247,29 +204,6 @@ describe('send_email module', () => { "trace": [MockFunction], "warn": [MockFunction], }, - Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, ] `); @@ -331,6 +265,8 @@ describe('send_email module', () => { await sendEmail(mockLogger, sendEmailOptions, connectorTokenClient); expect(requestOAuthClientCredentialsTokenMock.mock.calls.length).toBe(0); + delete sendEmailGraphApiMock.mock.calls[0][0].options.configurationUtilities; + sendEmailGraphApiMock.mock.calls[0].pop(); expect(sendEmailGraphApiMock.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -342,29 +278,6 @@ describe('send_email module', () => { "messageHTML": "

a message

", "options": Object { - "configurationUtilities": Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, "connectorId": "1", "content": Object { "message": "a message", @@ -402,29 +315,6 @@ describe('send_email module', () => { "trace": [MockFunction], "warn": [MockFunction], }, - Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, ] `); @@ -510,6 +400,8 @@ describe('send_email module', () => { await sendEmail(mockLogger, sendEmailOptions, connectorTokenClient); expect(requestOAuthClientCredentialsTokenMock.mock.calls.length).toBe(1); + delete sendEmailGraphApiMock.mock.calls[0][0].options.configurationUtilities; + sendEmailGraphApiMock.mock.calls[0].pop(); expect(sendEmailGraphApiMock.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -521,29 +413,6 @@ describe('send_email module', () => { "messageHTML": "

a message

", "options": Object { - "configurationUtilities": Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, "connectorId": "1", "content": Object { "message": "a message", @@ -581,29 +450,6 @@ describe('send_email module', () => { "trace": [MockFunction], "warn": [MockFunction], }, - Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, ] `); @@ -647,6 +493,8 @@ describe('send_email module', () => { `Not able to update connector token for connectorId: 1 due to error: Fail`, ]); + delete sendEmailGraphApiMock.mock.calls[0][0].options.configurationUtilities; + sendEmailGraphApiMock.mock.calls[0].pop(); expect(sendEmailGraphApiMock.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -658,29 +506,6 @@ describe('send_email module', () => { "messageHTML": "

a message

", "options": Object { - "configurationUtilities": Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, "connectorId": "1", "content": Object { "message": "a message", @@ -742,29 +567,6 @@ describe('send_email module', () => { ], }, }, - Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, ] `); }); @@ -801,6 +603,8 @@ describe('send_email module', () => { expect(requestOAuthClientCredentialsTokenMock.mock.calls.length).toBe(1); expect(connectorTokenClientM.deleteConnectorTokens.mock.calls.length).toBe(1); + delete sendEmailGraphApiMock.mock.calls[0][0].options.configurationUtilities; + sendEmailGraphApiMock.mock.calls[0].pop(); expect(sendEmailGraphApiMock.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { @@ -812,29 +616,6 @@ describe('send_email module', () => { "messageHTML": "

a message

", "options": Object { - "configurationUtilities": Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, "connectorId": "1", "content": Object { "message": "a message", @@ -872,29 +653,6 @@ describe('send_email module', () => { "trace": [MockFunction], "warn": [MockFunction], }, - Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, ] `); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts index 270abfed3fb35..4475832e1a7f7 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts @@ -26,6 +26,9 @@ import { ExternalIncidentServiceConfigurationBaseSchema, } from './schema'; import { ActionsConfigurationUtilities } from '../../actions_config'; +import { SNProductsConfigValue } from '../../../common'; + +export type { SNProductsConfigValue, SNProductsConfig } from '../../../common'; export type ServiceNowPublicConfigurationBaseType = TypeOf< typeof ExternalIncidentServiceConfigurationBaseSchema @@ -247,17 +250,6 @@ export interface GetApplicationInfoResponse { version: string; } -export interface SNProductsConfigValue { - table: string; - appScope: string; - useImportAPI: boolean; - importSetTable: string; - commentFieldKey: string; - appId?: string; -} - -export type SNProductsConfig = Record; - export enum ObservableTypes { ip4 = 'ipv4-addr', url = 'URL', diff --git a/x-pack/plugins/actions/server/builtin_action_types/teams.test.ts b/x-pack/plugins/actions/server/builtin_action_types/teams.test.ts index eb4ce4fda4cc6..23bc0fba603d4 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/teams.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/teams.test.ts @@ -160,22 +160,10 @@ describe('execute()', () => { secrets: { webhookUrl: 'http://example.com' }, params: { message: 'this invocation should succeed' }, }); + delete requestMock.mock.calls[0][0].configurationUtilities; expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` Object { "axios": undefined, - "configurationUtilities": Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction], - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, "data": Object { "text": "this invocation should succeed", }, @@ -225,22 +213,10 @@ describe('execute()', () => { secrets: { webhookUrl: 'http://example.com' }, params: { message: 'this invocation should succeed' }, }); + delete requestMock.mock.calls[0][0].configurationUtilities; expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` Object { "axios": undefined, - "configurationUtilities": Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction], - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, "data": Object { "text": "this invocation should succeed", }, diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts index 2f68c6465fa18..6b8a1a4e8447b 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts @@ -279,6 +279,7 @@ describe('execute()', () => { params: { body: 'some data' }, }); + delete requestMock.mock.calls[0][0].configurationUtilities; expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` Object { "auth": Object { @@ -286,19 +287,6 @@ describe('execute()', () => { "username": "abc", }, "axios": undefined, - "configurationUtilities": Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction], - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, "data": "some data", "headers": Object { "aheader": "a value", @@ -377,22 +365,10 @@ describe('execute()', () => { params: { body: 'some data' }, }); + delete requestMock.mock.calls[0][0].configurationUtilities; expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` Object { "axios": undefined, - "configurationUtilities": Object { - "ensureActionTypeEnabled": [MockFunction], - "ensureHostnameAllowed": [MockFunction], - "ensureUriAllowed": [MockFunction], - "getCustomHostSettings": [MockFunction], - "getMicrosoftGraphApiUrl": [MockFunction], - "getProxySettings": [MockFunction], - "getResponseSettings": [MockFunction], - "getSSLSettings": [MockFunction], - "isActionTypeEnabled": [MockFunction], - "isHostnameAllowed": [MockFunction], - "isUriAllowed": [MockFunction], - }, "data": "some data", "headers": Object { "aheader": "a value", diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts index 4f39956d910a9..e6e3a24db5214 100644 --- a/x-pack/plugins/actions/server/config.test.ts +++ b/x-pack/plugins/actions/server/config.test.ts @@ -212,6 +212,25 @@ describe('config validation', () => { } `); }); + + test('validates email.domain_allowlist', () => { + const config: Record = {}; + let result = configSchema.validate(config); + expect(result.email === undefined); + + config.email = {}; + expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot( + `"[email.domain_allowlist]: expected value of type [array] but got [undefined]"` + ); + + config.email = { domain_allowlist: [] }; + result = configSchema.validate(config); + expect(result.email?.domain_allowlist).toEqual([]); + + config.email = { domain_allowlist: ['a.com', 'b.c.com', 'd.e.f.com'] }; + result = configSchema.validate(config); + expect(result.email?.domain_allowlist).toEqual(['a.com', 'b.c.com', 'd.e.f.com']); + }); }); // object creator that ensures we can create a property named __proto__ on an diff --git a/x-pack/plugins/actions/server/config.ts b/x-pack/plugins/actions/server/config.ts index 1f279f20c27c3..4c8ca7ff9fff7 100644 --- a/x-pack/plugins/actions/server/config.ts +++ b/x-pack/plugins/actions/server/config.ts @@ -112,6 +112,11 @@ export const configSchema = schema.object({ pageSize: schema.number({ defaultValue: 100 }), }), microsoftGraphApiUrl: schema.maybe(schema.string()), + email: schema.maybe( + schema.object({ + domain_allowlist: schema.arrayOf(schema.string()), + }) + ), }); export type ActionsConfig = TypeOf; diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index 7b5863eabf0a9..6b0070af0b022 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.ts @@ -57,6 +57,9 @@ export const plugin = (initContext: PluginInitializerContext) => new ActionsPlug export const config: PluginConfigDescriptor = { schema: configSchema, + exposeToBrowser: { + email: { domain_allowlist: true }, + }, deprecations: ({ renameFromRoot, unused }) => [ renameFromRoot('xpack.actions.whitelistedHosts', 'xpack.actions.allowedHosts', { level: 'warning', diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 3cdf28a8d80f3..1fad2a6189693 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -314,6 +314,7 @@ export class ActionsPlugin implements Plugin(), this.licenseState, + actionsConfigUtils, this.usageCounter ); diff --git a/x-pack/plugins/actions/server/routes/index.ts b/x-pack/plugins/actions/server/routes/index.ts index 48d87e9d62c58..ab90141ae1c80 100644 --- a/x-pack/plugins/actions/server/routes/index.ts +++ b/x-pack/plugins/actions/server/routes/index.ts @@ -18,10 +18,12 @@ import { connectorTypesRoute } from './connector_types'; import { updateActionRoute } from './update'; import { getWellKnownEmailServiceRoute } from './get_well_known_email_service'; import { defineLegacyRoutes } from './legacy'; +import { ActionsConfigurationUtilities } from '../actions_config'; export function defineRoutes( router: IRouter, licenseState: ILicenseState, + actionsConfigUtils: ActionsConfigurationUtilities, usageCounter?: UsageCounter ) { defineLegacyRoutes(router, licenseState, usageCounter); diff --git a/x-pack/plugins/actions/tsconfig.json b/x-pack/plugins/actions/tsconfig.json index 95788811e43f8..aad127a6ca94c 100644 --- a/x-pack/plugins/actions/tsconfig.json +++ b/x-pack/plugins/actions/tsconfig.json @@ -10,7 +10,8 @@ "server/**/*", // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 "server/**/*.json", - "common/*" + "public/**/*", + "common/**/*" ], "references": [ { "path": "../../../src/core/tsconfig.json" }, diff --git a/x-pack/plugins/triggers_actions_ui/kibana.json b/x-pack/plugins/triggers_actions_ui/kibana.json index 4be95ed2db91d..a872ad6fab5c0 100644 --- a/x-pack/plugins/triggers_actions_ui/kibana.json +++ b/x-pack/plugins/triggers_actions_ui/kibana.json @@ -7,9 +7,9 @@ "version": "kibana", "server": true, "ui": true, - "optionalPlugins": ["alerting", "cloud", "features", "home", "spaces"], - "requiredPlugins": ["management", "charts", "data", "kibanaReact", "kibanaUtils", "savedObjects", "unifiedSearch", "dataViews"], + "optionalPlugins": ["cloud", "features", "home", "spaces"], + "requiredPlugins": ["management", "charts", "data", "kibanaReact", "kibanaUtils", "savedObjects", "unifiedSearch", "dataViews", "alerting", "actions"], "configPath": ["xpack", "trigger_actions_ui"], "extraPublicDirs": ["public/common", "public/common/constants"], - "requiredBundles": ["alerting", "esUiShared", "kibanaReact", "kibanaUtils"] + "requiredBundles": ["alerting", "esUiShared", "kibanaReact", "kibanaUtils", "actions"] } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.test.tsx index ab2cb5332d452..31ff85a20ed3b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.test.tsx @@ -10,13 +10,44 @@ import { registerBuiltInActionTypes } from '..'; import { ActionTypeModel } from '../../../../types'; import { EmailActionConnector } from '../types'; import { getEmailServices } from './email'; +import { + ValidatedEmail, + InvalidEmailReason, + ValidateEmailAddressesOptions, + MustacheInEmailRegExp, +} from '@kbn/actions-plugin/common'; const ACTION_TYPE_ID = '.email'; let actionTypeModel: ActionTypeModel; +const RegistrationServices = { + validateEmailAddresses: validateEmails, +}; + +// stub for the real validator +function validateEmails( + addresses: string[], + options?: ValidateEmailAddressesOptions +): ValidatedEmail[] { + return addresses.map((address) => { + if (address.includes('invalid')) + return { address, valid: false, reason: InvalidEmailReason.invalid }; + else if (address.includes('notallowed')) + return { address, valid: false, reason: InvalidEmailReason.notAllowed }; + else if (options?.treatMustacheTemplatesAsValid) return { address, valid: true }; + else if (address.match(MustacheInEmailRegExp)) + return { address, valid: false, reason: InvalidEmailReason.invalid }; + else return { address, valid: true }; + }); +} + +beforeEach(() => { + jest.resetAllMocks(); +}); + beforeAll(() => { const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry }); + registerBuiltInActionTypes({ actionTypeRegistry, services: RegistrationServices }); const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); if (getResult !== null) { actionTypeModel = getResult; @@ -138,7 +169,7 @@ describe('connector validation', () => { actionTypeId: '.email', name: 'email', config: { - from: 'test@test.com', + from: 'test@notallowed.com', hasAuth: true, service: 'other', }, @@ -147,7 +178,7 @@ describe('connector validation', () => { expect(await actionTypeModel.validateConnector(actionConnector)).toEqual({ config: { errors: { - from: [], + from: ['Email address test@notallowed.com is not allowed.'], port: ['Port is required.'], host: ['Host is required.'], service: [], @@ -163,7 +194,13 @@ describe('connector validation', () => { }, }, }); + + // also check that mustache is not valid + actionConnector.config.from = '{{mustached}}'; + const validation = await actionTypeModel.validateConnector(actionConnector); + expect(validation?.config?.errors?.from).toEqual(['Email address {{mustached}} is not valid.']); }); + test('connector validation fails when user specified but not password', async () => { const actionConnector = { secrets: { @@ -336,6 +373,7 @@ describe('action params validation', () => { const actionParams = { to: [], cc: ['test1@test.com'], + bcc: ['mustache {{\n}} template'], message: 'message {test}', subject: 'test', }; @@ -353,15 +391,17 @@ describe('action params validation', () => { test('action params validation fails when action params is not valid', async () => { const actionParams = { - to: ['test@test.com'], + to: ['invalid.com'], + cc: ['bob@notallowed.com'], + bcc: ['another-invalid.com'], subject: 'test', }; expect(await actionTypeModel.validateParams(actionParams)).toEqual({ errors: { - to: [], - cc: [], - bcc: [], + to: ['Email address invalid.com is not valid.'], + cc: ['Email address bob@notallowed.com is not allowed.'], + bcc: ['Email address another-invalid.com is not valid.'], message: ['Message is required.'], subject: [], }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.tsx index c5da19c305268..0add2396a74d0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.tsx @@ -5,16 +5,18 @@ * 2.0. */ +import { uniq } from 'lodash'; import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSelectOption } from '@elastic/eui'; -import { AdditionalEmailServices } from '@kbn/actions-plugin/common'; +import { AdditionalEmailServices, InvalidEmailReason } from '@kbn/actions-plugin/common'; import { ActionTypeModel, ConnectorValidationResult, GenericValidationResult, } from '../../../../types'; import { EmailActionParams, EmailConfig, EmailSecrets, EmailActionConnector } from '../types'; +import { RegistrationServices } from '..'; const emailServices: EuiSelectOption[] = [ { @@ -79,8 +81,9 @@ export function getEmailServices(isCloudEnabled: boolean) { : emailServices.filter((service) => service.value !== 'elastic_cloud'); } -export function getActionType(): ActionTypeModel { - const mailformat = /^[^@\s]+@[^@\s]+$/; +export function getActionType( + services: RegistrationServices +): ActionTypeModel { return { id: '.email', iconClass: 'email', @@ -122,9 +125,15 @@ export function getActionType(): ActionTypeModel(), }; const validationResult = { errors }; - if ( - (!(actionParams.to instanceof Array) || actionParams.to.length === 0) && - (!(actionParams.cc instanceof Array) || actionParams.cc.length === 0) && - (!(actionParams.bcc instanceof Array) || actionParams.bcc.length === 0) - ) { - const errorText = translations.TO_CC_REQUIRED; - errors.to.push(errorText); - errors.cc.push(errorText); - errors.bcc.push(errorText); - } + if (!actionParams.message?.length) { errors.message.push(translations.MESSAGE_REQUIRED); } if (!actionParams.subject?.length) { errors.subject.push(translations.SUBJECT_REQUIRED); } + + const toEmails = getToFields(actionParams); + const ccEmails = getCcFields(actionParams); + const bccEmails = getBccFields(actionParams); + + if (toEmails.length === 0 && ccEmails.length === 0 && bccEmails.length === 0) { + const errorText = translations.TO_CC_REQUIRED; + errors.to.push(errorText); + errors.cc.push(errorText); + errors.bcc.push(errorText); + } + + const allEmails = uniq(toEmails.concat(ccEmails).concat(bccEmails)); + const validatedEmails = services.validateEmailAddresses(allEmails, { + treatMustacheTemplatesAsValid: true, + }); + + const toEmailSet = new Set(toEmails); + const ccEmailSet = new Set(ccEmails); + const bccEmailSet = new Set(bccEmails); + + for (const validated of validatedEmails) { + if (!validated.valid) { + const email = validated.address; + const message = + validated.reason === InvalidEmailReason.notAllowed + ? translations.getNotAllowedEmailAddress(email) + : translations.getInvalidEmailAddress(email); + + if (toEmailSet.has(email)) errors.to.push(message); + if (ccEmailSet.has(email)) errors.cc.push(message); + if (bccEmailSet.has(email)) errors.bcc.push(message); + } + } + return validationResult; }, actionConnectorFields: lazy(() => import('./email_connector')), actionParamsFields: lazy(() => import('./email_params')), }; } + +function getToFields(actionParams: EmailActionParams): string[] { + if (!(actionParams.to instanceof Array)) return []; + return actionParams.to; +} + +function getCcFields(actionParams: EmailActionParams): string[] { + if (!(actionParams.cc instanceof Array)) return []; + return actionParams.cc; +} + +function getBccFields(actionParams: EmailActionParams): string[] { + if (!(actionParams.bcc instanceof Array)) return []; + return actionParams.bcc; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/translations.ts index 38e16f6046184..e1bee12d98993 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/translations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/translations.ts @@ -104,3 +104,20 @@ export const SUBJECT_REQUIRED = i18n.translate( defaultMessage: 'Subject is required.', } ); + +export function getInvalidEmailAddress(email: string) { + return i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.error.invalidEmail', + { + defaultMessage: 'Email address {email} is not valid.', + values: { email }, + } + ); +} + +export function getNotAllowedEmailAddress(email: string) { + return i18n.translate('xpack.triggersActionsUI.components.builtinActionTypes.error.notAllowed', { + defaultMessage: 'Email address {email} is not allowed.', + values: { email }, + }); +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.test.tsx index 4097adb7b067f..42dc8a16a8b19 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.test.tsx @@ -9,13 +9,14 @@ import { TypeRegistry } from '../../../type_registry'; import { registerBuiltInActionTypes } from '..'; import { ActionTypeModel } from '../../../../types'; import { EsIndexActionConnector } from '../types'; +import { registrationServicesMock } from '../../../../mocks'; const ACTION_TYPE_ID = '.index'; let actionTypeModel: ActionTypeModel; beforeAll(() => { const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry }); + registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); if (getResult !== null) { actionTypeModel = getResult; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts index 6817631e2150a..e2089221b4d60 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { ValidatedEmail, ValidateEmailAddressesOptions } from '@kbn/actions-plugin/common'; import { getServerLogActionType } from './server_log'; import { getSlackActionType } from './slack'; import { getEmailActionType } from './email'; @@ -24,14 +25,23 @@ import { getJiraActionType } from './jira'; import { getResilientActionType } from './resilient'; import { getTeamsActionType } from './teams'; +export interface RegistrationServices { + validateEmailAddresses: ( + addresses: string[], + options?: ValidateEmailAddressesOptions + ) => ValidatedEmail[]; +} + export function registerBuiltInActionTypes({ actionTypeRegistry, + services, }: { actionTypeRegistry: TypeRegistry; + services: RegistrationServices; }) { actionTypeRegistry.register(getServerLogActionType()); actionTypeRegistry.register(getSlackActionType()); - actionTypeRegistry.register(getEmailActionType()); + actionTypeRegistry.register(getEmailActionType(services)); actionTypeRegistry.register(getIndexActionType()); actionTypeRegistry.register(getPagerDutyActionType()); actionTypeRegistry.register(getSwimlaneActionType()); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx index 4becccec3483d..3bb023b135c40 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx @@ -9,13 +9,14 @@ import { TypeRegistry } from '../../../type_registry'; import { registerBuiltInActionTypes } from '..'; import { ActionTypeModel } from '../../../../types'; import { JiraActionConnector } from './types'; +import { registrationServicesMock } from '../../../../mocks'; const ACTION_TYPE_ID = '.jira'; let actionTypeModel: ActionTypeModel; beforeAll(() => { const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry }); + registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); if (getResult !== null) { actionTypeModel = getResult; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.test.tsx index 10bf1a45806e8..a8274729506af 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.test.tsx @@ -9,13 +9,14 @@ import { TypeRegistry } from '../../../type_registry'; import { registerBuiltInActionTypes } from '..'; import { ActionTypeModel } from '../../../../types'; import { PagerDutyActionConnector } from '../types'; +import { registrationServicesMock } from '../../../../mocks'; const ACTION_TYPE_ID = '.pagerduty'; let actionTypeModel: ActionTypeModel; beforeAll(() => { const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry }); + registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); if (getResult !== null) { actionTypeModel = getResult; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx index 67a51964dcb03..209606913dce6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx @@ -9,13 +9,14 @@ import { TypeRegistry } from '../../../type_registry'; import { registerBuiltInActionTypes } from '..'; import { ActionTypeModel } from '../../../../types'; import { ResilientActionConnector } from './types'; +import { registrationServicesMock } from '../../../../mocks'; const ACTION_TYPE_ID = '.resilient'; let actionTypeModel: ActionTypeModel; beforeAll(() => { const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry }); + registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); if (getResult !== null) { actionTypeModel = getResult; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log.test.tsx index 69c1c18bd06ea..012a233973012 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log.test.tsx @@ -8,13 +8,14 @@ import { TypeRegistry } from '../../../type_registry'; import { registerBuiltInActionTypes } from '..'; import { ActionTypeModel, UserConfiguredActionConnector } from '../../../../types'; +import { registrationServicesMock } from '../../../../mocks'; const ACTION_TYPE_ID = '.server-log'; let actionTypeModel: ActionTypeModel; beforeAll(() => { const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry }); + registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); if (getResult !== null) { actionTypeModel = getResult; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts index dad3e3e0d0170..a9e8b8f544d42 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts @@ -7,9 +7,7 @@ import { HttpSetup } from '@kbn/core/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { snExternalServiceConfig } from '@kbn/actions-plugin/server/builtin_action_types/servicenow/config'; -import { ActionTypeExecutorResult } from '@kbn/actions-plugin/common'; +import { ActionTypeExecutorResult, snExternalServiceConfig } from '@kbn/actions-plugin/common'; import { BASE_ACTION_API_PATH } from '../../../constants'; import { API_INFO_ERROR } from './translations'; import { AppInfo, RESTApiError } from './types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx index a52c48d4e8e27..9a634170fa793 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx @@ -9,6 +9,7 @@ import { TypeRegistry } from '../../../type_registry'; import { registerBuiltInActionTypes } from '..'; import { ActionTypeModel } from '../../../../types'; import { ServiceNowActionConnector } from './types'; +import { registrationServicesMock } from '../../../../mocks'; const SERVICENOW_ITSM_ACTION_TYPE_ID = '.servicenow'; const SERVICENOW_SIR_ACTION_TYPE_ID = '.servicenow-sir'; @@ -17,7 +18,7 @@ let actionTypeRegistry: TypeRegistry; beforeAll(() => { actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry }); + registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); }); describe('actionTypeRegistry.get() works', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx index a9ff9497bdf19..22afcd5255e44 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx @@ -8,8 +8,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { EuiSpacer } from '@elastic/eui'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { snExternalServiceConfig } from '@kbn/actions-plugin/server/builtin_action_types/servicenow/config'; +import { snExternalServiceConfig } from '@kbn/actions-plugin/common'; import { ActionConnectorFieldsProps } from '../../../../types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.tsx index 732aeed6313ac..de5cf4df5731a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.tsx @@ -21,8 +21,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { snExternalServiceConfig } from '@kbn/actions-plugin/server/builtin_action_types/servicenow/config'; +import { snExternalServiceConfig } from '@kbn/actions-plugin/common'; import { ActionConnectorFieldsProps } from '../../../../types'; import { ServiceNowActionConnector } from './types'; import { CredentialsApiUrl } from './credentials_api_url'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack.test.tsx index 95ce25a03349e..76a23ab94d972 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack.test.tsx @@ -9,13 +9,14 @@ import { TypeRegistry } from '../../../type_registry'; import { registerBuiltInActionTypes } from '..'; import { ActionTypeModel } from '../../../../types'; import { SlackActionConnector } from '../types'; +import { registrationServicesMock } from '../../../../mocks'; const ACTION_TYPE_ID = '.slack'; let actionTypeModel: ActionTypeModel; beforeAll(async () => { const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry }); + registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); if (getResult !== null) { actionTypeModel = getResult; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane.test.tsx index 5d69af2d08779..45d68c8ab39e8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane.test.tsx @@ -9,13 +9,14 @@ import { TypeRegistry } from '../../../type_registry'; import { registerBuiltInActionTypes } from '..'; import { ActionTypeModel } from '../../../../types'; import { SwimlaneActionConnector } from './types'; +import { registrationServicesMock } from '../../../../mocks'; const ACTION_TYPE_ID = '.swimlane'; let actionTypeModel: ActionTypeModel; beforeAll(() => { const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry }); + registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); if (getResult !== null) { actionTypeModel = getResult; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.test.tsx index da0a67fe79a20..8590433f39cc0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams.test.tsx @@ -9,13 +9,14 @@ import { TypeRegistry } from '../../../type_registry'; import { registerBuiltInActionTypes } from '..'; import { ActionTypeModel } from '../../../../types'; import { TeamsActionConnector } from '../types'; +import { registrationServicesMock } from '../../../../mocks'; const ACTION_TYPE_ID = '.teams'; let actionTypeModel: ActionTypeModel; beforeAll(async () => { const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry }); + registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); if (getResult !== null) { actionTypeModel = getResult; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx index 7551f647a5b4a..771786157ed4c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook.test.tsx @@ -9,13 +9,14 @@ import { TypeRegistry } from '../../../type_registry'; import { registerBuiltInActionTypes } from '..'; import { ActionTypeModel } from '../../../../types'; import { WebhookActionConnector } from '../types'; +import { registrationServicesMock } from '../../../../mocks'; const ACTION_TYPE_ID = '.webhook'; let actionTypeModel: ActionTypeModel; beforeAll(() => { const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry }); + registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); if (getResult !== null) { actionTypeModel = getResult; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.test.tsx index 957302d18a6fc..7e9dbc4cf885a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.test.tsx @@ -9,13 +9,14 @@ import { TypeRegistry } from '../../../type_registry'; import { registerBuiltInActionTypes } from '..'; import { ActionTypeModel } from '../../../../types'; import { XmattersActionConnector } from '../types'; +import { registrationServicesMock } from '../../../../mocks'; const ACTION_TYPE_ID = '.xmatters'; let actionTypeModel: ActionTypeModel; beforeAll(() => { const actionTypeRegistry = new TypeRegistry(); - registerBuiltInActionTypes({ actionTypeRegistry }); + registerBuiltInActionTypes({ actionTypeRegistry, services: registrationServicesMock }); const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); if (getResult !== null) { actionTypeModel = getResult; diff --git a/x-pack/plugins/triggers_actions_ui/public/mocks.ts b/x-pack/plugins/triggers_actions_ui/public/mocks.ts index 79edc1f08ac97..007b906e8747b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/mocks.ts +++ b/x-pack/plugins/triggers_actions_ui/public/mocks.ts @@ -5,13 +5,14 @@ * 2.0. */ +import type { ValidatedEmail } from '@kbn/actions-plugin/common'; import type { TriggersAndActionsUIPublicPluginStart } from './plugin'; import { getAddConnectorFlyoutLazy } from './common/get_add_connector_flyout'; import { getEditConnectorFlyoutLazy } from './common/get_edit_connector_flyout'; import { getAddAlertFlyoutLazy } from './common/get_add_alert_flyout'; import { getEditAlertFlyoutLazy } from './common/get_edit_alert_flyout'; - +import { RegistrationServices } from './application/components/builtin_action_types'; import { TypeRegistry } from './application/type_registry'; import { ActionTypeModel, @@ -69,3 +70,9 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart { export const triggersActionsUiMock = { createStart: createStartMock, }; + +function validateEmailAddresses(addresses: string[]): ValidatedEmail[] { + return addresses.map((address) => ({ address, valid: true })); +} + +export const registrationServicesMock: RegistrationServices = { validateEmailAddresses }; diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index ba2e869c82e0f..7997552a81023 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -16,6 +16,7 @@ import { ManagementAppMountParams, ManagementSetup } from '@kbn/management-plugi import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; import { ChartsPluginStart } from '@kbn/charts-plugin/public'; import { PluginStartContract as AlertingStart } from '@kbn/alerting-plugin/public'; +import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; @@ -81,6 +82,7 @@ interface PluginsSetup { management: ManagementSetup; home?: HomePublicPluginSetup; cloud?: { isCloudEnabled: boolean }; + actions: ActionsPublicPluginSetup; } interface PluginsStart { @@ -193,6 +195,9 @@ export class Plugin registerBuiltInActionTypes({ actionTypeRegistry: this.actionTypeRegistry, + services: { + validateEmailAddresses: plugins.actions.validateEmailAddresses, + }, }); if (this.experimentalFeatures.internalAlertsTable) { diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 02f1f42af5270..14039ad3360a0 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -24,6 +24,7 @@ interface CreateTestConfigOptions { preconfiguredAlertHistoryEsIndex?: boolean; customizeLocalHostSsl?: boolean; rejectUnauthorized?: boolean; // legacy + emailDomainsAllowed?: string[]; } // test.not-enabled is specifically not enabled @@ -62,6 +63,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) preconfiguredAlertHistoryEsIndex = false, customizeLocalHostSsl = false, rejectUnauthorized = true, // legacy + emailDomainsAllowed = undefined, } = options; return async ({ readConfigFile }: FtrConfigProviderContext) => { @@ -132,6 +134,10 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) ? [`--xpack.actions.customHostSettings=${JSON.stringify(customHostSettingsValue)}`] : []; + const emailSettings = emailDomainsAllowed + ? [`--xpack.actions.email.domain_allowlist=${JSON.stringify(emailDomainsAllowed)}`] + : []; + return { testFiles: [require.resolve(`../${name}/tests/`)], servers, @@ -173,6 +179,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) `--xpack.actions.ssl.verificationMode=${verificationMode}`, ...actionsProxyUrl, ...customHostSettings, + ...emailSettings, '--xpack.eventLog.logEntries=true', '--xpack.task_manager.ephemeral_tasks.enabled=false', `--xpack.task_manager.unsafe.exclude_task_types=${JSON.stringify([ diff --git a/x-pack/test/alerting_api_integration/spaces_only/config.ts b/x-pack/test/alerting_api_integration/spaces_only/config.ts index 204f5b27da9d5..dcf1c70cf8dca 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/config.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/config.ts @@ -7,6 +7,8 @@ import { createTestConfig } from '../common/config'; +export const EmailDomainsAllowed = ['example.org', 'test.com']; + // eslint-disable-next-line import/no-default-export export default createTestConfig('spaces_only', { disabledPlugins: ['security'], @@ -15,4 +17,5 @@ export default createTestConfig('spaces_only', { verificationMode: 'none', customizeLocalHostSsl: true, preconfiguredAlertHistoryEsIndex: true, + emailDomainsAllowed: EmailDomainsAllowed, }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/email.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/email.ts new file mode 100644 index 0000000000000..22d46b3918932 --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/email.ts @@ -0,0 +1,175 @@ +/* + * 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 { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { ObjectRemover } from '../../../../common/lib'; +import { EmailDomainsAllowed } from '../../../config'; + +const EmailDomainAllowed = EmailDomainsAllowed[EmailDomainsAllowed.length - 1]; + +// eslint-disable-next-line import/no-default-export +export default function emailTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const objectRemover = new ObjectRemover(supertest); + + describe('email connector', () => { + afterEach(() => objectRemover.removeAll()); + + it('succeeds with allowed email domains', async () => { + const from = `bob@${EmailDomainAllowed}`; + const conn = await createConnector(from); + expect(conn.status).to.be(200); + + const { id } = conn.body; + expect(id).to.be.a('string'); + + const to = EmailDomainsAllowed.map((domain) => `jeb@${domain}`).sort(); + const cc = EmailDomainsAllowed.map((domain) => `jim@${domain}`).sort(); + const bcc = EmailDomainsAllowed.map((domain) => `joe@${domain}`).sort(); + + const ccNames = cc.map((email) => `Jimmy Jack <${email}>`); + + const run = await runConnector(id, to, ccNames, bcc); + expect(run.status).to.be(200); + + const { status, data } = run.body || {}; + expect(status).to.be('ok'); + + const { message } = data || {}; + const { from: fromMsg } = message || {}; + + expect(fromMsg?.address).to.be(from); + expect(addressesFromMessage(message, 'to')).to.eql(to); + expect(addressesFromMessage(message, 'cc')).to.eql(cc); + expect(addressesFromMessage(message, 'bcc')).to.eql(bcc); + + const ccNamesMsg = namesFromMessage(message, 'cc'); + for (const ccName of ccNamesMsg) { + expect(ccName).to.be('Jimmy Jack'); + } + }); + + describe('fails for invalid email domains', () => { + it('in create when invalid "from" used', async () => { + const from = `bob@not.allowed`; + const { status, body } = await createConnector(from); + expect(status).to.be(400); + + const { message = 'no message returned' } = body || {}; + expect(message).to.match(/not allowed emails: bob@not.allowed/); + }); + + it('in execute when invalid "to", "cc" or "bcc" used', async () => { + const from = `bob@${EmailDomainAllowed}`; + const conn = await createConnector(from); + expect(conn.status).to.be(200); + + const { id } = conn.body || {}; + expect(id).to.be.a('string'); + + const to = EmailDomainsAllowed.map((domain) => `jeb@${domain}`).sort(); + const cc = EmailDomainsAllowed.map((domain) => `jim@${domain}`).sort(); + const bcc = EmailDomainsAllowed.map((domain) => `joe@${domain}`).sort(); + + to.push('jeb1@not.allowed'); + cc.push('jeb2@not.allowed'); + bcc.push('jeb3@not.allowed'); + + const { status, body } = await runConnector(id, to, cc, bcc); + expect(status).to.be(200); + + expect(body?.status).to.be('error'); + expect(body?.message).to.match( + /not allowed emails: jeb1@not.allowed, jeb2@not.allowed, jeb3@not.allowed/ + ); + }); + }); + }); + + /* returns the following `body`, for the special email __json service: + { + "status": "ok", + "data": { + "envelope": { + "from": "bob@example.org", + "to": [ "jeb@example.com", ...] + }, + "messageId": "", + "message": { + "from": { "address": "bob@example.org", "name": "" }, + "to": [ { "address": "jeb@example.com", "name": "" }, ...], + "cc": [ ... ], + "bcc": [ ... ], + ... + } + }, + ... + } + */ + async function createConnector(from: string): Promise<{ status: number; body: any }> { + const { status, body } = await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name: `An email connector from ${__filename}`, + connector_type_id: '.email', + config: { + service: '__json', + from, + hasAuth: true, + }, + secrets: { + user: 'bob', + password: 'changeme', + }, + }); + + if (status === 200) { + objectRemover.add('default', body.id, 'connector', 'actions'); + } + + return { status, body }; + } + + async function runConnector( + id: string, + to: string[], + cc: string[], + bcc: string[] + ): Promise<{ status: number; body: any }> { + const subject = 'email-subject'; + const message = 'email-message'; + const { status, body } = await supertest + .post(`/api/actions/connector/${id}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ params: { to, cc, bcc, subject, message } }); + + return { status, body }; + } +} + +function addressesFromMessage(message: any, which: 'to' | 'cc' | 'bcc'): string[] { + return addressFieldFromMessage(message, which, 'address'); +} + +function namesFromMessage(message: any, which: 'to' | 'cc' | 'bcc'): string[] { + return addressFieldFromMessage(message, which, 'name'); +} + +function addressFieldFromMessage( + message: any, + which1: 'to' | 'cc' | 'bcc', + which2: 'name' | 'address' +): string[] { + const result: string[] = []; + + const list = message?.[which1]; + if (!Array.isArray(list)) return result; + + return list.map((entry) => `${entry?.[which2]}`).sort(); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts index fc0b23290a865..20498981ac2eb 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts @@ -22,6 +22,7 @@ export default function actionsTests({ loadTestFile, getService }: FtrProviderCo loadTestFile(require.resolve('./update')); loadTestFile(require.resolve('./execute')); loadTestFile(require.resolve('./enqueue')); + loadTestFile(require.resolve('./builtin_action_types/email')); loadTestFile(require.resolve('./builtin_action_types/es_index')); loadTestFile(require.resolve('./builtin_action_types/webhook')); loadTestFile(require.resolve('./builtin_action_types/preconfigured_alert_history_connector')); diff --git a/yarn.lock b/yarn.lock index bd4e88bf2fcd6..4d1428bc991a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13094,6 +13094,11 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +email-addresses@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/email-addresses/-/email-addresses-5.0.0.tgz#7ae9e7f58eef7d5e3e2c2c2d3ea49b78dc854fa6" + integrity sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw== + emittery@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.1.tgz#c02375a927a40948c0345cc903072597f5270451" From 823dec208c6e88118d3171c58cac1298e6061522 Mon Sep 17 00:00:00 2001 From: "Joey F. Poon" Date: Tue, 26 Apr 2022 09:34:10 -0500 Subject: [PATCH 10/27] [Security Solution] fix flashing authz on endpoint integration (#130933) --- .../endpoint_package_custom_extension.test.tsx | 12 ++++++++++++ .../endpoint_package_custom_extension.tsx | 12 +++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.test.tsx index 6aba0483fc945..eff4671fe2fa0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.test.tsx @@ -97,4 +97,16 @@ describe('When displaying the EndpointPackageCustomExtension fleet UI extension' expect(renderResult.queryByTestId('hostIsolationExceptions-fleetCard')).toBeNull(); }); }); + + it('should only show loading spinner if loading', () => { + useEndpointPrivilegesMock.mockReturnValue({ + ...getEndpointPrivilegesInitialStateMock(), + loading: true, + }); + render(); + + expect(renderResult.getByTestId('endpointExtensionLoadingSpinner')).toBeInTheDocument(); + expect(renderResult.queryByTestId('fleetEndpointPackageCustomContent')).toBeNull(); + expect(renderResult.queryByTestId('noIngestPermissions')).toBeNull(); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.tsx index 7700c1f71fbab..72cc9852b0e7d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.tsx @@ -6,7 +6,7 @@ */ import React, { memo, useMemo } from 'react'; -import { EuiSpacer } from '@elastic/eui'; +import { EuiSpacer, EuiLoadingSpinner } from '@elastic/eui'; import { PackageCustomExtensionComponentProps } from '@kbn/fleet-plugin/public'; import { useHttp } from '../../../../../../common/lib/kibana'; import { useCanSeeHostIsolationExceptionsMenu } from '../../../../host_isolation_exceptions/view/hooks'; @@ -34,7 +34,7 @@ export const EndpointPackageCustomExtension = memo { const http = useHttp(); const canSeeHostIsolationExceptions = useCanSeeHostIsolationExceptionsMenu(); - const { canAccessEndpointManagement } = useEndpointPrivileges(); + const { loading, canAccessEndpointManagement } = useEndpointPrivileges(); const trustedAppsApiClientInstance = useMemo( () => TrustedAppsApiClient.getInstance(http), @@ -106,7 +106,13 @@ export const EndpointPackageCustomExtension = memo; + return loading ? ( + + ) : canAccessEndpointManagement ? ( + artifactCards + ) : ( + + ); } ); From e0f8fac7ce2193ea796f36ab9783d6104977b7d5 Mon Sep 17 00:00:00 2001 From: Miriam <31922082+MiriamAparicio@users.noreply.github.com> Date: Tue, 26 Apr 2022 16:48:09 +0200 Subject: [PATCH 11/27] add-field-to-correlations-exclude-list (#130950) --- x-pack/plugins/apm/common/correlations/constants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/apm/common/correlations/constants.ts b/x-pack/plugins/apm/common/correlations/constants.ts index 11b9a9a109dbf..115e50582823a 100644 --- a/x-pack/plugins/apm/common/correlations/constants.ts +++ b/x-pack/plugins/apm/common/correlations/constants.ts @@ -25,6 +25,7 @@ export const FIELDS_TO_EXCLUDE_AS_CANDIDATE = new Set([ 'transaction.id', 'process.pid', 'process.ppid', + 'process.parent.pid', 'processor.event', 'processor.name', 'transaction.sampled', From 5d85976da454f050dd8743178cccf81403753370 Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Tue, 26 Apr 2022 10:50:11 -0400 Subject: [PATCH 12/27] [Fleet] Add `cache-control` headers to key `/epm` endpoints in Fleet API (#130921) * Add cache-control header to /categories endpoint * Add cache-control header + includeInstallStatus parameter to /packages endpoint * Add cache-control header parameter to filepath endpoint * Fix installation status type * Fix getLimitedPackages call * Fix cypress tests * Fix checks + integration test * Swap includeInstallStatus -> excludeInstallStatus query parameter Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/fleet/common/openapi/bundled.yaml | 10 ++++++- .../common/openapi/paths/epm@packages.yaml | 8 ++++- .../plugins/fleet/common/types/models/epm.ts | 5 ++++ .../fleet/common/types/rest_spec/epm.ts | 1 + .../integration/integrations_real.spec.ts | 29 +++++++++++++++++-- .../epm/screens/home/available_packages.tsx | 1 + .../fleet/server/routes/epm/handlers.ts | 17 +++++++---- .../fleet/server/services/epm/packages/get.ts | 26 +++++++++++++++-- .../fleet/server/types/rest_spec/epm.ts | 1 + 9 files changed, 86 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 1592f4124fbb7..011a242df7fdc 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -184,7 +184,15 @@ paths: schema: $ref: '#/components/schemas/get_packages_response' operationId: list-all-packages - parameters: [] + parameters: + - in: query + name: includeInstallStatus + schema: + type: boolean + default: false + description: >- + Whether to include the install status of each package. Defaults to + false to allow for caching of package requests. /epm/packages/_bulk: post: summary: Packages - Bulk install diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml index fe1aadedbcd1c..295ab298745fe 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml @@ -9,4 +9,10 @@ get: schema: $ref: ../components/schemas/get_packages_response.yaml operationId: list-all-packages -parameters: [] +parameters: + - in: query + name: includeInstallStatus + schema: + type: boolean + default: false + description: Whether to include the install status of each package. Defaults to false to allow for caching of package requests. diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 41564fdb10b3b..5217a6232a18c 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -427,12 +427,17 @@ export interface PackageUsageStats { } export type Installable = + | InstallStatusExcluded | InstalledRegistry | Installing | NotInstalled | InstallFailed | InstalledBundled; +export type InstallStatusExcluded = T & { + status: undefined; +}; + export type InstalledRegistry = T & { status: InstallationStatus['Installed']; savedObject: SavedObject; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/epm.ts b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts index f1ccaae05487b..e12bdbb202321 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/epm.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts @@ -32,6 +32,7 @@ export interface GetPackagesRequest { query: { category?: string; experimental?: boolean; + excludeInstallStatus?: boolean; }; } diff --git a/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts b/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts index e06b3d3ed5670..9bfd725497ffc 100644 --- a/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts +++ b/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts @@ -50,7 +50,19 @@ describe('Add Integration - Real API', () => { }); function addAndVerifyIntegration() { - cy.intercept('GET', '/api/fleet/epm/packages?*').as('packages'); + cy.intercept( + '/api/fleet/epm/packages?*', + { + middleware: true, + }, + (req) => { + req.on('before:response', (res) => { + // force all API responses to not be cached + res.headers['cache-control'] = 'no-store'; + }); + } + ).as('packages'); + navigateTo(INTEGRATIONS); cy.wait('@packages'); cy.get('.euiLoadingSpinner').should('not.exist'); @@ -75,7 +87,20 @@ describe('Add Integration - Real API', () => { .map((policy: any) => policy.id); cy.visit(`/app/fleet/policies/${agentPolicyId}`); - cy.intercept('GET', '/api/fleet/epm/packages?*').as('packages'); + + cy.intercept( + '/api/fleet/epm/packages?*', + { + middleware: true, + }, + (req) => { + req.on('before:response', (res) => { + // force all API responses to not be cached + res.headers['cache-control'] = 'no-store'; + }); + } + ).as('packages'); + cy.getBySel(ADD_PACKAGE_POLICY_BTN).click(); cy.wait('@packages'); cy.get('.euiLoadingSpinner').should('not.exist'); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/available_packages.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/available_packages.tsx index 88343528afa0c..154669409b457 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/available_packages.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/available_packages.tsx @@ -214,6 +214,7 @@ export const AvailablePackages: React.FC = memo(() => { error: eprPackageLoadingError, } = useGetPackages({ category: '', + excludeInstallStatus: true, }); const eprIntegrationList = useMemo( () => packageListToIntegrationsList(eprPackages?.items || []), diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index c5ac0a6c8202e..38c5042b6987e 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -10,7 +10,7 @@ import path from 'path'; import type { TypeOf } from '@kbn/config-schema'; import mime from 'mime-types'; import semverValid from 'semver/functions/valid'; -import type { ResponseHeaders, KnownHeaders } from '@kbn/core/server'; +import type { ResponseHeaders, KnownHeaders, HttpResponseOptions } from '@kbn/core/server'; import type { GetInfoResponse, @@ -62,6 +62,10 @@ import { getAsset } from '../../services/epm/archive/storage'; import { getPackageUsageStats } from '../../services/epm/packages/get'; import { updatePackage } from '../../services/epm/packages/update'; +const CACHE_CONTROL_10_MINUTES_HEADER: HttpResponseOptions['headers'] = { + 'cache-control': 'max-age=600', +}; + export const getCategoriesHandler: FleetRequestHandler< undefined, TypeOf @@ -72,7 +76,7 @@ export const getCategoriesHandler: FleetRequestHandler< items: res, response: res, }; - return response.ok({ body }); + return response.ok({ body, headers: { ...CACHE_CONTROL_10_MINUTES_HEADER } }); } catch (error) { return defaultIngestErrorHandler({ error, response }); } @@ -94,6 +98,9 @@ export const getListHandler: FleetRequestHandler< }; return response.ok({ body, + // Only cache responses where the installation status is excluded, otherwise the request + // needs up-to-date information on whether the package is installed so we can't cache it + headers: request.query.excludeInstallStatus ? { ...CACHE_CONTROL_10_MINUTES_HEADER } : {}, }); } catch (error) { return defaultIngestErrorHandler({ error, response }); @@ -164,13 +171,13 @@ export const getFileHandler: FleetRequestHandler< body: buffer, statusCode: 200, headers: { - 'cache-control': 'max-age=10, public', + ...CACHE_CONTROL_10_MINUTES_HEADER, 'content-type': contentType, }, }); } else { const registryResponse = await getFile(pkgName, pkgVersion, filePath); - const headersToProxy: KnownHeaders[] = ['content-type', 'cache-control']; + const headersToProxy: KnownHeaders[] = ['content-type']; const proxiedHeaders = headersToProxy.reduce((headers, knownHeader) => { const value = registryResponse.headers.get(knownHeader); if (value !== null) { @@ -182,7 +189,7 @@ export const getFileHandler: FleetRequestHandler< return response.custom({ body: registryResponse.body, statusCode: registryResponse.status, - headers: proxiedHeaders, + headers: { ...CACHE_CONTROL_10_MINUTES_HEADER, ...proxiedHeaders }, }); } } catch (error) { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index 0ba42585c1601..27468e77c8e9f 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -45,9 +45,10 @@ export async function getCategories(options: GetCategoriesRequest['query']) { export async function getPackages( options: { savedObjectsClient: SavedObjectsClientContract; + excludeInstallStatus?: boolean; } & Registry.SearchParams ) { - const { savedObjectsClient, experimental, category } = options; + const { savedObjectsClient, experimental, category, excludeInstallStatus = false } = options; const registryItems = await Registry.fetchList({ category, experimental }).then((items) => { return items.map((item) => Object.assign({}, item, { title: item.title || nameAsTitle(item.name) }, { id: item.name }) @@ -63,7 +64,23 @@ export async function getPackages( ) ) .sort(sortByName); - return packageList; + + if (!excludeInstallStatus) { + return packageList; + } + + // Exclude the `installStatus` value if the `excludeInstallStatus` query parameter is set to true + // to better facilitate response caching + const packageListWithoutStatus = packageList.map((pkg) => { + const newPkg = { + ...pkg, + status: undefined, + }; + + return newPkg; + }); + + return packageListWithoutStatus; } // Get package names for packages which cannot have more than one package policy on an agent policy @@ -71,7 +88,10 @@ export async function getLimitedPackages(options: { savedObjectsClient: SavedObjectsClientContract; }): Promise { const { savedObjectsClient } = options; - const allPackages = await getPackages({ savedObjectsClient, experimental: true }); + const allPackages = await getPackages({ + savedObjectsClient, + experimental: true, + }); const installedPackages = allPackages.filter( (pkg) => pkg.status === installationStatuses.Installed ); diff --git a/x-pack/plugins/fleet/server/types/rest_spec/epm.ts b/x-pack/plugins/fleet/server/types/rest_spec/epm.ts index c51a0127c2e29..1385c110f2e4e 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/epm.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/epm.ts @@ -18,6 +18,7 @@ export const GetPackagesRequestSchema = { query: schema.object({ category: schema.maybe(schema.string()), experimental: schema.maybe(schema.boolean()), + excludeInstallStatus: schema.maybe(schema.boolean({ defaultValue: false })), }), }; From c052e50b54a79b7f943ac29e8c7b51687f6d9ce8 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Tue, 26 Apr 2022 11:10:46 -0400 Subject: [PATCH 13/27] [CI] Balance various Default CI Groups (#130932) --- x-pack/test/functional/apps/canvas/index.js | 2 +- .../test/observability_functional/apps/observability/index.ts | 2 +- .../reporting_and_deprecated_security/index.ts | 2 +- .../test/reporting_functional/reporting_and_security/index.ts | 2 +- x-pack/test/security_api_integration/tests/kerberos/index.ts | 2 +- x-pack/test/security_api_integration/tests/pki/index.ts | 2 +- x-pack/test/security_api_integration/tests/saml/index.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/test/functional/apps/canvas/index.js b/x-pack/test/functional/apps/canvas/index.js index e4d59a038af74..784aeb6655768 100644 --- a/x-pack/test/functional/apps/canvas/index.js +++ b/x-pack/test/functional/apps/canvas/index.js @@ -27,7 +27,7 @@ export default function canvasApp({ loadTestFile, getService }) { await security.testUser.restoreDefaults(); }); - this.tags('ciGroup2'); // CI requires tags ヽ(゜Q。)ノ? + this.tags('ciGroup2'); loadTestFile(require.resolve('./smoke_test')); loadTestFile(require.resolve('./expression')); loadTestFile(require.resolve('./filters')); diff --git a/x-pack/test/observability_functional/apps/observability/index.ts b/x-pack/test/observability_functional/apps/observability/index.ts index 972067b2a6b68..9ec3791aef35f 100644 --- a/x-pack/test/observability_functional/apps/observability/index.ts +++ b/x-pack/test/observability_functional/apps/observability/index.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('ObservabilityApp', function () { - this.tags('ciGroup6'); + this.tags('ciGroup22'); loadTestFile(require.resolve('./alerts')); loadTestFile(require.resolve('./alerts/add_to_case')); diff --git a/x-pack/test/reporting_functional/reporting_and_deprecated_security/index.ts b/x-pack/test/reporting_functional/reporting_and_deprecated_security/index.ts index a59939deeae0b..4725cb1eae82e 100644 --- a/x-pack/test/reporting_functional/reporting_and_deprecated_security/index.ts +++ b/x-pack/test/reporting_functional/reporting_and_deprecated_security/index.ts @@ -43,7 +43,7 @@ export default function (context: FtrProviderContext) { }; describe('Reporting Functional Tests with Deprecated Security configuration enabled', function () { - this.tags('ciGroup2'); + this.tags('ciGroup20'); before(async () => { const reportingAPI = context.getService('reportingAPI'); diff --git a/x-pack/test/reporting_functional/reporting_and_security/index.ts b/x-pack/test/reporting_functional/reporting_and_security/index.ts index 22057c9be77dc..4b06eb426389e 100644 --- a/x-pack/test/reporting_functional/reporting_and_security/index.ts +++ b/x-pack/test/reporting_functional/reporting_and_security/index.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function ({ getService, loadTestFile }: FtrProviderContext) { describe('Reporting Functional Tests with Security enabled', function () { - this.tags('ciGroup2'); + this.tags('ciGroup20'); before(async () => { const reportingFunctional = getService('reportingFunctional'); diff --git a/x-pack/test/security_api_integration/tests/kerberos/index.ts b/x-pack/test/security_api_integration/tests/kerberos/index.ts index 39aac8cc4ca2f..cec92939a5194 100644 --- a/x-pack/test/security_api_integration/tests/kerberos/index.ts +++ b/x-pack/test/security_api_integration/tests/kerberos/index.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - Kerberos', function () { - this.tags('ciGroup16'); + this.tags('ciGroup31'); loadTestFile(require.resolve('./kerberos_login')); }); diff --git a/x-pack/test/security_api_integration/tests/pki/index.ts b/x-pack/test/security_api_integration/tests/pki/index.ts index 4ec858da28dbd..c3b733d0b31f8 100644 --- a/x-pack/test/security_api_integration/tests/pki/index.ts +++ b/x-pack/test/security_api_integration/tests/pki/index.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - PKI', function () { - this.tags('ciGroup6'); + this.tags('ciGroup22'); loadTestFile(require.resolve('./pki_auth')); }); diff --git a/x-pack/test/security_api_integration/tests/saml/index.ts b/x-pack/test/security_api_integration/tests/saml/index.ts index dbabb835ee980..e7ffdbd410de3 100644 --- a/x-pack/test/security_api_integration/tests/saml/index.ts +++ b/x-pack/test/security_api_integration/tests/saml/index.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - SAML', function () { - this.tags('ciGroup18'); + this.tags('ciGroup27'); loadTestFile(require.resolve('./saml_login')); }); From 799ec1b2d4c6ec4ed1d2ed4ed240d2f275b35d62 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Tue, 26 Apr 2022 11:11:15 -0400 Subject: [PATCH 14/27] [CI] Various lens test/combobox speedups (#130934) --- test/functional/services/combo_box.ts | 11 ++++++-- .../web_element_wrapper.ts | 28 +++++++++++-------- .../test/functional/page_objects/lens_page.ts | 2 +- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/test/functional/services/combo_box.ts b/test/functional/services/combo_box.ts index 88201b0ec7e19..8b43364c23a22 100644 --- a/test/functional/services/combo_box.ts +++ b/test/functional/services/combo_box.ts @@ -157,7 +157,7 @@ export class ComboBoxService extends FtrService { * @param comboBoxElement element that wraps up EuiComboBox */ private async waitForOptionsListLoading(comboBoxElement: WebElementWrapper): Promise { - await comboBoxElement.waitForDeletedByCssSelector('.euiLoadingSpinner'); + await comboBoxElement.waitForDeletedByCssSelector('.euiLoadingSpinner', 50); } /** @@ -255,7 +255,9 @@ export class ComboBoxService extends FtrService { * @param comboBoxElement element that wraps up EuiComboBox */ public async closeOptionsList(comboBoxElement: WebElementWrapper): Promise { - const isOptionsListOpen = await this.testSubjects.exists('~comboBoxOptionsList'); + const isOptionsListOpen = await this.testSubjects.exists('~comboBoxOptionsList', { + timeout: 50, + }); if (isOptionsListOpen) { const input = await comboBoxElement.findByTagName('input'); await input.pressKeys(this.browser.keys.ESCAPE); @@ -268,7 +270,10 @@ export class ComboBoxService extends FtrService { * @param comboBoxElement element that wraps up EuiComboBox */ public async openOptionsList(comboBoxElement: WebElementWrapper): Promise { - const isOptionsListOpen = await this.testSubjects.exists('~comboBoxOptionsList'); + const isOptionsListOpen = await this.testSubjects.exists('~comboBoxOptionsList', { + timeout: 50, + }); + if (!isOptionsListOpen) { await this.retry.try(async () => { const toggleBtn = await comboBoxElement.findByTestSubject('comboBoxInput'); diff --git a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts index 1a35f7c39aa28..a4f0d40b6db95 100644 --- a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts +++ b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts @@ -686,17 +686,23 @@ export class WebElementWrapper { * @param {string} className * @return {Promise} */ - public async waitForDeletedByCssSelector(selector: string): Promise { - await this.driver.manage().setTimeouts({ implicit: 1000 }); - await this.driver.wait( - async () => { - const found = await this._webElement.findElements(this.By.css(selector)); - return found.length === 0; - }, - this.timeout, - `The element with ${selector} selector was still present after ${this.timeout} sec.` - ); - await this.driver.manage().setTimeouts({ implicit: this.timeout }); + public async waitForDeletedByCssSelector( + selector: string, + implicitTimeout = 1000 + ): Promise { + try { + await this.driver.manage().setTimeouts({ implicit: implicitTimeout }); + await this.driver.wait( + async () => { + const found = await this._webElement.findElements(this.By.css(selector)); + return found.length === 0; + }, + this.timeout, + `The element with ${selector} selector was still present after ${this.timeout} sec.` + ); + } finally { + await this.driver.manage().setTimeouts({ implicit: this.timeout }); + } } /** diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 00bee191a6368..244e69c43f8e3 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -737,7 +737,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }, async openChartSwitchPopover() { - if (await testSubjects.exists('lnsChartSwitchList')) { + if (await testSubjects.exists('lnsChartSwitchList', { timeout: 50 })) { return; } await retry.try(async () => { From 8168e7e888ff554c7d727da2463088e3e45120ba Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 26 Apr 2022 08:20:46 -0700 Subject: [PATCH 15/27] [bootstrap] fix windows support (#130938) --- packages/kbn-tooling-log/BUILD.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kbn-tooling-log/BUILD.bazel b/packages/kbn-tooling-log/BUILD.bazel index a4953521171cb..e5d196bd4649c 100644 --- a/packages/kbn-tooling-log/BUILD.bazel +++ b/packages/kbn-tooling-log/BUILD.bazel @@ -54,6 +54,7 @@ TYPES_DEPS = [ "@npm//tslib", "@npm//@types/node", "@npm//@types/jest", + "//packages/kbn-jest-serializers:npm_module_types" # needed for windows development, only used in tests ] jsts_transpiler( From 60049fd085b38d608321a5802d7039034596146d Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Tue, 26 Apr 2022 17:25:00 +0200 Subject: [PATCH 16/27] [Discover] Fix adding/removing a filter for a scripted field in Document Explorer view (#130895) --- .../discover_grid_cell_actions.test.tsx | 12 +++++-- .../discover_grid_cell_actions.tsx | 31 +++++++++++-------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.test.tsx index c94b660b26893..9a75a74396ff0 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.test.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.test.tsx @@ -48,7 +48,11 @@ describe('Discover cell actions ', function () { ); const button = findTestSubject(component, 'filterForButton'); await button.simulate('click'); - expect(contextMock.onFilter).toHaveBeenCalledWith('extension', 'jpg', '+'); + expect(contextMock.onFilter).toHaveBeenCalledWith( + indexPatternMock.fields.getByName('extension'), + 'jpg', + '+' + ); }); it('triggers filter function when FilterOutBtn is clicked', async () => { const contextMock = { @@ -76,6 +80,10 @@ describe('Discover cell actions ', function () { ); const button = findTestSubject(component, 'filterOutButton'); await button.simulate('click'); - expect(contextMock.onFilter).toHaveBeenCalledWith('extension', 'jpg', '-'); + expect(contextMock.onFilter).toHaveBeenCalledWith( + indexPatternMock.fields.getByName('extension'), + 'jpg', + '-' + ); }); }); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.tsx index 1ac99da2ee8c0..318e1719c08f8 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.tsx @@ -11,7 +11,22 @@ import { EuiDataGridColumnCellActionProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { DataViewField } from '@kbn/data-views-plugin/public'; import { flattenHit } from '@kbn/data-plugin/public'; -import { DiscoverGridContext } from './discover_grid_context'; +import { DiscoverGridContext, GridContext } from './discover_grid_context'; + +function onFilterCell( + context: GridContext, + rowIndex: EuiDataGridColumnCellActionProps['rowIndex'], + columnId: EuiDataGridColumnCellActionProps['columnId'], + mode: '+' | '-' +) { + const row = context.rows[rowIndex]; + const flattened = flattenHit(row, context.indexPattern); + const field = context.indexPattern.fields.getByName(columnId); + + if (flattened && field) { + context.onFilter(field, flattened[columnId], mode); + } +} export const FilterInBtn = ({ Component, @@ -27,12 +42,7 @@ export const FilterInBtn = ({ return ( { - const row = context.rows[rowIndex]; - const flattened = flattenHit(row, context.indexPattern); - - if (flattened) { - context.onFilter(columnId, flattened[columnId], '+'); - } + onFilterCell(context, rowIndex, columnId, '+'); }} iconType="plusInCircle" aria-label={buttonTitle} @@ -60,12 +70,7 @@ export const FilterOutBtn = ({ return ( { - const row = context.rows[rowIndex]; - const flattened = flattenHit(row, context.indexPattern); - - if (flattened) { - context.onFilter(columnId, flattened[columnId], '-'); - } + onFilterCell(context, rowIndex, columnId, '-'); }} iconType="minusInCircle" aria-label={buttonTitle} From 7a93538c5a3cb69835bee44cd8bab4715a327e8b Mon Sep 17 00:00:00 2001 From: Cristina Amico Date: Tue, 26 Apr 2022 17:40:06 +0200 Subject: [PATCH 17/27] [Fleet] Remove licence check in UI for upcoming agents rolling upgrade work (#130981) --- .../sections/agents/agent_list_page/index.tsx | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx index 8e8091d13a794..7b3f1fedb160a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx @@ -32,7 +32,6 @@ import { useUrlParams, useLink, useBreadcrumbs, - useLicense, useKibanaVersion, useStartServices, } from '../../../hooks'; @@ -153,7 +152,6 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { const { getHref } = useLink(); const defaultKuery: string = (useUrlParams().urlParams.kuery as string) || ''; const hasFleetAllPrivileges = useAuthz().fleet.all; - const isGoldPlus = useLicense().isGoldPlus(); const kibanaVersion = useKibanaVersion(); // Agent data states @@ -667,27 +665,23 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { pageSizeOptions, }} isSelectable={true} - selection={ - isGoldPlus - ? { - onSelectionChange: (newAgents: Agent[]) => { - setSelectedAgents(newAgents); - setSelectionMode('manual'); - }, - selectable: isAgentSelectable, - selectableMessage: (selectable, agent) => { - if (selectable) return ''; - if (!agent.active) { - return 'This agent is not active'; - } - if (agent.policy_id && agentPoliciesIndexedById[agent.policy_id].is_managed) { - return 'This action is not available for agents enrolled in an externally managed agent policy'; - } - return ''; - }, - } - : undefined - } + selection={{ + onSelectionChange: (newAgents: Agent[]) => { + setSelectedAgents(newAgents); + setSelectionMode('manual'); + }, + selectable: isAgentSelectable, + selectableMessage: (selectable, agent) => { + if (selectable) return ''; + if (!agent.active) { + return 'This agent is not active'; + } + if (agent.policy_id && agentPoliciesIndexedById[agent.policy_id].is_managed) { + return 'This action is not available for agents enrolled in an externally managed agent policy'; + } + return ''; + }, + }} onChange={({ page }: { page: { index: number; size: number } }) => { const newPagination = { ...pagination, From fc04b3088a61624d7b3beae1d2e736d8188827b6 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 26 Apr 2022 11:47:28 -0400 Subject: [PATCH 18/27] [Alerting] Tracking number of alerts in event log `execute` document and adding telemetry for it. (#130479) * Adding telemetry for number of scheduled actions * Adding percentile by type types * Parsing percentiles by rule type and adding tests * Adding functional tests * Adding alert count fields to event log * wip * Cleanup * Fixing checks * Tests and checks * Adding functional tests * Adding total number of alerts * Adding telemetry for number of alerts * Cleanup * PR feedback Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/alerting/common/rule.ts | 4 - .../alerting/common/rule_task_instance.ts | 30 +- .../server/lib/alert_execution_store.test.ts | 115 ------ .../server/lib/rule_execution_status.test.ts | 112 +++--- .../server/lib/rule_execution_status.ts | 60 ++-- .../server/lib/rule_run_metrics_store.test.ts | 165 +++++++++ ...ion_store.ts => rule_run_metrics_store.ts} | 66 +++- .../server/lib/wrap_scoped_cluster_client.ts | 9 +- .../create_execution_handler.test.ts | 86 ++--- .../task_runner/create_execution_handler.ts | 30 +- .../alerting/server/task_runner/fixtures.ts | 9 + .../server/task_runner/task_runner.test.ts | 85 ++++- .../server/task_runner/task_runner.ts | 142 +++++--- .../task_runner/task_runner_cancel.test.ts | 28 +- .../alerting/server/task_runner/types.ts | 28 +- .../server/usage/alerting_telemetry.test.ts | 78 +++++ .../server/usage/alerting_telemetry.ts | 28 ++ .../server/usage/alerting_usage_collector.ts | 12 + x-pack/plugins/alerting/server/usage/task.ts | 3 + x-pack/plugins/alerting/server/usage/types.ts | 10 + .../plugins/event_log/generated/mappings.json | 12 + x-pack/plugins/event_log/generated/schemas.ts | 4 + x-pack/plugins/event_log/scripts/mappings.js | 12 + .../schema/xpack_plugins.json | 326 ++++++++++++++++++ .../tests/alerting/alerts.ts | 4 + .../tests/telemetry/alerting_telemetry.ts | 74 +++- .../spaces_only/tests/alerting/event_log.ts | 163 ++++++--- 27 files changed, 1264 insertions(+), 431 deletions(-) delete mode 100644 x-pack/plugins/alerting/server/lib/alert_execution_store.test.ts create mode 100644 x-pack/plugins/alerting/server/lib/rule_run_metrics_store.test.ts rename x-pack/plugins/alerting/server/lib/{alert_execution_store.ts => rule_run_metrics_store.ts} (64%) diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 3d3b66f18a436..c8f282bf695d7 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -11,7 +11,6 @@ import { SavedObjectsResolveResponse, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '@kbn/core/server'; -import { RuleExecutionMetrics } from '.'; import { RuleNotifyWhenType } from './rule_notify_when_type'; export type RuleTypeState = Record; @@ -49,9 +48,6 @@ export enum RuleExecutionStatusWarningReasons { export interface RuleExecutionStatus { status: RuleExecutionStatuses; - numberOfTriggeredActions?: number; - numberOfGeneratedActions?: number; - metrics?: RuleExecutionMetrics; lastExecutionDate: Date; lastDuration?: number; error?: { diff --git a/x-pack/plugins/alerting/common/rule_task_instance.ts b/x-pack/plugins/alerting/common/rule_task_instance.ts index 529b65e719ddd..77537bafc4bb9 100644 --- a/x-pack/plugins/alerting/common/rule_task_instance.ts +++ b/x-pack/plugins/alerting/common/rule_task_instance.ts @@ -8,7 +8,11 @@ import * as t from 'io-ts'; import { rawAlertInstance } from './alert_instance'; import { DateFromString } from './date_from_string'; -import { IntervalSchedule, RuleMonitoring } from './rule'; + +export enum ActionsCompletion { + COMPLETE = 'complete', + PARTIAL = 'partial', +} export const ruleStateSchema = t.partial({ alertTypeState: t.record(t.string, t.unknown), @@ -16,24 +20,8 @@ export const ruleStateSchema = t.partial({ previousStartedAt: t.union([t.null, DateFromString]), }); -const ruleExecutionMetricsSchema = t.partial({ - numSearches: t.number, - totalSearchDurationMs: t.number, - esSearchDurationMs: t.number, -}); - -const alertExecutionMetrics = t.partial({ - numberOfTriggeredActions: t.number, - numberOfGeneratedActions: t.number, - triggeredActionsStatus: t.string, -}); - -export type RuleExecutionMetrics = t.TypeOf; +// This is serialized in the rule task document export type RuleTaskState = t.TypeOf; -export type RuleExecutionState = RuleTaskState & { - metrics: RuleExecutionMetrics; - alertExecutionMetrics: t.TypeOf; -}; export const ruleParamsSchema = t.intersection([ t.type({ @@ -44,9 +32,3 @@ export const ruleParamsSchema = t.intersection([ }), ]); export type RuleTaskParams = t.TypeOf; - -export interface RuleExecutionRunResult { - state: RuleExecutionState; - monitoring: RuleMonitoring | undefined; - schedule: IntervalSchedule | undefined; -} diff --git a/x-pack/plugins/alerting/server/lib/alert_execution_store.test.ts b/x-pack/plugins/alerting/server/lib/alert_execution_store.test.ts deleted file mode 100644 index 905ef53cda9db..0000000000000 --- a/x-pack/plugins/alerting/server/lib/alert_execution_store.test.ts +++ /dev/null @@ -1,115 +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 { AlertExecutionStore } from './alert_execution_store'; -import { ActionsCompletion } from '../task_runner/types'; - -describe('AlertExecutionStore', () => { - const alertExecutionStore = new AlertExecutionStore(); - const testConnectorId = 'test-connector-id'; - - // Getter Setter - test('returns the default values if there is no change', () => { - expect(alertExecutionStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.COMPLETE); - expect(alertExecutionStore.getNumberOfTriggeredActions()).toBe(0); - expect(alertExecutionStore.getNumberOfGeneratedActions()).toBe(0); - expect(alertExecutionStore.getStatusByConnectorType('any')).toBe(undefined); - }); - - test('sets and returns numberOfTriggeredActions', () => { - alertExecutionStore.setNumberOfTriggeredActions(5); - expect(alertExecutionStore.getNumberOfTriggeredActions()).toBe(5); - }); - - test('sets and returns numberOfGeneratedActions', () => { - alertExecutionStore.setNumberOfGeneratedActions(15); - expect(alertExecutionStore.getNumberOfGeneratedActions()).toBe(15); - }); - - test('sets and returns triggeredActionsStatusByConnectorType', () => { - alertExecutionStore.setTriggeredActionsStatusByConnectorType({ - actionTypeId: testConnectorId, - status: ActionsCompletion.PARTIAL, - }); - expect( - alertExecutionStore.getStatusByConnectorType(testConnectorId).triggeredActionsStatus - ).toBe(ActionsCompletion.PARTIAL); - expect(alertExecutionStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.PARTIAL); - }); - - // increment - test('increments numberOfTriggeredActions by 1', () => { - alertExecutionStore.incrementNumberOfTriggeredActions(); - expect(alertExecutionStore.getNumberOfTriggeredActions()).toBe(6); - }); - - test('increments incrementNumberOfGeneratedActions by x', () => { - alertExecutionStore.incrementNumberOfGeneratedActions(2); - expect(alertExecutionStore.getNumberOfGeneratedActions()).toBe(17); - }); - - test('increments numberOfTriggeredActionsByConnectorType by 1', () => { - alertExecutionStore.incrementNumberOfTriggeredActionsByConnectorType(testConnectorId); - expect( - alertExecutionStore.getStatusByConnectorType(testConnectorId).numberOfTriggeredActions - ).toBe(1); - }); - - test('increments NumberOfGeneratedActionsByConnectorType by 1', () => { - alertExecutionStore.incrementNumberOfGeneratedActionsByConnectorType(testConnectorId); - expect( - alertExecutionStore.getStatusByConnectorType(testConnectorId).numberOfGeneratedActions - ).toBe(1); - }); - - // Checker - test('checks if it has reached the executable actions limit', () => { - expect(alertExecutionStore.hasReachedTheExecutableActionsLimit({ default: { max: 10 } })).toBe( - false - ); - - expect(alertExecutionStore.hasReachedTheExecutableActionsLimit({ default: { max: 5 } })).toBe( - true - ); - }); - - test('checks if it has reached the executable actions limit by connector type', () => { - alertExecutionStore.incrementNumberOfTriggeredActionsByConnectorType(testConnectorId); - alertExecutionStore.incrementNumberOfTriggeredActionsByConnectorType(testConnectorId); - alertExecutionStore.incrementNumberOfTriggeredActionsByConnectorType(testConnectorId); - alertExecutionStore.incrementNumberOfTriggeredActionsByConnectorType(testConnectorId); - alertExecutionStore.incrementNumberOfTriggeredActionsByConnectorType(testConnectorId); - - expect( - alertExecutionStore.hasReachedTheExecutableActionsLimitByConnectorType({ - actionsConfigMap: { - default: { max: 20 }, - [testConnectorId]: { - max: 5, - }, - }, - actionTypeId: testConnectorId, - }) - ).toBe(true); - - expect( - alertExecutionStore.hasReachedTheExecutableActionsLimitByConnectorType({ - actionsConfigMap: { - default: { max: 20 }, - [testConnectorId]: { - max: 8, - }, - }, - actionTypeId: testConnectorId, - }) - ).toBe(false); - }); - - test('checks if a connector type it has already reached the executable actions limit', () => { - expect(alertExecutionStore.hasConnectorTypeReachedTheLimit(testConnectorId)).toBe(true); - }); -}); 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 52de76d4b4dd9..8551bb00287c7 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,9 +7,9 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import { + ActionsCompletion, RuleExecutionStatusErrorReasons, RuleExecutionStatusWarningReasons, - RuleExecutionState, } from '../types'; import { executionStatusFromState, @@ -19,98 +19,80 @@ import { } from './rule_execution_status'; import { ErrorWithReason } from './error_with_reason'; import { translations } from '../constants/translations'; -import { ActionsCompletion } from '../task_runner/types'; +import { RuleRunMetrics, RuleRunMetricsStore } from './rule_run_metrics_store'; const MockLogger = loggingSystemMock.create().get(); -const metrics = { numSearches: 1, esSearchDurationMs: 10, totalSearchDurationMs: 20 }; +const executionMetrics = { + numSearches: 1, + esSearchDurationMs: 10, + totalSearchDurationMs: 20, + numberOfTriggeredActions: 32, + numberOfGeneratedActions: 11, + numberOfActiveAlerts: 2, + numberOfNewAlerts: 3, + numberOfRecoveredAlerts: 13, + triggeredActionsStatus: ActionsCompletion.COMPLETE, +}; describe('RuleExecutionStatus', () => { beforeEach(() => { jest.resetAllMocks(); }); + function testExpectedMetrics(received: RuleRunMetrics, expected: RuleRunMetrics) { + expect(received.numSearches).toEqual(expected.numSearches); + expect(received.totalSearchDurationMs).toEqual(expected.totalSearchDurationMs); + expect(received.esSearchDurationMs).toEqual(expected.esSearchDurationMs); + expect(received.numberOfTriggeredActions).toEqual(expected.numberOfTriggeredActions); + expect(received.numberOfGeneratedActions).toEqual(expected.numberOfGeneratedActions); + expect(received.numberOfActiveAlerts).toEqual(expected.numberOfActiveAlerts); + expect(received.numberOfRecoveredAlerts).toEqual(expected.numberOfRecoveredAlerts); + expect(received.numberOfNewAlerts).toEqual(expected.numberOfNewAlerts); + expect(received.triggeredActionsStatus).toEqual(expected.triggeredActionsStatus); + } + describe('executionStatusFromState()', () => { test('empty task state', () => { - const status = executionStatusFromState({ - alertExecutionMetrics: { - numberOfTriggeredActions: 0, - numberOfGeneratedActions: 0, - triggeredActionsStatus: ActionsCompletion.COMPLETE, - }, - } as RuleExecutionState); + const emptyRuleRunState = new RuleRunMetricsStore().getMetrics(); + const { status, metrics } = executionStatusFromState({ metrics: emptyRuleRunState }); checkDateIsNearNow(status.lastExecutionDate); - expect(status.numberOfTriggeredActions).toBe(0); - expect(status.numberOfGeneratedActions).toBe(0); expect(status.status).toBe('ok'); expect(status.error).toBe(undefined); expect(status.warning).toBe(undefined); + + testExpectedMetrics(metrics!, emptyRuleRunState); }); test('task state with no instances', () => { - const status = executionStatusFromState({ + const { status, metrics } = executionStatusFromState({ alertInstances: {}, - alertExecutionMetrics: { - numberOfTriggeredActions: 0, - numberOfGeneratedActions: 0, - triggeredActionsStatus: ActionsCompletion.COMPLETE, - }, - metrics, + metrics: executionMetrics, }); checkDateIsNearNow(status.lastExecutionDate); - expect(status.numberOfTriggeredActions).toBe(0); - expect(status.numberOfGeneratedActions).toBe(0); expect(status.status).toBe('ok'); expect(status.error).toBe(undefined); expect(status.warning).toBe(undefined); - expect(status.metrics).toBe(metrics); + + testExpectedMetrics(metrics!, executionMetrics); }); test('task state with one instance', () => { - const status = executionStatusFromState({ + const { status, metrics } = executionStatusFromState({ alertInstances: { a: {} }, - alertExecutionMetrics: { - numberOfTriggeredActions: 0, - numberOfGeneratedActions: 0, - triggeredActionsStatus: ActionsCompletion.COMPLETE, - }, - metrics, + metrics: executionMetrics, }); checkDateIsNearNow(status.lastExecutionDate); - expect(status.numberOfTriggeredActions).toBe(0); - expect(status.numberOfGeneratedActions).toBe(0); expect(status.status).toBe('active'); expect(status.error).toBe(undefined); expect(status.warning).toBe(undefined); - expect(status.metrics).toBe(metrics); - }); - test('task state with numberOfTriggeredActions', () => { - const status = executionStatusFromState({ - alertExecutionMetrics: { - numberOfTriggeredActions: 1, - numberOfGeneratedActions: 2, - triggeredActionsStatus: ActionsCompletion.COMPLETE, - }, - alertInstances: { a: {} }, - metrics, - }); - checkDateIsNearNow(status.lastExecutionDate); - expect(status.numberOfTriggeredActions).toBe(1); - expect(status.numberOfGeneratedActions).toBe(2); - expect(status.status).toBe('active'); - expect(status.error).toBe(undefined); - expect(status.warning).toBe(undefined); - expect(status.metrics).toBe(metrics); + testExpectedMetrics(metrics!, executionMetrics); }); test('task state with warning', () => { - const status = executionStatusFromState({ + const { status, metrics } = executionStatusFromState({ alertInstances: { a: {} }, - alertExecutionMetrics: { - numberOfTriggeredActions: 3, - triggeredActionsStatus: ActionsCompletion.PARTIAL, - }, - metrics, + metrics: { ...executionMetrics, triggeredActionsStatus: ActionsCompletion.PARTIAL }, }); checkDateIsNearNow(status.lastExecutionDate); expect(status.warning).toEqual({ @@ -119,12 +101,17 @@ describe('RuleExecutionStatus', () => { }); expect(status.status).toBe('warning'); expect(status.error).toBe(undefined); + + testExpectedMetrics(metrics!, { + ...executionMetrics, + triggeredActionsStatus: ActionsCompletion.PARTIAL, + }); }); }); describe('executionStatusFromError()', () => { test('error with no reason', () => { - const status = executionStatusFromError(new Error('boo!')); + const { status, metrics } = executionStatusFromError(new Error('boo!')); expect(status.status).toBe('error'); expect(status.error).toMatchInlineSnapshot(` Object { @@ -132,10 +119,11 @@ describe('RuleExecutionStatus', () => { "reason": "unknown", } `); + expect(metrics).toBeNull(); }); test('error with a reason', () => { - const status = executionStatusFromError( + const { status, metrics } = executionStatusFromError( new ErrorWithReason(RuleExecutionStatusErrorReasons.Execute, new Error('hoo!')) ); expect(status.status).toBe('error'); @@ -145,6 +133,7 @@ describe('RuleExecutionStatus', () => { "reason": "execute", } `); + expect(metrics).toBeNull(); }); }); @@ -195,9 +184,12 @@ describe('RuleExecutionStatus', () => { `); }); - test('status with a numberOfTriggeredActions', () => { + test('status with a alerts and actions counts', () => { expect( - ruleExecutionStatusToRaw({ lastExecutionDate: date, status, numberOfTriggeredActions: 5 }) + ruleExecutionStatusToRaw({ + lastExecutionDate: date, + status, + }) ).toMatchInlineSnapshot(` Object { "error": null, 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 0b9d2520ae455..28eaf24ef8a2d 100644 --- a/x-pack/plugins/alerting/server/lib/rule_execution_status.ts +++ b/x-pack/plugins/alerting/server/lib/rule_execution_status.ts @@ -11,19 +11,27 @@ import { RuleExecutionStatusValues, RuleExecutionStatusWarningReasons, RawRuleExecutionStatus, - RuleExecutionState, } from '../types'; import { getReasonFromError } from './error_with_reason'; import { getEsErrorMessage } from './errors'; -import { RuleExecutionStatuses } from '../../common'; +import { ActionsCompletion, RuleExecutionStatuses } from '../../common'; import { translations } from '../constants/translations'; -import { ActionsCompletion } from '../task_runner/types'; +import { RuleTaskStateAndMetrics } from '../task_runner/types'; +import { RuleRunMetrics } from './rule_run_metrics_store'; -export function executionStatusFromState(state: RuleExecutionState): RuleExecutionStatus { - const alertIds = Object.keys(state.alertInstances ?? {}); +export interface IExecutionStatusAndMetrics { + status: RuleExecutionStatus; + metrics: RuleRunMetrics | null; +} + +export function executionStatusFromState( + stateWithMetrics: RuleTaskStateAndMetrics, + lastExecutionDate?: Date +): IExecutionStatusAndMetrics { + const alertIds = Object.keys(stateWithMetrics.alertInstances ?? {}); const hasIncompleteAlertExecution = - state.alertExecutionMetrics.triggeredActionsStatus === ActionsCompletion.PARTIAL; + stateWithMetrics.metrics.triggeredActionsStatus === ActionsCompletion.PARTIAL; let status: RuleExecutionStatuses = alertIds.length === 0 ? RuleExecutionStatusValues[0] : RuleExecutionStatusValues[1]; @@ -33,28 +41,34 @@ export function executionStatusFromState(state: RuleExecutionState): RuleExecuti } return { - metrics: state.metrics, - numberOfTriggeredActions: state.alertExecutionMetrics.numberOfTriggeredActions, - numberOfGeneratedActions: state.alertExecutionMetrics.numberOfGeneratedActions, - lastExecutionDate: new Date(), - status, - ...(hasIncompleteAlertExecution && { - warning: { - reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, - message: translations.taskRunner.warning.maxExecutableActions, - }, - }), + status: { + lastExecutionDate: lastExecutionDate ?? new Date(), + status, + ...(hasIncompleteAlertExecution && { + warning: { + reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, + message: translations.taskRunner.warning.maxExecutableActions, + }, + }), + }, + metrics: stateWithMetrics.metrics, }; } -export function executionStatusFromError(error: Error): RuleExecutionStatus { +export function executionStatusFromError( + error: Error, + lastExecutionDate?: Date +): IExecutionStatusAndMetrics { return { - lastExecutionDate: new Date(), - status: 'error', - error: { - reason: getReasonFromError(error), - message: getEsErrorMessage(error), + status: { + lastExecutionDate: lastExecutionDate ?? new Date(), + status: 'error', + error: { + reason: getReasonFromError(error), + message: getEsErrorMessage(error), + }, }, + metrics: null, }; } diff --git a/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.test.ts b/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.test.ts new file mode 100644 index 0000000000000..b27dc37a459e0 --- /dev/null +++ b/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.test.ts @@ -0,0 +1,165 @@ +/* + * 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 { RuleRunMetricsStore } from './rule_run_metrics_store'; +import { ActionsCompletion } from '../types'; + +describe('RuleRunMetricsStore', () => { + const ruleRunMetricsStore = new RuleRunMetricsStore(); + const testConnectorId = 'test-connector-id'; + + // Getter Setter + test('returns the default values if there is no change', () => { + expect(ruleRunMetricsStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.COMPLETE); + expect(ruleRunMetricsStore.getNumSearches()).toBe(0); + expect(ruleRunMetricsStore.getTotalSearchDurationMs()).toBe(0); + expect(ruleRunMetricsStore.getEsSearchDurationMs()).toBe(0); + expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(0); + expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(0); + expect(ruleRunMetricsStore.getNumberOfActiveAlerts()).toBe(0); + expect(ruleRunMetricsStore.getNumberOfRecoveredAlerts()).toBe(0); + expect(ruleRunMetricsStore.getNumberOfNewAlerts()).toBe(0); + expect(ruleRunMetricsStore.getStatusByConnectorType('any')).toBe(undefined); + }); + + test('sets and returns numSearches', () => { + ruleRunMetricsStore.setNumSearches(1); + expect(ruleRunMetricsStore.getNumSearches()).toBe(1); + }); + + test('sets and returns totalSearchDurationMs', () => { + ruleRunMetricsStore.setTotalSearchDurationMs(2); + expect(ruleRunMetricsStore.getTotalSearchDurationMs()).toBe(2); + }); + + test('sets and returns esSearchDurationMs', () => { + ruleRunMetricsStore.setEsSearchDurationMs(3); + expect(ruleRunMetricsStore.getEsSearchDurationMs()).toBe(3); + }); + + test('sets and returns numberOfTriggeredActions', () => { + ruleRunMetricsStore.setNumberOfTriggeredActions(5); + expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(5); + }); + + test('sets and returns numberOfGeneratedActions', () => { + ruleRunMetricsStore.setNumberOfGeneratedActions(15); + expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(15); + }); + + test('sets and returns numberOfActiveAlerts', () => { + ruleRunMetricsStore.setNumberOfActiveAlerts(10); + expect(ruleRunMetricsStore.getNumberOfActiveAlerts()).toBe(10); + }); + + test('sets and returns numberOfRecoveredAlerts', () => { + ruleRunMetricsStore.setNumberOfRecoveredAlerts(11); + expect(ruleRunMetricsStore.getNumberOfRecoveredAlerts()).toBe(11); + }); + + test('sets and returns numberOfNewAlerts', () => { + ruleRunMetricsStore.setNumberOfNewAlerts(12); + expect(ruleRunMetricsStore.getNumberOfNewAlerts()).toBe(12); + }); + + test('sets and returns triggeredActionsStatusByConnectorType', () => { + ruleRunMetricsStore.setTriggeredActionsStatusByConnectorType({ + actionTypeId: testConnectorId, + status: ActionsCompletion.PARTIAL, + }); + expect( + ruleRunMetricsStore.getStatusByConnectorType(testConnectorId).triggeredActionsStatus + ).toBe(ActionsCompletion.PARTIAL); + expect(ruleRunMetricsStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.PARTIAL); + }); + + test('gets metrics', () => { + expect(ruleRunMetricsStore.getMetrics()).toEqual({ + triggeredActionsStatus: 'partial', + esSearchDurationMs: 3, + numSearches: 1, + numberOfActiveAlerts: 10, + numberOfGeneratedActions: 15, + numberOfNewAlerts: 12, + numberOfRecoveredAlerts: 11, + numberOfTriggeredActions: 5, + totalSearchDurationMs: 2, + }); + }); + + // increment + test('increments numberOfTriggeredActions by 1', () => { + ruleRunMetricsStore.incrementNumberOfTriggeredActions(); + expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(6); + }); + + test('increments incrementNumberOfGeneratedActions by x', () => { + ruleRunMetricsStore.incrementNumberOfGeneratedActions(2); + expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(17); + }); + + test('increments numberOfTriggeredActionsByConnectorType by 1', () => { + ruleRunMetricsStore.incrementNumberOfTriggeredActionsByConnectorType(testConnectorId); + expect( + ruleRunMetricsStore.getStatusByConnectorType(testConnectorId).numberOfTriggeredActions + ).toBe(1); + }); + + test('increments NumberOfGeneratedActionsByConnectorType by 1', () => { + ruleRunMetricsStore.incrementNumberOfGeneratedActionsByConnectorType(testConnectorId); + expect( + ruleRunMetricsStore.getStatusByConnectorType(testConnectorId).numberOfGeneratedActions + ).toBe(1); + }); + + // Checker + test('checks if it has reached the executable actions limit', () => { + expect(ruleRunMetricsStore.hasReachedTheExecutableActionsLimit({ default: { max: 10 } })).toBe( + false + ); + + expect(ruleRunMetricsStore.hasReachedTheExecutableActionsLimit({ default: { max: 5 } })).toBe( + true + ); + }); + + test('checks if it has reached the executable actions limit by connector type', () => { + ruleRunMetricsStore.incrementNumberOfTriggeredActionsByConnectorType(testConnectorId); + ruleRunMetricsStore.incrementNumberOfTriggeredActionsByConnectorType(testConnectorId); + ruleRunMetricsStore.incrementNumberOfTriggeredActionsByConnectorType(testConnectorId); + ruleRunMetricsStore.incrementNumberOfTriggeredActionsByConnectorType(testConnectorId); + ruleRunMetricsStore.incrementNumberOfTriggeredActionsByConnectorType(testConnectorId); + + expect( + ruleRunMetricsStore.hasReachedTheExecutableActionsLimitByConnectorType({ + actionsConfigMap: { + default: { max: 20 }, + [testConnectorId]: { + max: 5, + }, + }, + actionTypeId: testConnectorId, + }) + ).toBe(true); + + expect( + ruleRunMetricsStore.hasReachedTheExecutableActionsLimitByConnectorType({ + actionsConfigMap: { + default: { max: 20 }, + [testConnectorId]: { + max: 8, + }, + }, + actionTypeId: testConnectorId, + }) + ).toBe(false); + }); + + test('checks if a connector type it has already reached the executable actions limit', () => { + expect(ruleRunMetricsStore.hasConnectorTypeReachedTheLimit(testConnectorId)).toBe(true); + }); +}); diff --git a/x-pack/plugins/alerting/server/lib/alert_execution_store.ts b/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.ts similarity index 64% rename from x-pack/plugins/alerting/server/lib/alert_execution_store.ts rename to x-pack/plugins/alerting/server/lib/rule_run_metrics_store.ts index b601a76b809a4..97fa14214e4f7 100644 --- a/x-pack/plugins/alerting/server/lib/alert_execution_store.ts +++ b/x-pack/plugins/alerting/server/lib/rule_run_metrics_store.ts @@ -6,12 +6,18 @@ */ import { set } from 'lodash'; +import { ActionsCompletion } from '../types'; import { ActionsConfigMap } from './get_actions_config_map'; -import { ActionsCompletion } from '../task_runner/types'; interface State { + numSearches: number; + totalSearchDurationMs: number; + esSearchDurationMs: number; numberOfTriggeredActions: number; numberOfGeneratedActions: number; + numberOfActiveAlerts: number; + numberOfRecoveredAlerts: number; + numberOfNewAlerts: number; connectorTypes: { [key: string]: { triggeredActionsStatus: ActionsCompletion; @@ -21,10 +27,19 @@ interface State { }; } -export class AlertExecutionStore { +export type RuleRunMetrics = Omit & { + triggeredActionsStatus: ActionsCompletion; +}; +export class RuleRunMetricsStore { private state: State = { + numSearches: 0, + totalSearchDurationMs: 0, + esSearchDurationMs: 0, numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, + numberOfActiveAlerts: 0, + numberOfRecoveredAlerts: 0, + numberOfNewAlerts: 0, connectorTypes: {}, }; @@ -35,25 +50,66 @@ export class AlertExecutionStore { ); return hasPartial ? ActionsCompletion.PARTIAL : ActionsCompletion.COMPLETE; }; + public getNumSearches = () => { + return this.state.numSearches; + }; + public getTotalSearchDurationMs = () => { + return this.state.totalSearchDurationMs; + }; + public getEsSearchDurationMs = () => { + return this.state.esSearchDurationMs; + }; public getNumberOfTriggeredActions = () => { return this.state.numberOfTriggeredActions; }; public getNumberOfGeneratedActions = () => { return this.state.numberOfGeneratedActions; }; + public getNumberOfActiveAlerts = () => { + return this.state.numberOfActiveAlerts; + }; + public getNumberOfRecoveredAlerts = () => { + return this.state.numberOfRecoveredAlerts; + }; + public getNumberOfNewAlerts = () => { + return this.state.numberOfNewAlerts; + }; public getStatusByConnectorType = (actionTypeId: string) => { return this.state.connectorTypes[actionTypeId]; }; + public getMetrics = (): RuleRunMetrics => { + const { connectorTypes, ...metrics } = this.state; + return { + ...metrics, + triggeredActionsStatus: this.getTriggeredActionsStatus(), + }; + }; // Setters + public setNumSearches = (numSearches: number) => { + this.state.numSearches = numSearches; + }; + public setTotalSearchDurationMs = (totalSearchDurationMs: number) => { + this.state.totalSearchDurationMs = totalSearchDurationMs; + }; + public setEsSearchDurationMs = (esSearchDurationMs: number) => { + this.state.esSearchDurationMs = esSearchDurationMs; + }; public setNumberOfTriggeredActions = (numberOfTriggeredActions: number) => { this.state.numberOfTriggeredActions = numberOfTriggeredActions; }; - public setNumberOfGeneratedActions = (numberOfGeneratedActions: number) => { this.state.numberOfGeneratedActions = numberOfGeneratedActions; }; - + public setNumberOfActiveAlerts = (numberOfActiveAlerts: number) => { + this.state.numberOfActiveAlerts = numberOfActiveAlerts; + }; + public setNumberOfRecoveredAlerts = (numberOfRecoveredAlerts: number) => { + this.state.numberOfRecoveredAlerts = numberOfRecoveredAlerts; + }; + public setNumberOfNewAlerts = (numberOfNewAlerts: number) => { + this.state.numberOfNewAlerts = numberOfNewAlerts; + }; public setTriggeredActionsStatusByConnectorType = ({ actionTypeId, status, @@ -90,11 +146,9 @@ export class AlertExecutionStore { public incrementNumberOfTriggeredActions = () => { this.state.numberOfTriggeredActions++; }; - public incrementNumberOfGeneratedActions = (incrementBy: number) => { this.state.numberOfGeneratedActions += incrementBy; }; - public incrementNumberOfTriggeredActionsByConnectorType = (actionTypeId: string) => { const currentVal = this.state.connectorTypes[actionTypeId]?.numberOfTriggeredActions || 0; set(this.state, `connectorTypes["${actionTypeId}"].numberOfTriggeredActions`, currentVal + 1); 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 af77191474d89..28c5301e9a8b9 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 @@ -21,10 +21,15 @@ import type { AggregationsAggregate, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { IScopedClusterClient, ElasticsearchClient, Logger } from '@kbn/core/server'; -import { RuleExecutionMetrics } from '../types'; import { Rule } from '../types'; +import { RuleRunMetrics } from './rule_run_metrics_store'; type RuleInfo = Pick & { spaceId: string }; +type SearchMetrics = Pick< + RuleRunMetrics, + 'numSearches' | 'totalSearchDurationMs' | 'esSearchDurationMs' +>; + interface WrapScopedClusterClientFactoryOpts { scopedClusterClient: IScopedClusterClient; rule: RuleInfo; @@ -61,7 +66,7 @@ export function createWrappedScopedClusterClientFactory(opts: WrapScopedClusterC return { client: () => wrappedClient, - getMetrics: (): RuleExecutionMetrics => { + getMetrics: (): SearchMetrics => { return { esSearchDurationMs, totalSearchDurationMs, 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 0a51bc06b6e43..81f7fa7da02d2 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 @@ -6,7 +6,7 @@ */ import { createExecutionHandler } from './create_execution_handler'; -import { ActionsCompletion, CreateExecutionHandlerOptions } from './types'; +import { CreateExecutionHandlerOptions } from './types'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import { actionsClientMock, @@ -18,8 +18,14 @@ import { KibanaRequest } from '@kbn/core/server'; import { asSavedObjectExecutionSource } from '@kbn/actions-plugin/server'; import { InjectActionParamsOpts } from './inject_action_params'; import { NormalizedRuleType } from '../rule_type_registry'; -import { AlertInstanceContext, AlertInstanceState, RuleTypeParams, RuleTypeState } from '../types'; -import { AlertExecutionStore } from '../lib/alert_execution_store'; +import { + ActionsCompletion, + AlertInstanceContext, + AlertInstanceState, + RuleTypeParams, + RuleTypeState, +} from '../types'; +import { RuleRunMetricsStore } from '../lib/rule_run_metrics_store'; jest.mock('./inject_action_params', () => ({ injectActionParams: jest.fn(), @@ -105,7 +111,7 @@ const createExecutionHandlerParams: jest.Mocked< }, }, }; -let alertExecutionStore: AlertExecutionStore; +let ruleRunMetricsStore: RuleRunMetricsStore; describe('Create Execution Handler', () => { beforeEach(() => { @@ -121,7 +127,7 @@ describe('Create Execution Handler', () => { mockActionsPlugin.renderActionParameterTemplates.mockImplementation( renderActionParameterTemplatesDefault ); - alertExecutionStore = new AlertExecutionStore(); + ruleRunMetricsStore = new RuleRunMetricsStore(); }); test('enqueues execution per selected action', async () => { @@ -131,10 +137,10 @@ describe('Create Execution Handler', () => { state: {}, context: {}, alertId: '2', - alertExecutionStore, + ruleRunMetricsStore, }); - expect(alertExecutionStore.getNumberOfTriggeredActions()).toBe(1); - expect(alertExecutionStore.getNumberOfGeneratedActions()).toBe(1); + expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(1); + expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(1); expect(mockActionsPlugin.getActionsClientWithRequest).toHaveBeenCalledWith( createExecutionHandlerParams.request ); @@ -242,7 +248,7 @@ describe('Create Execution Handler', () => { }, }); - expect(alertExecutionStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.COMPLETE); + expect(ruleRunMetricsStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.COMPLETE); }); test(`doesn't call actionsPlugin.execute for disabled actionTypes`, async () => { @@ -280,10 +286,10 @@ describe('Create Execution Handler', () => { state: {}, context: {}, alertId: '2', - alertExecutionStore, + ruleRunMetricsStore, }); - expect(alertExecutionStore.getNumberOfTriggeredActions()).toBe(1); - expect(alertExecutionStore.getNumberOfGeneratedActions()).toBe(2); + expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(1); + expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(2); expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.enqueueExecution).toHaveBeenCalledWith({ consumer: 'rule-consumer', @@ -344,10 +350,10 @@ describe('Create Execution Handler', () => { state: {}, context: {}, alertId: '2', - alertExecutionStore, + ruleRunMetricsStore, }); - expect(alertExecutionStore.getNumberOfTriggeredActions()).toBe(0); - expect(alertExecutionStore.getNumberOfGeneratedActions()).toBe(2); + expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(0); + expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(2); expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(0); mockActionsPlugin.isActionExecutable.mockImplementation(() => true); @@ -360,7 +366,7 @@ describe('Create Execution Handler', () => { state: {}, context: {}, alertId: '2', - alertExecutionStore, + ruleRunMetricsStore, }); expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(1); }); @@ -372,10 +378,10 @@ describe('Create Execution Handler', () => { state: {}, context: {}, alertId: '2', - alertExecutionStore, + ruleRunMetricsStore, }); - expect(alertExecutionStore.getNumberOfTriggeredActions()).toBe(0); - expect(alertExecutionStore.getNumberOfGeneratedActions()).toBe(0); + expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(0); + expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(0); expect(actionsClient.enqueueExecution).not.toHaveBeenCalled(); }); @@ -386,10 +392,10 @@ describe('Create Execution Handler', () => { context: { value: 'context-val' }, state: {}, alertId: '2', - alertExecutionStore, + ruleRunMetricsStore, }); - expect(alertExecutionStore.getNumberOfTriggeredActions()).toBe(1); - expect(alertExecutionStore.getNumberOfGeneratedActions()).toBe(1); + expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(1); + expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(1); expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.enqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` Array [ @@ -432,7 +438,7 @@ describe('Create Execution Handler', () => { context: {}, state: { value: 'state-val' }, alertId: '2', - alertExecutionStore, + ruleRunMetricsStore, }); expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.enqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` @@ -478,15 +484,15 @@ describe('Create Execution Handler', () => { context: {}, state: {}, alertId: '2', - alertExecutionStore, + ruleRunMetricsStore, }); expect(createExecutionHandlerParams.logger.error).toHaveBeenCalledWith( 'Invalid action group "invalid-group" for rule "test".' ); - expect(alertExecutionStore.getNumberOfTriggeredActions()).toBe(0); - expect(alertExecutionStore.getNumberOfGeneratedActions()).toBe(0); - expect(alertExecutionStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.COMPLETE); + expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(0); + expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(0); + expect(ruleRunMetricsStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.COMPLETE); }); test('Stops triggering actions when the number of total triggered actions is reached the number of max executable actions', async () => { @@ -531,19 +537,19 @@ describe('Create Execution Handler', () => { ], }); - alertExecutionStore = new AlertExecutionStore(); + ruleRunMetricsStore = new RuleRunMetricsStore(); await executionHandler({ actionGroup: 'default', context: {}, state: { value: 'state-val' }, alertId: '2', - alertExecutionStore, + ruleRunMetricsStore, }); - expect(alertExecutionStore.getNumberOfTriggeredActions()).toBe(2); - expect(alertExecutionStore.getNumberOfGeneratedActions()).toBe(3); - expect(alertExecutionStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.PARTIAL); + expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(2); + expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(3); + expect(ruleRunMetricsStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.PARTIAL); expect(createExecutionHandlerParams.logger.debug).toHaveBeenCalledTimes(1); expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(2); }); @@ -604,27 +610,27 @@ describe('Create Execution Handler', () => { ], }); - alertExecutionStore = new AlertExecutionStore(); + ruleRunMetricsStore = new RuleRunMetricsStore(); await executionHandler({ actionGroup: 'default', context: {}, state: { value: 'state-val' }, alertId: '2', - alertExecutionStore, + ruleRunMetricsStore, }); - expect(alertExecutionStore.getNumberOfTriggeredActions()).toBe(4); - expect(alertExecutionStore.getNumberOfGeneratedActions()).toBe(5); - expect(alertExecutionStore.getStatusByConnectorType('test').numberOfTriggeredActions).toBe(1); + expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(4); + expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(5); + expect(ruleRunMetricsStore.getStatusByConnectorType('test').numberOfTriggeredActions).toBe(1); expect( - alertExecutionStore.getStatusByConnectorType('test-action-type-id').numberOfTriggeredActions + ruleRunMetricsStore.getStatusByConnectorType('test-action-type-id').numberOfTriggeredActions ).toBe(1); expect( - alertExecutionStore.getStatusByConnectorType('another-action-type-id') + ruleRunMetricsStore.getStatusByConnectorType('another-action-type-id') .numberOfTriggeredActions ).toBe(2); - expect(alertExecutionStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.PARTIAL); + expect(ruleRunMetricsStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.PARTIAL); expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(4); }); }); 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 bc0e92c954f23..ce212a3cbff1b 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 @@ -10,11 +10,17 @@ import { isEphemeralTaskRejectedDueToCapacityError } from '@kbn/task-manager-plu import { transformActionParams } from './transform_action_params'; import { EVENT_LOG_ACTIONS } from '../plugin'; import { injectActionParams } from './inject_action_params'; -import { AlertInstanceContext, AlertInstanceState, RuleTypeParams, RuleTypeState } from '../types'; +import { + ActionsCompletion, + AlertInstanceContext, + AlertInstanceState, + RuleTypeParams, + RuleTypeState, +} from '../types'; import { UntypedNormalizedRuleType } from '../rule_type_registry'; import { createAlertEventLogRecordObject } from '../lib/create_alert_event_log_record_object'; -import { ActionsCompletion, CreateExecutionHandlerOptions, ExecutionHandlerOptions } from './types'; +import { CreateExecutionHandlerOptions, ExecutionHandlerOptions } from './types'; export type ExecutionHandler = ( options: ExecutionHandlerOptions @@ -65,7 +71,7 @@ export function createExecutionHandler< actionSubgroup, context, state, - alertExecutionStore, + ruleRunMetricsStore, alertId, }: ExecutionHandlerOptions) => { if (!ruleTypeActionGroups.has(actionGroup)) { @@ -109,7 +115,7 @@ export function createExecutionHandler< }), })); - alertExecutionStore.incrementNumberOfGeneratedActions(actions.length); + ruleRunMetricsStore.incrementNumberOfGeneratedActions(actions.length); const ruleLabel = `${ruleType.id}:${ruleId}: '${ruleName}'`; @@ -119,10 +125,10 @@ export function createExecutionHandler< for (const action of actions) { const { actionTypeId } = action; - alertExecutionStore.incrementNumberOfGeneratedActionsByConnectorType(actionTypeId); + ruleRunMetricsStore.incrementNumberOfGeneratedActionsByConnectorType(actionTypeId); - if (alertExecutionStore.hasReachedTheExecutableActionsLimit(actionsConfigMap)) { - alertExecutionStore.setTriggeredActionsStatusByConnectorType({ + if (ruleRunMetricsStore.hasReachedTheExecutableActionsLimit(actionsConfigMap)) { + ruleRunMetricsStore.setTriggeredActionsStatusByConnectorType({ actionTypeId, status: ActionsCompletion.PARTIAL, }); @@ -133,17 +139,17 @@ export function createExecutionHandler< } if ( - alertExecutionStore.hasReachedTheExecutableActionsLimitByConnectorType({ + ruleRunMetricsStore.hasReachedTheExecutableActionsLimitByConnectorType({ actionTypeId, actionsConfigMap, }) ) { - if (!alertExecutionStore.hasConnectorTypeReachedTheLimit(actionTypeId)) { + if (!ruleRunMetricsStore.hasConnectorTypeReachedTheLimit(actionTypeId)) { logger.debug( `Rule "${ruleId}" skipped scheduling action "${action.id}" because the maximum number of allowed actions for connector type ${actionTypeId} has been reached.` ); } - alertExecutionStore.setTriggeredActionsStatusByConnectorType({ + ruleRunMetricsStore.setTriggeredActionsStatusByConnectorType({ actionTypeId, status: ActionsCompletion.PARTIAL, }); @@ -157,8 +163,8 @@ export function createExecutionHandler< continue; } - alertExecutionStore.incrementNumberOfTriggeredActions(); - alertExecutionStore.incrementNumberOfTriggeredActionsByConnectorType(actionTypeId); + ruleRunMetricsStore.incrementNumberOfTriggeredActions(); + ruleRunMetricsStore.incrementNumberOfTriggeredActionsByConnectorType(actionTypeId); const namespace = spaceId === 'default' ? {} : { namespace: spaceId }; diff --git a/x-pack/plugins/alerting/server/task_runner/fixtures.ts b/x-pack/plugins/alerting/server/task_runner/fixtures.ts index fae450ac7e60d..74a121e9c8026 100644 --- a/x-pack/plugins/alerting/server/task_runner/fixtures.ts +++ b/x-pack/plugins/alerting/server/task_runner/fixtures.ts @@ -212,6 +212,9 @@ export const generateEventLog = ({ status, numberOfTriggeredActions, numberOfGeneratedActions, + numberOfActiveAlerts, + numberOfRecoveredAlerts, + numberOfNewAlerts, savedObjects = [generateAlertSO('1')], }: GeneratorParams = {}) => ({ ...(status === 'error' && { @@ -239,6 +242,12 @@ export const generateEventLog = ({ metrics: { number_of_triggered_actions: numberOfTriggeredActions, number_of_generated_actions: numberOfGeneratedActions, + number_of_active_alerts: numberOfActiveAlerts ?? 0, + number_of_new_alerts: numberOfNewAlerts ?? 0, + number_of_recovered_alerts: numberOfRecoveredAlerts ?? 0, + total_number_of_alerts: + ((numberOfActiveAlerts ?? 0) as number) + + ((numberOfRecoveredAlerts ?? 0) as number), number_of_searches: 3, es_search_duration_ms: 33, total_search_duration_ms: 23423, 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 4a5be740949ad..111c0a7689504 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 @@ -236,11 +236,15 @@ describe('Task Runner', () => { expect(call.services).toBeTruthy(); const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(3); + expect(logger.debug).toHaveBeenCalledTimes(4); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, - 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}' + 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}' + ); + expect(logger.debug).nthCalledWith( + 3, + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"triggeredActionsStatus":"complete"}' ); const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; @@ -312,7 +316,7 @@ describe('Task Runner', () => { expect(enqueueFunction).toHaveBeenCalledWith(generateEnqueueFunctionInput()); const logger = customTaskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(4); + expect(logger.debug).toHaveBeenCalledTimes(5); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, @@ -320,7 +324,11 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 3, - 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + ); + expect(logger.debug).nthCalledWith( + 4, + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"triggeredActionsStatus":"complete"}' ); const eventLogger = customTaskRunnerFactoryInitializerParams.eventLogger; @@ -377,6 +385,8 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 1, numberOfGeneratedActions: 1, + numberOfActiveAlerts: 1, + numberOfNewAlerts: 1, task: true, consumer: 'bar', }) @@ -416,7 +426,7 @@ describe('Task Runner', () => { expect(actionsClient.ephemeralEnqueuedExecution).toHaveBeenCalledTimes(0); const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(5); + expect(logger.debug).toHaveBeenCalledTimes(6); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, @@ -428,7 +438,11 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + ); + expect(logger.debug).nthCalledWith( + 5, + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"triggeredActionsStatus":"complete"}' ); const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; @@ -472,6 +486,8 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, + numberOfActiveAlerts: 1, + numberOfNewAlerts: 1, task: true, consumer: 'bar', }) @@ -581,7 +597,7 @@ describe('Task Runner', () => { expect(enqueueFunction).toHaveBeenCalledTimes(1); const logger = customTaskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(5); + expect(logger.debug).toHaveBeenCalledTimes(6); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, @@ -593,7 +609,11 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + ); + expect(logger.debug).nthCalledWith( + 5, + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":2,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":2,"triggeredActionsStatus":"complete"}' ); expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled(); } @@ -698,7 +718,7 @@ describe('Task Runner', () => { await taskRunner.run(); expect(enqueueFunction).toHaveBeenCalledTimes(1); const logger = customTaskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(5); + expect(logger.debug).toHaveBeenCalledTimes(6); expect(logger.debug).nthCalledWith( 3, `skipping scheduling of actions for '2' in rule test:1: '${RULE_NAME}': rule is muted` @@ -783,6 +803,7 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, + numberOfActiveAlerts: 1, task: true, consumer: 'bar', }) @@ -848,6 +869,7 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 1, numberOfGeneratedActions: 1, + numberOfActiveAlerts: 1, task: true, consumer: 'bar', }) @@ -922,6 +944,7 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 1, numberOfGeneratedActions: 1, + numberOfActiveAlerts: 1, task: true, consumer: 'bar', }) @@ -1040,6 +1063,8 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 1, numberOfGeneratedActions: 1, + numberOfActiveAlerts: 1, + numberOfNewAlerts: 1, task: true, consumer: 'bar', }) @@ -1109,7 +1134,7 @@ describe('Task Runner', () => { ); const logger = customTaskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(5); + expect(logger.debug).toHaveBeenCalledTimes(6); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, @@ -1121,7 +1146,11 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + ); + expect(logger.debug).nthCalledWith( + 5, + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":1,"numberOfNewAlerts":0,"triggeredActionsStatus":"complete"}' ); const eventLogger = customTaskRunnerFactoryInitializerParams.eventLogger; @@ -1189,6 +1218,8 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 2, numberOfGeneratedActions: 2, + numberOfActiveAlerts: 1, + numberOfRecoveredAlerts: 1, task: true, consumer: 'bar', }) @@ -1262,7 +1293,11 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - `ruleExecutionStatus for test:${alertId}: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}` + `ruleRunStatus for test:${alertId}: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}` + ); + expect(logger.debug).nthCalledWith( + 5, + `ruleRunMetrics for test:${alertId}: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":1,"numberOfNewAlerts":0,"triggeredActionsStatus":"complete"}` ); const eventLogger = customTaskRunnerFactoryInitializerParams.eventLogger; @@ -1451,6 +1486,8 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 0, numberOfGeneratedActions: 2, + numberOfActiveAlerts: 1, + numberOfRecoveredAlerts: 1, task: true, consumer: 'bar', }) @@ -1458,7 +1495,7 @@ describe('Task Runner', () => { expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled(); }); - test('validates params before executing the alert type', async () => { + test('validates params before running the rule type', async () => { const taskRunner = new TaskRunner( { ...ruleType, @@ -1546,7 +1583,7 @@ describe('Task Runner', () => { expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled(); }); - test('rescheduled the Alert if the schedule has update during a task run', async () => { + test('rescheduled the rule if the schedule has update during a task run', async () => { const taskRunner = new TaskRunner( ruleType, mockedTaskInstance, @@ -2057,6 +2094,8 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, + numberOfActiveAlerts: 2, + numberOfNewAlerts: 2, task: true, consumer: 'bar', }) @@ -2160,6 +2199,7 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, + numberOfActiveAlerts: 2, task: true, consumer: 'bar', }) @@ -2252,6 +2292,7 @@ describe('Task Runner', () => { consumer: 'bar', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, + numberOfActiveAlerts: 2, task: true, }) ); @@ -2342,6 +2383,7 @@ describe('Task Runner', () => { consumer: 'bar', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, + numberOfRecoveredAlerts: 2, task: true, }) ); @@ -2428,6 +2470,7 @@ describe('Task Runner', () => { consumer: 'bar', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, + numberOfRecoveredAlerts: 2, task: true, }) ); @@ -2485,11 +2528,15 @@ describe('Task Runner', () => { expect(call.services).toBeTruthy(); const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(3); + expect(logger.debug).toHaveBeenCalledTimes(4); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, - 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}' + 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}' + ); + expect(logger.debug).nthCalledWith( + 3, + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"triggeredActionsStatus":"complete"}' ); const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; @@ -2751,7 +2798,7 @@ describe('Task Runner', () => { ); const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(5); + expect(logger.debug).toHaveBeenCalledTimes(6); expect(logger.debug).nthCalledWith( 3, @@ -2833,6 +2880,8 @@ describe('Task Runner', () => { status: 'warning', numberOfTriggeredActions: actionsConfigMap.default.max, numberOfGeneratedActions: mockActions.length, + numberOfActiveAlerts: 1, + numberOfNewAlerts: 1, reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, task: true, consumer: 'bar', @@ -2961,7 +3010,7 @@ describe('Task Runner', () => { ); const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(5); + expect(logger.debug).toHaveBeenCalledTimes(6); expect(logger.debug).nthCalledWith( 3, 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 00c62af65f382..6c161332bb58b 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -34,8 +34,6 @@ import { RawAlertInstance, RawRule, RawRuleExecutionStatus, - RuleExecutionRunResult, - RuleExecutionState, RuleMonitoring, RuleMonitoringHistory, RuleTaskState, @@ -71,8 +69,11 @@ import { RuleTaskRunResult, ScheduleActionsForRecoveredAlertsParams, TrackAlertDurationsParams, + RuleRunResult, + RuleTaskStateAndMetrics, } from './types'; -import { AlertExecutionStore } from '../lib/alert_execution_store'; +import { IExecutionStatusAndMetrics } from '../lib/rule_execution_status'; +import { RuleRunMetricsStore } from '../lib/rule_run_metrics_store'; const FALLBACK_RETRY_INTERVAL = '5m'; const CONNECTIVITY_RETRY_INTERVAL = '5m'; @@ -290,7 +291,7 @@ export class TaskRunner< alertId: string, alert: Alert, executionHandler: ExecutionHandler, - alertExecutionStore: AlertExecutionStore + ruleRunMetricsStore: RuleRunMetricsStore ) { const { actionGroup, @@ -306,18 +307,18 @@ export class TaskRunner< context, state, alertId, - alertExecutionStore, + ruleRunMetricsStore, }); } - private async executeAlerts( + private async executeRule( fakeRequest: KibanaRequest, rule: SanitizedRule, params: Params, executionHandler: ExecutionHandler, spaceId: string, event: Event - ): Promise { + ): Promise { const { alertTypeId, consumer, @@ -451,7 +452,12 @@ export class TaskRunner< name: rule.name, }; + const ruleRunMetricsStore = new RuleRunMetricsStore(); + const searchMetrics = wrappedScopedClusterClient.getMetrics(); + ruleRunMetricsStore.setNumSearches(searchMetrics.numSearches); + ruleRunMetricsStore.setTotalSearchDurationMs(searchMetrics.totalSearchDurationMs); + ruleRunMetricsStore.setEsSearchDurationMs(searchMetrics.esSearchDurationMs); // Cleanup alerts that are no longer scheduling actions to avoid over populating the alertInstances object const alertsWithScheduledActions = pickBy( @@ -488,11 +494,10 @@ export class TaskRunner< ruleType, rule, spaceId, + ruleRunMetricsStore, }); } - const alertExecutionStore = new AlertExecutionStore(); - const ruleIsSnoozed = this.isRuleSnoozed(rule); if (!ruleIsSnoozed && this.shouldLogAndScheduleActionsForAlerts()) { const mutedAlertIdsSet = new Set(mutedInstanceIds); @@ -527,7 +532,7 @@ export class TaskRunner< await Promise.all( alertsWithExecutableActions.map( ([alertId, alert]: [string, Alert]) => - this.executeAlert(alertId, alert, executionHandler, alertExecutionStore) + this.executeAlert(alertId, alert, executionHandler, ruleRunMetricsStore) ) ); @@ -542,7 +547,7 @@ export class TaskRunner< mutedAlertIdsSet, logger: this.logger, ruleLabel, - alertExecutionStore, + ruleRunMetricsStore, }); } else { if (ruleIsSnoozed) { @@ -562,12 +567,7 @@ export class TaskRunner< } return { - metrics: searchMetrics, - alertExecutionMetrics: { - numberOfTriggeredActions: alertExecutionStore.getNumberOfTriggeredActions(), - numberOfGeneratedActions: alertExecutionStore.getNumberOfGeneratedActions(), - triggeredActionsStatus: alertExecutionStore.getTriggeredActionsStatus(), - }, + metrics: ruleRunMetricsStore.getMetrics(), alertTypeState: updatedRuleTypeState || undefined, alertInstances: mapValues< Record>, @@ -599,12 +599,10 @@ export class TaskRunner< rule.params, fakeRequest ); - return this.executeAlerts(fakeRequest, rule, validatedParams, executionHandler, spaceId, event); + return this.executeRule(fakeRequest, rule, validatedParams, executionHandler, spaceId, event); } - private async loadRuleAttributesAndRun( - event: Event - ): Promise> { + private async loadRuleAttributesAndRun(event: Event): Promise> { const { params: { alertId: ruleId, spaceId }, } = this.taskInstance; @@ -670,7 +668,7 @@ export class TaskRunner< } return { monitoring: asOk(rule.monitoring), - state: await promiseResult( + stateWithMetrics: await promiseResult( this.validateAndExecuteRule(fakeRequest, apiKey, rule, event) ), schedule: asOk( @@ -751,7 +749,7 @@ export class TaskRunner< eventLogger.logEvent(startEvent); - const { state, schedule, monitoring } = await errorAsRuleTaskRunResult( + const { stateWithMetrics, schedule, monitoring } = await errorAsRuleTaskRunResult( this.loadRuleAttributesAndRun(event) ); @@ -760,10 +758,14 @@ export class TaskRunner< return getDefaultRuleMonitoring(); }) ?? getDefaultRuleMonitoring(); - const executionStatus = map( - state, - (ruleExecutionState) => executionStatusFromState(ruleExecutionState), - (err: ElasticsearchError) => executionStatusFromError(err) + const { status: executionStatus, metrics: executionMetrics } = map< + RuleTaskStateAndMetrics, + ElasticsearchError, + IExecutionStatusAndMetrics + >( + stateWithMetrics, + (ruleRunStateWithMetrics) => executionStatusFromState(ruleRunStateWithMetrics, runDate), + (err: ElasticsearchError) => executionStatusFromError(err, runDate) ); // set the executionStatus date to same as event, if it's set if (event.event?.start) { @@ -779,8 +781,13 @@ export class TaskRunner< } this.logger.debug( - `ruleExecutionStatus for ${this.ruleType.id}:${ruleId}: ${JSON.stringify(executionStatus)}` + `ruleRunStatus for ${this.ruleType.id}:${ruleId}: ${JSON.stringify(executionStatus)}` ); + if (executionMetrics) { + this.logger.debug( + `ruleRunMetrics for ${this.ruleType.id}:${ruleId}: ${JSON.stringify(executionMetrics)}` + ); + } eventLogger.stopTiming(event); set(event, 'kibana.alerting.status', executionStatus.status); @@ -815,34 +822,57 @@ export class TaskRunner< set(event, 'event.reason', executionStatus.warning?.reason || 'unknown'); set(event, 'message', executionStatus.warning?.message || event?.message); } - set( - event, - 'kibana.alert.rule.execution.metrics.number_of_triggered_actions', - executionStatus.numberOfTriggeredActions - ); - set( - event, - 'kibana.alert.rule.execution.metrics.number_of_generated_actions', - executionStatus.numberOfGeneratedActions - ); + if (executionMetrics) { + set( + event, + 'kibana.alert.rule.execution.metrics.number_of_triggered_actions', + executionMetrics.numberOfTriggeredActions + ); + set( + event, + 'kibana.alert.rule.execution.metrics.number_of_generated_actions', + executionMetrics.numberOfGeneratedActions + ); + set( + event, + 'kibana.alert.rule.execution.metrics.number_of_active_alerts', + executionMetrics.numberOfActiveAlerts + ); + set( + event, + 'kibana.alert.rule.execution.metrics.number_of_new_alerts', + executionMetrics.numberOfNewAlerts + ); + set( + event, + 'kibana.alert.rule.execution.metrics.total_number_of_alerts', + (executionMetrics.numberOfActiveAlerts ?? 0) + + (executionMetrics.numberOfRecoveredAlerts ?? 0) + ); + set( + event, + 'kibana.alert.rule.execution.metrics.number_of_recovered_alerts', + executionMetrics.numberOfRecoveredAlerts + ); + } } // Copy search stats into event log - if (executionStatus.metrics) { + if (executionMetrics) { set( event, 'kibana.alert.rule.execution.metrics.number_of_searches', - executionStatus.metrics.numSearches ?? 0 + executionMetrics.numSearches ?? 0 ); set( event, 'kibana.alert.rule.execution.metrics.es_search_duration_ms', - executionStatus.metrics.esSearchDurationMs ?? 0 + executionMetrics.esSearchDurationMs ?? 0 ); set( event, 'kibana.alert.rule.execution.metrics.total_search_duration_ms', - executionStatus.metrics.totalSearchDurationMs ?? 0 + executionMetrics.totalSearchDurationMs ?? 0 ); } @@ -870,19 +900,20 @@ export class TaskRunner< }); } - const transformExecutionStateToTaskState = ( - executionState: RuleExecutionState + const transformRunStateToTaskState = ( + runStateWithMetrics: RuleTaskStateAndMetrics ): RuleTaskState => { return { - ...omit(executionState, ['alertExecutionMetrics', 'metrics']), + ...omit(runStateWithMetrics, ['metrics']), previousStartedAt: startedAt, }; }; return { - state: map( - state, - (executionState: RuleExecutionState) => transformExecutionStateToTaskState(executionState), + state: map( + stateWithMetrics, + (ruleRunStateWithMetrics: RuleTaskStateAndMetrics) => + transformRunStateToTaskState(ruleRunStateWithMetrics), (err: ElasticsearchError) => { const message = `Executing Rule ${spaceId}:${ this.ruleType.id @@ -1070,6 +1101,7 @@ function generateNewAndRecoveredAlertEvents< rule, ruleType, spaceId, + ruleRunMetricsStore, } = params; const originalAlertIds = Object.keys(originalAlerts); const currentAlertIds = Object.keys(currentAlerts); @@ -1082,6 +1114,10 @@ function generateNewAndRecoveredAlertEvents< }); } + ruleRunMetricsStore.setNumberOfActiveAlerts(currentAlertIds.length); + ruleRunMetricsStore.setNumberOfNewAlerts(newIds.length); + ruleRunMetricsStore.setNumberOfRecoveredAlerts(recoveredAlertIds.length); + for (const id of recoveredAlertIds) { const { group: actionGroup, subgroup: actionSubgroup } = recoveredAlerts[id].getLastScheduledActions() ?? {}; @@ -1198,7 +1234,7 @@ async function scheduleActionsForRecoveredAlerts< executionHandler, mutedAlertIdsSet, ruleLabel, - alertExecutionStore, + ruleRunMetricsStore, } = params; const recoveredIds = Object.keys(recoveredAlerts); @@ -1216,7 +1252,7 @@ async function scheduleActionsForRecoveredAlerts< context: alert.getContext(), state: {}, alertId: id, - alertExecutionStore, + ruleRunMetricsStore, }); alert.scheduleActions(recoveryActionGroup.id); } @@ -1281,13 +1317,13 @@ function logActiveAndRecoveredAlerts< * so that we can treat each field independantly */ async function errorAsRuleTaskRunResult( - future: Promise> -): Promise> { + future: Promise> +): Promise> { try { return await future; } catch (e) { return { - state: asErr(e), + stateWithMetrics: asErr(e), schedule: asErr(e), monitoring: asErr(e), }; 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 e0b7449d09b41..4986359115377 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 @@ -334,6 +334,10 @@ describe('Task Runner Cancel', () => { number_of_searches: 3, number_of_triggered_actions: 0, number_of_generated_actions: 0, + number_of_active_alerts: 0, + number_of_new_alerts: 0, + number_of_recovered_alerts: 0, + total_number_of_alerts: 0, es_search_duration_ms: 33, total_search_duration_ms: 23423, }, @@ -499,7 +503,7 @@ describe('Task Runner Cancel', () => { await promise; const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(7); + expect(logger.debug).toHaveBeenCalledTimes(8); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, @@ -523,7 +527,11 @@ describe('Task Runner Cancel', () => { ); expect(logger.debug).nthCalledWith( 7, - 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + ); + expect(logger.debug).nthCalledWith( + 8, + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"triggeredActionsStatus":"complete"}' ); const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; @@ -618,6 +626,10 @@ describe('Task Runner Cancel', () => { number_of_searches: 3, number_of_triggered_actions: 0, number_of_generated_actions: 0, + number_of_active_alerts: 0, + number_of_recovered_alerts: 0, + number_of_new_alerts: 0, + total_number_of_alerts: 0, es_search_duration_ms: 33, total_search_duration_ms: 23423, }, @@ -663,7 +675,7 @@ describe('Task Runner Cancel', () => { function testActionsExecute() { const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(6); + expect(logger.debug).toHaveBeenCalledTimes(7); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, @@ -683,7 +695,11 @@ describe('Task Runner Cancel', () => { ); expect(logger.debug).nthCalledWith( 6, - 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + ); + expect(logger.debug).nthCalledWith( + 7, + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"triggeredActionsStatus":"complete"}' ); const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; @@ -897,6 +913,10 @@ describe('Task Runner Cancel', () => { number_of_searches: 3, number_of_triggered_actions: 1, number_of_generated_actions: 1, + number_of_active_alerts: 1, + number_of_new_alerts: 1, + number_of_recovered_alerts: 0, + total_number_of_alerts: 1, es_search_duration_ms: 33, total_search_duration_ms: 23423, }, diff --git a/x-pack/plugins/alerting/server/task_runner/types.ts b/x-pack/plugins/alerting/server/task_runner/types.ts index 246e7721d6ed4..1f4a31fa1d9ac 100644 --- a/x-pack/plugins/alerting/server/task_runner/types.ts +++ b/x-pack/plugins/alerting/server/task_runner/types.ts @@ -18,7 +18,6 @@ import { RuleTypeParams, RuleTypeState, IntervalSchedule, - RuleExecutionState, RuleMonitoring, RuleTaskState, SanitizedRule, @@ -28,13 +27,7 @@ import { NormalizedRuleType } from '../rule_type_registry'; import { ExecutionHandler } from './create_execution_handler'; import { RawRule } from '../types'; import { ActionsConfigMap } from '../lib/get_actions_config_map'; -import { AlertExecutionStore } from '../lib/alert_execution_store'; - -export interface RuleTaskRunResultWithActions { - state: RuleExecutionState; - monitoring: RuleMonitoring | undefined; - schedule: IntervalSchedule | undefined; -} +import { RuleRunMetrics, RuleRunMetricsStore } from '../lib/rule_run_metrics_store'; export interface RuleTaskRunResult { state: RuleTaskState; @@ -42,6 +35,15 @@ export interface RuleTaskRunResult { schedule: IntervalSchedule | undefined; } +// This is the state of the alerting task after rule execution, which includes run metrics plus the task state +export type RuleTaskStateAndMetrics = RuleTaskState & { + metrics: RuleRunMetrics; +}; + +export type RuleRunResult = Pick & { + stateWithMetrics: RuleTaskStateAndMetrics; +}; + export interface RuleTaskInstance extends ConcreteTaskInstance { state: RuleTaskState; } @@ -82,6 +84,7 @@ export interface GenerateNewAndRecoveredAlertEventsParams< >; rule: SanitizedRule; spaceId: string; + ruleRunMetricsStore: RuleRunMetricsStore; } export interface ScheduleActionsForRecoveredAlertsParams< @@ -95,7 +98,7 @@ export interface ScheduleActionsForRecoveredAlertsParams< executionHandler: ExecutionHandler; mutedAlertIdsSet: Set; ruleLabel: string; - alertExecutionStore: AlertExecutionStore; + ruleRunMetricsStore: RuleRunMetricsStore; } export interface LogActiveAndRecoveredAlertsParams< @@ -156,10 +159,5 @@ export interface ExecutionHandlerOptions { alertId: string; context: AlertInstanceContext; state: AlertInstanceState; - alertExecutionStore: AlertExecutionStore; -} - -export enum ActionsCompletion { - COMPLETE = 'complete', - PARTIAL = 'partial', + ruleRunMetricsStore: RuleRunMetricsStore; } diff --git a/x-pack/plugins/alerting/server/usage/alerting_telemetry.test.ts b/x-pack/plugins/alerting/server/usage/alerting_telemetry.test.ts index e43283e14e822..2989bc6a47015 100644 --- a/x-pack/plugins/alerting/server/usage/alerting_telemetry.test.ts +++ b/x-pack/plugins/alerting/server/usage/alerting_telemetry.test.ts @@ -189,6 +189,13 @@ Object { '99.0': 26.0, }, }, + percentileAlerts: { + values: { + '50.0': 10.0, + '90.0': 22.0, + '99.0': 22.0, + }, + }, aggsByType: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, @@ -203,6 +210,13 @@ Object { '99.0': 26.0, }, }, + percentileAlerts: { + values: { + '50.0': 10.0, + '90.0': 22.0, + '99.0': 22.0, + }, + }, }, { key: 'logs.alert.document.count', @@ -214,6 +228,13 @@ Object { '99.0': 10.0, }, }, + percentileAlerts: { + values: { + '50.0': 5.0, + '90.0': 13.0, + '99.0': 13.0, + }, + }, }, ], }, @@ -283,6 +304,25 @@ Object { logs__alert__document__count: 10, }, }, + alertsPercentiles: { + p50: 10, + p90: 22, + p99: 22, + }, + alertsPercentilesByType: { + p50: { + '__index-threshold': 10, + logs__alert__document__count: 5, + }, + p90: { + '__index-threshold': 22, + logs__alert__document__count: 13, + }, + p99: { + '__index-threshold': 22, + logs__alert__document__count: 13, + }, + }, }); }); @@ -387,6 +427,13 @@ Object { '99.0': 26.0, }, }, + percentileAlerts: { + values: { + '50.0': 3.0, + '90.0': 22.0, + '99.0': 22.0, + }, + }, }, { key: 'logs.alert.document.count', @@ -398,6 +445,13 @@ Object { '99.0': 10.0, }, }, + percentileAlerts: { + values: { + '50.0': 5.0, + '90.0': 16.0, + '99.0': 16.0, + }, + }, }, { key: 'document.test.', @@ -409,6 +463,13 @@ Object { '99.0': null, }, }, + percentileAlerts: { + values: { + '50.0': null, + '90.0': null, + '99.0': null, + }, + }, }, ], }; @@ -431,6 +492,23 @@ Object { logs__alert__document__count: 10, }, }); + expect(parsePercentileAggsByRuleType(aggsByType.buckets, 'percentileAlerts.values')).toEqual({ + p50: { + '__index-threshold': 3, + document__test__: 0, + logs__alert__document__count: 5, + }, + p90: { + '__index-threshold': 22, + document__test__: 0, + logs__alert__document__count: 16, + }, + p99: { + '__index-threshold': 22, + document__test__: 0, + logs__alert__document__count: 16, + }, + }); }); test('parsePercentileAggsByRuleType handles unknown path', () => { diff --git a/x-pack/plugins/alerting/server/usage/alerting_telemetry.ts b/x-pack/plugins/alerting/server/usage/alerting_telemetry.ts index 78ec88d959a8b..17dfd957e6df9 100644 --- a/x-pack/plugins/alerting/server/usage/alerting_telemetry.ts +++ b/x-pack/plugins/alerting/server/usage/alerting_telemetry.ts @@ -54,6 +54,13 @@ const generatedActionsPercentilesAgg = { }, }; +const alertsPercentilesAgg = { + percentiles: { + field: 'kibana.alert.rule.execution.metrics.total_number_of_alerts', + percents: [50, 90, 99], + }, +}; + const ruleTypeExecutionsWithDurationMetric = { scripted_metric: { init_script: @@ -426,6 +433,7 @@ export async function getExecutionsPerDayCount( avg: { field: 'kibana.alert.rule.execution.metrics.total_search_duration_ms' }, }, percentileScheduledActions: generatedActionsPercentilesAgg, + percentileAlerts: alertsPercentilesAgg, aggsByType: { terms: { field: 'rule.category', @@ -433,6 +441,7 @@ export async function getExecutionsPerDayCount( }, aggs: { percentileScheduledActions: generatedActionsPercentilesAgg, + percentileAlerts: alertsPercentilesAgg, }, }, }, @@ -469,6 +478,10 @@ export async function getExecutionsPerDayCount( // @ts-expect-error aggegation type is not specified searchResult.aggregations.percentileScheduledActions.values; + const aggsAlertsPercentiles = + // @ts-expect-error aggegation type is not specified + searchResult.aggregations.percentileAlerts.values; + const aggsByTypeBuckets = // @ts-expect-error aggegation type is not specified searchResult.aggregations.aggsByType.buckets; @@ -586,6 +599,21 @@ export async function getExecutionsPerDayCount( aggsByTypeBuckets, 'percentileScheduledActions.values' ), + alertsPercentiles: Object.keys(aggsAlertsPercentiles).reduce( + // ES DSL aggregations are returned as `any` by esClient.search + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (acc: any, curr: string) => ({ + ...acc, + ...(percentileFieldNameMapping[curr] + ? { [percentileFieldNameMapping[curr]]: aggsAlertsPercentiles[curr] } + : {}), + }), + {} + ), + alertsPercentilesByType: parsePercentileAggsByRuleType( + aggsByTypeBuckets, + 'percentileAlerts.values' + ), }; } diff --git a/x-pack/plugins/alerting/server/usage/alerting_usage_collector.ts b/x-pack/plugins/alerting/server/usage/alerting_usage_collector.ts index b80afbc81e77e..c3bca512a2bbd 100644 --- a/x-pack/plugins/alerting/server/usage/alerting_usage_collector.ts +++ b/x-pack/plugins/alerting/server/usage/alerting_usage_collector.ts @@ -186,6 +186,16 @@ export function createAlertingUsageCollector( p90: {}, p99: {}, }, + percentile_num_alerts_per_day: { + p50: 0, + p90: 0, + p99: 0, + }, + percentile_num_alerts_by_type_per_day: { + p50: {}, + p90: {}, + p99: {}, + }, }; } }, @@ -239,6 +249,8 @@ export function createAlertingUsageCollector( avg_total_search_duration_by_type_per_day: byTypeSchema, percentile_num_generated_actions_per_day: byPercentileSchema, percentile_num_generated_actions_by_type_per_day: byPercentileSchemaByType, + percentile_num_alerts_per_day: byPercentileSchema, + percentile_num_alerts_by_type_per_day: byPercentileSchemaByType, }, }); } diff --git a/x-pack/plugins/alerting/server/usage/task.ts b/x-pack/plugins/alerting/server/usage/task.ts index cb2f1e7af1f2f..b64ee27fb9968 100644 --- a/x-pack/plugins/alerting/server/usage/task.ts +++ b/x-pack/plugins/alerting/server/usage/task.ts @@ -148,6 +148,9 @@ export function telemetryTaskRunner( dailyExecutionCounts.generatedActionsPercentiles, percentile_num_generated_actions_by_type_per_day: dailyExecutionCounts.generatedActionsPercentilesByType, + percentile_num_alerts_per_day: dailyExecutionCounts.alertsPercentiles, + percentile_num_alerts_by_type_per_day: + dailyExecutionCounts.alertsPercentilesByType, }, runAt: getNextMidnight(), }; diff --git a/x-pack/plugins/alerting/server/usage/types.ts b/x-pack/plugins/alerting/server/usage/types.ts index 3023d9ab0e13c..b045fd5009c70 100644 --- a/x-pack/plugins/alerting/server/usage/types.ts +++ b/x-pack/plugins/alerting/server/usage/types.ts @@ -35,6 +35,16 @@ export interface AlertingUsage { p90: Record; p99: Record; }; + percentile_num_alerts_per_day: { + p50: number; + p90: number; + p99: number; + }; + percentile_num_alerts_by_type_per_day: { + p50: Record; + p90: Record; + p99: Record; + }; avg_execution_time_per_day: number; avg_execution_time_by_type_per_day: Record; avg_es_search_duration_per_day: number; diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json index 982699274e1e3..2ceeb99685dda 100644 --- a/x-pack/plugins/event_log/generated/mappings.json +++ b/x-pack/plugins/event_log/generated/mappings.json @@ -299,6 +299,18 @@ "number_of_generated_actions": { "type": "long" }, + "number_of_new_alerts": { + "type": "long" + }, + "number_of_active_alerts": { + "type": "long" + }, + "number_of_recovered_alerts": { + "type": "long" + }, + "total_number_of_alerts": { + "type": "long" + }, "number_of_searches": { "type": "long" }, diff --git a/x-pack/plugins/event_log/generated/schemas.ts b/x-pack/plugins/event_log/generated/schemas.ts index e030a0ce1e99c..990db4dd4c4ff 100644 --- a/x-pack/plugins/event_log/generated/schemas.ts +++ b/x-pack/plugins/event_log/generated/schemas.ts @@ -131,6 +131,10 @@ export const EventSchema = schema.maybe( schema.object({ number_of_triggered_actions: ecsNumber(), number_of_generated_actions: ecsNumber(), + number_of_new_alerts: ecsNumber(), + number_of_active_alerts: ecsNumber(), + number_of_recovered_alerts: ecsNumber(), + total_number_of_alerts: ecsNumber(), number_of_searches: ecsNumber(), total_indexing_duration_ms: ecsNumber(), es_search_duration_ms: ecsNumber(), diff --git a/x-pack/plugins/event_log/scripts/mappings.js b/x-pack/plugins/event_log/scripts/mappings.js index b4bb17eba42a1..4c21329863269 100644 --- a/x-pack/plugins/event_log/scripts/mappings.js +++ b/x-pack/plugins/event_log/scripts/mappings.js @@ -81,6 +81,18 @@ exports.EcsCustomPropertyMappings = { number_of_generated_actions: { type: 'long', }, + number_of_new_alerts: { + type: 'long', + }, + number_of_active_alerts: { + type: 'long', + }, + number_of_recovered_alerts: { + type: 'long', + }, + total_number_of_alerts: { + type: 'long', + }, number_of_searches: { type: 'long', }, diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index c8cd2b27210ff..9eba9b7fde512 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -2269,6 +2269,332 @@ } } } + }, + "percentile_num_alerts_per_day": { + "properties": { + "p50": { + "type": "long" + }, + "p90": { + "type": "long" + }, + "p99": { + "type": "long" + } + } + }, + "percentile_num_alerts_by_type_per_day": { + "properties": { + "p50": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "siem__eqlRule": { + "type": "long" + }, + "siem__indicatorRule": { + "type": "long" + }, + "siem__mlRule": { + "type": "long" + }, + "siem__queryRule": { + "type": "long" + }, + "siem__savedQueryRule": { + "type": "long" + }, + "siem__thresholdRule": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } + }, + "p90": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "siem__eqlRule": { + "type": "long" + }, + "siem__indicatorRule": { + "type": "long" + }, + "siem__mlRule": { + "type": "long" + }, + "siem__queryRule": { + "type": "long" + }, + "siem__savedQueryRule": { + "type": "long" + }, + "siem__thresholdRule": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } + }, + "p99": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "siem__eqlRule": { + "type": "long" + }, + "siem__indicatorRule": { + "type": "long" + }, + "siem__mlRule": { + "type": "long" + }, + "siem__queryRule": { + "type": "long" + }, + "siem__savedQueryRule": { + "type": "long" + }, + "siem__thresholdRule": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } + } + } } } }, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts index 15e648bb2166c..601efa34341cb 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts @@ -1332,6 +1332,10 @@ instanceStateValue: true expect(event?.kibana?.alert?.rule?.execution?.metrics?.number_of_searches).to.be(0); expect(event?.kibana?.alert?.rule?.execution?.metrics?.es_search_duration_ms).to.be(0); expect(event?.kibana?.alert?.rule?.execution?.metrics?.total_search_duration_ms).to.be(0); + expect(event?.kibana?.alert?.rule?.execution?.metrics?.number_of_active_alerts).to.be(1); + expect(event?.kibana?.alert?.rule?.execution?.metrics?.number_of_new_alerts).to.be(1); + expect(event?.kibana?.alert?.rule?.execution?.metrics?.number_of_recovered_alerts).to.be(0); + expect(event?.kibana?.alert?.rule?.execution?.metrics?.total_number_of_alerts).to.be(1); expect(event?.rule).to.eql({ id: alertId, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/telemetry/alerting_telemetry.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/telemetry/alerting_telemetry.ts index 4a5395b76c694..afc39bf1c6b74 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/telemetry/alerting_telemetry.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/telemetry/alerting_telemetry.ts @@ -382,8 +382,8 @@ export default function createAlertingTelemetryTests({ getService }: FtrProvider // percentile calculations for number of scheduled actions expect(telemetry.percentile_num_generated_actions_per_day.p50 >= 0).to.be(true); - expect(telemetry.percentile_num_generated_actions_per_day.p90 > 0).to.be(true); - expect(telemetry.percentile_num_generated_actions_per_day.p99 > 0).to.be(true); + expect(telemetry.percentile_num_generated_actions_per_day.p90).to.be.greaterThan(0); + expect(telemetry.percentile_num_generated_actions_per_day.p99).to.be.greaterThan(0); // percentile calculations by rule type. most of these rule types don't schedule actions so they should all be 0 expect( @@ -432,17 +432,69 @@ export default function createAlertingTelemetryTests({ getService }: FtrProvider // this rule type does schedule actions so should be least 1 action scheduled expect( - telemetry.percentile_num_generated_actions_by_type_per_day.p50['test__cumulative-firing'] >= - 1 - ).to.be(true); + telemetry.percentile_num_generated_actions_by_type_per_day.p50['test__cumulative-firing'] + ).to.be.greaterThan(0); expect( - telemetry.percentile_num_generated_actions_by_type_per_day.p90['test__cumulative-firing'] >= - 1 - ).to.be(true); + telemetry.percentile_num_generated_actions_by_type_per_day.p90['test__cumulative-firing'] + ).to.be.greaterThan(0); expect( - telemetry.percentile_num_generated_actions_by_type_per_day.p99['test__cumulative-firing'] >= - 1 - ).to.be(true); + telemetry.percentile_num_generated_actions_by_type_per_day.p99['test__cumulative-firing'] + ).to.be.greaterThan(0); + + // percentile calculations for number of alerts + expect(telemetry.percentile_num_alerts_per_day.p50 >= 0).to.be(true); + expect(telemetry.percentile_num_alerts_per_day.p90).to.be.greaterThan(0); + expect(telemetry.percentile_num_alerts_per_day.p99).to.be.greaterThan(0); + + // percentile calculations by rule type. most of these rule types don't generate alerts so they should all be 0 + expect( + telemetry.percentile_num_alerts_by_type_per_day.p50['example__always-firing'] + ).to.equal(0); + expect( + telemetry.percentile_num_alerts_by_type_per_day.p90['example__always-firing'] + ).to.equal(0); + expect( + telemetry.percentile_num_alerts_by_type_per_day.p99['example__always-firing'] + ).to.equal(0); + + expect( + telemetry.percentile_num_alerts_by_type_per_day.p50.test__onlyContextVariables + ).to.equal(0); + expect( + telemetry.percentile_num_alerts_by_type_per_day.p90.test__onlyContextVariables + ).to.equal(0); + expect( + telemetry.percentile_num_alerts_by_type_per_day.p99.test__onlyContextVariables + ).to.equal(0); + + expect(telemetry.percentile_num_alerts_by_type_per_day.p50.test__noop).to.equal(0); + expect(telemetry.percentile_num_alerts_by_type_per_day.p90.test__noop).to.equal(0); + expect(telemetry.percentile_num_alerts_by_type_per_day.p99.test__noop).to.equal(0); + + expect(telemetry.percentile_num_alerts_by_type_per_day.p50.test__throw).to.equal(0); + expect(telemetry.percentile_num_alerts_by_type_per_day.p90.test__throw).to.equal(0); + expect(telemetry.percentile_num_alerts_by_type_per_day.p99.test__throw).to.equal(0); + + expect(telemetry.percentile_num_alerts_by_type_per_day.p50.test__multipleSearches).to.equal( + 0 + ); + expect(telemetry.percentile_num_alerts_by_type_per_day.p90.test__multipleSearches).to.equal( + 0 + ); + expect(telemetry.percentile_num_alerts_by_type_per_day.p99.test__multipleSearches).to.equal( + 0 + ); + + // this rule type does generate alerts so should be least 1 alert + expect( + telemetry.percentile_num_alerts_by_type_per_day.p50['test__cumulative-firing'] + ).to.be.greaterThan(0); + expect( + telemetry.percentile_num_alerts_by_type_per_day.p90['test__cumulative-firing'] + ).to.be.greaterThan(0); + expect( + telemetry.percentile_num_alerts_by_type_per_day.p99['test__cumulative-firing'] + ).to.be.greaterThan(0); }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts index 909b3315600c2..5777b4978894f 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts @@ -139,6 +139,9 @@ export default function eventLogTests({ getService }: FtrProviderContext) { // validate each event let executeCount = 0; + let numActiveAlerts = 0; + let numNewAlerts = 0; + let numRecoveredAlerts = 0; let currentExecutionId; const executionIds = []; const executeStatuses = ['ok', 'active', 'active']; @@ -165,28 +168,6 @@ export default function eventLogTests({ getService }: FtrProviderContext) { consumer: 'alertsFixture', }); break; - case 'execute': - validateEvent(event, { - spaceId: space.id, - savedObjects: [ - { type: 'alert', id: alertId, rel: 'primary', type_id: 'test.patternFiring' }, - ], - outcome: 'success', - message: `rule executed: test.patternFiring:${alertId}: 'abc'`, - status: executeStatuses[executeCount++], - shouldHaveTask: true, - executionId: currentExecutionId, - ruleTypeId: response.body.rule_type_id, - rule: { - id: alertId, - category: response.body.rule_type_id, - license: 'basic', - ruleset: 'alertsFixture', - name: response.body.name, - }, - consumer: 'alertsFixture', - }); - break; case 'execute-action': validateEvent(event, { spaceId: space.id, @@ -210,6 +191,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { }); break; case 'new-instance': + numNewAlerts++; validateInstanceEvent( event, `created new alert: 'instance'`, @@ -218,6 +200,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { ); break; case 'recovered-instance': + numRecoveredAlerts++; validateInstanceEvent( event, `alert 'instance' has recovered`, @@ -226,6 +209,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { ); break; case 'active-instance': + numActiveAlerts++; validateInstanceEvent( event, `active alert: 'instance' in actionGroup: 'default'`, @@ -233,6 +217,34 @@ export default function eventLogTests({ getService }: FtrProviderContext) { currentExecutionId ); break; + case 'execute': + validateEvent(event, { + spaceId: space.id, + savedObjects: [ + { type: 'alert', id: alertId, rel: 'primary', type_id: 'test.patternFiring' }, + ], + outcome: 'success', + message: `rule executed: test.patternFiring:${alertId}: 'abc'`, + status: executeStatuses[executeCount++], + shouldHaveTask: true, + executionId: currentExecutionId, + ruleTypeId: response.body.rule_type_id, + rule: { + id: alertId, + category: response.body.rule_type_id, + license: 'basic', + ruleset: 'alertsFixture', + name: response.body.name, + }, + consumer: 'alertsFixture', + numActiveAlerts, + numNewAlerts, + numRecoveredAlerts, + }); + numActiveAlerts = 0; + numNewAlerts = 0; + numRecoveredAlerts = 0; + break; // this will get triggered as we add new event actions default: throw new Error(`unexpected event action "${event?.event?.action}"`); @@ -343,11 +355,23 @@ export default function eventLogTests({ getService }: FtrProviderContext) { // validate each event let currentExecutionId; + let numActiveAlerts = 0; + let numNewAlerts = 0; + let numRecoveredAlerts = 0; for (const event of events) { switch (event?.event?.action) { case 'execute-start': currentExecutionId = event?.kibana?.alert?.rule?.execution?.uuid; break; + case 'new-instance': + numNewAlerts++; + break; + case 'recovered-instance': + numRecoveredAlerts++; + break; + case 'active-instance': + numActiveAlerts++; + break; case 'execute': validateEvent(event, { spaceId: space.id, @@ -369,7 +393,13 @@ export default function eventLogTests({ getService }: FtrProviderContext) { name: response.body.name, }, consumer: 'alertsFixture', + numActiveAlerts, + numNewAlerts, + numRecoveredAlerts, }); + numActiveAlerts = 0; + numNewAlerts = 0; + numRecoveredAlerts = 0; expect(event?.kibana?.alert?.rule?.execution?.metrics?.number_of_searches).to.be( numSearches ); @@ -477,6 +507,9 @@ export default function eventLogTests({ getService }: FtrProviderContext) { // validate each event let executeCount = 0; + let numActiveAlerts = 0; + let numNewAlerts = 0; + let numRecoveredAlerts = 0; let currentExecutionId; const executeStatuses = ['ok', 'active', 'active']; for (const event of events) { @@ -501,28 +534,6 @@ export default function eventLogTests({ getService }: FtrProviderContext) { consumer: 'alertsFixture', }); break; - case 'execute': - validateEvent(event, { - spaceId: space.id, - savedObjects: [ - { type: 'alert', id: alertId, rel: 'primary', type_id: 'test.patternFiring' }, - ], - outcome: 'success', - message: `rule executed: test.patternFiring:${alertId}: 'abc'`, - status: executeStatuses[executeCount++], - shouldHaveTask: true, - executionId: currentExecutionId, - ruleTypeId: response.body.rule_type_id, - rule: { - id: alertId, - category: response.body.rule_type_id, - license: 'basic', - ruleset: 'alertsFixture', - name: response.body.name, - }, - consumer: 'alertsFixture', - }); - break; case 'execute-action': expect( [firstSubgroup, secondSubgroup].includes( @@ -551,6 +562,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { }); break; case 'new-instance': + numNewAlerts++; validateInstanceEvent( event, `created new alert: 'instance'`, @@ -559,6 +571,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { ); break; case 'recovered-instance': + numRecoveredAlerts++; validateInstanceEvent( event, `alert 'instance' has recovered`, @@ -567,6 +580,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { ); break; case 'active-instance': + numActiveAlerts++; expect( [firstSubgroup, secondSubgroup].includes( event?.kibana?.alerting?.action_subgroup! @@ -579,6 +593,34 @@ export default function eventLogTests({ getService }: FtrProviderContext) { currentExecutionId ); break; + case 'execute': + validateEvent(event, { + spaceId: space.id, + savedObjects: [ + { type: 'alert', id: alertId, rel: 'primary', type_id: 'test.patternFiring' }, + ], + outcome: 'success', + message: `rule executed: test.patternFiring:${alertId}: 'abc'`, + status: executeStatuses[executeCount++], + shouldHaveTask: true, + executionId: currentExecutionId, + ruleTypeId: response.body.rule_type_id, + rule: { + id: alertId, + category: response.body.rule_type_id, + license: 'basic', + ruleset: 'alertsFixture', + name: response.body.name, + }, + consumer: 'alertsFixture', + numActiveAlerts, + numNewAlerts, + numRecoveredAlerts, + }); + numActiveAlerts = 0; + numNewAlerts = 0; + numRecoveredAlerts = 0; + break; // this will get triggered as we add new event actions default: throw new Error(`unexpected event action "${event?.event?.action}"`); @@ -687,6 +729,9 @@ export default function eventLogTests({ getService }: FtrProviderContext) { ruleset: 'alertsFixture', }, consumer: 'alertsFixture', + numActiveAlerts: 0, + numNewAlerts: 0, + numRecoveredAlerts: 0, }); }); }); @@ -715,6 +760,9 @@ interface ValidateEventLogParams { reason?: string; executionId?: string; numTriggeredActions?: number; + numActiveAlerts?: number; + numRecoveredAlerts?: number; + numNewAlerts?: number; consumer?: string; ruleTypeId: string; rule?: { @@ -741,6 +789,9 @@ export function validateEvent(event: IValidatedEvent, params: ValidateEventLogPa shouldHaveTask, executionId, numTriggeredActions = 1, + numActiveAlerts, + numNewAlerts, + numRecoveredAlerts, consumer, ruleTypeId, } = params; @@ -776,6 +827,30 @@ export function validateEvent(event: IValidatedEvent, params: ValidateEventLogPa expect(event?.kibana?.alert?.rule?.consumer).to.be(consumer); } + if (numActiveAlerts) { + expect(event?.kibana?.alert?.rule?.execution?.metrics?.number_of_active_alerts).to.be( + numActiveAlerts + ); + } + + if (numRecoveredAlerts) { + expect(event?.kibana?.alert?.rule?.execution?.metrics?.number_of_recovered_alerts).to.be( + numRecoveredAlerts + ); + } + + if (numNewAlerts) { + expect(event?.kibana?.alert?.rule?.execution?.metrics?.number_of_new_alerts).to.be( + numNewAlerts + ); + } + + if (numActiveAlerts && numRecoveredAlerts) { + expect(event?.kibana?.alert?.rule?.execution?.metrics?.total_number_of_alerts).to.be( + numActiveAlerts + numRecoveredAlerts + ); + } + expect(event?.kibana?.alert?.rule?.rule_type_id).to.be(ruleTypeId); expect(event?.kibana?.space_ids?.[0]).to.equal(spaceId); From 801301954e25509c481b98b409a5bc7db7a1a0e2 Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Tue, 26 Apr 2022 18:00:03 +0200 Subject: [PATCH 19/27] groups add exceptions tests in one spec file (#130908) --- ...om_alert.spec.ts => add_exception.spec.ts} | 47 ++++++++-- .../integration/exceptions/from_rule.spec.ts | 89 ------------------- .../es_archives/exceptions_2/data.json | 2 +- .../es_archives/exceptions_2/mappings.json | 2 +- 4 files changed, 42 insertions(+), 98 deletions(-) rename x-pack/plugins/security_solution/cypress/integration/exceptions/{from_alert.spec.ts => add_exception.spec.ts} (70%) delete mode 100644 x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/add_exception.spec.ts similarity index 70% rename from x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts rename to x-pack/plugins/security_solution/cypress/integration/exceptions/add_exception.spec.ts index 9ab4c1559c4cd..6d43ed81cd68e 100644 --- a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/add_exception.spec.ts @@ -18,6 +18,7 @@ import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver'; import { login, visitWithoutDateRange } from '../../tasks/login'; import { addsException, + addsExceptionFromRuleSettings, goToAlertsTab, goToExceptionsTab, removeException, @@ -27,16 +28,16 @@ import { import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common'; -describe('From alert', () => { +describe('Adds rule exception', () => { const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1 alert'; before(() => { cleanKibana(); login(); + esArchiverLoad('exceptions'); }); beforeEach(() => { - esArchiverLoad('exceptions'); deleteAlertsAndRules(); createCustomRule( { ...getNewRule(), customQuery: 'agent.name:*', index: ['exceptions*'] }, @@ -48,17 +49,19 @@ describe('From alert', () => { goToRuleDetails(); waitForTheRuleToBeExecuted(); waitForAlertsToPopulate(); - - cy.get(ALERTS_COUNT).should('exist'); - cy.get(NUMBER_OF_ALERTS).should('have.text', NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS); }); afterEach(() => { - esArchiverUnload('exceptions'); esArchiverUnload('exceptions_2'); }); - it('Creates an exception and deletes it', () => { + after(() => { + esArchiverUnload('exceptions'); + }); + + it('Creates an exception from an alert and deletes it', () => { + cy.get(ALERTS_COUNT).should('exist'); + cy.get(NUMBER_OF_ALERTS).should('have.text', NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS); // Create an exception from the alerts actions menu that matches // the existing alert addExceptionFromFirstAlert(); @@ -86,4 +89,34 @@ describe('From alert', () => { cy.get(ALERTS_COUNT).should('exist'); cy.get(NUMBER_OF_ALERTS).should('have.text', `${NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS}`); }); + + it('Creates an exception from a rule and deletes it', () => { + // Create an exception from the exception tab that matches + // the existing alert + goToExceptionsTab(); + addsExceptionFromRuleSettings(getException()); + + // Alerts table should now be empty from having added exception and closed + // matching alert + goToAlertsTab(); + cy.get(EMPTY_ALERT_TABLE).should('exist'); + + // Closed alert should appear in table + goToClosedAlerts(); + cy.get(ALERTS_COUNT).should('exist'); + cy.get(NUMBER_OF_ALERTS).should('have.text', `${NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS}`); + + // Remove the exception and load an event that would have matched that exception + // to show that said exception now starts to show up again + goToExceptionsTab(); + removeException(); + esArchiverLoad('exceptions_2'); + goToAlertsTab(); + goToOpenedAlerts(); + waitForTheRuleToBeExecuted(); + waitForAlertsToPopulate(); + + cy.get(ALERTS_COUNT).should('exist'); + cy.get(NUMBER_OF_ALERTS).should('have.text', `${NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS}`); + }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts deleted file mode 100644 index 5e8c542b369a6..0000000000000 --- a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts +++ /dev/null @@ -1,89 +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 { getException } from '../../objects/exception'; -import { getNewRule } from '../../objects/rule'; - -import { ALERTS_COUNT, EMPTY_ALERT_TABLE, NUMBER_OF_ALERTS } from '../../screens/alerts'; - -import { goToClosedAlerts, goToOpenedAlerts } from '../../tasks/alerts'; -import { createCustomRule } from '../../tasks/api_calls/rules'; -import { goToRuleDetails } from '../../tasks/alerts_detection_rules'; -import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; -import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver'; -import { login, visitWithoutDateRange } from '../../tasks/login'; -import { - addsExceptionFromRuleSettings, - goToAlertsTab, - goToExceptionsTab, - removeException, - waitForTheRuleToBeExecuted, -} from '../../tasks/rule_details'; - -import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; -import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common'; - -describe('From rule', () => { - const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1'; - before(() => { - cleanKibana(); - login(); - }); - - beforeEach(() => { - esArchiverLoad('exceptions'); - deleteAlertsAndRules(); - createCustomRule( - { ...getNewRule(), customQuery: 'agent.name:*', index: ['exceptions*'] }, - 'rule_testing', - '5s', - true - ); - visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - goToRuleDetails(); - waitForTheRuleToBeExecuted(); - waitForAlertsToPopulate(); - - cy.get(ALERTS_COUNT).should('exist'); - cy.get(NUMBER_OF_ALERTS).should('have.text', `${NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS} alert`); - }); - - afterEach(() => { - esArchiverUnload('exceptions'); - esArchiverUnload('exceptions_2'); - }); - - it('Creates an exception and deletes it', () => { - // Create an exception from the exception tab that matches - // the existing alert - goToExceptionsTab(); - addsExceptionFromRuleSettings(getException()); - - // Alerts table should now be empty from having added exception and closed - // matching alert - goToAlertsTab(); - cy.get(EMPTY_ALERT_TABLE).should('exist'); - - // Closed alert should appear in table - goToClosedAlerts(); - cy.get(ALERTS_COUNT).should('exist'); - cy.get(NUMBER_OF_ALERTS).should('have.text', `${NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS} alert`); - - // Remove the exception and load an event that would have matched that exception - // to show that said exception now starts to show up again - goToExceptionsTab(); - removeException(); - esArchiverLoad('exceptions_2'); - goToAlertsTab(); - goToOpenedAlerts(); - waitForTheRuleToBeExecuted(); - waitForAlertsToPopulate(); - - cy.get(ALERTS_COUNT).should('exist'); - cy.get(NUMBER_OF_ALERTS).should('have.text', `${NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS} alert`); - }); -}); diff --git a/x-pack/test/security_solution_cypress/es_archives/exceptions_2/data.json b/x-pack/test/security_solution_cypress/es_archives/exceptions_2/data.json index ae2e267abc7cf..3ad636b20a9cc 100644 --- a/x-pack/test/security_solution_cypress/es_archives/exceptions_2/data.json +++ b/x-pack/test/security_solution_cypress/es_archives/exceptions_2/data.json @@ -2,7 +2,7 @@ "type": "doc", "value": { "id": "_aZE5nwBOpWiDweSth_E", - "index": "exceptions-0001", + "index": "exceptions-0002", "source": { "@timestamp": "2019-09-02T00:41:06.527Z", "agent": { diff --git a/x-pack/test/security_solution_cypress/es_archives/exceptions_2/mappings.json b/x-pack/test/security_solution_cypress/es_archives/exceptions_2/mappings.json index 3b5cc2dae545c..f5f07c23046aa 100644 --- a/x-pack/test/security_solution_cypress/es_archives/exceptions_2/mappings.json +++ b/x-pack/test/security_solution_cypress/es_archives/exceptions_2/mappings.json @@ -11,7 +11,7 @@ "refresh_interval": "5s" } }, - "index": "exceptions-0001", + "index": "exceptions-0002", "mappings": { "properties": { "@timestamp": { From 3f20878699597e396f988e6cab4908abea2c3ebd Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 Apr 2022 12:23:34 -0400 Subject: [PATCH 20/27] [8.2.1] [Session View] Fix jump to alert can add extra space to the right of detail panel (#130935) * Fix jump to alert can add extra space to the right of detail panel * Fix resizable panels initial size * Fix alerts tab toggle view bottom gap --- .../detail_panel_alert_tab/styles.ts | 2 - .../public/components/session_view/index.tsx | 160 +++++++++--------- .../session_view_detail_panel/helpers.ts | 2 +- .../session_view_detail_panel/index.test.tsx | 4 +- .../session_view_detail_panel/index.tsx | 2 +- 5 files changed, 82 insertions(+), 88 deletions(-) diff --git a/x-pack/plugins/session_view/public/components/detail_panel_alert_tab/styles.ts b/x-pack/plugins/session_view/public/components/detail_panel_alert_tab/styles.ts index a906744cdafb2..702d4f20f3554 100644 --- a/x-pack/plugins/session_view/public/components/detail_panel_alert_tab/styles.ts +++ b/x-pack/plugins/session_view/public/components/detail_panel_alert_tab/styles.ts @@ -24,12 +24,10 @@ export const useStyles = () => { top: 0, zIndex: 1, backgroundColor: colors.emptyShade, - paddingTop: size.base, }; const viewMode: CSSObject = { margin: size.base, - marginBottom: 0, }; return { diff --git a/x-pack/plugins/session_view/public/components/session_view/index.tsx b/x-pack/plugins/session_view/public/components/session_view/index.tsx index b1678a5265ee2..7d11096ff18bd 100644 --- a/x-pack/plugins/session_view/public/components/session_view/index.tsx +++ b/x-pack/plugins/session_view/public/components/session_view/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useState, useCallback, useEffect, useMemo } from 'react'; +import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react'; import { EuiEmptyPrompt, EuiButton, @@ -78,6 +78,8 @@ export const SessionView = ({ const styles = useStyles({ height, isFullScreen }); + const detailPanelCollapseFn = useRef(() => {}); + // to give an indication to the user that there may be more search results if they turn on verbose mode. const showVerboseSearchTooltip = useMemo(() => { return !!(!displayOptions?.verboseMode && searchQuery && searchResults?.length === 0); @@ -122,7 +124,6 @@ export const SessionView = ({ const hasData = alerts && data && data.pages?.[0].events.length > 0; const hasError = error || alertsError; const renderIsLoading = (isFetching || alertsFetching) && !(data && alerts); - const renderDetails = isDetailOpen && selectedProcess; const { data: newUpdatedAlertsStatus } = useFetchAlertStatus( updatedAlertsStatus, fetchAlertStatus[0] ?? '' @@ -141,6 +142,7 @@ export const SessionView = ({ }, []); const toggleDetailPanel = useCallback(() => { + detailPanelCollapseFn.current(); setIsDetailOpen(!isDetailOpen); }, [isDetailOpen]); @@ -240,89 +242,83 @@ export const SessionView = ({ - {(EuiResizablePanel, EuiResizableButton) => ( - <> - - {hasError && ( - - - - } - body={ -

- -

- } - /> - )} + {(EuiResizablePanel, EuiResizableButton, { togglePanel }) => { + detailPanelCollapseFn.current = () => { + togglePanel?.('session-detail-panel', { direction: 'left' }); + }; - {hasData && ( -
- + + {hasError && ( + + + + } + body={ +

+ +

+ } /> -
- )} -
+ )} - {renderDetails ? ( - <> - - - - - - ) : ( - <> - {/* Returning an empty element here (instead of false) to avoid a bug in EuiResizableContainer */} - - )} - - )} + {hasData && ( +
+ +
+ )} +
+ + + + + + + ); + }} diff --git a/x-pack/plugins/session_view/public/components/session_view_detail_panel/helpers.ts b/x-pack/plugins/session_view/public/components/session_view_detail_panel/helpers.ts index 083bc35ac49d4..e4e6cb0134bbc 100644 --- a/x-pack/plugins/session_view/public/components/session_view_detail_panel/helpers.ts +++ b/x-pack/plugins/session_view/public/components/session_view_detail_panel/helpers.ts @@ -37,7 +37,7 @@ const getDetailPanelProcessLeader = (leader: ProcessFields | undefined) => ({ entryMetaSourceIp: leader?.entry_meta?.source?.ip ?? DEFAULT_PROCESS_DATA.entryMetaSourceIp, }); -export const getDetailPanelProcess = (process: Process | undefined) => { +export const getDetailPanelProcess = (process: Process | null) => { const processData = {} as DetailPanelProcess; if (!process) { return { diff --git a/x-pack/plugins/session_view/public/components/session_view_detail_panel/index.test.tsx b/x-pack/plugins/session_view/public/components/session_view_detail_panel/index.test.tsx index f2d7249e43c90..9ddefa25cea07 100644 --- a/x-pack/plugins/session_view/public/components/session_view_detail_panel/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/session_view_detail_panel/index.test.tsx @@ -38,10 +38,10 @@ describe('SessionView component', () => { expect(renderResult.queryByText('8e4daeb2-4a4e-56c4-980e-f0dcfdbc3726')).toBeVisible(); }); - it('should should default state with selectedProcess undefined', async () => { + it('should should default state with selectedProcess null', async () => { renderResult = mockedContext.render( diff --git a/x-pack/plugins/session_view/public/components/session_view_detail_panel/index.tsx b/x-pack/plugins/session_view/public/components/session_view_detail_panel/index.tsx index 86cfacfb72d06..130d15ca4df30 100644 --- a/x-pack/plugins/session_view/public/components/session_view_detail_panel/index.tsx +++ b/x-pack/plugins/session_view/public/components/session_view_detail_panel/index.tsx @@ -17,7 +17,7 @@ import { DetailPanelAlertTab } from '../detail_panel_alert_tab'; import { ALERT_COUNT_THRESHOLD } from '../../../common/constants'; interface SessionViewDetailPanelDeps { - selectedProcess: Process | undefined; + selectedProcess: Process | null; alerts?: ProcessEvent[]; investigatedAlertId?: string; onJumpToEvent: (event: ProcessEvent) => void; From ffe1cdddc76b7bc0a2b9410fc9a0a501e545e6f9 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Tue, 26 Apr 2022 17:27:09 +0100 Subject: [PATCH 21/27] [Security Solution] Removes internal tags (#130664) Addresses: - https://github.com/elastic/kibana/issues/124899 ## Summary - removes usage of internal tags (tags start with `__internal`) in codebase - migrates rules (removes existing internal tags from rules) ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../server/saved_objects/migrations.test.ts | 35 +++++ .../server/saved_objects/migrations.ts | 33 +++++ .../fixtures/saved_objects/rule.ndjson | 5 +- .../security_solution/common/constants.ts | 9 -- .../detection_engine/alerts/mock.ts | 28 +--- .../detection_engine/rules/api.test.ts | 6 +- .../rules/use_rule_with_fallback.test.tsx | 2 - .../detection_engine/rules/utils.test.ts | 17 ++- .../detection_engine/rules/utils.ts | 13 +- .../notifications/legacy_add_tags.test.ts | 28 ---- .../notifications/legacy_add_tags.ts | 14 -- .../legacy_create_notifications.ts | 4 +- .../legacy_read_notifications.ts | 3 +- .../routes/__mocks__/request_responses.ts | 6 +- .../get_prepackaged_rules_status_route.ts | 2 +- .../legacy_create_legacy_notification.ts | 4 +- .../routes/rules/utils.test.ts | 23 +-- .../detection_engine/routes/rules/utils.ts | 5 - .../rule_types/factories/utils/build_alert.ts | 3 +- .../detection_engine/rules/add_tags.test.ts | 49 ------- .../lib/detection_engine/rules/add_tags.ts | 20 --- .../detection_engine/rules/create_rules.ts | 3 +- .../rules/duplicate_rule.test.ts | 5 +- .../detection_engine/rules/duplicate_rule.ts | 3 +- .../lib/detection_engine/rules/edit_rule.ts | 6 +- .../rules/get_existing_prepackaged_rules.ts | 5 +- .../rules/get_export_by_object_ids.ts | 7 +- .../lib/detection_engine/rules/patch_rules.ts | 3 +- .../lib/detection_engine/rules/read_rules.ts | 5 +- .../detection_engine/rules/update_rules.ts | 3 +- .../schemas/rule_converters.ts | 6 +- .../scripts/find_rule_by_filter.sh | 6 +- .../signals/saved_object_references/README.md | 5 +- .../detection_engine/tags/read_tags.test.ts | 136 +----------------- .../lib/detection_engine/tags/read_tags.ts | 16 +-- .../usage/detections/get_metrics.test.ts | 2 +- .../detections/ml_jobs/get_metrics.mocks.ts | 15 +- .../get_rule_object_correlations.ts | 6 +- .../usage/queries/utils/is_elastic_rule.ts | 11 -- .../spaces_only/tests/alerting/migrations.ts | 20 ++- .../tests/resolve_read_rules.ts | 5 +- .../utils/downgrade_immutable_rule.ts | 3 +- .../utils/find_immutable_rule_by_id.ts | 8 +- .../functional/es_archives/alerts/data.json | 110 ++++++++++++-- 44 files changed, 252 insertions(+), 446 deletions(-) delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_add_tags.test.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_add_tags.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.test.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.ts delete mode 100644 x-pack/plugins/security_solution/server/usage/queries/utils/is_elastic_rule.ts diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts index 6b736006fc074..921412d4e79e8 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts @@ -2253,6 +2253,41 @@ describe('successful migrations', () => { }); }); + describe('8.3.0', () => { + test('removes internal tags', () => { + const migration830 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.3.0']; + const alert = getMockData( + { + tags: [ + '__internal_immutable:false', + '__internal_rule_id:064e3fed-6328-416b-bb85-c08265088f41', + 'test-tag', + ], + alertTypeId: 'siem.queryRule', + }, + true + ); + + const migratedAlert830 = migration830(alert, migrationContext); + + expect(migratedAlert830.attributes.tags).toEqual(['test-tag']); + }); + + test('do not remove internal tags if rule is not Security solution rule', () => { + const migration830 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.3.0']; + const alert = getMockData( + { + tags: ['__internal_immutable:false', 'tag-1'], + }, + true + ); + + const migratedAlert830 = migration830(alert, migrationContext); + + expect(migratedAlert830.attributes.tags).toEqual(['__internal_immutable:false', 'tag-1']); + }); + }); + describe('Metrics Inventory Threshold rule', () => { test('Migrates incorrect action group spelling', () => { const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0']; diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.ts index 18de27eb6919e..69d88e196dcfd 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.ts @@ -152,6 +152,12 @@ export function getMigrations( pipeMigrations(addMappedParams) ); + const migrationRules830 = createEsoMigration( + encryptedSavedObjects, + (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations(removeInternalTags) + ); + return { '7.10.0': executeMigrationWithErrorHandling(migrationWhenRBACWasIntroduced, '7.10.0'), '7.11.0': executeMigrationWithErrorHandling(migrationAlertUpdatedAtAndNotifyWhen, '7.11.0'), @@ -163,6 +169,7 @@ export function getMigrations( '8.0.0': executeMigrationWithErrorHandling(migrationRules800, '8.0.0'), '8.0.1': executeMigrationWithErrorHandling(migrationRules801, '8.0.1'), '8.2.0': executeMigrationWithErrorHandling(migrationRules820, '8.2.0'), + '8.3.0': executeMigrationWithErrorHandling(migrationRules830, '8.3.0'), }; } @@ -864,6 +871,32 @@ function getCorrespondingAction( ) as RawRuleAction; } } +/** + * removes internal tags(starts with '__internal') from Security Solution rules + * @param doc rule to be migrated + * @returns migrated rule if it's Security Solution rule or unchanged if not + */ +function removeInternalTags( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + if (!isDetectionEngineAADRuleType(doc)) { + return doc; + } + + const { + attributes: { tags }, + } = doc; + + const filteredTags = (tags ?? []).filter((tag) => !tag.startsWith('__internal_')); + + return { + ...doc, + attributes: { + ...doc.attributes, + tags: filteredTags, + }, + }; +} function pipeMigrations(...migrations: AlertMigration[]): AlertMigration { return (doc: SavedObjectUnsanitizedDoc) => diff --git a/x-pack/plugins/osquery/cypress/fixtures/saved_objects/rule.ndjson b/x-pack/plugins/osquery/cypress/fixtures/saved_objects/rule.ndjson index 75bdecb5be428..f688dc0731c7f 100644 --- a/x-pack/plugins/osquery/cypress/fixtures/saved_objects/rule.ndjson +++ b/x-pack/plugins/osquery/cypress/fixtures/saved_objects/rule.ndjson @@ -8,10 +8,7 @@ "version": "WzE5MjksMV0=", "attributes": { "name": "Test-rule", - "tags": [ - "__internal_rule_id:22308402-5e0e-421b-8d22-a47ddc4b0188", - "__internal_immutable:false" - ], + "tags": [], "alertTypeId": "siem.queryRule", "consumer": "siem", "params": { diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 74f9bff078b89..d71c001f64dd1 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -206,15 +206,6 @@ export const IP_REPUTATION_LINKS_SETTING_DEFAULT = `[ */ export const LEGACY_NOTIFICATIONS_ID = `siem.notifications` as const; -/** - * Special internal structure for tags for signals. This is used - * to filter out tags that have internal structures within them. - */ -export const INTERNAL_IDENTIFIER = '__internal' as const; -export const INTERNAL_RULE_ID_KEY = `${INTERNAL_IDENTIFIER}_rule_id` as const; -export const INTERNAL_RULE_ALERT_ID_KEY = `${INTERNAL_IDENTIFIER}_rule_alert_id` as const; -export const INTERNAL_IMMUTABLE_KEY = `${INTERNAL_IDENTIFIER}_immutable` as const; - /** * Internal actions route */ diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts index 58ec564c6911b..f35c4158fa236 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts @@ -188,12 +188,7 @@ export const alertsMock: AlertSearchResponse = { query: 'host.name : *', references: ['https://google.com'], severity: 'high', - tags: [ - 'host.name exists', - 'for testing', - '__internal_rule_id:82b2b065-a2ee-49fc-9d6d-781a75c3d280', - '__internal_immutable:false', - ], + tags: ['host.name exists', 'for testing'], type: 'query', to: 'now', enabled: true, @@ -428,12 +423,7 @@ export const alertsMock: AlertSearchResponse = { query: 'host.name : *', references: ['https://google.com'], severity: 'high', - tags: [ - 'host.name exists', - 'for testing', - '__internal_rule_id:82b2b065-a2ee-49fc-9d6d-781a75c3d280', - '__internal_immutable:false', - ], + tags: ['host.name exists', 'for testing'], type: 'query', to: 'now', enabled: true, @@ -634,12 +624,7 @@ export const alertsMock: AlertSearchResponse = { query: 'host.name : *', references: ['https://google.com'], severity: 'high', - tags: [ - 'host.name exists', - 'for testing', - '__internal_rule_id:82b2b065-a2ee-49fc-9d6d-781a75c3d280', - '__internal_immutable:false', - ], + tags: ['host.name exists', 'for testing'], type: 'query', to: 'now', enabled: true, @@ -838,12 +823,7 @@ export const alertsMock: AlertSearchResponse = { query: 'host.name : *', references: ['https://google.com'], severity: 'high', - tags: [ - 'host.name exists', - 'for testing', - '__internal_rule_id:82b2b065-a2ee-49fc-9d6d-781a75c3d280', - '__internal_immutable:false', - ], + tags: ['host.name exists', 'for testing'], type: 'query', to: 'now', enabled: true, diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts index 3c534ca7294a5..b999040674a91 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts @@ -200,7 +200,7 @@ describe('Detections Rules API', () => { expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules/_find', { method: 'GET', query: { - filter: 'alert.attributes.tags: "__internal_immutable:false"', + filter: 'alert.attributes.params.immutable: false', page: 1, per_page: 20, sort_field: 'enabled', @@ -228,7 +228,7 @@ describe('Detections Rules API', () => { expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules/_find', { method: 'GET', query: { - filter: 'alert.attributes.tags: "__internal_immutable:true"', + filter: 'alert.attributes.params.immutable: true', page: 1, per_page: 20, sort_field: 'enabled', @@ -383,7 +383,7 @@ describe('Detections Rules API', () => { method: 'GET', query: { filter: - 'alert.attributes.tags: "__internal_immutable:false" AND alert.attributes.tags: "__internal_immutable:true" AND alert.attributes.tags:("hello" AND "world") AND (alert.attributes.name: "ruleName" OR alert.attributes.params.index: "ruleName" OR alert.attributes.params.threat.tactic.id: "ruleName" OR alert.attributes.params.threat.tactic.name: "ruleName" OR alert.attributes.params.threat.technique.id: "ruleName" OR alert.attributes.params.threat.technique.name: "ruleName" OR alert.attributes.params.threat.technique.subtechnique.id: "ruleName" OR alert.attributes.params.threat.technique.subtechnique.name: "ruleName")', + 'alert.attributes.tags:("hello" AND "world") AND (alert.attributes.name: "ruleName" OR alert.attributes.params.index: "ruleName" OR alert.attributes.params.threat.tactic.id: "ruleName" OR alert.attributes.params.threat.tactic.name: "ruleName" OR alert.attributes.params.threat.technique.id: "ruleName" OR alert.attributes.params.threat.technique.name: "ruleName" OR alert.attributes.params.threat.technique.subtechnique.id: "ruleName" OR alert.attributes.params.threat.technique.subtechnique.name: "ruleName")', page: 1, per_page: 20, sort_field: 'enabled', diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx index 40d2e8663b618..d7c4ad8772bd2 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx @@ -154,8 +154,6 @@ describe('useRuleWithFallback', () => { "tags": Array [ "host.name exists", "for testing", - "__internal_rule_id:82b2b065-a2ee-49fc-9d6d-781a75c3d280", - "__internal_immutable:false", ], "threat": Array [ Object { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.test.ts index a26a4aec3ec02..dff02aa3d3a75 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.test.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { INTERNAL_IMMUTABLE_KEY } from '../../../../../common/constants'; import { FilterOptions } from './types'; import { convertRulesFilterToKQL } from './utils'; @@ -42,13 +41,23 @@ describe('convertRulesFilterToKQL', () => { it('handles presence of "showCustomRules" properly', () => { const kql = convertRulesFilterToKQL({ ...filterOptions, showCustomRules: true }); - expect(kql).toBe(`alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:false"`); + expect(kql).toBe(`alert.attributes.params.immutable: false`); }); it('handles presence of "showElasticRules" properly', () => { const kql = convertRulesFilterToKQL({ ...filterOptions, showElasticRules: true }); - expect(kql).toBe(`alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:true"`); + expect(kql).toBe(`alert.attributes.params.immutable: true`); + }); + + it('handles presence of "showElasticRules" and "showCustomRules" at the same time properly', () => { + const kql = convertRulesFilterToKQL({ + ...filterOptions, + showElasticRules: true, + showCustomRules: true, + }); + + expect(kql).toBe(''); }); it('handles presence of "tags" properly', () => { @@ -66,7 +75,7 @@ describe('convertRulesFilterToKQL', () => { }); expect(kql).toBe( - `alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:true" AND alert.attributes.tags:("tag1" AND "tag2") AND (alert.attributes.name: "foo" OR alert.attributes.params.index: "foo" OR alert.attributes.params.threat.tactic.id: "foo" OR alert.attributes.params.threat.tactic.name: "foo" OR alert.attributes.params.threat.technique.id: "foo" OR alert.attributes.params.threat.technique.name: "foo" OR alert.attributes.params.threat.technique.subtechnique.id: "foo" OR alert.attributes.params.threat.technique.subtechnique.name: "foo")` + `alert.attributes.params.immutable: true AND alert.attributes.tags:("tag1" AND "tag2") AND (alert.attributes.name: "foo" OR alert.attributes.params.index: "foo" OR alert.attributes.params.threat.tactic.id: "foo" OR alert.attributes.params.threat.tactic.name: "foo" OR alert.attributes.params.threat.technique.id: "foo" OR alert.attributes.params.threat.technique.name: "foo" OR alert.attributes.params.threat.technique.subtechnique.id: "foo" OR alert.attributes.params.threat.technique.subtechnique.name: "foo")` ); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.ts index 069746223731c..8c576979a15c0 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { INTERNAL_IMMUTABLE_KEY } from '../../../../../common/constants'; import { escapeKuery } from '../../../../common/lib/keury'; import { FilterOptions } from './types'; @@ -35,12 +34,12 @@ export const convertRulesFilterToKQL = ({ }: FilterOptions): string => { const filters: string[] = []; - if (showCustomRules) { - filters.push(`alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:false"`); - } - - if (showElasticRules) { - filters.push(`alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:true"`); + if (showCustomRules && showElasticRules) { + // if both showCustomRules && showElasticRules selected we omit filter, as it includes all existing rules + } else if (showElasticRules) { + filters.push('alert.attributes.params.immutable: true'); + } else if (showCustomRules) { + filters.push('alert.attributes.params.immutable: false'); } if (tags.length > 0) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_add_tags.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_add_tags.test.ts deleted file mode 100644 index 95051ad3d8021..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_add_tags.test.ts +++ /dev/null @@ -1,28 +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. - */ - -// eslint-disable-next-line no-restricted-imports -import { legacyAddTags } from './legacy_add_tags'; -import { INTERNAL_RULE_ALERT_ID_KEY } from '../../../../common/constants'; - -describe('legacyAdd_tags', () => { - test('it should add a rule id as an internal structure', () => { - const tags = legacyAddTags([], 'rule-1'); - expect(tags).toEqual([`${INTERNAL_RULE_ALERT_ID_KEY}:rule-1`]); - }); - - test('it should not allow duplicate tags to be created', () => { - const tags = legacyAddTags(['tag-1', 'tag-1'], 'rule-1'); - expect(tags).toEqual(['tag-1', `${INTERNAL_RULE_ALERT_ID_KEY}:rule-1`]); - }); - - test('it should not allow duplicate internal tags to be created when called two times in a row', () => { - const tags1 = legacyAddTags(['tag-1'], 'rule-1'); - const tags2 = legacyAddTags(tags1, 'rule-1'); - expect(tags2).toEqual(['tag-1', `${INTERNAL_RULE_ALERT_ID_KEY}:rule-1`]); - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_add_tags.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_add_tags.ts deleted file mode 100644 index b17d8d7226a64..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_add_tags.ts +++ /dev/null @@ -1,14 +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 { INTERNAL_RULE_ALERT_ID_KEY } 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 const legacyAddTags = (tags: string[], ruleAlertId: string): string[] => - Array.from(new Set([...tags, `${INTERNAL_RULE_ALERT_ID_KEY}:${ruleAlertId}`])); 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 dec6d51aa9082..e82b19faa40bd 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 @@ -9,8 +9,6 @@ import { SanitizedRule } from '@kbn/alerting-plugin/common'; import { SERVER_APP_ID, LEGACY_NOTIFICATIONS_ID } from '../../../../common/constants'; // eslint-disable-next-line no-restricted-imports import { CreateNotificationParams, LegacyRuleNotificationAlertTypeParams } from './legacy_types'; -// eslint-disable-next-line no-restricted-imports -import { legacyAddTags } from './legacy_add_tags'; /** * @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 @@ -26,7 +24,7 @@ export const legacyCreateNotifications = async ({ rulesClient.create({ data: { name, - tags: legacyAddTags([], ruleAlertId), + tags: [], alertTypeId: LEGACY_NOTIFICATIONS_ID, consumer: SERVER_APP_ID, params: { 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 05f64e19df26e..3e28ac879e2c3 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 @@ -10,7 +10,6 @@ import { RuleTypeParams, SanitizedRule } from '@kbn/alerting-plugin/common'; import { LegacyReadNotificationParams, legacyIsAlertType } from './legacy_types'; // eslint-disable-next-line no-restricted-imports import { legacyFindNotifications } from './legacy_find_notifications'; -import { INTERNAL_RULE_ALERT_ID_KEY } 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 @@ -39,7 +38,7 @@ export const legacyReadNotifications = async ({ } else if (ruleAlertId != null) { const notificationFromFind = await legacyFindNotifications({ rulesClient, - filter: `alert.attributes.tags: "${INTERNAL_RULE_ALERT_ID_KEY}:${ruleAlertId}"`, + filter: `alert.attributes.params.ruleAlertId: "${ruleAlertId}"`, page: 1, }); if ( 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 10607e352000d..8e2f0da4e65c9 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 @@ -17,8 +17,6 @@ import { DETECTION_ENGINE_SIGNALS_STATUS_URL, DETECTION_ENGINE_PRIVILEGES_URL, DETECTION_ENGINE_QUERY_SIGNALS_URL, - INTERNAL_RULE_ID_KEY, - INTERNAL_IMMUTABLE_KEY, DETECTION_ENGINE_PREPACKAGED_URL, DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL, DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL, @@ -393,7 +391,7 @@ export const nonRuleAlert = () => ({ export const getRuleMock = (params: T): SanitizedRule => ({ id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', name: 'Detect Root/Admin Users', - tags: [`${INTERNAL_RULE_ID_KEY}:rule-1`, `${INTERNAL_IMMUTABLE_KEY}:false`], + tags: [], alertTypeId: ruleTypeMappings[params.type], consumer: 'siem', params, @@ -693,7 +691,7 @@ export const getSignalsMigrationStatusRequest = () => export const legacyGetNotificationResult = (): LegacyRuleNotificationAlertType => ({ id: '200dbf2f-b269-4bf9-aa85-11ba32ba73ba', name: 'Notification for Rule Test', - tags: ['__internal_rule_alert_id:85b64e8a-2e40-4096-86af-5ac172c10825'], + tags: [], alertTypeId: 'siem.notifications', consumer: 'siem', params: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts index d35df4d51ee02..0f70a12e463cf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts @@ -61,7 +61,7 @@ export const getPrepackagedRulesStatusRoute = ( page: 1, sortField: 'enabled', sortOrder: 'desc', - filter: 'alert.attributes.tags:"__internal_immutable:false"', + filter: 'alert.attributes.params.immutable: false', fields: undefined, }); const frameworkRequest = await buildFrameworkRequest(context, security, request); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/legacy_create_legacy_notification.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/legacy_create_legacy_notification.ts index 6b6e65329c06f..ffccedb691db4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/legacy_create_legacy_notification.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/legacy_create_legacy_notification.ts @@ -16,8 +16,6 @@ import { legacyReadNotifications } from '../../notifications/legacy_read_notific // eslint-disable-next-line no-restricted-imports import { LegacyRuleNotificationAlertTypeParams } from '../../notifications/legacy_types'; // eslint-disable-next-line no-restricted-imports -import { legacyAddTags } from '../../notifications/legacy_add_tags'; -// eslint-disable-next-line no-restricted-imports import { legacyCreateNotifications } from '../../notifications/legacy_create_notifications'; /** @@ -72,7 +70,7 @@ export const legacyCreateLegacyNotificationRoute = ( await rulesClient.update({ id: notification.id, data: { - tags: legacyAddTags([], ruleAlertId), + tags: [], name, schedule: { interval, 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 6e2a5d08becbc..2fb1dccc64f80 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 @@ -13,7 +13,6 @@ import { getIdError, transformFindAlerts, transform, - transformTags, getIdBulkError, transformAlertsToRules, getDuplicates, @@ -23,7 +22,6 @@ import { migrateLegacyActionsIds, } from './utils'; import { getRuleMock } from '../__mocks__/request_responses'; -import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; import { PartialFilter } from '../../types'; import { BulkError, createBulkErrorObject } from '../utils'; import { getOutputRuleAlertForRest } from '../__mocks__/utils'; @@ -99,9 +97,9 @@ describe('utils', () => { expect(ruleWithEnabledFalse).toEqual(expected); }); - test('should work with tags but filter out any internal tags', () => { + test('should work with tags', () => { const fullRule = getRuleMock(getQueryRuleParams()); - fullRule.tags = ['tag 1', 'tag 2', `${INTERNAL_IDENTIFIER}_some_other_value`]; + fullRule.tags = ['tag 1', 'tag 2']; const rule = internalRuleToAPIResponse(fullRule); const expected = getOutputRuleAlertForRest(); expected.tags = ['tag 1', 'tag 2']; @@ -382,23 +380,6 @@ describe('utils', () => { }); }); - describe('transformTags', () => { - test('it returns tags that have no internal structures', () => { - expect(transformTags(['tag 1', 'tag 2'])).toEqual(['tag 1', 'tag 2']); - }); - - test('it returns empty tags given empty tags', () => { - expect(transformTags([])).toEqual([]); - }); - - test('it returns tags with internal tags stripped out', () => { - expect(transformTags(['tag 1', `${INTERNAL_IDENTIFIER}_some_value`, 'tag 2'])).toEqual([ - 'tag 1', - 'tag 2', - ]); - }); - }); - describe('getIdBulkError', () => { test('outputs message about id and rule_id not being found if both are not null', () => { const error = getIdBulkError({ id: '123', ruleId: '456' }); 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 27f3f160a5288..850d971b39c59 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 @@ -17,7 +17,6 @@ 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 { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; import { RuleAlertType, isAlertType } from '../../rules/types'; import { createBulkErrorObject, BulkError, OutputError } from '../utils'; import { internalRuleToAPIResponse } from '../../schemas/rule_converters'; @@ -88,10 +87,6 @@ export const getIdBulkError = ({ } }; -export const transformTags = (tags: string[]): string[] => { - return tags.filter((tag) => !tag.startsWith(INTERNAL_IDENTIFIER)); -}; - export const transformAlertsToRules = ( rules: RuleAlertType[], legacyRuleActions: Record diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts index e1259be5062a0..8492ec6cd2c26 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts @@ -78,7 +78,6 @@ import { commonParamsCamelToSnake, typeSpecificCamelToSnake, } from '../../../schemas/rule_converters'; -import { transformTags } from '../../../routes/rules/utils'; import { transformAlertToRuleAction } from '../../../../../../common/detection_engine/transform_actions'; import { AncestorLatest, @@ -215,7 +214,7 @@ export const buildAlert = ( [ALERT_RULE_RULE_ID]: params.ruleId, [ALERT_RULE_RULE_NAME_OVERRIDE]: params.ruleNameOverride, [ALERT_RULE_SEVERITY_MAPPING]: params.severityMapping, - [ALERT_RULE_TAGS]: transformTags(tags), + [ALERT_RULE_TAGS]: tags, [ALERT_RULE_THREAT]: params.threat, [ALERT_RULE_THROTTLE]: throttle ?? undefined, [ALERT_RULE_TIMELINE_ID]: params.timelineId, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.test.ts deleted file mode 100644 index 93fddc06b8068..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.test.ts +++ /dev/null @@ -1,49 +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 { addTags } from './add_tags'; -import { INTERNAL_RULE_ID_KEY, INTERNAL_IMMUTABLE_KEY } from '../../../../common/constants'; - -describe('add_tags', () => { - test('it should add a rule id as an internal structure with immutable true', () => { - const tags = addTags([], 'rule-1', true); - expect(tags).toEqual([`${INTERNAL_RULE_ID_KEY}:rule-1`, `${INTERNAL_IMMUTABLE_KEY}:true`]); - }); - - test('it should add a rule id as an internal structure with immutable false', () => { - const tags = addTags([], 'rule-1', false); - expect(tags).toEqual([`${INTERNAL_RULE_ID_KEY}:rule-1`, `${INTERNAL_IMMUTABLE_KEY}:false`]); - }); - - test('it should not allow duplicate tags to be created', () => { - const tags = addTags(['tag-1', 'tag-1'], 'rule-1', false); - expect(tags).toEqual([ - 'tag-1', - `${INTERNAL_RULE_ID_KEY}:rule-1`, - `${INTERNAL_IMMUTABLE_KEY}:false`, - ]); - }); - - test('it should not allow duplicate internal tags to be created when called two times in a row', () => { - const tags1 = addTags(['tag-1'], 'rule-1', false); - const tags2 = addTags(tags1, 'rule-1', false); - expect(tags2).toEqual([ - 'tag-1', - `${INTERNAL_RULE_ID_KEY}:rule-1`, - `${INTERNAL_IMMUTABLE_KEY}:false`, - ]); - }); - - test('it should overwrite existing immutable tag if it exists', () => { - const tags1 = addTags(['tag-1', `${INTERNAL_IMMUTABLE_KEY}:true`], 'rule-1', false); - expect(tags1).toEqual([ - 'tag-1', - `${INTERNAL_RULE_ID_KEY}:rule-1`, - `${INTERNAL_IMMUTABLE_KEY}:false`, - ]); - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.ts deleted file mode 100644 index d66f961b38598..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.ts +++ /dev/null @@ -1,20 +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 { INTERNAL_RULE_ID_KEY, INTERNAL_IMMUTABLE_KEY } from '../../../../common/constants'; - -export const addTags = (tags: string[], ruleId: string, immutable: boolean): string[] => { - return Array.from( - new Set([ - ...tags.filter( - (tag) => !(tag.startsWith(INTERNAL_RULE_ID_KEY) || tag.startsWith(INTERNAL_IMMUTABLE_KEY)) - ), - `${INTERNAL_RULE_ID_KEY}:${ruleId}`, - `${INTERNAL_IMMUTABLE_KEY}:${immutable}`, - ]) - ); -}; 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 ed2d1e823cd1c..24017adc20626 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 @@ -19,7 +19,6 @@ import { SERVER_APP_ID, } from '../../../../common/constants'; import { CreateRulesOptions } from './types'; -import { addTags } from './add_tags'; import { PartialFilter } from '../types'; import { transformToAlertThrottle, transformToNotifyWhen } from './utils'; @@ -83,7 +82,7 @@ export const createRules = async ({ }, data: { name, - tags: addTags(tags, ruleId, immutable), + tags, alertTypeId: ruleTypeMappings[type], consumer: SERVER_APP_ID, params: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts index f93312ade4cfc..04d8e66a076fb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts @@ -6,7 +6,6 @@ */ import uuid from 'uuid'; -import { INTERNAL_IMMUTABLE_KEY } from '../../../../common/constants'; import { duplicateRule } from './duplicate_rule'; jest.mock('uuid', () => ({ @@ -22,7 +21,7 @@ describe('duplicateRule', () => { id: 'oldTestRuleId', notifyWhen: 'onActiveAlert', name: 'test', - tags: ['test', '__internal_rule_id:oldTestRuleId', `${INTERNAL_IMMUTABLE_KEY}:false`], + tags: ['test'], alertTypeId: 'siem.signals', consumer: 'siem', params: { @@ -125,8 +124,6 @@ describe('duplicateRule', () => { }, "tags": Array [ "test", - "__internal_rule_id:newId", - "__internal_immutable:false", ], "throttle": null, } 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 d3dba0f61df2e..4ef21d0450517 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 @@ -13,7 +13,6 @@ import { ruleTypeMappings } from '@kbn/securitysolution-rules'; import { SanitizedRule } from '@kbn/alerting-plugin/common'; import { SERVER_APP_ID } from '../../../../common/constants'; import { InternalRuleCreate, RuleParams } from '../schemas/rule_schemas'; -import { addTags } from './add_tags'; const DUPLICATE_TITLE = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.cloneRule.duplicateTitle', @@ -26,7 +25,7 @@ export const duplicateRule = (rule: SanitizedRule): InternalRuleCrea const newRuleId = uuid.v4(); return { name: `${rule.name} [${DUPLICATE_TITLE}]`, - tags: addTags(rule.tags, newRuleId, false), + tags: rule.tags, alertTypeId: ruleTypeMappings[rule.params.type], consumer: SERVER_APP_ID, params: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/edit_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/edit_rule.ts index 93a297e6965b4..c7f17e100205c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/edit_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/edit_rule.ts @@ -11,7 +11,6 @@ import { validate } from '@kbn/securitysolution-io-ts-utils'; import type { RulesClient } from '@kbn/alerting-plugin/server'; import { RuleAlertType } from './types'; import { InternalRuleUpdate, internalRuleUpdate } from '../schemas/rule_schemas'; -import { addTags } from './add_tags'; class EditRuleError extends Error { public readonly statusCode: number; @@ -103,10 +102,7 @@ const validateAndSanitizeChanges = ( throw new EditRuleError(`Internal rule editing error: can't change "params.version"`, 500); } - return { - ...changed, - tags: addTags(changed.tags, changed.params.ruleId, changed.params.immutable), - }; + return changed; }; const isRuleChanged = (originalRule: RuleAlertType, editedRule: RuleAlertType): boolean => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts index a15cc1d3eec1f..440d3db36bd63 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts @@ -6,12 +6,11 @@ */ import { RulesClient } from '@kbn/alerting-plugin/server'; -import { INTERNAL_IMMUTABLE_KEY } from '../../../../common/constants'; import { RuleAlertType, isAlertTypes } from './types'; import { findRules } from './find_rules'; -export const FILTER_NON_PREPACKED_RULES = `alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:false"`; -export const FILTER_PREPACKED_RULES = `alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:true"`; +export const FILTER_NON_PREPACKED_RULES = 'alert.attributes.params.immutable: false'; +export const FILTER_PREPACKED_RULES = 'alert.attributes.params.immutable: true'; export const getNonPackagedRulesCount = async ({ rulesClient, 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 836c9b035f305..dcf3f7532b53c 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 @@ -16,7 +16,6 @@ import { RulesSchema } from '../../../../common/detection_engine/schemas/respons import { getExportDetailsNdjson } from './get_export_details_ndjson'; import { isAlertType } from './types'; -import { INTERNAL_RULE_ID_KEY } from '../../../../common/constants'; import { findRules } from './find_rules'; import { getRuleExceptionsForExport } from './get_export_rule_exceptions'; @@ -92,10 +91,8 @@ export const getRulesFromObjects = async ( const chunkedObjects = chunk(objects, 1024); const filter = chunkedObjects .map((chunkedArray) => { - const joinedIds = chunkedArray - .map((object) => `"${INTERNAL_RULE_ID_KEY}:${object.rule_id}"`) - .join(' OR '); - return `alert.attributes.tags: (${joinedIds})`; + const joinedIds = chunkedArray.map((object) => object.rule_id).join(' OR '); + return `alert.attributes.params.ruleId: (${joinedIds})`; }) .join(' OR '); const rules = await findRules({ 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 05f58e44ecaa2..ad2443b34fa95 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 @@ -14,7 +14,6 @@ import { normalizeThresholdObject, } from '../../../../common/detection_engine/utils'; import { internalRuleUpdate, RuleParams } from '../schemas/rule_schemas'; -import { addTags } from './add_tags'; import { PatchRulesOptions } from './types'; import { calculateInterval, @@ -190,7 +189,7 @@ export const patchRules = async ({ ); const newRule = { - tags: addTags(tags ?? rule.tags, rule.params.ruleId, rule.params.immutable), + tags: tags ?? rule.tags, name: calculateName({ updatedName: name, originalName: rule.name }), schedule: { interval: calculateInterval(interval, rule.schedule.interval), 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 14a63df87ebe8..ef9d867105e10 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 @@ -6,7 +6,6 @@ */ import { ResolvedSanitizedRule, SanitizedRule } from '@kbn/alerting-plugin/common'; -import { INTERNAL_RULE_ID_KEY } from '../../../../common/constants'; import { RuleParams } from '../schemas/rule_schemas'; import { findRules } from './find_rules'; import { isAlertType, ReadRuleOptions } from './types'; @@ -17,7 +16,7 @@ import { isAlertType, ReadRuleOptions } from './types'; * and the id will either be found through `rulesClient.get({ id })` or it will not * be returned as a not-found or a thrown error that is not 404. * @param ruleId - This is a close second to being fast as long as it can find the rule_id from - * a filter query against the tags using `alert.attributes.tags: "__internal:${ruleId}"]` + * a filter query against the ruleId property in params using `alert.attributes.params.ruleId: "${ruleId}"` */ export const readRules = async ({ rulesClient, @@ -49,7 +48,7 @@ export const readRules = async ({ } else if (ruleId != null) { const ruleFromFind = await findRules({ rulesClient, - filter: `alert.attributes.tags: "${INTERNAL_RULE_ID_KEY}:${ruleId}"`, + filter: `alert.attributes.params.ruleId: "${ruleId}"`, page: 1, fields: undefined, perPage: undefined, 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 df9a09ba98bb5..ba65b76f01c4a 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 @@ -12,7 +12,6 @@ import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants'; import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; import { UpdateRulesOptions } from './types'; -import { addTags } from './add_tags'; import { typeSpecificSnakeToCamel } from '../schemas/rule_converters'; import { internalRuleUpdate, RuleParams } from '../schemas/rule_schemas'; import { maybeMute, transformToAlertThrottle, transformToNotifyWhen } from './utils'; @@ -39,7 +38,7 @@ export const updateRules = async ({ const enabled = ruleUpdate.enabled ?? true; const newInternalRule = { name: ruleUpdate.name, - tags: addTags(ruleUpdate.tags ?? [], existingRule.params.ruleId, existingRule.params.immutable), + tags: ruleUpdate.tags ?? [], params: { author: ruleUpdate.author ?? [], buildingBlockType: ruleUpdate.building_block_type, 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 8b7fdcf568539..fd80bec1f6ad9 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 @@ -29,10 +29,8 @@ import { ResponseTypeSpecific, } from '../../../../common/detection_engine/schemas/request'; 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 { transformTags } from '../routes/rules/utils'; import { transformFromAlertThrottle, transformToAlertThrottle, @@ -133,7 +131,7 @@ export const convertCreateAPIToInternalSchema = ( const newRuleId = input.rule_id ?? uuid.v4(); return { name: input.name, - tags: addTags(input.tags ?? [], newRuleId, false), + tags: input.tags ?? [], alertTypeId: ruleTypeMappings[input.type], consumer: SERVER_APP_ID, params: { @@ -301,7 +299,7 @@ export const internalRuleToAPIResponse = ( created_at: rule.createdAt.toISOString(), created_by: rule.createdBy ?? 'elastic', name: rule.name, - tags: transformTags(rule.tags), + tags: rule.tags, interval: rule.schedule.interval, enabled: rule.enabled, // Security solution shared rule params diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/find_rule_by_filter.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/find_rule_by_filter.sh index 6aab0cd3c9728..35199b775b33c 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/find_rule_by_filter.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/find_rule_by_filter.sh @@ -26,13 +26,13 @@ FILTER=${1:-'alert.attributes.enabled:%20true'} # ./find_rule_by_filter.sh "alert.attributes.tags:tag_1" # Example get all pre-packaged rules -# ./find_rule_by_filter.sh "alert.attributes.tags:%20%22__internal_immutable:true%22" +# ./find_rule_by_filter.sh "alert.attributes.params.immutable:%20true" # Example get all non pre-packaged rules -# ./find_rule_by_filter.sh "alert.attributes.tags:%20%22__internal_immutable:false%22" +# ./find_rule_by_filter.sh "alert.attributes.params.immutable:%20false" # Example get all non pre-packaged rules and a tag_1 -# ./find_rule_by_filter.sh "alert.attributes.tags:%20%22__internal_immutable:false%22%20AND%20alert.attributes.tags:tag_1" +# ./find_rule_by_filter.sh "alert.attributes.params.immutable:%20false%20AND%20alert.attributes.tags:tag_1" curl -s -k \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ -X GET ${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_find?filter=$FILTER | jq . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md index c76a69db084ca..cf8e3262e2826 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md @@ -53,10 +53,7 @@ to any newly saved rule: "_source" : { "alert" : { "name" : "kql test rule 1", - "tags" : [ - "__internal_rule_id:4ec223b9-77fa-4895-8539-6b3e586a2858", - "__internal_immutable:false" - ], + "tags" : [], "alertTypeId" : "siem.signals", "other data... other data": "other data...other data", "exceptionsList" : [ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.test.ts index 4d6327e05f2d3..2154e672e0089 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.test.ts @@ -6,10 +6,8 @@ */ import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; - import { getRuleMock, getFindResultWithMultiHits } from '../routes/__mocks__/request_responses'; -import { INTERNAL_RULE_ID_KEY, INTERNAL_IDENTIFIER } from '../../../../common/constants'; -import { readRawTags, readTags, convertTagsToSet, convertToTags, isTags } from './read_tags'; +import { readTags, convertTagsToSet, convertToTags, isTags } from './read_tags'; import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; describe('read_tags', () => { @@ -17,88 +15,6 @@ describe('read_tags', () => { jest.resetAllMocks(); }); - describe('readRawTags', () => { - test('it should return the intersection of tags to where none are repeating', async () => { - const result1 = getRuleMock(getQueryRuleParams()); - result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - result1.params.ruleId = 'rule-1'; - result1.tags = ['tag 1', 'tag 2', 'tag 3']; - - const result2 = getRuleMock(getQueryRuleParams()); - result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - result2.params.ruleId = 'rule-2'; - result2.tags = ['tag 1', 'tag 2', 'tag 3', 'tag 4']; - - const rulesClient = rulesClientMock.create(); - rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] })); - - const tags = await readRawTags({ rulesClient }); - expect(tags).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4']); - }); - - test('it should return the intersection of tags to where some are repeating values', async () => { - const result1 = getRuleMock(getQueryRuleParams()); - result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - result1.params.ruleId = 'rule-1'; - result1.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3']; - - const result2 = getRuleMock(getQueryRuleParams()); - result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - result2.params.ruleId = 'rule-2'; - result2.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3', 'tag 4']; - - const rulesClient = rulesClientMock.create(); - rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] })); - - const tags = await readRawTags({ rulesClient }); - expect(tags).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4']); - }); - - test('it should work with no tags defined between two results', async () => { - const result1 = getRuleMock(getQueryRuleParams()); - result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - result1.params.ruleId = 'rule-1'; - result1.tags = []; - - const result2 = getRuleMock(getQueryRuleParams()); - result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - result2.params.ruleId = 'rule-2'; - result2.tags = []; - - const rulesClient = rulesClientMock.create(); - rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] })); - - const tags = await readRawTags({ rulesClient }); - expect(tags).toEqual([]); - }); - - test('it should work with a single tag which has repeating values in it', async () => { - const result1 = getRuleMock(getQueryRuleParams()); - result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - result1.params.ruleId = 'rule-1'; - result1.tags = ['tag 1', 'tag 1', 'tag 1', 'tag 2']; - - const rulesClient = rulesClientMock.create(); - rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] })); - - const tags = await readRawTags({ rulesClient }); - expect(tags).toEqual(['tag 1', 'tag 2']); - }); - - test('it should work with a single tag which has empty tags', async () => { - const result1 = getRuleMock(getQueryRuleParams()); - result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - result1.params.ruleId = 'rule-1'; - result1.tags = []; - - const rulesClient = rulesClientMock.create(); - rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] })); - - const tags = await readRawTags({ rulesClient }); - expect(tags).toEqual([]); - }); - }); - describe('readTags', () => { test('it should return the intersection of tags to where none are repeating', async () => { const result1 = getRuleMock(getQueryRuleParams()); @@ -179,56 +95,6 @@ describe('read_tags', () => { const tags = await readTags({ rulesClient }); expect(tags).toEqual([]); }); - - test('it should filter out any __internal tags for things such as alert_id', async () => { - const result1 = getRuleMock(getQueryRuleParams()); - result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - result1.params.ruleId = 'rule-1'; - result1.tags = [ - `${INTERNAL_IDENTIFIER}_some_value`, - `${INTERNAL_RULE_ID_KEY}_some_value`, - 'tag 1', - ]; - - const rulesClient = rulesClientMock.create(); - rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] })); - - const tags = await readTags({ rulesClient }); - expect(tags).toEqual(['tag 1']); - }); - - test('it should filter out any __internal tags with two different results', async () => { - const result1 = getRuleMock(getQueryRuleParams()); - result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - result1.params.ruleId = 'rule-1'; - result1.tags = [ - `${INTERNAL_IDENTIFIER}_some_value`, - `${INTERNAL_RULE_ID_KEY}_some_value`, - 'tag 1', - 'tag 2', - 'tag 3', - 'tag 4', - 'tag 5', - ]; - - const result2 = getRuleMock(getQueryRuleParams()); - result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - result2.params.ruleId = 'rule-2'; - result2.tags = [ - `${INTERNAL_IDENTIFIER}_some_value`, - `${INTERNAL_RULE_ID_KEY}_some_value`, - 'tag 1', - 'tag 2', - 'tag 3', - 'tag 4', - ]; - - const rulesClient = rulesClientMock.create(); - rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] })); - - const tags = await readTags({ rulesClient }); - expect(tags).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4', 'tag 5']); - }); }); describe('convertTagsToSet', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.ts index 8f3e1468278cf..4ab3ccc831af1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.ts @@ -7,7 +7,6 @@ import { has } from 'lodash/fp'; import { RulesClient } from '@kbn/alerting-plugin/server'; -import { INTERNAL_IDENTIFIER } from '../../../../common/constants'; import { findRules } from '../rules/find_rules'; export interface TagType { @@ -20,11 +19,11 @@ export const isTags = (obj: object): obj is TagType => { }; export const convertToTags = (tagObjects: object[]): string[] => { - const tags = tagObjects.reduce((accum, tagObj) => { + const tags = tagObjects.reduce((acc, tagObj) => { if (isTags(tagObj)) { - return [...accum, ...tagObj.tags]; + return [...acc, ...tagObj.tags]; } else { - return accum; + return acc; } }, []); return tags; @@ -41,15 +40,6 @@ export const convertTagsToSet = (tagObjects: object[]): Set => { // Ref: https://www.elastic.co/guide/en/kibana/master/saved-objects-api.html export const readTags = async ({ rulesClient, -}: { - rulesClient: RulesClient; -}): Promise => { - const tags = await readRawTags({ rulesClient }); - return tags.filter((tag) => !tag.startsWith(INTERNAL_IDENTIFIER)); -}; - -export const readRawTags = async ({ - rulesClient, }: { rulesClient: RulesClient; perPage?: number; diff --git a/x-pack/plugins/security_solution/server/usage/detections/get_metrics.test.ts b/x-pack/plugins/security_solution/server/usage/detections/get_metrics.test.ts index 4e782f914df70..9c40d662544c1 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/get_metrics.test.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/get_metrics.test.ts @@ -131,7 +131,7 @@ describe('Detections Usage and Metrics', () => { esClient.search.mockResponseOnce(getEventLogElasticRules()); esClient.search.mockResponseOnce(getElasticLogCustomRules()); esClient.search.mockResponseOnce(getMockRuleAlertsResponse(800)); - savedObjectsClient.find.mockResolvedValueOnce(getMockRuleSearchResponse('not_immutable')); + savedObjectsClient.find.mockResolvedValueOnce(getMockRuleSearchResponse(false)); savedObjectsClient.find.mockResolvedValueOnce(getMockAlertCaseCommentsResponse()); // Get empty saved object for legacy notification system. savedObjectsClient.find.mockResolvedValueOnce(getEmptySavedObjectResponse()); diff --git a/x-pack/plugins/security_solution/server/usage/detections/ml_jobs/get_metrics.mocks.ts b/x-pack/plugins/security_solution/server/usage/detections/ml_jobs/get_metrics.mocks.ts index 0863ca6cef411..4ad8f2213a682 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/ml_jobs/get_metrics.mocks.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/ml_jobs/get_metrics.mocks.ts @@ -291,7 +291,7 @@ export const getMockMlDatafeedStatsResponse = () => ({ }); export const getMockRuleSearchResponse = ( - immutableTag: string = '__internal_immutable:true' + immutable: boolean = true ): SavedObjectsFindResponse => ({ page: 1, @@ -304,16 +304,7 @@ export const getMockRuleSearchResponse = ( namespaces: ['default'], attributes: { name: 'Azure Diagnostic Settings Deletion', - tags: [ - 'Elastic', - 'Cloud', - 'Azure', - 'Continuous Monitoring', - 'SecOps', - 'Monitoring', - '__internal_rule_id:5370d4cd-2bb3-4d71-abf5-1e1d0ff5a2de', - `${immutableTag}`, - ], + tags: ['Elastic', 'Cloud', 'Azure', 'Continuous Monitoring', 'SecOps', 'Monitoring'], alertTypeId: 'siem.queryRule', consumer: 'siem', params: { @@ -326,7 +317,7 @@ export const getMockRuleSearchResponse = ( 'Deletion of diagnostic settings may be done by a system or network administrator. Verify whether the username, hostname, and/or resource name should be making changes in your environment. Diagnostic settings deletion from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule.', ], from: 'now-25m', - immutable: true, + immutable, query: 'event.dataset:azure.activitylogs and azure.activitylogs.operation_name:"MICROSOFT.INSIGHTS/DIAGNOSTICSETTINGS/DELETE" and event.outcome:(Success or success)', language: 'kuery', diff --git a/x-pack/plugins/security_solution/server/usage/detections/rules/transform_utils/get_rule_object_correlations.ts b/x-pack/plugins/security_solution/server/usage/detections/rules/transform_utils/get_rule_object_correlations.ts index 169cb502521df..5387c63aace3e 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/rules/transform_utils/get_rule_object_correlations.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/rules/transform_utils/get_rule_object_correlations.ts @@ -9,8 +9,6 @@ import type { SavedObjectsFindResult } from '@kbn/core/server'; import type { RuleMetric } from '../types'; import type { RuleSearchResult } from '../../../types'; -import { isElasticRule } from '../../../queries/utils/is_elastic_rule'; - export interface RuleObjectCorrelationsOptions { ruleResults: Array>; legacyNotificationRuleIds: Map< @@ -32,7 +30,6 @@ export const getRuleObjectCorrelations = ({ return ruleResults.map((result) => { const ruleId = result.id; const { attributes } = result; - const isElastic = isElasticRule(attributes.tags); // Even if the legacy notification is set to "no_actions" we still count the rule as having a legacy notification that is not migrated yet. const hasLegacyNotification = legacyNotificationRuleIds.get(ruleId) != null; @@ -50,7 +47,8 @@ export const getRuleObjectCorrelations = ({ rule_type: attributes.params.type, rule_version: attributes.params.version, enabled: attributes.enabled, - elastic_rule: isElastic, + // if rule immutable, it's Elastic/prebuilt + elastic_rule: attributes.params.immutable, created_on: attributes.createdAt, updated_on: attributes.updatedAt, alert_count_daily: alertsCounts.get(ruleId) || 0, diff --git a/x-pack/plugins/security_solution/server/usage/queries/utils/is_elastic_rule.ts b/x-pack/plugins/security_solution/server/usage/queries/utils/is_elastic_rule.ts deleted file mode 100644 index f08959702b290..0000000000000 --- a/x-pack/plugins/security_solution/server/usage/queries/utils/is_elastic_rule.ts +++ /dev/null @@ -1,11 +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 { INTERNAL_IMMUTABLE_KEY } from '../../../../common/constants'; - -export const isElasticRule = (tags: string[] = []) => - tags.includes(`${INTERNAL_IMMUTABLE_KEY}:true`); 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 33cb64dcdbed1..4622e84081506 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 @@ -398,14 +398,9 @@ export default function createGetTests({ getService }: FtrProviderContext) { // Only the rule that was enabled should be tagged expect(responseEnabledBeforeMigration.body._source?.alert?.tags).to.eql([ - '__internal_rule_id:064e3fed-6328-416b-bb85-c08265088f41', - '__internal_immutable:false', 'auto_disabled_8.0', ]); - expect(responseDisabledBeforeMigration.body._source?.alert?.tags).to.eql([ - '__internal_rule_id:364e3fed-6328-416b-bb85-c08265088f41', - '__internal_immutable:false', - ]); + expect(responseDisabledBeforeMigration.body._source?.alert?.tags).to.eql([]); }); it('8.2.0 migrates params to mapped_params for specific params properties', async () => { @@ -423,5 +418,18 @@ export default function createGetTests({ getService }: FtrProviderContext) { severity: '80-critical', }); }); + + it('8.3.0 removes internal tags in Security Solution rule', async () => { + const response = await es.get<{ alert: RawRule }>( + { + index: '.kibana', + id: 'alert:8990af61-c09a-11ec-9164-4bfd6fc32c43', + }, + { meta: true } + ); + + expect(response.statusCode).to.equal(200); + expect(response.body._source?.alert?.tags).to.eql(['test-tag-1', 'foo-tag']); + }); }); } diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/resolve_read_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/resolve_read_rules.ts index 2b4b31bb5510b..5c2bdafbd5c32 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/resolve_read_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/resolve_read_rules.ts @@ -65,10 +65,7 @@ export default ({ getService }: FtrProviderContext) => { body: { alert: { name: 'test 7.14', - tags: [ - '__internal_rule_id:82747bb8-bae0-4b59-8119-7f65ac564e14', - '__internal_immutable:false', - ], + tags: [], alertTypeId: 'siem.queryRule', consumer: 'siem', params: { diff --git a/x-pack/test/detection_engine_api_integration/utils/downgrade_immutable_rule.ts b/x-pack/test/detection_engine_api_integration/utils/downgrade_immutable_rule.ts index 36ebfaee231d9..57f20a1c0e645 100644 --- a/x-pack/test/detection_engine_api_integration/utils/downgrade_immutable_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/downgrade_immutable_rule.ts @@ -7,7 +7,6 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type { Client } from '@elastic/elasticsearch'; -import { INTERNAL_RULE_ID_KEY } from '@kbn/security-solution-plugin/common/constants'; import { countDownES } from './count_down_es'; export const downgradeImmutableRule = async ( @@ -29,7 +28,7 @@ export const downgradeImmutableRule = async ( }, query: { term: { - 'alert.tags': `${INTERNAL_RULE_ID_KEY}:${ruleId}`, + 'alert.params.ruleId': ruleId, }, }, }, diff --git a/x-pack/test/detection_engine_api_integration/utils/find_immutable_rule_by_id.ts b/x-pack/test/detection_engine_api_integration/utils/find_immutable_rule_by_id.ts index 2d77cffcdfe41..0e6fe73685c48 100644 --- a/x-pack/test/detection_engine_api_integration/utils/find_immutable_rule_by_id.ts +++ b/x-pack/test/detection_engine_api_integration/utils/find_immutable_rule_by_id.ts @@ -9,11 +9,7 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type SuperTest from 'supertest'; import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; -import { - DETECTION_ENGINE_RULES_URL, - INTERNAL_IMMUTABLE_KEY, - INTERNAL_RULE_ID_KEY, -} from '@kbn/security-solution-plugin/common/constants'; +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; /** * Helper to cut down on the noise in some of the tests. This @@ -32,7 +28,7 @@ export const findImmutableRuleById = async ( }> => { const response = await supertest .get( - `${DETECTION_ENGINE_RULES_URL}/_find?filter=alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:true" AND alert.attributes.tags: "${INTERNAL_RULE_ID_KEY}:${ruleId}"` + `${DETECTION_ENGINE_RULES_URL}/_find?filter=alert.attributes.params.immutable: true AND alert.attributes.params.ruleId: "${ruleId}"` ) .set('kbn-xsrf', 'true') .send(); diff --git a/x-pack/test/functional/es_archives/alerts/data.json b/x-pack/test/functional/es_archives/alerts/data.json index 39ce6248c7ebb..1c096d9df9930 100644 --- a/x-pack/test/functional/es_archives/alerts/data.json +++ b/x-pack/test/functional/es_archives/alerts/data.json @@ -671,10 +671,7 @@ "source":{ "alert":{ "name":"enabled 7.16.1 query rule", - "tags":[ - "__internal_rule_id:064e3fed-6328-416b-bb85-c08265088f41", - "__internal_immutable:false" - ], + "tags":[], "alertTypeId":"siem.signals", "consumer":"siem", "params":{ @@ -767,10 +764,7 @@ "source":{ "alert":{ "name":"disabled 7.16.1 query rule", - "tags":[ - "__internal_rule_id:364e3fed-6328-416b-bb85-c08265088f41", - "__internal_immutable:false" - ], + "tags":[], "alertTypeId":"siem.signals", "consumer":"siem", "params":{ @@ -895,4 +889,104 @@ ] } } +} + +{ + "type":"doc", + "value":{ + "id":"alert:8990af61-c09a-11ec-9164-4bfd6fc32c43", + "index":".kibana_1", + "source":{ + "alert":{ + "name":"Test remove internal tags", + "alertTypeId":"siem.queryRule", + "consumer":"siem", + "params":{ + "immutable":true, + "ruleId":"bf9638eb-7d3c-4f40-83d7-8c40a7c80f2e", + "author":[ + + ], + "description":"remove interns tags mock rule", + "falsePositives":[ + + ], + "from":"now-36000060s", + "license":"", + "outputIndex":".siem-signals-default", + "meta":{ + "from":"10000h" + }, + "maxSignals":100, + "riskScore":21, + "riskScoreMapping":[ + + ], + "severity":"low", + "severityMapping":[ + + ], + "threat":[ + + ], + "to":"now", + "references":[ + + ], + "version":4, + "exceptionsList":[ + ], + "type":"query", + "language":"kuery", + "index":[ + "apm-*-transaction*", + "traces-apm*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*", + "test-index-3" + ], + "query":"*:*", + "filters":[ + + ] + }, + "schedule":{ + "interval":"1m" + }, + "enabled":true, + "actions":[ + + ], + "throttle":null, + "apiKeyOwner":null, + "apiKey":null, + "createdBy":"elastic", + "updatedBy":"elastic", + "createdAt":"2021-07-27T20:42:55.896Z", + "muteAll":false, + "mutedInstanceIds":[ + + ], + "scheduledTaskId":null, + "tags":[ + "__internal_rule_id:364e3fed-6328-416b-bb85-c08265088f41", + "__internal_immutable:false", + "test-tag-1", + "foo-tag" + ] + }, + "type":"alert", + "migrationVersion":{ + "alert":"8.2.0" + }, + "updated_at":"2021-08-13T23:00:11.985Z", + "references":[ + + ] + } + } } \ No newline at end of file From caedd861b0958f74f954a9e188db8c3884377a29 Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Tue, 26 Apr 2022 12:49:07 -0400 Subject: [PATCH 22/27] Fix open API docs for excludeInstallStatus query param (#130986) --- .../plugins/fleet/common/openapi/bundled.json | 559 ++++++++++++++---- .../plugins/fleet/common/openapi/bundled.yaml | 9 +- .../common/openapi/paths/epm@packages.yaml | 8 +- 3 files changed, 459 insertions(+), 117 deletions(-) diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 77dd66a5c20d1..b3c37f5e567c3 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -115,7 +115,10 @@ "type": "string" } }, - "required": ["name", "version"] + "required": [ + "name", + "version" + ] } } } @@ -292,7 +295,17 @@ }, "operationId": "list-all-packages" }, - "parameters": [] + "parameters": [ + { + "in": "query", + "name": "excludeInstallStatus", + "schema": { + "type": "boolean", + "default": false + }, + "description": "Whether to exclude the install status of each package. Enabling this option will opt in to caching for the response via `cache-control` headers. If you don't need up-to-date installation info for a package, and are querying for a list of available packages, providing this flag can improve performance substantially." + } + ] }, "/epm/packages/_bulk": { "post": { @@ -337,13 +350,21 @@ "properties": { "status": { "type": "string", - "enum": ["installed", "installing", "install_failed", "not_installed"] + "enum": [ + "installed", + "installing", + "install_failed", + "not_installed" + ] }, "savedObject": { "type": "string" } }, - "required": ["status", "savedObject"] + "required": [ + "status", + "savedObject" + ] } ] } @@ -399,11 +420,16 @@ ] } }, - "required": ["id", "type"] + "required": [ + "id", + "type" + ] } } }, - "required": ["response"] + "required": [ + "response" + ] } } } @@ -462,11 +488,16 @@ ] } }, - "required": ["id", "type"] + "required": [ + "id", + "type" + ] } } }, - "required": ["response"] + "required": [ + "response" + ] } } } @@ -518,13 +549,21 @@ "properties": { "status": { "type": "string", - "enum": ["installed", "installing", "install_failed", "not_installed"] + "enum": [ + "installed", + "installing", + "install_failed", + "not_installed" + ] }, "savedObject": { "type": "string" } }, - "required": ["status", "savedObject"] + "required": [ + "status", + "savedObject" + ] } ] } @@ -587,7 +626,10 @@ ] } }, - "required": ["id", "type"] + "required": [ + "id", + "type" + ] } }, "_meta": { @@ -595,12 +637,18 @@ "properties": { "install_source": { "type": "string", - "enum": ["registry", "upload", "bundled"] + "enum": [ + "registry", + "upload", + "bundled" + ] } } } }, - "required": ["items"] + "required": [ + "items" + ] } } } @@ -661,11 +709,16 @@ ] } }, - "required": ["id", "type"] + "required": [ + "id", + "type" + ] } } }, - "required": ["items"] + "required": [ + "items" + ] } } } @@ -736,11 +789,16 @@ ] } }, - "required": ["id", "type"] + "required": [ + "id", + "type" + ] } } }, - "required": ["items"] + "required": [ + "items" + ] } } } @@ -840,7 +898,9 @@ "$ref": "#/components/schemas/package_usage_stats" } }, - "required": ["response"] + "required": [ + "response" + ] } } } @@ -915,7 +975,10 @@ "type": "string" } }, - "required": ["admin_username", "admin_password"] + "required": [ + "admin_username", + "admin_password" + ] } } } @@ -1156,7 +1219,9 @@ "type": "string" } }, - "required": ["success"] + "required": [ + "success" + ] } } } @@ -1217,7 +1282,9 @@ "$ref": "#/components/schemas/agent" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -1240,7 +1307,9 @@ "$ref": "#/components/schemas/agent" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -1266,10 +1335,14 @@ "properties": { "action": { "type": "string", - "enum": ["deleted"] + "enum": [ + "deleted" + ] } }, - "required": ["action"] + "required": [ + "action" + ] } } } @@ -1389,7 +1462,9 @@ "type": "string" } }, - "required": ["policy_id"] + "required": [ + "policy_id" + ] } } } @@ -1436,7 +1511,9 @@ }, "statusCode": { "type": "number", - "enum": [400] + "enum": [ + 400 + ] } } } @@ -1544,7 +1621,9 @@ "type": "string" } }, - "required": ["success"] + "required": [ + "success" + ] } } } @@ -1580,7 +1659,10 @@ ] } }, - "required": ["policy_id", "agents"] + "required": [ + "policy_id", + "agents" + ] } } } @@ -1608,7 +1690,9 @@ "type": "string" } }, - "required": ["success"] + "required": [ + "success" + ] } } } @@ -1647,7 +1731,9 @@ ] } }, - "required": ["agents"] + "required": [ + "agents" + ] } } } @@ -1682,7 +1768,12 @@ "type": "number" } }, - "required": ["items", "total", "page", "perPage"] + "required": [ + "items", + "total", + "page", + "perPage" + ] } } } @@ -1766,7 +1857,9 @@ "$ref": "#/components/schemas/agent_policy" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -1791,7 +1884,9 @@ "$ref": "#/components/schemas/agent_policy" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -1845,7 +1940,9 @@ "$ref": "#/components/schemas/agent_policy" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -1864,7 +1961,9 @@ "type": "string" } }, - "required": ["name"] + "required": [ + "name" + ] } } }, @@ -1891,7 +1990,10 @@ "type": "boolean" } }, - "required": ["id", "success"] + "required": [ + "id", + "success" + ] } } } @@ -1907,7 +2009,9 @@ "type": "string" } }, - "required": ["agentPolicyId"] + "required": [ + "agentPolicyId" + ] } } } @@ -1983,7 +2087,12 @@ "type": "number" } }, - "required": ["items", "page", "perPage", "total"] + "required": [ + "items", + "page", + "perPage", + "total" + ] } } } @@ -2009,7 +2118,9 @@ }, "action": { "type": "string", - "enum": ["created"] + "enum": [ + "created" + ] } } } @@ -2052,7 +2163,9 @@ "$ref": "#/components/schemas/enrollment_api_key" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -2074,10 +2187,14 @@ "properties": { "action": { "type": "string", - "enum": ["deleted"] + "enum": [ + "deleted" + ] } }, - "required": ["action"] + "required": [ + "action" + ] } } } @@ -2127,7 +2244,12 @@ "type": "number" } }, - "required": ["items", "page", "perPage", "total"] + "required": [ + "items", + "page", + "perPage", + "total" + ] } } } @@ -2152,7 +2274,9 @@ }, "action": { "type": "string", - "enum": ["created"] + "enum": [ + "created" + ] } } } @@ -2194,7 +2318,9 @@ "$ref": "#/components/schemas/enrollment_api_key" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -2215,10 +2341,14 @@ "properties": { "action": { "type": "string", - "enum": ["deleted"] + "enum": [ + "deleted" + ] } }, - "required": ["action"] + "required": [ + "action" + ] } } } @@ -2260,7 +2390,9 @@ "type": "number" } }, - "required": ["items"] + "required": [ + "items" + ] } } } @@ -2286,7 +2418,9 @@ "$ref": "#/components/schemas/package_policy" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -2348,7 +2482,9 @@ "type": "boolean" } }, - "required": ["packagePolicyIds"] + "required": [ + "packagePolicyIds" + ] } } } @@ -2373,7 +2509,10 @@ "type": "boolean" } }, - "required": ["id", "success"] + "required": [ + "id", + "success" + ] } } } @@ -2404,7 +2543,9 @@ } } }, - "required": ["packagePolicyIds"] + "required": [ + "packagePolicyIds" + ] } } } @@ -2429,7 +2570,10 @@ "type": "boolean" } }, - "required": ["id", "success"] + "required": [ + "id", + "success" + ] } } } @@ -2458,7 +2602,9 @@ "type": "string" } }, - "required": ["packagePolicyIds"] + "required": [ + "packagePolicyIds" + ] } } } @@ -2483,7 +2629,9 @@ "$ref": "#/components/schemas/upgrade_agent_diff" } }, - "required": ["hasErrors"] + "required": [ + "hasErrors" + ] } } } @@ -2508,7 +2656,9 @@ "$ref": "#/components/schemas/package_policy" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -2553,7 +2703,10 @@ "type": "boolean" } }, - "required": ["item", "sucess"] + "required": [ + "item", + "sucess" + ] } } } @@ -2636,7 +2789,9 @@ }, "type": { "type": "string", - "enum": ["elasticsearch"] + "enum": [ + "elasticsearch" + ] }, "is_default": { "type": "boolean" @@ -2657,7 +2812,10 @@ "type": "string" } }, - "required": ["name", "type"] + "required": [ + "name", + "type" + ] } } } @@ -2681,7 +2839,9 @@ "$ref": "#/components/schemas/output" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -2714,7 +2874,9 @@ "type": "string" } }, - "required": ["id"] + "required": [ + "id" + ] } } } @@ -2740,7 +2902,9 @@ }, "type": { "type": "string", - "enum": ["elasticsearch"] + "enum": [ + "elasticsearch" + ] }, "is_default": { "type": "boolean" @@ -2764,7 +2928,10 @@ "type": "string" } }, - "required": ["name", "type"] + "required": [ + "name", + "type" + ] } } } @@ -2781,7 +2948,9 @@ "$ref": "#/components/schemas/output" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -2900,11 +3069,17 @@ "type": "string" } }, - "required": ["name", "message"] + "required": [ + "name", + "message" + ] } } }, - "required": ["isInitialized", "nonFatalErrors"] + "required": [ + "isInitialized", + "nonFatalErrors" + ] }, "preconfigured_agent_policies": { "title": "Preconfigured agent policies", @@ -2926,7 +3101,10 @@ "type": "array", "items": { "type": "string", - "enum": ["logs", "metrics"] + "enum": [ + "logs", + "metrics" + ] } }, "namespace": { @@ -3023,14 +3201,20 @@ } } }, - "required": ["type"] + "required": [ + "type" + ] } } } } } }, - "required": ["name", "namespace", "package_policies"] + "required": [ + "name", + "namespace", + "package_policies" + ] }, "settings": { "title": "Settings", @@ -3052,7 +3236,10 @@ } } }, - "required": ["fleet_server_hosts", "id"] + "required": [ + "fleet_server_hosts", + "id" + ] }, "fleet_settings_response": { "title": "Fleet settings response", @@ -3062,7 +3249,9 @@ "$ref": "#/components/schemas/settings" } }, - "required": ["item"] + "required": [ + "item" + ] }, "get_categories_response": { "title": "Get categories response", @@ -3084,7 +3273,11 @@ "type": "number" } }, - "required": ["id", "title", "count"] + "required": [ + "id", + "title", + "count" + ] } }, "items": { @@ -3102,11 +3295,17 @@ "type": "number" } }, - "required": ["id", "title", "count"] + "required": [ + "id", + "title", + "count" + ] } } }, - "required": ["items"] + "required": [ + "items" + ] }, "search_result": { "title": "Search result", @@ -3173,7 +3372,9 @@ } } }, - "required": ["items"] + "required": [ + "items" + ] }, "bulk_install_packages_response": { "title": "Bulk install packages response", @@ -3209,7 +3410,9 @@ } } }, - "required": ["items"] + "required": [ + "items" + ] }, "package_info": { "title": "Package information", @@ -3289,7 +3492,10 @@ "type": "string" } }, - "required": ["src", "path"] + "required": [ + "src", + "path" + ] } }, "icons": { @@ -3339,7 +3545,10 @@ "type": "string" } }, - "required": ["name", "default"] + "required": [ + "name", + "default" + ] } }, "type": { @@ -3349,7 +3558,14 @@ "type": "string" } }, - "required": ["title", "name", "release", "ingeset_pipeline", "type", "package"] + "required": [ + "title", + "name", + "release", + "ingeset_pipeline", + "type", + "package" + ] } }, "download": { @@ -3411,7 +3627,9 @@ "type": "integer" } }, - "required": ["agent_policy_count"] + "required": [ + "agent_policy_count" + ] }, "fleet_status_response": { "title": "Fleet status response", @@ -3424,23 +3642,38 @@ "type": "array", "items": { "type": "string", - "enum": ["tls_required", "api_keys", "fleet_admin_user", "fleet_server"] + "enum": [ + "tls_required", + "api_keys", + "fleet_admin_user", + "fleet_server" + ] } }, "missing_optional_features": { "type": "array", "items": { "type": "string", - "enum": ["encrypted_saved_object_encryption_key_required"] + "enum": [ + "encrypted_saved_object_encryption_key_required" + ] } } }, - "required": ["isReady", "missing_requirements", "missing_optional_features"] + "required": [ + "isReady", + "missing_requirements", + "missing_optional_features" + ] }, "agent_type": { "type": "string", "title": "Agent type", - "enum": ["PERMANENT", "EPHEMERAL", "TEMPORARY"] + "enum": [ + "PERMANENT", + "EPHEMERAL", + "TEMPORARY" + ] }, "agent_metadata": { "title": "Agent metadata", @@ -3449,7 +3682,13 @@ "agent_status": { "type": "string", "title": "Agent status", - "enum": ["offline", "error", "online", "inactive", "warning"] + "enum": [ + "offline", + "error", + "online", + "inactive", + "warning" + ] }, "agent": { "title": "Agent", @@ -3504,7 +3743,13 @@ "type": "string" } }, - "required": ["type", "active", "enrolled_at", "id", "status"] + "required": [ + "type", + "active", + "enrolled_at", + "id", + "status" + ] }, "get_agents_response": { "title": "Get Agent response", @@ -3533,7 +3778,12 @@ "type": "number" } }, - "required": ["items", "total", "page", "perPage"] + "required": [ + "items", + "total", + "page", + "perPage" + ] }, "bulk_upgrade_agents": { "title": "Bulk upgrade agents", @@ -3559,7 +3809,10 @@ ] } }, - "required": ["agents", "version"] + "required": [ + "agents", + "version" + ] }, "upgrade_agent": { "title": "Upgrade agent", @@ -3571,7 +3824,9 @@ "type": "string" } }, - "required": ["version"] + "required": [ + "version" + ] }, { "type": "object", @@ -3583,7 +3838,9 @@ "type": "string" } }, - "required": ["version"] + "required": [ + "version" + ] } ] }, @@ -3600,7 +3857,12 @@ }, "type": { "type": "string", - "enum": ["POLICY_CHANGE", "UNENROLL", "UPGRADE", "POLICY_REASSIGN"] + "enum": [ + "POLICY_CHANGE", + "UNENROLL", + "UPGRADE", + "POLICY_REASSIGN" + ] } } }, @@ -3614,7 +3876,12 @@ "properties": { "log_level": { "type": "string", - "enum": ["debug", "info", "warning", "error"] + "enum": [ + "debug", + "info", + "warning", + "error" + ] } } } @@ -3642,7 +3909,10 @@ "type": "array", "items": { "type": "string", - "enum": ["metrics", "logs"] + "enum": [ + "metrics", + "logs" + ] } }, "data_output_id": { @@ -3654,7 +3924,10 @@ "nullable": true } }, - "required": ["name", "namespace"] + "required": [ + "name", + "namespace" + ] }, "new_package_policy": { "title": "New package policy", @@ -3677,7 +3950,10 @@ "type": "string" } }, - "required": ["name", "version"] + "required": [ + "name", + "version" + ] }, "namespace": { "type": "string" @@ -3713,7 +3989,10 @@ "type": "object" } }, - "required": ["type", "enabled"] + "required": [ + "type", + "enabled" + ] } }, "policy_id": { @@ -3726,7 +4005,10 @@ "type": "string" } }, - "required": ["inputs", "name"] + "required": [ + "inputs", + "name" + ] }, "package_policy": { "title": "Package policy", @@ -3745,7 +4027,10 @@ "items": {} } }, - "required": ["id", "revision"] + "required": [ + "id", + "revision" + ] }, { "$ref": "#/components/schemas/new_package_policy" @@ -3765,7 +4050,10 @@ }, "status": { "type": "string", - "enum": ["active", "inactive"] + "enum": [ + "active", + "inactive" + ] }, "packagePolicies": { "oneOf": [ @@ -3803,7 +4091,10 @@ "type": "number" } }, - "required": ["id", "status"] + "required": [ + "id", + "status" + ] } ] }, @@ -3880,7 +4171,13 @@ "type": "string" } }, - "required": ["id", "api_key_id", "api_key", "active", "created_at"] + "required": [ + "id", + "api_key_id", + "api_key", + "active", + "created_at" + ] }, "upgrade_diff": { "title": "Package policy Upgrade dryrun", @@ -3946,10 +4243,16 @@ "type": "string" } }, - "required": ["dataset", "type"] + "required": [ + "dataset", + "type" + ] } }, - "required": ["id", "data_stream"] + "required": [ + "id", + "data_stream" + ] } ] }, @@ -3979,7 +4282,9 @@ "type": "string" } }, - "required": ["namespace"] + "required": [ + "namespace" + ] }, "use_output": { "type": "string" @@ -3998,7 +4303,10 @@ "type": "string" } }, - "required": ["name", "version"] + "required": [ + "name", + "version" + ] } } }, @@ -4006,7 +4314,14 @@ "$ref": "#/components/schemas/full_agent_policy_input_stream" } }, - "required": ["id", "name", "revision", "type", "data_stream", "use_output"] + "required": [ + "id", + "name", + "revision", + "type", + "data_stream", + "use_output" + ] } ] }, @@ -4044,7 +4359,11 @@ "type": "string" } }, - "required": ["name", "title", "version"] + "required": [ + "name", + "title", + "version" + ] }, "namespace": { "type": "string" @@ -4080,7 +4399,11 @@ "type": "object" } }, - "required": ["type", "enabled", "streams"] + "required": [ + "type", + "enabled", + "streams" + ] } }, "policy_id": { @@ -4096,7 +4419,12 @@ "type": "boolean" } }, - "required": ["name", "namespace", "policy_id", "enabled"] + "required": [ + "name", + "namespace", + "policy_id", + "enabled" + ] }, "output": { "title": "Output", @@ -4116,7 +4444,9 @@ }, "type": { "type": "string", - "enum": ["elasticsearch"] + "enum": [ + "elasticsearch" + ] }, "hosts": { "type": "array", @@ -4154,7 +4484,12 @@ } } }, - "required": ["id", "is_default", "name", "type"] + "required": [ + "id", + "is_default", + "name", + "type" + ] } } }, @@ -4163,4 +4498,4 @@ "basicAuth": [] } ] -} +} \ No newline at end of file diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 011a242df7fdc..36175ffc59a88 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -186,13 +186,16 @@ paths: operationId: list-all-packages parameters: - in: query - name: includeInstallStatus + name: excludeInstallStatus schema: type: boolean default: false description: >- - Whether to include the install status of each package. Defaults to - false to allow for caching of package requests. + Whether to exclude the install status of each package. Enabling this + option will opt in to caching for the response via `cache-control` + headers. If you don't need up-to-date installation info for a package, + and are querying for a list of available packages, providing this flag + can improve performance substantially. /epm/packages/_bulk: post: summary: Packages - Bulk install diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml index 295ab298745fe..9c29b9d18357c 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml @@ -11,8 +11,12 @@ get: operationId: list-all-packages parameters: - in: query - name: includeInstallStatus + name: excludeInstallStatus schema: type: boolean default: false - description: Whether to include the install status of each package. Defaults to false to allow for caching of package requests. + description: >- + Whether to exclude the install status of each package. Enabling this option will opt in to + caching for the response via `cache-control` headers. If you don't need up-to-date installation + info for a package, and are querying for a list of available packages, providing this flag can + improve performance substantially. From 7ff1ffca5e8a684157bcbba59df4456a8afe190f Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Tue, 26 Apr 2022 13:07:32 -0400 Subject: [PATCH 23/27] [CI] Split OSS CI Group 11 (#130927) --- .buildkite/pipelines/es_snapshots/verify.yml | 2 +- .buildkite/pipelines/on_merge.yml | 2 +- .buildkite/pipelines/pull_request/base.yml | 2 +- .ci/ci_groups.yml | 1 + test/functional/apps/visualize/index.ts | 7 ++++++- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.buildkite/pipelines/es_snapshots/verify.yml b/.buildkite/pipelines/es_snapshots/verify.yml index 23ec38085c3d9..a4a6b1303f70d 100755 --- a/.buildkite/pipelines/es_snapshots/verify.yml +++ b/.buildkite/pipelines/es_snapshots/verify.yml @@ -42,7 +42,7 @@ steps: - command: .buildkite/scripts/steps/functional/oss_cigroup.sh label: 'OSS CI Group' - parallelism: 11 + parallelism: 12 agents: queue: ci-group-4d depends_on: build diff --git a/.buildkite/pipelines/on_merge.yml b/.buildkite/pipelines/on_merge.yml index 8702493d9f4cf..8b94f4d7c22c7 100644 --- a/.buildkite/pipelines/on_merge.yml +++ b/.buildkite/pipelines/on_merge.yml @@ -66,7 +66,7 @@ steps: - command: .buildkite/scripts/steps/functional/oss_cigroup.sh label: 'OSS CI Group' - parallelism: 11 + parallelism: 12 agents: queue: n2-4-spot depends_on: build diff --git a/.buildkite/pipelines/pull_request/base.yml b/.buildkite/pipelines/pull_request/base.yml index 658d855d86cfd..fc12a96964fb2 100644 --- a/.buildkite/pipelines/pull_request/base.yml +++ b/.buildkite/pipelines/pull_request/base.yml @@ -32,7 +32,7 @@ steps: - command: .buildkite/scripts/steps/functional/oss_cigroup.sh label: 'OSS CI Group' - parallelism: 11 + parallelism: 12 agents: queue: n2-4-spot depends_on: build diff --git a/.ci/ci_groups.yml b/.ci/ci_groups.yml index c3786f299d4c0..508f118ce9dd7 100644 --- a/.ci/ci_groups.yml +++ b/.ci/ci_groups.yml @@ -10,6 +10,7 @@ root: - ciGroup9 - ciGroup10 - ciGroup11 + - ciGroup12 xpack: - ciGroup1 diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index 5ccb59e17880e..d68fb4b253123 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -97,8 +97,13 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { describe('visualize ciGroup11', function () { this.tags('ciGroup11'); - loadTestFile(require.resolve('./_tag_cloud')); loadTestFile(require.resolve('./_tsvb_time_series')); + }); + + describe('visualize ciGroup12', function () { + this.tags('ciGroup12'); + + loadTestFile(require.resolve('./_tag_cloud')); loadTestFile(require.resolve('./_tsvb_markdown')); loadTestFile(require.resolve('./_tsvb_table')); loadTestFile(require.resolve('./_vega_chart')); From 181626b9c665327226cbb183b5909f2c26270e1d Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Tue, 26 Apr 2022 13:32:08 -0400 Subject: [PATCH 24/27] [CI] Split default ciGroup24 up a bit (#130997) --- .../tests/exception_operators_data_types/index.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/index.ts index 85cc484146032..ded3df8b6716c 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/index.ts @@ -22,13 +22,23 @@ export default ({ loadTestFile }: FtrProviderContext): void => { describe('', function () { this.tags('ciGroup24'); - loadTestFile(require.resolve('./ip')); - loadTestFile(require.resolve('./ip_array')); loadTestFile(require.resolve('./keyword')); loadTestFile(require.resolve('./keyword_array')); loadTestFile(require.resolve('./long')); loadTestFile(require.resolve('./text')); loadTestFile(require.resolve('./text_array')); }); + + describe('', function () { + this.tags('ciGroup16'); + + loadTestFile(require.resolve('./ip')); + }); + + describe('', function () { + this.tags('ciGroup21'); + + loadTestFile(require.resolve('./ip_array')); + }); }); }; From 26217f3a7a0c266bf9ddf079633a0fcc10877e27 Mon Sep 17 00:00:00 2001 From: liza-mae Date: Tue, 26 Apr 2022 11:52:36 -0600 Subject: [PATCH 25/27] Upgrade patch reporting issue, add more tests, reorganize files (#130839) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- test/functional/page_objects/home_page.ts | 15 +++++ .../functional/page_objects/graph_page.ts | 5 ++ .../upgrade/apps/canvas/canvas_smoke_tests.ts | 20 +++---- .../apps/canvas/{index.js => index.ts} | 6 +- .../apps/dashboard/dashboard_smoke_tests.ts | 4 +- .../apps/dashboard/{index.js => index.ts} | 6 +- .../apps/discover/discover_smoke_tests.ts | 57 +++++++++++++++++++ x-pack/test/upgrade/apps/discover/index.ts | 14 +++++ .../upgrade/apps/graph/graph_smoke_tests.ts | 45 +++++++++++++++ x-pack/test/upgrade/apps/graph/index.ts | 14 +++++ x-pack/test/upgrade/apps/logs/index.ts | 14 +++++ .../upgrade/apps/logs/logs_smoke_tests.ts | 39 +++++++++++++ .../upgrade/apps/maps/{index.js => index.ts} | 6 +- .../upgrade/apps/maps/maps_smoke_tests.ts | 8 +-- .../apps/reporting/{index.js => index.ts} | 6 +- .../apps/reporting/reporting_smoke_tests.ts | 23 +++++--- x-pack/test/upgrade/config.ts | 9 ++- x-pack/test/upgrade/ftr_provider_context.d.ts | 2 +- x-pack/test/upgrade/page_objects.ts | 10 ---- .../{services.ts => services/index.ts} | 4 +- .../{ => services}/maps_upgrade_services.ts | 2 +- .../reporting_upgrade_services.ts} | 8 +-- 22 files changed, 265 insertions(+), 52 deletions(-) rename x-pack/test/upgrade/apps/canvas/{index.js => index.ts} (72%) rename x-pack/test/upgrade/apps/dashboard/{index.js => index.ts} (72%) create mode 100644 x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts create mode 100644 x-pack/test/upgrade/apps/discover/index.ts create mode 100644 x-pack/test/upgrade/apps/graph/graph_smoke_tests.ts create mode 100644 x-pack/test/upgrade/apps/graph/index.ts create mode 100644 x-pack/test/upgrade/apps/logs/index.ts create mode 100644 x-pack/test/upgrade/apps/logs/logs_smoke_tests.ts rename x-pack/test/upgrade/apps/maps/{index.js => index.ts} (72%) rename x-pack/test/upgrade/apps/reporting/{index.js => index.ts} (72%) delete mode 100644 x-pack/test/upgrade/page_objects.ts rename x-pack/test/upgrade/{services.ts => services/index.ts} (73%) rename x-pack/test/upgrade/{ => services}/maps_upgrade_services.ts (97%) rename x-pack/test/upgrade/{reporting_services.ts => services/reporting_upgrade_services.ts} (95%) diff --git a/test/functional/page_objects/home_page.ts b/test/functional/page_objects/home_page.ts index 235afa27f0c82..1e3e6a9634f4c 100644 --- a/test/functional/page_objects/home_page.ts +++ b/test/functional/page_objects/home_page.ts @@ -93,6 +93,21 @@ export class HomePageObject extends FtrService { await this.find.clickByLinkText('Map'); } + async launchSampleLogs(id: string) { + await this.launchSampleDataSet(id); + await this.find.clickByLinkText('Logs'); + } + + async launchSampleGraph(id: string) { + await this.launchSampleDataSet(id); + await this.find.clickByLinkText('Graph'); + } + + async launchSampleML(id: string) { + await this.launchSampleDataSet(id); + await this.find.clickByLinkText('ML jobs'); + } + async launchSampleDataSet(id: string) { await this.addSampleDataSet(id); await this.common.closeToastIfExists(); diff --git a/x-pack/test/functional/page_objects/graph_page.ts b/x-pack/test/functional/page_objects/graph_page.ts index 1203d4077b5b8..aec58d27acbbe 100644 --- a/x-pack/test/functional/page_objects/graph_page.ts +++ b/x-pack/test/functional/page_objects/graph_page.ts @@ -283,4 +283,9 @@ export class GraphPageObject extends FtrService { const el = await this.find.byCssSelector('small.gphLinkSummary__term--2'); return await el.getVisibleText(); } + + async getAllGraphNodes() { + const el = await this.find.allByCssSelector('.gphNode'); + return el.length; + } } diff --git a/x-pack/test/upgrade/apps/canvas/canvas_smoke_tests.ts b/x-pack/test/upgrade/apps/canvas/canvas_smoke_tests.ts index b12b751e94c07..7de0e1247f01a 100644 --- a/x-pack/test/upgrade/apps/canvas/canvas_smoke_tests.ts +++ b/x-pack/test/upgrade/apps/canvas/canvas_smoke_tests.ts @@ -14,7 +14,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const browser = getService('browser'); - describe('canvas smoke tests', function describeIndexTests() { + describe('upgrade canvas smoke tests', function describeIndexTests() { const spaces = [ { space: 'default', basePath: '' }, { space: 'automation', basePath: 's/automation' }, @@ -28,17 +28,17 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ]; spaces.forEach(({ space, basePath }) => { - describe('space ' + space, () => { - beforeEach(async () => { - await PageObjects.common.navigateToActualUrl('home', '/tutorial_directory/sampleData', { - basePath, - }); - await PageObjects.header.waitUntilLoadingHasFinished(); - }); - canvasTests.forEach(({ name, numElements, page }) => { - it('renders elements on workpad ' + name + ' page ' + page, async () => { + canvasTests.forEach(({ name, numElements, page }) => { + describe('space: ' + space, () => { + before(async () => { + await PageObjects.common.navigateToActualUrl('home', '/tutorial_directory/sampleData', { + basePath, + }); + await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.home.launchSampleCanvas(name); await PageObjects.header.waitUntilLoadingHasFinished(); + }); + it('renders elements on workpad ' + name + ' page ' + page, async () => { const currentUrl = await browser.getCurrentUrl(); const [, hash] = currentUrl.split('#/'); if (hash.length === 0) { diff --git a/x-pack/test/upgrade/apps/canvas/index.js b/x-pack/test/upgrade/apps/canvas/index.ts similarity index 72% rename from x-pack/test/upgrade/apps/canvas/index.js rename to x-pack/test/upgrade/apps/canvas/index.ts index 0ecc2e98ea67a..f28bff9a1820c 100644 --- a/x-pack/test/upgrade/apps/canvas/index.js +++ b/x-pack/test/upgrade/apps/canvas/index.ts @@ -5,8 +5,10 @@ * 2.0. */ -export default ({ loadTestFile }) => { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { describe('upgrade', function () { loadTestFile(require.resolve('./canvas_smoke_tests')); }); -}; +} diff --git a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts index 2f35f0e1e12d3..b87ec4dfe4159 100644 --- a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts +++ b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts @@ -18,7 +18,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const browser = getService('browser'); - describe('dashboard smoke tests', function describeIndexTests() { + describe('upgrade dashboard smoke tests', function describeIndexTests() { const spaces = [ { space: 'default', basePath: '' }, { space: 'automation', basePath: 's/automation' }, @@ -31,7 +31,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ]; spaces.forEach(({ space, basePath }) => { - describe('space ' + space, () => { + describe('space: ' + space, () => { beforeEach(async () => { await PageObjects.common.navigateToActualUrl('home', '/tutorial_directory/sampleData', { basePath, diff --git a/x-pack/test/upgrade/apps/dashboard/index.js b/x-pack/test/upgrade/apps/dashboard/index.ts similarity index 72% rename from x-pack/test/upgrade/apps/dashboard/index.js rename to x-pack/test/upgrade/apps/dashboard/index.ts index b12d1270f79f9..1f380a18dfb70 100644 --- a/x-pack/test/upgrade/apps/dashboard/index.js +++ b/x-pack/test/upgrade/apps/dashboard/index.ts @@ -5,8 +5,10 @@ * 2.0. */ -export default ({ loadTestFile }) => { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { describe('upgrade', function () { loadTestFile(require.resolve('./dashboard_smoke_tests')); }); -}; +} diff --git a/x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts b/x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts new file mode 100644 index 0000000000000..150458919d41d --- /dev/null +++ b/x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts @@ -0,0 +1,57 @@ +/* + * 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 { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'header', 'home', 'discover', 'timePicker']); + + describe('upgrade discover smoke tests', function describeIndexTests() { + const spaces = [ + { space: 'default', basePath: '' }, + { space: 'automation', basePath: 's/automation' }, + ]; + + const discoverTests = [ + { name: 'kibana_sample_data_flights', timefield: true, hits: '' }, + { name: 'kibana_sample_data_logs', timefield: true, hits: '' }, + { name: 'kibana_sample_data_ecommerce', timefield: true, hits: '' }, + ]; + + spaces.forEach(({ space, basePath }) => { + discoverTests.forEach(({ name, timefield, hits }) => { + describe('space: ' + space + ', name: ' + name, () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { + basePath, + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.selectIndexPattern(name); + await PageObjects.discover.waitUntilSearchingHasFinished(); + if (timefield) { + await PageObjects.timePicker.setCommonlyUsedTime('Last_24 hours'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + } + }); + it('shows hit count greater than zero', async () => { + const hitCount = await PageObjects.discover.getHitCount(); + if (hits === '') { + expect(hitCount).to.be.greaterThan(0); + } else { + expect(hitCount).to.be.equal(hits); + } + }); + it('shows table rows not empty', async () => { + const tableRows = await PageObjects.discover.getDocTableRows(); + expect(tableRows.length).to.be.greaterThan(0); + }); + }); + }); + }); + }); +} diff --git a/x-pack/test/upgrade/apps/discover/index.ts b/x-pack/test/upgrade/apps/discover/index.ts new file mode 100644 index 0000000000000..18fdd6992b892 --- /dev/null +++ b/x-pack/test/upgrade/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 function ({ loadTestFile }: FtrProviderContext) { + describe('upgrade', function () { + loadTestFile(require.resolve('./discover_smoke_tests')); + }); +} diff --git a/x-pack/test/upgrade/apps/graph/graph_smoke_tests.ts b/x-pack/test/upgrade/apps/graph/graph_smoke_tests.ts new file mode 100644 index 0000000000000..cca4e220c7aa5 --- /dev/null +++ b/x-pack/test/upgrade/apps/graph/graph_smoke_tests.ts @@ -0,0 +1,45 @@ +/* + * 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 { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'header', 'home', 'graph']); + + describe('upgrade graph smoke tests', function describeIndexTests() { + const spaces = [ + { space: 'default', basePath: '' }, + { space: 'automation', basePath: 's/automation' }, + ]; + + const graphTests = [ + { name: 'flights', numNodes: 91 }, + { name: 'logs', numNodes: 27 }, + { name: 'ecommerce', numNodes: 12 }, + ]; + + spaces.forEach(({ space, basePath }) => { + graphTests.forEach(({ name, numNodes }) => { + describe('space: ' + space, () => { + before(async () => { + await PageObjects.common.navigateToActualUrl('home', '/tutorial_directory/sampleData', { + basePath, + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.home.launchSampleGraph(name); + await PageObjects.header.waitUntilLoadingHasFinished(); + }); + it('renders graph for ' + name, async () => { + const elements = await PageObjects.graph.getAllGraphNodes(); + expect(elements).to.be.equal(numNodes); + }); + }); + }); + }); + }); +} diff --git a/x-pack/test/upgrade/apps/graph/index.ts b/x-pack/test/upgrade/apps/graph/index.ts new file mode 100644 index 0000000000000..649b1a740bb6c --- /dev/null +++ b/x-pack/test/upgrade/apps/graph/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 function ({ loadTestFile }: FtrProviderContext) { + describe('upgrade', function () { + loadTestFile(require.resolve('./graph_smoke_tests')); + }); +} diff --git a/x-pack/test/upgrade/apps/logs/index.ts b/x-pack/test/upgrade/apps/logs/index.ts new file mode 100644 index 0000000000000..ed4181f75d76d --- /dev/null +++ b/x-pack/test/upgrade/apps/logs/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 function ({ loadTestFile }: FtrProviderContext) { + describe('upgrade', function () { + loadTestFile(require.resolve('./logs_smoke_tests')); + }); +} diff --git a/x-pack/test/upgrade/apps/logs/logs_smoke_tests.ts b/x-pack/test/upgrade/apps/logs/logs_smoke_tests.ts new file mode 100644 index 0000000000000..8d9964f25422e --- /dev/null +++ b/x-pack/test/upgrade/apps/logs/logs_smoke_tests.ts @@ -0,0 +1,39 @@ +/* + * 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 { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'header', 'home']); + const logsUi = getService('logsUi'); + + describe('upgrade logs smoke tests', function describeIndexTests() { + const spaces = [ + { space: 'default', basePath: '' }, + { space: 'automation', basePath: 's/automation' }, + ]; + + spaces.forEach(({ space, basePath }) => { + describe('space: ' + space, () => { + before(async () => { + await PageObjects.common.navigateToActualUrl('home', '/tutorial_directory/sampleData', { + basePath, + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.home.launchSampleLogs('logs'); + await PageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('should show log streams', async () => { + const logStreamEntries = await logsUi.logStreamPage.getStreamEntries(); + expect(logStreamEntries.length).to.be.greaterThan(100); + }); + }); + }); + }); +} diff --git a/x-pack/test/upgrade/apps/maps/index.js b/x-pack/test/upgrade/apps/maps/index.ts similarity index 72% rename from x-pack/test/upgrade/apps/maps/index.js rename to x-pack/test/upgrade/apps/maps/index.ts index 57d175a62ceb3..7fac9ae891128 100644 --- a/x-pack/test/upgrade/apps/maps/index.js +++ b/x-pack/test/upgrade/apps/maps/index.ts @@ -5,8 +5,10 @@ * 2.0. */ -export default ({ loadTestFile }) => { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { describe('upgrade', function () { loadTestFile(require.resolve('./maps_smoke_tests')); }); -}; +} diff --git a/x-pack/test/upgrade/apps/maps/maps_smoke_tests.ts b/x-pack/test/upgrade/apps/maps/maps_smoke_tests.ts index 98e31129a3ccd..ecb000d691ab0 100644 --- a/x-pack/test/upgrade/apps/maps/maps_smoke_tests.ts +++ b/x-pack/test/upgrade/apps/maps/maps_smoke_tests.ts @@ -82,7 +82,7 @@ export default function ({ // Only update the baseline images from Jenkins session images after comparing them // These tests might fail locally because of scaling factors and resolution. - describe('maps smoke tests', function describeIndexTests() { + describe('upgrade maps smoke tests', function describeIndexTests() { const spaces = [ { space: 'default', basePath: '' }, { space: 'automation', basePath: 's/automation' }, @@ -101,7 +101,7 @@ export default function ({ }); spaces.forEach(({ space, basePath }) => { - describe('space ' + space + ' ecommerce', () => { + describe('space: ' + space + ', name: ecommerce', () => { before(async () => { await PageObjects.common.navigateToActualUrl('home', '/tutorial_directory/sampleData', { basePath, @@ -129,7 +129,7 @@ export default function ({ expect(percentDifference.toFixed(3)).to.be.lessThan(0.031); }); }); - describe('space ' + space + ' flights', () => { + describe('space: ' + space + ', name: flights', () => { before(async () => { await PageObjects.common.navigateToActualUrl('home', '/tutorial_directory/sampleData', { basePath, @@ -153,7 +153,7 @@ export default function ({ expect(percentDifference.toFixed(3)).to.be.lessThan(0.031); }); }); - describe('space ' + space + ' web logs', () => { + describe('space: ' + space + ', name: web logs', () => { before(async () => { await PageObjects.common.navigateToActualUrl('home', '/tutorial_directory/sampleData', { basePath, diff --git a/x-pack/test/upgrade/apps/reporting/index.js b/x-pack/test/upgrade/apps/reporting/index.ts similarity index 72% rename from x-pack/test/upgrade/apps/reporting/index.js rename to x-pack/test/upgrade/apps/reporting/index.ts index 5bd5081a3568c..c4c70660935ad 100644 --- a/x-pack/test/upgrade/apps/reporting/index.js +++ b/x-pack/test/upgrade/apps/reporting/index.ts @@ -5,8 +5,10 @@ * 2.0. */ -export default ({ loadTestFile }) => { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { describe('upgrade', function () { loadTestFile(require.resolve('./reporting_smoke_tests')); }); -}; +} diff --git a/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts b/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts index 14136b23abfd5..f7b0eef003c91 100644 --- a/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts +++ b/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { parse } from 'url'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { ReportingUsageStats } from '../../reporting_services'; +import { ReportingUsageStats } from '../../services/reporting_upgrade_services'; interface UsageStats { reporting: ReportingUsageStats; @@ -21,6 +21,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const PageObjects = getPageObjects(['common', 'header', 'home', 'dashboard', 'share']); const testSubjects = getService('testSubjects'); + const log = getService('log'); const spaces = [ { space: 'default', basePath: '' }, @@ -39,7 +40,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { { name: 'ecommerce', type: 'png', link: 'PNG Reports' }, ]; - describe('reporting smoke tests', () => { + describe('upgrade reporting smoke tests', () => { let completedReportCount: number; let usage: UsageStats; describe('initial state', () => { @@ -53,7 +54,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); spaces.forEach(({ space, basePath }) => { - describe('generate report space ' + space, () => { + describe('generate report for space ' + space, () => { beforeEach(async () => { await PageObjects.common.navigateToActualUrl('home', '/tutorial_directory/sampleData', { basePath, @@ -63,7 +64,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { completedReportCount = reportingAPI.getCompletedReportCount(usage); }); reportingTests.forEach(({ name, type, link }) => { - it('name ' + name + ' type ' + type, async () => { + it('name: ' + name + ' type: ' + type, async () => { await PageObjects.home.launchSampleDashboard(name); await PageObjects.share.openShareMenuItem(link); if (type === 'pdf_optimize') { @@ -89,9 +90,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const postUrl = await find.byXPath(`//button[descendant::*[text()='Copy POST URL']]`); await postUrl.click(); const url = await browser.getClipboardValue(); - await reportingAPI.expectAllJobsToFinishSuccessfully([ - await reportingAPI.postJob(parse(url).pathname + '?' + parse(url).query), - ]); + // Add try/catch for https://github.com/elastic/elastic-stack-testing/issues/1199 + // Waiting for job to finish sometimes gets socket hang up error, from what I + // observed during debug testing the command does complete. + // Checking expected report count will still fail if the job did not finish. + try { + await reportingAPI.expectAllJobsToFinishSuccessfully([ + await reportingAPI.postJob(parse(url).pathname + '?' + parse(url).query), + ]); + } catch (e) { + log.debug(`Error waiting for job to finish: ${e}`); + } usage = (await usageAPI.getUsageStats()) as UsageStats; reportingAPI.expectCompletedReportCount(usage, completedReportCount + 1); }); diff --git a/x-pack/test/upgrade/config.ts b/x-pack/test/upgrade/config.ts index 7722c244223cf..78d61d5239556 100644 --- a/x-pack/test/upgrade/config.ts +++ b/x-pack/test/upgrade/config.ts @@ -6,9 +6,9 @@ */ import { FtrConfigProviderContext } from '@kbn/test'; -import { pageObjects } from './page_objects'; -import { ReportingAPIProvider } from './reporting_services'; -import { MapsHelper } from './maps_upgrade_services'; +import { pageObjects } from '../functional/page_objects'; +import { ReportingAPIProvider } from './services/reporting_upgrade_services'; +import { MapsHelper } from './services/maps_upgrade_services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const apiConfig = await readConfigFile(require.resolve('../api_integration/config')); @@ -20,6 +20,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [ require.resolve('./apps/canvas'), require.resolve('./apps/dashboard'), + require.resolve('./apps/discover'), + require.resolve('./apps/graph'), + require.resolve('./apps/logs'), require.resolve('./apps/maps'), require.resolve('./apps/reporting'), ], diff --git a/x-pack/test/upgrade/ftr_provider_context.d.ts b/x-pack/test/upgrade/ftr_provider_context.d.ts index 24f5087ef7fe2..2cd67b6698a70 100644 --- a/x-pack/test/upgrade/ftr_provider_context.d.ts +++ b/x-pack/test/upgrade/ftr_provider_context.d.ts @@ -7,7 +7,7 @@ import { GenericFtrProviderContext } from '@kbn/test'; -import { pageObjects } from './page_objects'; +import { pageObjects } from '../functional/page_objects'; import { services } from './services'; export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/upgrade/page_objects.ts b/x-pack/test/upgrade/page_objects.ts deleted file mode 100644 index c8b0c9050dbb9..0000000000000 --- a/x-pack/test/upgrade/page_objects.ts +++ /dev/null @@ -1,10 +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 { pageObjects } from '../functional/page_objects'; - -export { pageObjects }; diff --git a/x-pack/test/upgrade/services.ts b/x-pack/test/upgrade/services/index.ts similarity index 73% rename from x-pack/test/upgrade/services.ts rename to x-pack/test/upgrade/services/index.ts index ca5c23ba335e3..cfd227cd86aff 100644 --- a/x-pack/test/upgrade/services.ts +++ b/x-pack/test/upgrade/services/index.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { services as functionalServices } from '../functional/services'; -import { services as reportingServices } from './reporting_services'; +import { services as functionalServices } from '../../functional/services'; +import { services as reportingServices } from './reporting_upgrade_services'; import { services as mapsUpgradeServices } from './maps_upgrade_services'; export const services = { diff --git a/x-pack/test/upgrade/maps_upgrade_services.ts b/x-pack/test/upgrade/services/maps_upgrade_services.ts similarity index 97% rename from x-pack/test/upgrade/maps_upgrade_services.ts rename to x-pack/test/upgrade/services/maps_upgrade_services.ts index b5553eeb9366d..7b80bce7682f0 100644 --- a/x-pack/test/upgrade/maps_upgrade_services.ts +++ b/x-pack/test/upgrade/services/maps_upgrade_services.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from './ftr_provider_context'; +import { FtrProviderContext } from '../ftr_provider_context'; export function MapsHelper({ getPageObjects, getService }: FtrProviderContext) { const PageObjects = getPageObjects(['maps']); diff --git a/x-pack/test/upgrade/reporting_services.ts b/x-pack/test/upgrade/services/reporting_upgrade_services.ts similarity index 95% rename from x-pack/test/upgrade/reporting_services.ts rename to x-pack/test/upgrade/services/reporting_upgrade_services.ts index 2467cee0ad822..1a1e6113c3735 100644 --- a/x-pack/test/upgrade/reporting_services.ts +++ b/x-pack/test/upgrade/services/reporting_upgrade_services.ts @@ -7,9 +7,9 @@ import expect from '@kbn/expect'; import { indexTimestamp } from '@kbn/reporting-plugin/server/lib/store/index_timestamp'; -import { services as xpackServices } from '../functional/services'; -import { services as apiIntegrationServices } from '../api_integration/services'; -import { FtrProviderContext } from './ftr_provider_context'; +import { services as xpackServices } from '../../functional/services'; +import { services as apiIntegrationServices } from '../../api_integration/services'; +import { FtrProviderContext } from '../ftr_provider_context'; interface PDFAppCounts { app: { @@ -104,7 +104,7 @@ export function ReportingAPIProvider({ getService }: FtrProviderContext) { * * @return {Promise} A function to call to clean up the index alias that was added. */ - async coerceReportsIntoExistingIndex(indexName: string) { + async coerceReportsIntoExistingIndex(indexName: string): Promise { log.debug(`ReportingAPI.coerceReportsIntoExistingIndex(${indexName})`); // Adding an index alias coerces the report to be generated on an existing index which means any new From ac8df39f079da6182f8ba7f17cfd398a90866aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Tue, 26 Apr 2022 20:27:30 +0200 Subject: [PATCH 26/27] Move `node-libs-browser` to `ui-shared-deps-npm` (#130877) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- ...libs_browser_polyfills_in_entry_bundles.ts | 53 +++++++++++++++++++ .../src/babel_runtime_helpers/index.ts | 1 + .../src/babel_runtime_helpers/parse_stats.ts | 1 + .../kbn-ui-shared-deps-npm/webpack.config.js | 4 ++ ...find_node_libs_browser_polyfills_in_use.js | 10 ++++ 5 files changed, 69 insertions(+) create mode 100644 packages/kbn-optimizer/src/babel_runtime_helpers/find_node_libs_browser_polyfills_in_entry_bundles.ts create mode 100644 scripts/find_node_libs_browser_polyfills_in_use.js diff --git a/packages/kbn-optimizer/src/babel_runtime_helpers/find_node_libs_browser_polyfills_in_entry_bundles.ts b/packages/kbn-optimizer/src/babel_runtime_helpers/find_node_libs_browser_polyfills_in_entry_bundles.ts new file mode 100644 index 0000000000000..06ad13da2b2f2 --- /dev/null +++ b/packages/kbn-optimizer/src/babel_runtime_helpers/find_node_libs_browser_polyfills_in_entry_bundles.ts @@ -0,0 +1,53 @@ +/* + * 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 Path from 'path'; + +import { run } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; + +import { OptimizerConfig } from '../optimizer'; +import { parseStats, inAnyEntryChunk } from './parse_stats'; + +export async function runFindNodeLibsBrowserPolyfillsInEntryBundlesCli() { + run(async ({ log }) => { + const config = OptimizerConfig.create({ + includeCoreBundle: true, + repoRoot: REPO_ROOT, + }); + + const paths = config.bundles.map((b) => Path.resolve(b.outputDir, 'stats.json')); + + log.info('analyzing', paths.length, 'stats files'); + log.verbose(paths); + + const imports = new Set(); + for (const path of paths) { + const stats = parseStats(path); + + for (const module of stats.modules) { + if (!inAnyEntryChunk(stats, module)) { + continue; + } + + // Relying on module name instead of actual imports because these are usual polyfills that assume the global + // Node.js environment when development (i.e.: Buffer doesn't require an import to be used). + if (module.name.includes('node-libs-browser/node_modules/')) { + imports.add(module.name); + } + } + } + + log.success('found', imports.size, 'node-libs-browser/* imports in entry bundles'); + log.write( + Array.from(imports, (i) => `'${i}',`) + .sort() + .join('\n') + ); + }); +} diff --git a/packages/kbn-optimizer/src/babel_runtime_helpers/index.ts b/packages/kbn-optimizer/src/babel_runtime_helpers/index.ts index 58a3ddf263a1d..3a7987f867bc5 100644 --- a/packages/kbn-optimizer/src/babel_runtime_helpers/index.ts +++ b/packages/kbn-optimizer/src/babel_runtime_helpers/index.ts @@ -7,3 +7,4 @@ */ export * from './find_babel_runtime_helpers_in_entry_bundles'; +export * from './find_node_libs_browser_polyfills_in_entry_bundles'; diff --git a/packages/kbn-optimizer/src/babel_runtime_helpers/parse_stats.ts b/packages/kbn-optimizer/src/babel_runtime_helpers/parse_stats.ts index fac0b099b5195..9b9ba11f90c9a 100644 --- a/packages/kbn-optimizer/src/babel_runtime_helpers/parse_stats.ts +++ b/packages/kbn-optimizer/src/babel_runtime_helpers/parse_stats.ts @@ -20,6 +20,7 @@ const partialObject =

(props: P) => { export type Module = TypeOf; const moduleSchema = partialObject({ identifier: schema.string(), + name: schema.string(), chunks: schema.arrayOf(schema.oneOf([schema.string(), schema.number()])), reasons: schema.arrayOf( partialObject({ diff --git a/packages/kbn-ui-shared-deps-npm/webpack.config.js b/packages/kbn-ui-shared-deps-npm/webpack.config.js index 0a709181b04d2..ddcb71fdb2c86 100644 --- a/packages/kbn-ui-shared-deps-npm/webpack.config.js +++ b/packages/kbn-ui-shared-deps-npm/webpack.config.js @@ -37,6 +37,10 @@ module.exports = (_, argv) => { 'regenerator-runtime/runtime', 'whatwg-fetch', 'symbol-observable', + // Parts of node-libs-browser that are used in many places across Kibana + 'buffer', + 'punycode', + 'util', /** * babel runtime helpers referenced from entry chunks diff --git a/scripts/find_node_libs_browser_polyfills_in_use.js b/scripts/find_node_libs_browser_polyfills_in_use.js new file mode 100644 index 0000000000000..4e53e5e551075 --- /dev/null +++ b/scripts/find_node_libs_browser_polyfills_in_use.js @@ -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 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. + */ + +require('../src/setup_node_env/no_transpilation'); +require('@kbn/optimizer').runFindNodeLibsBrowserPolyfillsInEntryBundlesCli(); From b655b48c16dde29e98103742642dd50292938ff4 Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Tue, 26 Apr 2022 15:54:21 -0400 Subject: [PATCH 27/27] [Fleet] Add redesigned Fleet Server flyout (#127786) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP new fleet server flyout * WIP finish up quick start tab * Refactor quick start steps into separate files: * Initial refactor of existing fleet server instructions * Move quick start form return value to explicit type * Flesh out fleet server commands * Fix translation error * Migrate on prem instructions component over to new file structure * Makes quick start tab actually create policy * Fix type errors * Fix missing hooks + update snapshots * Fix paths in mocks * Fix translations * WIP test fixes * Implement enabled/disabled state for new steps * Fix cypress tests * Force re-render to get last test passing * Fix failing tests * Fix import errors after conflicts * Fix snapshot tests * Use id instead of full policy for policy selector * Replace Fleet Server instructions w/ Advanced Tab contents * First pass at integrating add agent/fleet server flyouts * Test commit * Fix imports * Fix imports * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Enforce https-only fleet server URL's + improve errors * Fix failing tests * Fix fleet server command in quick start * Show success state in Quick start when policy exists + use first fleet server host if it exists * Set initial service token value when Fleet Server policy ID is initiall set * Generate service token instead of enrollment token * Fix fleet server flyout opening from unhealthy callout * Revert service token change + use EuiComboBox for fleet server host * Fix checks + use custom option text * Move fleet server host combobox to component * Use new combobox in advanced tab * Fix translations * Fix unused import * Don't recreate quick start policy if it already exists * Actually use quick start policy fields 🙃 * Fix policy check * Update x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/fleet_server_host_combobox.tsx Co-authored-by: Mark Hopkin * Update x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/install_fleet_server.tsx Co-authored-by: Mark Hopkin * Update x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/set_deployment_mode.tsx Co-authored-by: Mark Hopkin * Fix formatting issue * Clean up fleet server settings variable declaration per PR review Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Mark Hopkin --- .../plugins/fleet/.storybook/context/cloud.ts | 4 +- .../plugins/fleet/.storybook/context/http.ts | 56 +- .../fleet/.storybook/context/index.tsx | 2 +- .../integration/fleet_settings.spec.ts | 8 +- .../cypress/integration/fleet_startup.spec.ts | 3 + .../advanced_tab.tsx | 66 ++ .../components/fleet_server_host_combobox.tsx | 77 ++ .../components/index.ts | 7 + .../flyout.stories.tsx | 33 + .../fleet_server_instructions/hooks/index.ts | 18 + .../hooks/use_advanced_form.ts | 46 ++ .../hooks/use_fleet_server_host.ts | 103 +++ .../hooks/use_quick_start_form.ts | 140 ++++ .../hooks/use_select_fleet_server_policy.ts | 42 + .../hooks/use_service_token.ts | 37 + .../hooks/use_wait_for_fleet_server.ts | 61 ++ .../fleet_server_instructions/index.tsx | 154 ++++ .../instructions.stories.tsx | 23 + .../quick_start_tab.tsx | 41 + .../steps/add_fleet_server_host.tsx | 165 ++++ .../steps/confirm_fleet_server_connection.tsx | 66 ++ .../steps/create_service_token.tsx | 136 ++++ .../steps/get_started.tsx | 130 +++ .../fleet_server_instructions/steps/index.ts | 14 + .../steps/install_fleet_server.tsx | 97 +++ .../steps/select_agent_policy.tsx | 81 ++ .../steps/set_deployment_mode.tsx | 117 +++ .../fleet_server_instructions/utils/index.ts | 8 + .../utils}/install_command_utils.test.ts | 153 +++- .../utils/install_command_utils.ts | 119 +++ .../applications/fleet/components/index.ts | 1 + .../agent_policy_advanced_fields/index.tsx | 4 +- .../components/actions_menu.tsx | 3 +- .../sections/agents/agent_list_page/index.tsx | 13 +- .../components/enrollment_recommendation.tsx | 104 +++ .../fleet_server_on_prem_instructions.tsx | 748 ------------------ .../components/index.tsx | 2 +- .../components/install_command_utils.ts | 65 -- .../fleet_server_requirement_page.tsx | 28 +- .../fleet/sections/agents/index.tsx | 90 ++- .../use_fleet_server_host_form.test.tsx | 8 +- .../use_fleet_server_host_form.tsx | 2 +- .../agent_enrollment_flyout.test.mocks.ts | 14 +- .../agent_enrollment_flyout.test.tsx | 108 +-- .../agent_policy_select_create.tsx | 6 +- .../agent_policy_selection.test.tsx | 2 +- .../agent_policy_selection.tsx | 44 +- .../agent_enrollment_flyout/instructions.tsx | 17 +- .../steps/agent_policy_selection_step.tsx | 2 +- .../steps/compute_steps.tsx | 83 -- .../steps/install_managed_agent_step.tsx | 2 +- .../steps/install_standalone_agent_step.tsx | 3 +- .../install_section.tsx | 7 +- .../standalone/index.tsx | 2 +- .../plugins/fleet/public/components/index.ts | 1 + .../manual => }/platform_selector.tsx | 4 +- .../agents => }/services/has_fleet_server.ts | 4 +- x-pack/plugins/fleet/public/services/index.ts | 1 + .../translations/translations/fr-FR.json | 8 - .../translations/translations/ja-JP.json | 8 - .../translations/translations/zh-CN.json | 8 - 61 files changed, 2262 insertions(+), 1137 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/fleet_server_host_combobox.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/index.ts create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/flyout.stories.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/index.ts create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_advanced_form.ts create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_fleet_server_host.ts create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_quick_start_form.ts create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_select_fleet_server_policy.ts create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_service_token.ts create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_wait_for_fleet_server.ts create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/instructions.stories.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/quick_start_tab.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/add_fleet_server_host.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/confirm_fleet_server_connection.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/create_service_token.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/index.ts create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/install_fleet_server.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/select_agent_policy.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/set_deployment_mode.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/index.ts rename x-pack/plugins/fleet/public/applications/fleet/{sections/agents/agent_requirements_page/components => components/fleet_server_instructions/utils}/install_command_utils.test.ts (56%) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/install_command_utils.ts create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/enrollment_recommendation.tsx delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts rename x-pack/plugins/fleet/public/components/{enrollment_instructions/manual => }/platform_selector.tsx (96%) rename x-pack/plugins/fleet/public/{applications/fleet/sections/agents => }/services/has_fleet_server.ts (79%) diff --git a/x-pack/plugins/fleet/.storybook/context/cloud.ts b/x-pack/plugins/fleet/.storybook/context/cloud.ts index 1291f94cea788..eccb41d6aa8c0 100644 --- a/x-pack/plugins/fleet/.storybook/context/cloud.ts +++ b/x-pack/plugins/fleet/.storybook/context/cloud.ts @@ -11,9 +11,9 @@ export const getCloud = ({ isCloudEnabled }: { isCloudEnabled: boolean }) => { const cloud: CloudSetup = { isCloudEnabled, baseUrl: 'https://base.url', - cloudId: 'cloud-id', + cloudId: isCloudEnabled ? 'cloud-id' : undefined, cname: 'found.io', - deploymentUrl: 'https://deployment.url', + deploymentUrl: isCloudEnabled ? 'https://deployment.url' : undefined, organizationUrl: 'https://organization.url', profileUrl: 'https://profile.url', snapshotsUrl: 'https://snapshots.url', diff --git a/x-pack/plugins/fleet/.storybook/context/http.ts b/x-pack/plugins/fleet/.storybook/context/http.ts index 5167ad5500364..ff5b36a82aa66 100644 --- a/x-pack/plugins/fleet/.storybook/context/http.ts +++ b/x-pack/plugins/fleet/.storybook/context/http.ts @@ -27,7 +27,7 @@ export const getHttp = (basepath = BASE_PATH) => { serverBasePath: basepath, }, get: (async (path: string, options: HttpFetchOptions) => { - action('get')(path, options); + action('get')(path, JSON.stringify(options)); // TODO: all of this needs revision, as it's far too clunky... but it works for now, // with the few paths we're supporting. if (path === '/api/fleet/agents/setup') { @@ -35,7 +35,7 @@ export const getHttp = (basepath = BASE_PATH) => { isReady = true; return { isReady: false, missing_requirements: ['api_keys', 'fleet_server'] }; } - return { isInitialized: true, nonFatalErrors: [] }; + return { isReady: true, isInitialized: true, nonFatalErrors: [], missing_requirements: [] }; } if (path === '/api/fleet/epm/categories') { @@ -79,9 +79,59 @@ export const getHttp = (basepath = BASE_PATH) => { return { success: true }; } - action(path)('KP: UNSUPPORTED ROUTE'); + if (path.match('/api/fleet/agent_policies')) { + return { items: [] }; + } + + if (path.match('/api/fleet/settings')) { + return { item: { fleet_server_hosts: [] } }; + } + + if (path.match('/api/fleet/outputs')) { + return { + items: [{ name: 'Default Output', is_default: true, hosts: ['https://test.es:9200'] }], + }; + } + + action(path)(`UNSUPPORTED ROUTE: GET ${path}`); return {}; }) as HttpHandler, + post: (async (path: string, options: HttpFetchOptions) => { + action('post')(path, JSON.stringify(options)); + + if (path.match('/api/fleet/settings')) { + return { items: [] }; + } + + if (path.match('/api/fleet/service_tokens')) { + return { + name: 'test-token', + value: 'test-token-value', + }; + } + + if (path.match('/api/fleet/agent_policies')) { + return { + item: { + id: 'test-policy', + name: 'Test Policy', + namespace: 'default', + description: 'Test Policy', + monitoring_enabled: ['metrics'], + data_output_id: 'test-output', + monitoring_output_id: 'test-output', + status: 'active', + packagePolicies: ['test-package-policy'], + updated_on: new Date(), + updated_by: 'elastic', + revision: 0, + agents: 0, + }, + }; + } + + action(path)(`UNSUPPORTED ROUTE: POST ${path}`); + }) as HttpHandler, } as unknown as HttpStart; return http; diff --git a/x-pack/plugins/fleet/.storybook/context/index.tsx b/x-pack/plugins/fleet/.storybook/context/index.tsx index a60ecaf5a1572..15ee77506cc0e 100644 --- a/x-pack/plugins/fleet/.storybook/context/index.tsx +++ b/x-pack/plugins/fleet/.storybook/context/index.tsx @@ -66,7 +66,7 @@ export const StorybookContext: React.FC<{ storyContext?: StoryContext }> = ({ chrome: getChrome(), cloud: { ...getCloud({ isCloudEnabled }), - CloudContextProvider: () => <>, + CloudContextProvider: ({ children }) => <>{children}, }, customIntegrations: { ContextProvider: getStorybookContextProvider(), diff --git a/x-pack/plugins/fleet/cypress/integration/fleet_settings.spec.ts b/x-pack/plugins/fleet/cypress/integration/fleet_settings.spec.ts index 76c8f129584bd..338030e91f3da 100644 --- a/x-pack/plugins/fleet/cypress/integration/fleet_settings.spec.ts +++ b/x-pack/plugins/fleet/cypress/integration/fleet_settings.spec.ts @@ -30,20 +30,20 @@ describe('Edit settings', () => { it('should update Fleet server hosts', () => { cy.getBySel('editHostsBtn').click(); - cy.get('[placeholder="Specify host URL"').type('http://localhost:8220'); + cy.get('[placeholder="Specify host URL"').type('https://localhost:8220'); cy.intercept('/api/fleet/settings', { - item: { id: 'fleet-default-settings', fleet_server_hosts: ['http://localhost:8220'] }, + item: { id: 'fleet-default-settings', fleet_server_hosts: ['https://localhost:8220'] }, }); cy.intercept('PUT', '/api/fleet/settings', { - fleet_server_hosts: ['http://localhost:8220'], + fleet_server_hosts: ['https://localhost:8220'], }).as('updateSettings'); cy.getBySel('saveApplySettingsBtn').click(); cy.getBySel(CONFIRM_MODAL_BTN).click(); cy.wait('@updateSettings').then((interception) => { - expect(interception.request.body.fleet_server_hosts[0]).to.equal('http://localhost:8220'); + expect(interception.request.body.fleet_server_hosts[0]).to.equal('https://localhost:8220'); }); }); diff --git a/x-pack/plugins/fleet/cypress/integration/fleet_startup.spec.ts b/x-pack/plugins/fleet/cypress/integration/fleet_startup.spec.ts index 4f7f4378c0fc2..c269d1fe2ba2d 100644 --- a/x-pack/plugins/fleet/cypress/integration/fleet_startup.spec.ts +++ b/x-pack/plugins/fleet/cypress/integration/fleet_startup.spec.ts @@ -77,12 +77,15 @@ describe('Fleet startup', () => { }); it('should create Fleet Server policy', () => { + cy.getBySel('fleetServerFlyoutTab-advanced').click(); cy.getBySel('createFleetServerPolicyBtn').click(); // verify policy is created and has fleet server and system package verifyPolicy('Fleet Server policy 1', ['Fleet Server', 'System']); navigateToTab(AGENTS_TAB); + cy.getBySel('fleetServerFlyoutTab-advanced').click(); + // verify create button changed to dropdown cy.getBySel('agentPolicyDropdown'); diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx new file mode 100644 index 0000000000000..87b4a1bda7ff7 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx @@ -0,0 +1,66 @@ +/* + * 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 { EuiSteps } from '@elastic/eui'; +import React from 'react'; + +import { useAdvancedForm } from './hooks'; + +import { + getAddFleetServerHostStep, + getSelectAgentPolicyStep, + getGenerateServiceTokenStep, + getSetDeploymentModeStep, + getInstallFleetServerStep, + getConfirmFleetServerConnectionStep, +} from './steps'; + +export const AdvancedTab: React.FunctionComponent = () => { + const { + eligibleFleetServerPolicies, + refreshEligibleFleetServerPolicies, + fleetServerPolicyId, + setFleetServerPolicyId, + isFleetServerReady, + serviceToken, + isLoadingServiceToken, + generateServiceToken, + fleetServerHostForm, + deploymentMode, + setDeploymentMode, + } = useAdvancedForm(); + + const steps = [ + getSelectAgentPolicyStep({ + policyId: fleetServerPolicyId, + setPolicyId: setFleetServerPolicyId, + eligibleFleetServerPolicies, + refreshEligibleFleetServerPolicies, + }), + getSetDeploymentModeStep({ + deploymentMode, + setDeploymentMode, + disabled: !Boolean(fleetServerPolicyId), + }), + getAddFleetServerHostStep({ fleetServerHostForm, disabled: !Boolean(fleetServerPolicyId) }), + getGenerateServiceTokenStep({ + serviceToken, + generateServiceToken, + isLoadingServiceToken, + disabled: !Boolean(fleetServerHostForm.isFleetServerHostSubmitted), + }), + getInstallFleetServerStep({ + isFleetServerReady, + serviceToken, + fleetServerHost: fleetServerHostForm.fleetServerHost, + fleetServerPolicyId, + disabled: !Boolean(serviceToken), + }), + getConfirmFleetServerConnectionStep({ isFleetServerReady, disabled: !Boolean(serviceToken) }), + ]; + + return ; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/fleet_server_host_combobox.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/fleet_server_host_combobox.tsx new file mode 100644 index 0000000000000..0508bd9108d3a --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/fleet_server_host_combobox.tsx @@ -0,0 +1,77 @@ +/* + * 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, { useState } from 'react'; +import type { EuiComboBoxOptionOption } from '@elastic/eui'; +import { EuiComboBox, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; + +interface Props { + fleetServerHost: string | undefined; + fleetServerHostSettings: string[]; + isDisabled: boolean; + isInvalid: boolean; + onFleetServerHostChange: (host: string) => void; +} + +export const FleetServerHostComboBox: React.FunctionComponent = ({ + fleetServerHost, + fleetServerHostSettings, + isDisabled = false, + isInvalid = false, + onFleetServerHostChange, +}) => { + // Track options that are created inline + const [createdOptions, setCreatedOptions] = useState([]); + + const options = [...createdOptions, ...fleetServerHostSettings].map((option) => ({ + label: option, + value: option, + })); + + const handleChange = (selectedOptions: Array>) => { + const host = selectedOptions[0].value ?? ''; + onFleetServerHostChange(host); + }; + + const handleCreateOption = (option: string) => { + setCreatedOptions([...createdOptions, option]); + onFleetServerHostChange(option); + }; + + return ( + + fullWidth + isClearable={false} + singleSelection={{ asPlainText: true }} + placeholder="https://fleet-server-host.com:8220" + options={options} + customOptionText={i18n.translate( + 'xpack.fleet.fleetServerSetup.addFleetServerHostCustomOptionText', + { + defaultMessage: 'Add {searchValuePlaceholder} as a new Fleet Server host', + values: { searchValuePlaceholder: '{searchValue}' }, + } + )} + selectedOptions={fleetServerHost ? [{ label: fleetServerHost, value: fleetServerHost }] : []} + prepend={ + + + + } + noSuggestions={fleetServerHostSettings.length === 0} + data-test-subj="fleetServerHostInput" + isDisabled={isDisabled} + isInvalid={isInvalid} + onChange={handleChange} + onCreateOption={handleCreateOption} + /> + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/index.ts new file mode 100644 index 0000000000000..904271187dc8b --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/index.ts @@ -0,0 +1,7 @@ +/* + * 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 './fleet_server_host_combobox'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/flyout.stories.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/flyout.stories.tsx new file mode 100644 index 0000000000000..b08c965d084c8 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/flyout.stories.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiButton } from '@elastic/eui'; + +import { FleetServerFlyout as FleetServerFlyoutComponent } from '.'; + +export const FleetServerFlyout = () => { + const [isOpen, setIsOpen] = React.useState(false); + + return ( +

+ setIsOpen(true)}> + Show flyout + + {isOpen && setIsOpen(false)} />} +
+ ); +}; + +FleetServerFlyout.args = { + isCloudEnabled: false, +}; + +export default { + component: FleetServerFlyout, + title: 'Sections/Fleet/Agents/Fleet Server Instructions/In Flyout', +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/index.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/index.ts new file mode 100644 index 0000000000000..6c702d5433ce0 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/index.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + +// These hooks are tightly coupled to each tab of the Fleet server instructions component, and provide +// all necessary data to drive those UI's +export * from './use_advanced_form'; +export * from './use_quick_start_form'; + +// These are individual hooks for one-off consumption. These are typically composed in the hooks above, +// but exported here to support individual usage. +export * from './use_wait_for_fleet_server'; +export * from './use_select_fleet_server_policy'; +export * from './use_service_token'; +export * from './use_fleet_server_host'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_advanced_form.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_advanced_form.ts new file mode 100644 index 0000000000000..5ed9f9faa0372 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_advanced_form.ts @@ -0,0 +1,46 @@ +/* + * 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 { useState } from 'react'; + +import type { DeploymentMode } from '../steps'; + +import { useFleetServerHost } from './use_fleet_server_host'; +import { useSelectFleetServerPolicy } from './use_select_fleet_server_policy'; +import { useServiceToken } from './use_service_token'; +import { useWaitForFleetServer } from './use_wait_for_fleet_server'; + +/** + * Provides all data/state required for the "advanced" tab in the Fleet Server instructions/flyout + */ +export const useAdvancedForm = (defaultAgentPolicyId?: string) => { + const { + eligibleFleetServerPolicies, + refreshEligibleFleetServerPolicies, + fleetServerPolicyId, + setFleetServerPolicyId, + } = useSelectFleetServerPolicy(defaultAgentPolicyId); + const { isFleetServerReady } = useWaitForFleetServer(); + const { serviceToken, isLoadingServiceToken, generateServiceToken } = useServiceToken(); + const fleetServerHostForm = useFleetServerHost(); + + const [deploymentMode, setDeploymentMode] = useState('quickstart'); + + return { + eligibleFleetServerPolicies, + refreshEligibleFleetServerPolicies, + fleetServerPolicyId, + setFleetServerPolicyId, + isFleetServerReady, + serviceToken, + isLoadingServiceToken, + generateServiceToken, + fleetServerHostForm, + deploymentMode, + setDeploymentMode, + }; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_fleet_server_host.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_fleet_server_host.ts new file mode 100644 index 0000000000000..05eeccf4a9312 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_fleet_server_host.ts @@ -0,0 +1,103 @@ +/* + * 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 { useCallback, useEffect, useState } from 'react'; + +import { sendPutSettings, useGetSettings } from '../../../hooks'; + +const URL_REGEX = /^(https):\/\/[^\s$.?#].[^\s]*$/gm; + +export interface FleetServerHostForm { + saveFleetServerHost: () => Promise; + fleetServerHost?: string; + fleetServerHostSettings: string[]; + isFleetServerHostSubmitted: boolean; + setFleetServerHost: React.Dispatch>; + error?: string; + validateFleetServerHost: () => boolean; +} + +export const useFleetServerHost = (): FleetServerHostForm => { + const [fleetServerHost, setFleetServerHost] = useState(); + const [isFleetServerHostSubmitted, setIsFleetServerHostSubmitted] = useState(false); + const [error, setError] = useState(); + + const { data: settings } = useGetSettings(); + + useEffect(() => { + const settingsFleetServerHosts = settings?.item.fleet_server_hosts ?? []; + + if (settingsFleetServerHosts.length) { + setFleetServerHost(settingsFleetServerHosts[0]); + } + }, [settings?.item.fleet_server_hosts]); + + const validateFleetServerHost = useCallback(() => { + if (!fleetServerHost) { + setError( + i18n.translate('xpack.fleet.fleetServerHost.requiredError', { + defaultMessage: 'Fleet server host is required.', + }) + ); + + return false; + } else if (!fleetServerHost.startsWith('https')) { + setError( + i18n.translate('xpack.fleet.fleetServerHost.requiresHttpsError', { + defaultMessage: 'Fleet server host must begin with "https"', + }) + ); + + return false; + } else if (!fleetServerHost.match(URL_REGEX)) { + setError( + i18n.translate('xpack.fleet.fleetServerSetup.addFleetServerHostInvalidUrlError', { + defaultMessage: 'Invalid URL', + }) + ); + + return false; + } + + return true; + }, [fleetServerHost]); + + const saveFleetServerHost = useCallback(async () => { + setIsFleetServerHostSubmitted(false); + + if (!validateFleetServerHost()) { + return; + } + + // If the Fleet Server host provided already exists in settings, don't submit it + if (settings?.item.fleet_server_hosts.includes(fleetServerHost!)) { + setIsFleetServerHostSubmitted(true); + return; + } + + const res = await sendPutSettings({ + fleet_server_hosts: [fleetServerHost!, ...(settings?.item.fleet_server_hosts || [])], + }); + + if (res.error) { + throw res.error; + } + + setIsFleetServerHostSubmitted(true); + }, [fleetServerHost, settings?.item.fleet_server_hosts, validateFleetServerHost]); + + return { + saveFleetServerHost, + fleetServerHost, + fleetServerHostSettings: settings?.item.fleet_server_hosts ?? [], + isFleetServerHostSubmitted, + setFleetServerHost, + error, + validateFleetServerHost, + }; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_quick_start_form.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_quick_start_form.ts new file mode 100644 index 0000000000000..84fd39aeec378 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_quick_start_form.ts @@ -0,0 +1,140 @@ +/* + * 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 { useState, useCallback, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { sendCreateAgentPolicy, sendGetOneAgentPolicy, useStartServices } from '../../../hooks'; + +import type { NewAgentPolicy } from '../../../types'; + +import { useSelectFleetServerPolicy } from './use_select_fleet_server_policy'; +import { useServiceToken } from './use_service_token'; +import { useFleetServerHost } from './use_fleet_server_host'; + +const QUICK_START_FLEET_SERVER_POLICY_FIELDS: NewAgentPolicy = { + id: 'fleet-server-policy', + name: 'Fleet Server Policy', + description: 'Fleet Server policy generated by Kibana', + namespace: 'default', + has_fleet_server: true, + monitoring_enabled: ['logs', 'metrics'], + is_default_fleet_server: true, +}; + +export type QuickStartCreateFormStatus = 'initial' | 'loading' | 'error' | 'success'; + +export interface QuickStartCreateForm { + status: QuickStartCreateFormStatus; + error?: string; + submit: () => void; + fleetServerHost?: string; + fleetServerHostSettings: string[]; + isFleetServerHostSubmitted: boolean; + onFleetServerHostChange: (value: string) => void; + fleetServerPolicyId?: string; + serviceToken?: string; +} + +/** + * Provides a unified interface that combines the following operations: + * 1. Setting a Fleet Server host in Fleet's settings + * 2. Creating an agent policy that contains the `fleet_server` integration + * 3. Generating a service token used by Fleet Server + */ +export const useQuickStartCreateForm = (): QuickStartCreateForm => { + const [status, setStatus] = useState<'initial' | 'loading' | 'error' | 'success'>('initial'); + const [error, setError] = useState(); + + const { + fleetServerHost, + fleetServerHostSettings, + isFleetServerHostSubmitted, + setFleetServerHost, + validateFleetServerHost, + saveFleetServerHost, + error: fleetServerError, + } = useFleetServerHost(); + + // When a validation error is surfaced from the Fleet Server host form, we want to treat it + // the same way we do errors from the service token or policy creation steps + useEffect(() => { + setStatus('error'); + setError(fleetServerError); + }, [fleetServerError]); + + const { notifications } = useStartServices(); + const { fleetServerPolicyId, setFleetServerPolicyId } = useSelectFleetServerPolicy(); + const { serviceToken, generateServiceToken } = useServiceToken(); + + const onFleetServerHostChange = useCallback( + (value: string) => { + setFleetServerHost(value); + }, + [setFleetServerHost] + ); + + const submit = useCallback(async () => { + try { + if (validateFleetServerHost()) { + setStatus('loading'); + await saveFleetServerHost(); + await generateServiceToken(); + + const existingPolicy = await sendGetOneAgentPolicy( + QUICK_START_FLEET_SERVER_POLICY_FIELDS.id! + ); + + // Don't attempt to create the policy if it's already been created in a previous quick start flow + if (existingPolicy.data?.item) { + setFleetServerPolicyId(existingPolicy.data?.item.id); + } else { + const createPolicyResponse = await sendCreateAgentPolicy( + QUICK_START_FLEET_SERVER_POLICY_FIELDS, + { + withSysMonitoring: true, + } + ); + + setFleetServerPolicyId(createPolicyResponse.data?.item.id); + } + + setFleetServerHost(fleetServerHost); + setStatus('success'); + } + } catch (err) { + notifications.toasts.addError(err, { + title: i18n.translate('xpack.fleet.fleetServerSetup.errorAddingFleetServerHostTitle', { + defaultMessage: 'Error adding Fleet Server host', + }), + }); + + setStatus('error'); + setError(err.message); + } + }, [ + validateFleetServerHost, + saveFleetServerHost, + generateServiceToken, + setFleetServerHost, + fleetServerHost, + setFleetServerPolicyId, + notifications.toasts, + ]); + + return { + status, + error, + submit, + fleetServerPolicyId, + fleetServerHost, + fleetServerHostSettings, + isFleetServerHostSubmitted, + onFleetServerHostChange, + serviceToken, + }; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_select_fleet_server_policy.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_select_fleet_server_policy.ts new file mode 100644 index 0000000000000..add318e85a7ae --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_select_fleet_server_policy.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 { useEffect, useMemo, useState } from 'react'; + +import { useGetAgentPolicies } from '../../../hooks'; +import { policyHasFleetServer } from '../../../services'; + +export const useSelectFleetServerPolicy = (defaultAgentPolicyId?: string) => { + const [fleetServerPolicyId, setFleetServerPolicyId] = useState( + defaultAgentPolicyId + ); + const { data: agentPoliciesData, resendRequest } = useGetAgentPolicies({ + full: true, + }); + + const eligibleFleetServerPolicies = useMemo( + () => + agentPoliciesData + ? agentPoliciesData.items?.filter((item) => policyHasFleetServer(item)) + : [], + [agentPoliciesData] + ); + + useEffect(() => { + // Default to the first policy found with a fleet server integration installed + if (eligibleFleetServerPolicies.length && !fleetServerPolicyId) { + setFleetServerPolicyId(eligibleFleetServerPolicies[0].id); + } + }, [eligibleFleetServerPolicies, fleetServerPolicyId]); + + return { + fleetServerPolicyId, + setFleetServerPolicyId, + eligibleFleetServerPolicies, + refreshEligibleFleetServerPolicies: resendRequest, + }; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_service_token.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_service_token.ts new file mode 100644 index 0000000000000..3b729a6776b52 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_service_token.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 { useState, useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { useStartServices, sendGenerateServiceToken } from '../../../hooks'; + +export const useServiceToken = () => { + const { notifications } = useStartServices(); + const [serviceToken, setServiceToken] = useState(); + const [isLoadingServiceToken, setIsLoadingServiceToken] = useState(false); + + const generateServiceToken = useCallback(async () => { + setIsLoadingServiceToken(true); + try { + const { data } = await sendGenerateServiceToken(); + if (data?.value) { + setServiceToken(data?.value); + } + } catch (err) { + notifications.toasts.addError(err, { + title: i18n.translate('xpack.fleet.fleetServerSetup.errorGeneratingTokenTitleText', { + defaultMessage: 'Error generating token', + }), + }); + } finally { + setIsLoadingServiceToken(false); + } + }, [notifications.toasts]); + + return { serviceToken, isLoadingServiceToken, generateServiceToken }; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_wait_for_fleet_server.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_wait_for_fleet_server.ts new file mode 100644 index 0000000000000..4da59560e408e --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_wait_for_fleet_server.ts @@ -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 { i18n } from '@kbn/i18n'; +import { useEffect, useState } from 'react'; + +import { sendGetFleetStatus, useStartServices } from '../../../hooks'; + +const REFRESH_INTERVAL = 10000; + +/** + * Polls the Fleet status endpoint until the `fleet_server` requirement does not appear + * in the `missing_requirements` list. + */ +export const useWaitForFleetServer = () => { + const [isFleetServerReady, setIsFleetServerReady] = useState(false); + const { notifications } = useStartServices(); + + useEffect(() => { + let interval: ReturnType | null = null; + + if (!isFleetServerReady) { + interval = setInterval(async () => { + try { + const res = await sendGetFleetStatus(); + + if (res.error) { + throw res.error; + } + if (res.data?.isReady && !res.data?.missing_requirements?.includes('fleet_server')) { + setIsFleetServerReady(true); + + if (interval) { + clearInterval(interval); + } + } + } catch (err) { + notifications.toasts.addError(err, { + title: i18n.translate('xpack.fleet.fleetServerSetup.errorRefreshingFleetServerStatus', { + defaultMessage: 'Error refreshing Fleet Server status', + }), + }); + } + }, REFRESH_INTERVAL); + } + + const cleanup = () => { + if (interval) { + clearInterval(interval); + } + }; + + return cleanup; + }, [notifications.toasts, isFleetServerReady]); + + return { isFleetServerReady }; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx new file mode 100644 index 0000000000000..6c48b499b9553 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx @@ -0,0 +1,154 @@ +/* + * 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, { useState } from 'react'; +import { + EuiFlexGroup, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiLink, + EuiSpacer, + EuiTab, + EuiTabs, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import styled from 'styled-components'; + +import { useStartServices } from '../../hooks'; + +import { QuickStartTab } from './quick_start_tab'; +import { AdvancedTab } from './advanced_tab'; + +const ContentWrapper = styled(EuiFlexGroup)` + height: 100%; + margin: 0 auto; +`; + +interface Props { + onClose: () => void; +} + +const useFleetServerTabs = () => { + const [currentTab, setCurrentTab] = useState('quickStart'); + + const quickStartTab = { + id: 'quickStart', + name: 'Quick Start', + content: , + }; + + const advancedTab = { + id: 'advanced', + name: 'Advanced', + content: , + }; + + const currentTabContent = + currentTab === 'quickStart' ? quickStartTab.content : advancedTab.content; + + return { tabs: [quickStartTab, advancedTab], currentTab, setCurrentTab, currentTabContent }; +}; + +const Header: React.FunctionComponent<{ + isFlyout?: boolean; + currentTab: string; + tabs: Array<{ id: string; name: string; content: React.ReactNode }>; + onTabClick: (id: string) => void; +}> = ({ isFlyout = false, currentTab: currentTabId, tabs, onTabClick }) => { + const { docLinks } = useStartServices(); + + return ( + <> + +

+ +

+
+ + + + + + + + ), + }} + /> + + + + + + {tabs.map((tab) => ( + onTabClick(tab.id)} + > + {tab.name} + + ))} + + + ); +}; + +// Renders instructions inside of a flyout +export const FleetServerFlyout: React.FunctionComponent = ({ onClose }) => { + const { tabs, currentTab, setCurrentTab, currentTabContent } = useFleetServerTabs(); + + return ( + + +
setCurrentTab(id)} + isFlyout + /> + + + {currentTabContent} + + ); +}; + +// Renders instructions directly +export const FleetServerInstructions: React.FunctionComponent = () => { + const { tabs, currentTab, setCurrentTab, currentTabContent } = useFleetServerTabs(); + + return ( + +
setCurrentTab(id)} /> + + + + {currentTabContent} + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/instructions.stories.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/instructions.stories.tsx new file mode 100644 index 0000000000000..9993fab723a44 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/instructions.stories.tsx @@ -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 React from 'react'; + +import { FleetServerInstructions as FleetServerInstructionsComponent } from '.'; + +export const FleetServerInstructions = () => { + return ; +}; + +FleetServerInstructions.args = { + isCloudEnabled: false, +}; + +export default { + component: FleetServerInstructions, + title: 'Sections/Fleet/Agents/Fleet Server Instructions/Without Flyout', +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/quick_start_tab.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/quick_start_tab.tsx new file mode 100644 index 0000000000000..cf8abc2fe9e16 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/quick_start_tab.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiSteps } from '@elastic/eui'; + +import { useQuickStartCreateForm, useWaitForFleetServer } from './hooks'; + +import { + getGettingStartedStep, + getConfirmFleetServerConnectionStep, + getInstallFleetServerStep, +} from './steps'; + +export const QuickStartTab: React.FunctionComponent = () => { + const quickStartCreateForm = useQuickStartCreateForm(); + const { isFleetServerReady } = useWaitForFleetServer(); + + const steps = [ + getGettingStartedStep({ + quickStartCreateForm, + }), + getInstallFleetServerStep({ + isFleetServerReady, + fleetServerHost: quickStartCreateForm.fleetServerHost, + fleetServerPolicyId: quickStartCreateForm.fleetServerPolicyId, + serviceToken: quickStartCreateForm.serviceToken, + disabled: quickStartCreateForm.status !== 'success', + }), + getConfirmFleetServerConnectionStep({ + isFleetServerReady, + disabled: quickStartCreateForm.status !== 'success', + }), + ]; + + return ; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/add_fleet_server_host.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/add_fleet_server_host.tsx new file mode 100644 index 0000000000000..e64e23f039f89 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/add_fleet_server_host.tsx @@ -0,0 +1,165 @@ +/* + * 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, { useState, useCallback } from 'react'; +import type { EuiStepProps } from '@elastic/eui'; +import { + EuiButton, + EuiCallOut, + EuiCode, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormErrorText, + EuiLink, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { useStartServices, useLink } from '../../../hooks'; +import type { FleetServerHostForm } from '../hooks'; +import { FleetServerHostComboBox } from '../components'; + +export const getAddFleetServerHostStep = ({ + fleetServerHostForm, + disabled, +}: { + fleetServerHostForm: FleetServerHostForm; + disabled: boolean; +}): EuiStepProps => { + return { + title: i18n.translate('xpack.fleet.fleetServerSetup.addFleetServerHostStepTitle', { + defaultMessage: 'Add your Fleet Server host', + }), + status: disabled ? 'disabled' : undefined, + children: disabled ? null : ( + + ), + }; +}; + +export const AddFleetServerHostStepContent = ({ + fleetServerHostForm, +}: { + fleetServerHostForm: FleetServerHostForm; +}) => { + const { + fleetServerHost, + fleetServerHostSettings, + setFleetServerHost, + validateFleetServerHost, + saveFleetServerHost, + error, + } = fleetServerHostForm; + + const [isLoading, setIsLoading] = useState(false); + const [submittedFleetServerHost, setSubmittedFleetServerHost] = useState(); + const { notifications } = useStartServices(); + const { getHref } = useLink(); + + const onSubmit = useCallback(async () => { + try { + setSubmittedFleetServerHost(''); + setIsLoading(true); + + if (validateFleetServerHost()) { + await saveFleetServerHost(); + setSubmittedFleetServerHost(fleetServerHost); + } + } catch (err) { + notifications.toasts.addError(err, { + title: i18n.translate('xpack.fleet.fleetServerSetup.errorAddingFleetServerHostTitle', { + defaultMessage: 'Error adding Fleet Server host', + }), + }); + } finally { + setIsLoading(false); + } + }, [validateFleetServerHost, saveFleetServerHost, fleetServerHost, notifications.toasts]); + + const onChange = useCallback( + (host: string) => { + setFleetServerHost(host); + + if (error) { + validateFleetServerHost(); + } + }, + [error, setFleetServerHost, validateFleetServerHost] + ); + + return ( + + + 8220 }} + /> + + + + + + {error && {error}} + + + + + + + + {submittedFleetServerHost && ( + <> + + + } + > + + + + ), + }} + /> + + + )} + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/confirm_fleet_server_connection.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/confirm_fleet_server_connection.tsx new file mode 100644 index 0000000000000..5aa5c01a108a4 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/confirm_fleet_server_connection.tsx @@ -0,0 +1,66 @@ +/* + * 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, { useContext } from 'react'; + +import type { EuiStepProps } from '@elastic/eui'; +import { EuiButton, EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { agentFlyoutContext } from '../../../sections/agents'; + +export function getConfirmFleetServerConnectionStep({ + disabled, + isFleetServerReady, +}: { + disabled: boolean; + isFleetServerReady: boolean; +}): EuiStepProps { + return { + title: isFleetServerReady + ? i18n.translate('xpack.fleet.fleetServerFlyout.confirmConnectionSuccessTitle', { + defaultMessage: 'Fleet Server connected', + }) + : i18n.translate('xpack.fleet.fleetServerFlyout.confirmConnectionTitle', { + defaultMessage: 'Confirm connection', + }), + status: isFleetServerReady ? 'complete' : 'disabled', + children: !disabled && ( + + ), + }; +} + +const ConfirmFleetServerConnectionStepContent: React.FunctionComponent<{ + isFleetServerReady: boolean; +}> = ({ isFleetServerReady }) => { + const addAgentFlyout = useContext(agentFlyoutContext); + + return isFleetServerReady ? ( + <> + + + + + + + + + + + ) : ( + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/create_service_token.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/create_service_token.tsx new file mode 100644 index 0000000000000..dcfa150c2d32c --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/create_service_token.tsx @@ -0,0 +1,136 @@ +/* + * 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 styled from 'styled-components'; + +import type { EuiStepProps } from '@elastic/eui'; +import { + EuiButton, + EuiCallOut, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +const FlexItemWithMinWidth = styled(EuiFlexItem)` + min-width: 0px; + max-width: 100%; +`; + +export const ContentWrapper = styled(EuiFlexGroup)` + height: 100%; + margin: 0 auto; + max-width: 800px; +`; + +// Otherwise the copy button is over the text +const CommandCode = styled.div.attrs(() => { + return { + className: 'eui-textBreakAll', + }; +})` + margin-right: ${(props) => props.theme.eui.paddingSizes.m}; +`; + +export const getGenerateServiceTokenStep = ({ + disabled = false, + serviceToken, + generateServiceToken, + isLoadingServiceToken, +}: { + disabled?: boolean; + serviceToken?: string; + generateServiceToken: () => void; + isLoadingServiceToken: boolean; +}): EuiStepProps => { + return { + title: i18n.translate('xpack.fleet.fleetServerSetup.stepGenerateServiceTokenTitle', { + defaultMessage: 'Generate a service token', + }), + status: disabled ? 'disabled' : undefined, + children: !disabled && ( + + ), + }; +}; + +const ServiceTokenStepContent: React.FunctionComponent<{ + serviceToken?: string; + generateServiceToken: () => void; + isLoadingServiceToken: boolean; +}> = ({ serviceToken, generateServiceToken, isLoadingServiceToken }) => { + return ( + <> + + + + + {!serviceToken ? ( + + + { + generateServiceToken(); + }} + data-test-subj="fleetServerGenerateServiceTokenBtn" + > + + + + + ) : ( + <> + + } + /> + + + + + + + + + + {serviceToken} + + + + + )} + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx new file mode 100644 index 0000000000000..50e9b6b72002c --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx @@ -0,0 +1,130 @@ +/* + * 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 type { EuiStepProps } from '@elastic/eui'; +import { + EuiButton, + EuiCallOut, + EuiCode, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormErrorText, + EuiLink, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { useLink } from '../../../hooks'; + +import type { QuickStartCreateForm } from '../hooks'; +import { FleetServerHostComboBox } from '../components'; + +export function getGettingStartedStep({ + quickStartCreateForm, +}: { + quickStartCreateForm: QuickStartCreateForm; +}): EuiStepProps { + return { + title: i18n.translate('xpack.fleet.fleetServerFlyout.getStartedTitle', { + defaultMessage: 'Get started with Fleet Server', + }), + status: quickStartCreateForm.status === 'success' ? 'complete' : 'current', + children: , + }; +} + +const GettingStartedStepContent: React.FunctionComponent<{ + quickStartCreateForm: QuickStartCreateForm; +}> = ({ quickStartCreateForm }) => { + const { getHref } = useLink(); + + const { fleetServerHost, fleetServerHostSettings, onFleetServerHostChange } = + quickStartCreateForm; + + if (quickStartCreateForm.status === 'success') { + return ( + + + {fleetServerHost}, + fleetSettingsLink: ( + + + + ), + }} + /> + + + ); + } + + return ( + <> + + 8220 }} + /> + + + + + + + + + + {quickStartCreateForm.status === 'error' && ( + {quickStartCreateForm.error} + )} + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/index.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/index.ts new file mode 100644 index 0000000000000..11adf2693927b --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/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. + */ + +export * from './add_fleet_server_host'; +export * from './confirm_fleet_server_connection'; +export * from './create_service_token'; +export * from './get_started'; +export * from './install_fleet_server'; +export * from './select_agent_policy'; +export * from './set_deployment_mode'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/install_fleet_server.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/install_fleet_server.tsx new file mode 100644 index 0000000000000..dac9555ba3149 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/install_fleet_server.tsx @@ -0,0 +1,97 @@ +/* + * 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 type { EuiStepProps } from '@elastic/eui'; +import { EuiSpacer, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import type { PLATFORM_TYPE } from '../../../hooks'; +import { useDefaultOutput, useKibanaVersion } from '../../../hooks'; + +import { PlatformSelector } from '../..'; + +import { getInstallCommandForPlatform } from '../utils'; + +export function getInstallFleetServerStep({ + isFleetServerReady, + disabled, + serviceToken, + fleetServerHost, + fleetServerPolicyId, +}: { + isFleetServerReady: boolean; + disabled: boolean; + serviceToken?: string; + fleetServerHost?: string; + fleetServerPolicyId?: string; +}): EuiStepProps { + return { + title: i18n.translate('xpack.fleet.fleetServerFlyout.installFleetServerTitle', { + defaultMessage: 'Install Fleet Server to a centralized host', + }), + status: disabled ? 'disabled' : isFleetServerReady ? 'complete' : 'incomplete', + children: !disabled && ( + + ), + }; +} + +const InstallFleetServerStepContent: React.FunctionComponent<{ + serviceToken?: string; + fleetServerHost?: string; + fleetServerPolicyId?: string; +}> = ({ serviceToken, fleetServerHost, fleetServerPolicyId }) => { + const kibanaVersion = useKibanaVersion(); + const { output } = useDefaultOutput(); + + const installCommands = (['linux', 'mac', 'windows', 'deb', 'rpm'] as PLATFORM_TYPE[]).reduce( + (acc, platform) => { + acc[platform] = getInstallCommandForPlatform( + platform, + output?.hosts?.[0] ?? '', + serviceToken ?? '', + fleetServerPolicyId, + fleetServerHost, + false, + output?.ca_trusted_fingerprint, + kibanaVersion + ); + + return acc; + }, + {} as Record + ); + + return ( + <> + + + + + + + + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/select_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/select_agent_policy.tsx new file mode 100644 index 0000000000000..778716bacb02e --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/select_agent_policy.tsx @@ -0,0 +1,81 @@ +/* + * 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 { EuiStepProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useEffect } from 'react'; + +import { SelectCreateAgentPolicy } from '../..'; + +import type { GetAgentPoliciesResponseItem } from '../../../types'; + +export const getSelectAgentPolicyStep = ({ + policyId, + setPolicyId, + eligibleFleetServerPolicies, + refreshEligibleFleetServerPolicies, +}: { + policyId?: string; + setPolicyId: (v?: string) => void; + eligibleFleetServerPolicies: GetAgentPoliciesResponseItem[]; + refreshEligibleFleetServerPolicies: () => void; +}): EuiStepProps => { + return { + title: + eligibleFleetServerPolicies.length === 0 + ? i18n.translate('xpack.fleet.fleetServerSetup.stepCreateAgentPolicyTitle', { + defaultMessage: 'Create a policy for Fleet Server', + }) + : i18n.translate('xpack.fleet.fleetServerSetup.stepSelectAgentPolicyTitle', { + defaultMessage: 'Select a policy for Fleet Server', + }), + status: policyId ? 'complete' : undefined, + children: ( + + ), + }; +}; + +const SelectAgentPolicyStepContent: React.FunctionComponent<{ + policyId?: string; + setPolicyId: (v?: string) => void; + eligibleFleetServerPolicies: GetAgentPoliciesResponseItem[]; + refreshEligibleFleetServerPolicies: () => void; +}> = ({ + policyId, + setPolicyId, + eligibleFleetServerPolicies, + refreshEligibleFleetServerPolicies, +}) => { + useEffect(() => { + // Select default value + if (eligibleFleetServerPolicies.length && !policyId) { + setPolicyId(eligibleFleetServerPolicies[0].id); + } + }, [eligibleFleetServerPolicies, policyId, setPolicyId]); + + const setSelectedPolicyId = (agentPolicyId?: string) => { + setPolicyId(agentPolicyId); + }; + + return ( + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/set_deployment_mode.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/set_deployment_mode.tsx new file mode 100644 index 0000000000000..9833fc1d4527f --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/set_deployment_mode.tsx @@ -0,0 +1,117 @@ +/* + * 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, useMemo } from 'react'; + +import type { EuiStepProps } from '@elastic/eui'; +import { EuiRadioGroup, EuiSpacer } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +export type DeploymentMode = 'production' | 'quickstart'; + +export const getSetDeploymentModeStep = ({ + deploymentMode, + setDeploymentMode, + disabled, +}: { + deploymentMode: DeploymentMode; + setDeploymentMode: (v: DeploymentMode) => void; + disabled: boolean; +}): EuiStepProps => { + return { + title: i18n.translate('xpack.fleet.fleetServerSetup.stepDeploymentModeTitle', { + defaultMessage: 'Choose a deployment mode for security', + }), + status: disabled ? 'disabled' : undefined, + children: disabled ? null : ( + + ), + }; +}; + +const DeploymentModeStepContent = ({ + deploymentMode, + setDeploymentMode, +}: { + deploymentMode: DeploymentMode; + setDeploymentMode: (v: DeploymentMode) => void; +}) => { + const onChangeCallback = useCallback( + (v: string) => { + const value = v.split('_')[0]; + if (value === 'production' || value === 'quickstart') { + setDeploymentMode(value); + } + }, + [setDeploymentMode] + ); + + // radio id has to be unique so that the component works even if appears twice in DOM (Agents tab, Add agent flyout) + const radioSuffix = useMemo(() => Date.now(), []); + + return ( + <> + + + + + + + + ), + }} + /> + ), + }, + { + id: `production_${radioSuffix}`, + label: ( + + + + ), + }} + /> + ), + }, + ]} + idSelected={`${deploymentMode}_${radioSuffix}`} + onChange={onChangeCallback} + name={`radio group ${radioSuffix}`} + /> + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/index.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/index.ts new file mode 100644 index 0000000000000..fcbab4728cbe2 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/index.ts @@ -0,0 +1,8 @@ +/* + * 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 './install_command_utils'; 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/components/fleet_server_instructions/utils/install_command_utils.test.ts similarity index 56% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts rename to x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/install_command_utils.test.ts index 774b7871f0353..89a246c5c6265 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/components/fleet_server_instructions/utils/install_command_utils.test.ts @@ -10,50 +10,85 @@ 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('http://elasticsearch:9200', 'service-token-1'); + const res = getInstallCommandForPlatform( + 'linux', + 'http://elasticsearch:9200', + 'service-token-1' + ); - expect(res.linux).toMatchInlineSnapshot(` - "sudo ./elastic-agent install \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--linux-x86_64.zip \\\\ + tar xzvf elastic-agent--linux-x86_64.zip \\\\ + cd elastic-agent--linux-x86_64 \\\\ + sudo ./elastic-agent install \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1" `); }); it('should return the correct command if the the policyId is not set for mac', () => { - const res = getInstallCommandForPlatform('http://elasticsearch:9200', 'service-token-1'); + const res = getInstallCommandForPlatform( + 'mac', + 'http://elasticsearch:9200', + 'service-token-1' + ); - expect(res.mac).toMatchInlineSnapshot(` - "sudo ./elastic-agent install \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--darwin-x86_64.tar.gz \\\\ + tar xzvf elastic-agent--darwin-x86_64.tar.gz \\\\ + cd elastic-agent--darwin-x86_64 \\\\ + sudo ./elastic-agent install \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1" `); }); it('should return the correct command if the the policyId is not set for windows', () => { - const res = getInstallCommandForPlatform('http://elasticsearch:9200', 'service-token-1'); + const res = getInstallCommandForPlatform( + 'windows', + 'http://elasticsearch:9200', + 'service-token-1' + ); - expect(res.windows).toMatchInlineSnapshot(` - ".\\\\elastic-agent.exe install \` + expect(res).toMatchInlineSnapshot(` + "wget https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--windows-x86_64.tar.gz -OutFile elastic-agent--windows-x86_64.tar.gz \` + Expand-Archive .\\\\elastic-agent--windows-x86_64.tar.gz \` + cd elastic-agent--windows-x86_64\` + .\\\\elastic-agent.exe install \` --fleet-server-es=http://elasticsearch:9200 \` --fleet-server-service-token=service-token-1" `); }); it('should return the correct command if the the policyId is not set for rpm', () => { - const res = getInstallCommandForPlatform('http://elasticsearch:9200', 'service-token-1'); + const res = getInstallCommandForPlatform( + 'rpm', + 'http://elasticsearch:9200', + 'service-token-1' + ); - expect(res.rpm).toMatchInlineSnapshot(` - "sudo elastic-agent enroll \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--x86_64.rpm \\\\ + tar xzvf elastic-agent--x86_64.rpm \\\\ + cd elastic-agent--x86_64 \\\\ + sudo elastic-agent enroll \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1" `); }); it('should return the correct command if the the policyId is not set for deb', () => { - const res = getInstallCommandForPlatform('http://elasticsearch:9200', 'service-token-1'); + const res = getInstallCommandForPlatform( + 'deb', + 'http://elasticsearch:9200', + 'service-token-1' + ); - expect(res.deb).toMatchInlineSnapshot(` - "sudo elastic-agent enroll \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--amd64.deb \\\\ + tar xzvf elastic-agent--amd64.deb \\\\ + cd elastic-agent--amd64 \\\\ + sudo elastic-agent enroll \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1" `); @@ -61,6 +96,7 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command sslCATrustedFingerprint option is passed', () => { const res = getInstallCommandForPlatform( + 'linux', 'http://elasticsearch:9200', 'service-token-1', undefined, @@ -69,8 +105,11 @@ describe('getInstallCommandForPlatform', () => { 'fingerprint123456' ); - expect(res.linux).toMatchInlineSnapshot(` - "sudo ./elastic-agent install \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--linux-x86_64.zip \\\\ + tar xzvf elastic-agent--linux-x86_64.zip \\\\ + cd elastic-agent--linux-x86_64 \\\\ + sudo ./elastic-agent install \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-es-ca-trusted-fingerprint=fingerprint123456" @@ -81,13 +120,17 @@ 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' ); - expect(res.linux).toMatchInlineSnapshot(` - "sudo ./elastic-agent install \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--linux-x86_64.zip \\\\ + tar xzvf elastic-agent--linux-x86_64.zip \\\\ + cd elastic-agent--linux-x86_64 \\\\ + sudo ./elastic-agent install \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1" @@ -96,13 +139,17 @@ 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' ); - expect(res.mac).toMatchInlineSnapshot(` - "sudo ./elastic-agent install \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--darwin-x86_64.tar.gz \\\\ + tar xzvf elastic-agent--darwin-x86_64.tar.gz \\\\ + cd elastic-agent--darwin-x86_64 \\\\ + sudo ./elastic-agent install \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1" @@ -111,13 +158,17 @@ 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' ); - expect(res.windows).toMatchInlineSnapshot(` - ".\\\\elastic-agent.exe install \` + expect(res).toMatchInlineSnapshot(` + "wget https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--windows-x86_64.tar.gz -OutFile elastic-agent--windows-x86_64.tar.gz \` + Expand-Archive .\\\\elastic-agent--windows-x86_64.tar.gz \` + cd elastic-agent--windows-x86_64\` + .\\\\elastic-agent.exe install \` --fleet-server-es=http://elasticsearch:9200 \` --fleet-server-service-token=service-token-1 \` --fleet-server-policy=policy-1" @@ -126,13 +177,17 @@ 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' ); - expect(res.rpm).toMatchInlineSnapshot(` - "sudo elastic-agent enroll \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--x86_64.rpm \\\\ + tar xzvf elastic-agent--x86_64.rpm \\\\ + cd elastic-agent--x86_64 \\\\ + sudo elastic-agent enroll \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1" @@ -141,13 +196,17 @@ 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' ); - expect(res.deb).toMatchInlineSnapshot(` - "sudo elastic-agent enroll \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--amd64.deb \\\\ + tar xzvf elastic-agent--amd64.deb \\\\ + cd elastic-agent--amd64 \\\\ + sudo elastic-agent enroll \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1" @@ -158,6 +217,7 @@ 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', @@ -165,8 +225,11 @@ describe('getInstallCommandForPlatform', () => { true ); - expect(res.linux).toMatchInlineSnapshot(` - "sudo ./elastic-agent install --url=http://fleetserver:8220 \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--linux-x86_64.zip \\\\ + tar xzvf elastic-agent--linux-x86_64.zip \\\\ + cd elastic-agent--linux-x86_64 \\\\ + sudo ./elastic-agent install--url=http://fleetserver:8220 \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1 \\\\ @@ -179,6 +242,7 @@ 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', @@ -186,8 +250,11 @@ describe('getInstallCommandForPlatform', () => { true ); - expect(res.mac).toMatchInlineSnapshot(` - "sudo ./elastic-agent install --url=http://fleetserver:8220 \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--darwin-x86_64.tar.gz \\\\ + tar xzvf elastic-agent--darwin-x86_64.tar.gz \\\\ + cd elastic-agent--darwin-x86_64 \\\\ + sudo ./elastic-agent install --url=http://fleetserver:8220 \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1 \\\\ @@ -200,6 +267,7 @@ 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', @@ -207,8 +275,11 @@ describe('getInstallCommandForPlatform', () => { true ); - expect(res.windows).toMatchInlineSnapshot(` - ".\\\\elastic-agent.exe install --url=http://fleetserver:8220 \` + expect(res).toMatchInlineSnapshot(` + "wget https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--windows-x86_64.tar.gz -OutFile elastic-agent--windows-x86_64.tar.gz \` + Expand-Archive .\\\\elastic-agent--windows-x86_64.tar.gz \` + cd elastic-agent--windows-x86_64\` + .\\\\elastic-agent.exe install --url=http://fleetserver:8220 \` --fleet-server-es=http://elasticsearch:9200 \` --fleet-server-service-token=service-token-1 \` --fleet-server-policy=policy-1 \` @@ -221,6 +292,7 @@ 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', @@ -228,8 +300,11 @@ describe('getInstallCommandForPlatform', () => { true ); - expect(res.rpm).toMatchInlineSnapshot(` - "sudo elastic-agent enroll --url=http://fleetserver:8220 \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--x86_64.rpm \\\\ + tar xzvf elastic-agent--x86_64.rpm \\\\ + cd elastic-agent--x86_64 \\\\ + sudo elastic-agent enroll --url=http://fleetserver:8220 \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1 \\\\ @@ -242,6 +317,7 @@ 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', @@ -249,8 +325,11 @@ describe('getInstallCommandForPlatform', () => { true ); - expect(res.deb).toMatchInlineSnapshot(` - "sudo elastic-agent enroll --url=http://fleetserver:8220 \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--amd64.deb \\\\ + tar xzvf elastic-agent--amd64.deb \\\\ + cd elastic-agent--amd64 \\\\ + sudo elastic-agent enroll --url=http://fleetserver:8220 \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1 \\\\ diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/install_command_utils.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/install_command_utils.ts new file mode 100644 index 0000000000000..525af7cf95103 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/install_command_utils.ts @@ -0,0 +1,119 @@ +/* + * 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 { PLATFORM_TYPE } from '../../../hooks'; + +export type CommandsByPlatform = { + [key in PLATFORM_TYPE]: string; +}; + +function getArtifact(platform: PLATFORM_TYPE, kibanaVersion: string) { + const ARTIFACT_BASE_URL = 'https://artifacts.elastic.co/downloads/beats/elastic-agent'; + + const artifactMap: Record< + PLATFORM_TYPE, + { fullUrl: string; filename: string; unpackedDir: string } + > = { + linux: { + fullUrl: `${ARTIFACT_BASE_URL}/elastic-agent-${kibanaVersion}-linux-x86_64.zip`, + filename: `elastic-agent-${kibanaVersion}-linux-x86_64.zip`, + unpackedDir: `elastic-agent-${kibanaVersion}-linux-x86_64`, + }, + mac: { + fullUrl: `${ARTIFACT_BASE_URL}/elastic-agent-${kibanaVersion}-darwin-x86_64.tar.gz`, + filename: `elastic-agent-${kibanaVersion}-darwin-x86_64.tar.gz`, + unpackedDir: `elastic-agent-${kibanaVersion}-darwin-x86_64`, + }, + windows: { + fullUrl: `${ARTIFACT_BASE_URL}/elastic-agent-${kibanaVersion}-windows-x86_64.tar.gz`, + filename: `elastic-agent-${kibanaVersion}-windows-x86_64.tar.gz`, + unpackedDir: `elastic-agent-${kibanaVersion}-windows-x86_64`, + }, + deb: { + fullUrl: `${ARTIFACT_BASE_URL}/elastic-agent-${kibanaVersion}-amd64.deb`, + filename: `elastic-agent-${kibanaVersion}-amd64.deb`, + unpackedDir: `elastic-agent-${kibanaVersion}-amd64`, + }, + rpm: { + fullUrl: `${ARTIFACT_BASE_URL}/elastic-agent-${kibanaVersion}-x86_64.rpm`, + filename: `elastic-agent-${kibanaVersion}-x86_64.rpm`, + unpackedDir: `elastic-agent-${kibanaVersion}-x86_64`, + }, + }; + + return artifactMap[platform]; +} + +export function getInstallCommandForPlatform( + platform: PLATFORM_TYPE, + esHost: string, + serviceToken: string, + policyId?: string, + fleetServerHost?: string, + isProductionDeployment?: boolean, + sslCATrustedFingerprint?: string, + kibanaVersion?: string +): string { + const newLineSeparator = platform === 'windows' ? '`\n' : '\\\n'; + + const artifact = getArtifact(platform, kibanaVersion ?? ''); + const downloadCommand = + platform === 'windows' + ? [ + `wget ${artifact.fullUrl} -OutFile ${artifact.filename}`, + `Expand-Archive .\\${artifact.filename}`, + `cd ${artifact.unpackedDir}`, + ].join(` ${newLineSeparator}`) + : [ + `curl -L -O ${artifact.fullUrl}`, + `tar xzvf ${artifact.filename}`, + `cd ${artifact.unpackedDir}`, + ].join(` ${newLineSeparator}`); + + const commandArguments = []; + + if (isProductionDeployment && fleetServerHost) { + commandArguments.push(['url', fleetServerHost]); + } + + commandArguments.push(['fleet-server-es', esHost]); + commandArguments.push(['fleet-server-service-token', serviceToken]); + if (policyId) { + commandArguments.push(['fleet-server-policy', policyId]); + } + + if (sslCATrustedFingerprint) { + commandArguments.push(['fleet-server-es-ca-trusted-fingerprint', sslCATrustedFingerprint]); + } + + if (isProductionDeployment) { + commandArguments.push(['certificate-authorities', '']); + if (!sslCATrustedFingerprint) { + commandArguments.push(['fleet-server-es-ca', '']); + } + commandArguments.push(['fleet-server-cert', '']); + commandArguments.push(['fleet-server-cert-key', '']); + } + + const commandArgumentsStr = commandArguments.reduce((acc, [key, val]) => { + if (acc === '' && key === 'url') { + return `--${key}=${val}`; + } + const valOrEmpty = val ? `=${val}` : ''; + return (acc += ` ${newLineSeparator} --${key}${valOrEmpty}`); + }, ''); + + const commands = { + linux: `${downloadCommand} ${newLineSeparator}sudo ./elastic-agent install${commandArgumentsStr}`, + mac: `${downloadCommand} ${newLineSeparator}sudo ./elastic-agent install ${commandArgumentsStr}`, + windows: `${downloadCommand}${newLineSeparator}.\\elastic-agent.exe install ${commandArgumentsStr}`, + deb: `${downloadCommand} ${newLineSeparator}sudo elastic-agent enroll ${commandArgumentsStr}`, + rpm: `${downloadCommand} ${newLineSeparator}sudo elastic-agent enroll ${commandArgumentsStr}`, + }; + + return commands[platform]; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/components/index.ts index 5e927c5b0e3d6..a299013ab547e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/components/index.ts @@ -8,3 +8,4 @@ export * from '../../../components'; export * from './search_bar'; +export * from './fleet_server_instructions'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx index 9fdcc0f73297f..e1c6bbafa21a7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx @@ -28,11 +28,11 @@ import { useStartServices } from '../../../../hooks'; import { AgentPolicyPackageBadge } from '../../../../components'; -import { policyHasFleetServer } from '../../../agents/services/has_fleet_server'; - import { AgentPolicyDeleteProvider } from '../agent_policy_delete_provider'; import type { ValidationResults } from '../agent_policy_validation'; +import { policyHasFleetServer } from '../../../../services'; + import { useOutputOptions, DEFAULT_OUTPUT_VALUE } from './hooks'; interface Props { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx index e9b04aacecb68..44e87d7fb4e63 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx @@ -18,8 +18,7 @@ import { AgentUpgradeAgentModal, } from '../../components'; import { useAgentRefresh } from '../hooks'; -import { isAgentUpgradeable } from '../../../../services'; -import { policyHasFleetServer } from '../../services/has_fleet_server'; +import { isAgentUpgradeable, policyHasFleetServer } from '../../../../services'; export const AgentDetailsActionMenu: React.FunctionComponent<{ agent: Agent; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx index 7b3f1fedb160a..d5757419e8ea7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, useMemo, useCallback, useRef, useEffect } from 'react'; +import React, { useState, useMemo, useCallback, useRef, useEffect, useContext } from 'react'; import { EuiBasicTable, EuiButton, @@ -52,6 +52,8 @@ import { } from '../components'; import { useFleetServerUnhealthy } from '../hooks/use_fleet_server_unhealthy'; +import { agentFlyoutContext } from '..'; + import { AgentTableHeader } from './components/table_header'; import type { SelectionMode } from './components/bulk_actions'; import { SearchAndFilterBar } from './components/search_and_filter_bar'; @@ -201,6 +203,8 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { isOpen: false, }); + const flyoutContext = useContext(agentFlyoutContext); + // Agent actions states const [agentToReassign, setAgentToReassign] = useState(undefined); const [agentToUnenroll, setAgentToUnenroll] = useState(undefined); @@ -378,11 +382,8 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { // Fleet server unhealthy status const { isUnhealthy: isFleetServerUnhealthy } = useFleetServerUnhealthy(); const onClickAddFleetServer = useCallback(() => { - setEnrollmentFlyoutState({ - isOpen: true, - selectedPolicyId: agentPolicies.length > 0 ? agentPolicies[0].id : undefined, - }); - }, [agentPolicies]); + flyoutContext?.openFleetServerFlyout(); + }, [flyoutContext]); const columns = [ { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/enrollment_recommendation.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/enrollment_recommendation.tsx new file mode 100644 index 0000000000000..86990d84d5130 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/enrollment_recommendation.tsx @@ -0,0 +1,104 @@ +/* + * 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, { useContext } from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { useStartServices } from '../../../../hooks'; + +import { agentFlyoutContext } from '../..'; + +export const EnrollmentRecommendation: React.FunctionComponent<{ + showStandaloneTab: () => void; +}> = ({ showStandaloneTab }) => { + const flyoutContext = useContext(agentFlyoutContext); + + const { docLinks } = useStartServices(); + + return ( + <> + + + + + + + +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + + + + ), + }} + /> +
  • +
+
+ + + + + + + + + + + + + + + + + + ); +}; 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 deleted file mode 100644 index 93547bba36de8..0000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx +++ /dev/null @@ -1,748 +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, { useState, useMemo, useCallback, useEffect } from 'react'; -import { - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiText, - EuiLink, - EuiSteps, - EuiCode, - EuiCodeBlock, - EuiCallOut, - EuiRadioGroup, - EuiFieldText, - EuiForm, - EuiFormErrorText, -} from '@elastic/eui'; -import type { EuiStepProps } from '@elastic/eui/src/components/steps/step'; -import styled from 'styled-components'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { DownloadStep, SelectCreateAgentPolicy } from '../../../../components'; -import { - useStartServices, - useDefaultOutput, - sendGenerateServiceToken, - usePlatform, - useGetAgentPolicies, - useGetSettings, - sendPutSettings, - sendGetFleetStatus, - useFleetStatus, - useLink, -} from '../../../../hooks'; -import type { PLATFORM_TYPE } from '../../../../hooks'; -import type { AgentPolicy } from '../../../../types'; -import { FleetServerOnPremRequiredCallout } from '../../components'; - -import { policyHasFleetServer } from '../../services/has_fleet_server'; - -import { PlatformSelector } from '../../../../../../components/enrollment_instructions/manual/platform_selector'; - -import type { CommandsByPlatform } from './install_command_utils'; -import { getInstallCommandForPlatform } from './install_command_utils'; - -const URL_REGEX = /^(https):\/\/[^\s$.?#].[^\s]*$/gm; -const REFRESH_INTERVAL = 10000; - -type DeploymentMode = 'production' | 'quickstart'; - -const FlexItemWithMinWidth = styled(EuiFlexItem)` - min-width: 0px; - max-width: 100%; -`; - -export const ContentWrapper = styled(EuiFlexGroup)` - height: 100%; - margin: 0 auto; - max-width: 800px; -`; - -// Otherwise the copy button is over the text -const CommandCode = styled.div.attrs(() => { - return { - className: 'eui-textBreakAll', - }; -})` - margin-right: ${(props) => props.theme.eui.paddingSizes.m}; -`; - -export const ServiceTokenStep = ({ - disabled = false, - serviceToken, - getServiceToken, - isLoadingServiceToken, -}: { - disabled?: boolean; - serviceToken?: string; - getServiceToken: () => void; - isLoadingServiceToken: boolean; -}): EuiStepProps => { - return { - title: i18n.translate('xpack.fleet.fleetServerSetup.stepGenerateServiceTokenTitle', { - defaultMessage: 'Generate a service token', - }), - status: disabled ? 'disabled' : undefined, - children: !disabled && ( - <> - - - - - {!serviceToken ? ( - - - { - getServiceToken(); - }} - data-test-subj="fleetServerGenerateServiceTokenBtn" - > - - - - - ) : ( - <> - - } - /> - - - - - - - - - - {serviceToken} - - - - - )} - - ), - }; -}; - -export const FleetServerCommandStep = ({ - serviceToken, - installCommand, - platform, - setPlatform, -}: { - serviceToken?: string; - installCommand: CommandsByPlatform; - platform: string; - setPlatform: (platform: PLATFORM_TYPE) => void; -}): EuiStepProps => { - const { docLinks } = useStartServices(); - - return { - title: i18n.translate('xpack.fleet.fleetServerSetup.stepInstallAgentTitle', { - defaultMessage: 'Start Fleet Server', - }), - status: !serviceToken ? 'disabled' : undefined, - children: serviceToken ? ( - <> - - - - - ), - }} - /> - - - - - ) : null, - }; -}; - -export const useFleetServerInstructions = (policyId?: string) => { - const { output } = useDefaultOutput(); - const { notifications } = useStartServices(); - const [serviceToken, setServiceToken] = useState(); - const [isLoadingServiceToken, setIsLoadingServiceToken] = useState(false); - const { platform, setPlatform } = usePlatform(); - const [deploymentMode, setDeploymentMode] = useState('production'); - const { data: settings, resendRequest: refreshSettings } = useGetSettings(); - const fleetServerHost = settings?.item.fleet_server_hosts?.[0]; - const esHost = output?.hosts?.[0]; - const sslCATrustedFingerprint: string | undefined = output?.ca_trusted_fingerprint; - - const installCommand = useMemo((): CommandsByPlatform => { - if (!serviceToken || !esHost) { - return { - linux: '', - mac: '', - windows: '', - deb: '', - rpm: '', - }; - } - - return getInstallCommandForPlatform( - esHost, - serviceToken, - policyId, - fleetServerHost, - deploymentMode === 'production', - sslCATrustedFingerprint - ); - }, [serviceToken, esHost, policyId, fleetServerHost, deploymentMode, sslCATrustedFingerprint]); - - const getServiceToken = useCallback(async () => { - setIsLoadingServiceToken(true); - try { - const { data } = await sendGenerateServiceToken(); - if (data?.value) { - setServiceToken(data?.value); - } - } catch (err) { - notifications.toasts.addError(err, { - title: i18n.translate('xpack.fleet.fleetServerSetup.errorGeneratingTokenTitleText', { - defaultMessage: 'Error generating token', - }), - }); - } - - setIsLoadingServiceToken(false); - }, [notifications.toasts]); - - const addFleetServerHost = useCallback( - async (host: string) => { - const res = await sendPutSettings({ - fleet_server_hosts: [host, ...(settings?.item.fleet_server_hosts || [])], - }); - if (res.error) { - throw res.error; - } - await refreshSettings(); - }, - [refreshSettings, settings?.item.fleet_server_hosts] - ); - - return { - addFleetServerHost, - fleetServerHost, - deploymentMode, - setDeploymentMode, - serviceToken, - getServiceToken, - isLoadingServiceToken, - installCommand, - platform, - setPlatform, - }; -}; - -const AgentPolicySelectionStep = ({ - selectedPolicy, - setPolicyId, - agentPolicies, - refreshAgentPolicies, -}: { - selectedPolicy?: AgentPolicy; - setPolicyId: (v?: string) => void; - agentPolicies: AgentPolicy[]; - refreshAgentPolicies: () => void; -}): EuiStepProps => { - return { - title: - agentPolicies.length === 0 - ? i18n.translate('xpack.fleet.fleetServerSetup.stepCreateAgentPolicyTitle', { - defaultMessage: 'Create an agent policy to host Fleet Server', - }) - : i18n.translate('xpack.fleet.fleetServerSetup.stepSelectAgentPolicyTitle', { - defaultMessage: 'Select an agent policy to host Fleet Server', - }), - status: undefined, - children: ( - <> - - - ), - }; -}; - -export const addFleetServerHostStep = ({ - addFleetServerHost, -}: { - addFleetServerHost: (v: string) => Promise; -}): EuiStepProps => { - return { - title: i18n.translate('xpack.fleet.fleetServerSetup.addFleetServerHostStepTitle', { - defaultMessage: 'Add your Fleet Server host', - }), - status: undefined, - children: , - }; -}; - -export const AddFleetServerHostStepContent = ({ - addFleetServerHost, -}: { - addFleetServerHost: (v: string) => Promise; -}) => { - const [calloutHost, setCalloutHost] = useState(); - const [isLoading, setIsLoading] = useState(false); - const [fleetServerHost, setFleetServerHost] = useState(''); - const [error, setError] = useState(); - const { notifications } = useStartServices(); - - const { getHref } = useLink(); - - const validate = useCallback( - (host: string) => { - if (host.match(URL_REGEX)) { - setError(undefined); - return true; - } else { - setError( - i18n.translate('xpack.fleet.fleetServerSetup.addFleetServerHostInvalidUrlError', { - defaultMessage: 'Valid https URL required.', - }) - ); - return false; - } - }, - [setError] - ); - - const onSubmit = useCallback(async () => { - try { - setIsLoading(true); - if (validate(fleetServerHost)) { - await addFleetServerHost(fleetServerHost); - setCalloutHost(fleetServerHost); - setFleetServerHost(''); - } else { - setCalloutHost(''); - } - } catch (err) { - notifications.toasts.addError(err, { - title: i18n.translate('xpack.fleet.fleetServerSetup.errorAddingFleetServerHostTitle', { - defaultMessage: 'Error adding Fleet Server host', - }), - }); - } finally { - setIsLoading(false); - } - }, [fleetServerHost, addFleetServerHost, validate, notifications.toasts]); - - const onChange = useCallback( - (e: React.ChangeEvent) => { - setFleetServerHost(e.target.value); - if (error) { - validate(e.target.value); - } - }, - [error, validate, setFleetServerHost] - ); - - return ( - - - 8220 }} - /> - - - - - - - - } - data-test-subj="fleetServerHostInput" - /> - {error && {error}} - - - - - - - - {calloutHost && ( - <> - - - } - > - - - - ), - }} - /> - - - )} - - ); -}; - -export const deploymentModeStep = ({ - deploymentMode, - setDeploymentMode, -}: { - deploymentMode: DeploymentMode; - setDeploymentMode: (v: DeploymentMode) => void; -}): EuiStepProps => { - return { - title: i18n.translate('xpack.fleet.fleetServerSetup.stepDeploymentModeTitle', { - defaultMessage: 'Choose a deployment mode for security', - }), - status: undefined, - children: ( - - ), - }; -}; - -const DeploymentModeStepContent = ({ - deploymentMode, - setDeploymentMode, -}: { - deploymentMode: DeploymentMode; - setDeploymentMode: (v: DeploymentMode) => void; -}) => { - const onChangeCallback = useCallback( - (v: string) => { - const value = v.split('_')[0]; - if (value === 'production' || value === 'quickstart') { - setDeploymentMode(value); - } - }, - [setDeploymentMode] - ); - - // radio id has to be unique so that the component works even if appears twice in DOM (Agents tab, Add agent flyout) - const radioSuffix = useMemo(() => Date.now(), []); - - return ( - <> - - - - - - - - ), - }} - /> - ), - }, - { - id: `production_${radioSuffix}`, - label: ( - - - - ), - }} - /> - ), - }, - ]} - idSelected={`${deploymentMode}_${radioSuffix}`} - onChange={onChangeCallback} - name={`radio group ${radioSuffix}`} - /> - - ); -}; - -const WaitingForFleetServerStep = ({ - status, -}: { - status: 'loading' | 'disabled' | 'complete'; -}): EuiStepProps => { - return { - title: i18n.translate('xpack.fleet.fleetServerSetup.stepWaitingForFleetServerTitle', { - defaultMessage: 'Waiting for Fleet Server to connect...', - }), - status, - children: undefined, - }; -}; - -const CompleteStep = (): EuiStepProps => { - const fleetStatus = useFleetStatus(); - - const onContinueClick = () => { - fleetStatus.refresh(); - }; - - return { - title: i18n.translate('xpack.fleet.fleetServerSetup.stepFleetServerCompleteTitle', { - defaultMessage: 'Fleet Server connected', - }), - status: 'complete', - children: ( - <> - - - - - - - ), - }; -}; - -const findPolicyById = (policies: AgentPolicy[], id: string | undefined) => { - if (!id) return undefined; - return policies.find((p) => p.id === id); -}; - -export const OnPremInstructions: React.FC = () => { - const { notifications } = useStartServices(); - - const { data, resendRequest: refreshAgentPolicies } = useGetAgentPolicies({ full: true }); - - const agentPolicies = useMemo( - () => (data ? data.items.filter((item) => policyHasFleetServer(item)) : []), - [data] - ); - - // Select default value - let defaultValue = ''; - if (agentPolicies.length) { - defaultValue = agentPolicies[0].id; - } - const [policyId, setPolicyId] = useState(defaultValue); - const selectedPolicy = findPolicyById(agentPolicies, policyId); - - const { - serviceToken, - getServiceToken, - isLoadingServiceToken, - installCommand, - platform, - setPlatform, - deploymentMode, - setDeploymentMode, - fleetServerHost, - addFleetServerHost, - } = useFleetServerInstructions(policyId); - - const { docLinks } = useStartServices(); - - const [isWaitingForFleetServer, setIsWaitingForFleetServer] = useState(true); - - useEffect(() => { - const interval = setInterval(async () => { - try { - const res = await sendGetFleetStatus(); - if (res.error) { - throw res.error; - } - if (res.data?.isReady && !res.data?.missing_requirements?.includes('fleet_server')) { - setIsWaitingForFleetServer(false); - } - } catch (err) { - notifications.toasts.addError(err, { - title: i18n.translate('xpack.fleet.fleetServerSetup.errorRefreshingFleetServerStatus', { - defaultMessage: 'Error refreshing Fleet Server status', - }), - }); - } - }, REFRESH_INTERVAL); - - return () => clearInterval(interval); - }, [notifications.toasts]); - - return ( - <> - - - -

- -

- - - - - ), - }} - /> -
- - - - ); -}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/index.tsx index 3118fda75400f..293be6d8b98e4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/index.tsx @@ -6,4 +6,4 @@ */ export { CloudInstructions } from './fleet_server_cloud_instructions'; -export * from './fleet_server_on_prem_instructions'; +export { EnrollmentRecommendation } from './enrollment_recommendation'; 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 deleted file mode 100644 index b73eb547b6ddf..0000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts +++ /dev/null @@ -1,65 +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 { PLATFORM_TYPE } from '../../../../hooks'; - -export type CommandsByPlatform = { - [key in PLATFORM_TYPE]: string; -}; - -export function getInstallCommandForPlatform( - esHost: string, - serviceToken: string, - policyId?: string, - fleetServerHost?: string, - isProductionDeployment?: boolean, - sslCATrustedFingerprint?: string -): CommandsByPlatform { - const commandArguments: Array<[string, string] | [string]> = []; - - if (isProductionDeployment && fleetServerHost) { - commandArguments.push(['url', fleetServerHost]); - } - - commandArguments.push(['fleet-server-es', esHost]); - commandArguments.push(['fleet-server-service-token', serviceToken]); - if (policyId) { - commandArguments.push(['fleet-server-policy', policyId]); - } - - if (sslCATrustedFingerprint) { - commandArguments.push(['fleet-server-es-ca-trusted-fingerprint', sslCATrustedFingerprint]); - } - - if (isProductionDeployment) { - commandArguments.push(['certificate-authorities', '']); - if (!sslCATrustedFingerprint) { - commandArguments.push(['fleet-server-es-ca', '']); - } - commandArguments.push(['fleet-server-cert', '']); - commandArguments.push(['fleet-server-cert-key', '']); - } - - 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('windows')}`, - deb: `sudo elastic-agent enroll ${commandArgumentsStr()}`, - rpm: `sudo elastic-agent enroll ${commandArgumentsStr()}`, - }; -} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx index bee7fbfd85371..b4818a6908adf 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx @@ -13,9 +13,11 @@ import { useStartServices, sendGetPermissionsCheck } from '../../../hooks'; import { FleetServerMissingPrivileges } from '../components/fleet_server_callouts'; -import { Loading } from '../../../components'; +import { Loading } from '../components'; -import { CloudInstructions, OnPremInstructions } from './components'; +import { FleetServerInstructions } from '../../../components'; + +import { CloudInstructions, EnrollmentRecommendation } from './components'; const FlexItemWithMinWidth = styled(EuiFlexItem)` min-width: 0px; @@ -27,7 +29,16 @@ const ContentWrapper = styled(EuiFlexGroup)` margin: 0 auto; `; -export const FleetServerRequirementPage = () => { +export const FleetServerRequirementPage: React.FunctionComponent< + | { + showEnrollmentRecommendation?: false; + showStandaloneTab?: never; + } + | { + showEnrollmentRecommendation?: true; + showStandaloneTab: () => void; + } +> = ({ showStandaloneTab = () => {}, showEnrollmentRecommendation = true }) => { const startService = useStartServices(); const deploymentUrl = startService.cloud?.deploymentUrl; @@ -56,12 +67,7 @@ export const FleetServerRequirementPage = () => { return ( <> - + {deploymentUrl ? ( @@ -69,8 +75,10 @@ export const FleetServerRequirementPage = () => { ) : permissionsError ? ( + ) : showEnrollmentRecommendation ? ( + ) : ( - + )} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx index d2434e109ca31..57da2fcf36d76 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx @@ -5,13 +5,13 @@ * 2.0. */ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { createContext, useCallback, useEffect, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { Router, Route, Switch, useHistory } from 'react-router-dom'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiPortal } from '@elastic/eui'; import { FLEET_ROUTING_PATHS } from '../../constants'; -import { Loading, Error, AgentEnrollmentFlyout } from '../../components'; +import { Loading, Error, AgentEnrollmentFlyout, FleetServerFlyout } from '../../components'; import { useConfig, useFleetStatus, useBreadcrumbs, useAuthz, useGetSettings } from '../../hooks'; import { DefaultLayout, WithoutHeaderLayout } from '../../layouts'; @@ -21,6 +21,18 @@ import { AgentDetailsPage } from './agent_details_page'; import { NoAccessPage } from './error_pages/no_access'; import { FleetServerUpgradeModal } from './components/fleet_server_upgrade_modal'; +// TODO: Move all instances of toggling these flyouts to a global context object to avoid cases in which +// we can render duplicate "stacked" flyouts +export const agentFlyoutContext = createContext< + | { + openEnrollmentFlyout: () => void; + closeEnrollmentFlyout: () => void; + openFleetServerFlyout: () => void; + closeFleetServerFlyout: () => void; + } + | undefined +>(undefined); + export const AgentsApp: React.FunctionComponent = () => { useBreadcrumbs('agent_list'); const history = useHistory(); @@ -31,6 +43,8 @@ export const AgentsApp: React.FunctionComponent = () => { const settings = useGetSettings(); const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); + const [isFleetServerFlyoutOpen, setIsFleetServerFlyoutOpen] = useState(false); + const [fleetServerModalVisible, setFleetServerModalVisible] = useState(false); const onCloseFleetServerModal = useCallback(() => { setFleetServerModalVisible(false); @@ -81,15 +95,6 @@ export const AgentsApp: React.FunctionComponent = () => { const rightColumn = hasOnlyFleetServerMissingRequirement ? ( <> - {isEnrollmentFlyoutOpen && ( - - setIsEnrollmentFlyoutOpen(false)} - /> - - )} { ) : undefined; return ( - - - - - - - - {fleetServerModalVisible && ( - - )} - {hasOnlyFleetServerMissingRequirement ? ( - - ) : ( - - )} - - - - + setIsEnrollmentFlyoutOpen(true), + closeEnrollmentFlyout: () => setIsEnrollmentFlyoutOpen(false), + openFleetServerFlyout: () => setIsFleetServerFlyoutOpen(true), + closeFleetServerFlyout: () => setIsFleetServerFlyoutOpen(false), + }} + > + + + + + + + + {fleetServerModalVisible && ( + + )} + {hasOnlyFleetServerMissingRequirement ? ( + + ) : ( + + )} + + + + + {isEnrollmentFlyoutOpen && ( + + setIsEnrollmentFlyoutOpen(false)} + /> + + )} + + {isFleetServerFlyoutOpen && ( + + setIsFleetServerFlyoutOpen(false)} /> + + )} + + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx index 151a3d5354c17..aeb49928f3d1e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx @@ -27,7 +27,7 @@ describe('useFleetServerHostsForm', () => { const { result } = testRenderer.renderHook(() => useFleetServerHostsForm([], onSucess)); act(() => - result.current.fleetServerHostsInput.props.onChange(['http://test.fr', 'http://test.fr']) + result.current.fleetServerHostsInput.props.onChange(['https://test.fr', 'https://test.fr']) ); await act(() => result.current.submit()); @@ -54,7 +54,7 @@ describe('useFleetServerHostsForm', () => { testRenderer.startServices.http.post.mockResolvedValue({}); const { result } = testRenderer.renderHook(() => useFleetServerHostsForm([], onSucess)); - act(() => result.current.fleetServerHostsInput.props.onChange(['http://test.fr'])); + act(() => result.current.fleetServerHostsInput.props.onChange(['https://test.fr'])); await act(() => result.current.submit()); expect(onSucess).toBeCalled(); @@ -67,14 +67,14 @@ describe('useFleetServerHostsForm', () => { const { result } = testRenderer.renderHook(() => useFleetServerHostsForm([], onSucess)); act(() => - result.current.fleetServerHostsInput.props.onChange(['http://test.fr', 'http://test.fr']) + result.current.fleetServerHostsInput.props.onChange(['https://test.fr', 'https://test.fr']) ); await act(() => result.current.submit()); expect(onSucess).not.toBeCalled(); expect(result.current.isDisabled).toBeTruthy(); - act(() => result.current.fleetServerHostsInput.props.onChange(['http://test.fr'])); + act(() => result.current.fleetServerHostsInput.props.onChange(['https://test.fr'])); expect(result.current.isDisabled).toBeFalsy(); await act(() => result.current.submit()); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx index ac196576ba889..b19e8ddda0427 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx @@ -15,7 +15,7 @@ import { isDiffPathProtocol } from '../../../../../../../common'; import { useConfirmModal } from '../../hooks/use_confirm_modal'; import { getAgentAndPolicyCount } from '../../services/agent_and_policies_count'; -const URL_REGEX = /^(https?):\/\/[^\s$.?#].[^\s]*$/gm; +const URL_REGEX = /^(https):\/\/[^\s$.?#].[^\s]*$/gm; const ConfirmTitle = () => ( { return { ...module, useGetSettings: jest.fn(), - sendGetFleetStatus: jest.fn(), sendGetOneAgentPolicy: jest.fn(), useGetAgents: jest.fn(), useGetAgentPolicies: jest.fn(), @@ -36,14 +35,14 @@ jest.mock('../../applications/fleet/sections/agents/hooks/use_fleet_server_unhea }); jest.mock( - '../../applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions', + '../../applications/fleet/components/fleet_server_instructions/hooks/use_advanced_form', () => { const module = jest.requireActual( - '../../applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions' + '../../applications/fleet/components/fleet_server_instructions/hooks/use_advanced_form' ); return { ...module, - useFleetServerInstructions: jest.fn(), + useAdvancedForm: jest.fn(), }; } ); @@ -82,6 +81,9 @@ jest.mock('./steps', () => { }; }); -jest.mock('../../applications/fleet/sections/agents/services/has_fleet_server', () => { - return { policyHasFleetServer: jest.fn().mockReturnValue(true) }; +jest.mock('../../services', () => { + return { + ...jest.requireActual('../../services'), + policyHasFleetServer: jest.fn().mockReturnValue(true), + }; }); diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx index b2680129dd7d5..6e46ec90d5faf 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx @@ -25,8 +25,7 @@ import { useFleetStatus, } from '../../hooks'; -import { useFleetServerInstructions } from '../../applications/fleet/sections/agents/agent_requirements_page/components'; - +import { useAdvancedForm } from '../../applications/fleet/components/fleet_server_instructions/hooks'; import { useFleetServerUnhealthy } from '../../applications/fleet/sections/agents/hooks/use_fleet_server_unhealthy'; import type { FlyOutProps } from './types'; @@ -44,8 +43,8 @@ const TestComponent = (props: FlyOutProps) => ( ); -const setup = async (props?: FlyOutProps) => { - const testBed = await registerTestBed(TestComponent)(props); +const setup = (props?: FlyOutProps) => { + const testBed = registerTestBed(TestComponent)(props); const { find, component } = testBed; return { @@ -78,7 +77,7 @@ const testAgentPolicy: AgentPolicy = { describe('', () => { let testBed: TestBed; - beforeEach(async () => { + beforeEach(() => { (useGetSettings as jest.Mock).mockReturnValue({ data: { item: { fleet_server_hosts: ['test'] } }, }); @@ -93,13 +92,24 @@ describe('', () => { data: { item: { package_policies: [] } }, }); - (useFleetServerInstructions as jest.Mock).mockReturnValue({ + (useAdvancedForm as jest.Mock).mockReturnValue({ + eligibleFleetServerPolicies: [{ name: 'test', id: 'test' }], + refreshEligibleFleetServerPolicies: jest.fn(), + fleetServerPolicyId: 'test', + setFleetServerPolicyId: jest.fn(), + isFleetServerReady: true, serviceToken: 'test', - getServiceToken: jest.fn(), isLoadingServiceToken: false, - installCommand: jest.fn(), - platform: 'test', - setPlatform: jest.fn(), + generateServiceToken: jest.fn(), + fleetServerHostForm: { + saveFleetServerHost: jest.fn(), + fleetServerHost: 'https://test.server:8220', + setFleetServerHost: jest.fn(), + error: '', + validateFleetServerHost: jest.fn(), + }, + deploymentMode: 'quickstart', + setDeploymentMode: jest.fn(), }); (useGetAgents as jest.Mock).mockReturnValue({ @@ -111,8 +121,8 @@ describe('', () => { refreshAgentPolicies: jest.fn(), }); - await act(async () => { - testBed = await setup({ + act(() => { + testBed = setup({ onClose: jest.fn(), }); testBed.component.update(); @@ -123,15 +133,15 @@ describe('', () => { jest.clearAllMocks(); }); - it('should show loading when agent policies are loading', async () => { + it('should show loading when agent policies are loading', () => { (useAgentEnrollmentFlyoutData as jest.Mock).mockReturnValue?.({ agentPolicies: [], refreshAgentPolicies: jest.fn(), isLoadingInitialAgentPolicies: true, }); - await act(async () => { - testBed = await setup({ + act(() => { + testBed = setup({ onClose: jest.fn(), }); testBed.component.update(); @@ -143,7 +153,7 @@ describe('', () => { }); describe('managed instructions', () => { - it('uses the agent policy selection step', async () => { + it('uses the agent policy selection step', () => { const { exists } = testBed; expect(exists('agentEnrollmentFlyout')).toBe(true); @@ -152,10 +162,10 @@ describe('', () => { }); describe('with a specific policy', () => { - beforeEach(async () => { + beforeEach(() => { jest.clearAllMocks(); - await act(async () => { - testBed = await setup({ + act(() => { + testBed = setup({ agentPolicy: testAgentPolicy, onClose: jest.fn(), }); @@ -172,10 +182,10 @@ describe('', () => { }); describe('with a specific policy when no agentPolicies set', () => { - beforeEach(async () => { + beforeEach(() => { jest.clearAllMocks(); - await act(async () => { - testBed = await setup({ + act(() => { + testBed = setup({ agentPolicy: testAgentPolicy, onClose: jest.fn(), }); @@ -189,41 +199,41 @@ describe('', () => { expect(exists('agent-enrollment-key-selection-step')).toBe(true); }); }); - }); - // Skipped due to UI changing in https://github.com/elastic/kibana/issues/125534. These tests should be rethought overall - // to provide value around the new flyout structure - describe.skip('standalone instructions', () => { - it('uses the agent policy selection step', async () => { - const { exists, actions } = testBed; - actions.goToStandaloneTab(); + // Skipped due to UI changing in https://github.com/elastic/kibana/issues/125534. These tests should be rethought overall + // to provide value around the new flyout structure + describe.skip('standalone instructions', () => { + it('uses the agent policy selection step', async () => { + const { exists, actions } = testBed; + actions.goToStandaloneTab(); - expect(exists('agentEnrollmentFlyout')).toBe(true); - expect(exists('agent-policy-selection-step')).toBe(true); - expect(exists('agent-enrollment-key-selection-step')).toBe(false); - }); + expect(exists('agentEnrollmentFlyout')).toBe(true); + expect(exists('agent-policy-selection-step')).toBe(true); + expect(exists('agent-enrollment-key-selection-step')).toBe(false); + }); - describe('with a specific policy', () => { - beforeEach(async () => { - jest.clearAllMocks(); - await act(async () => { - testBed = await setup({ - agentPolicy: testAgentPolicy, - onClose: jest.fn(), + describe('with a specific policy', () => { + beforeEach(() => { + jest.clearAllMocks(); + act(() => { + testBed = setup({ + agentPolicy: testAgentPolicy, + onClose: jest.fn(), + }); + testBed.component.update(); }); - testBed.component.update(); }); - }); - it('does not use either of the agent policy selection or enrollment key steps', () => { - const { exists, actions } = testBed; - jest.clearAllMocks(); + it('does not use either of the agent policy selection or enrollment key steps', () => { + const { exists, actions } = testBed; + jest.clearAllMocks(); - actions.goToStandaloneTab(); + actions.goToStandaloneTab(); - expect(exists('agentEnrollmentFlyout')).toBe(true); - expect(exists('agent-policy-selection-step')).toBe(false); - expect(exists('agent-enrollment-key-selection-step')).toBe(false); + expect(exists('agentEnrollmentFlyout')).toBe(true); + expect(exists('agent-policy-selection-step')).toBe(false); + expect(exists('agent-enrollment-key-selection-step')).toBe(false); + }); }); }); }); diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_select_create.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_select_create.tsx index 81ec1236920b8..304239ac77dad 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_select_create.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_select_create.tsx @@ -20,7 +20,7 @@ import { AgentPolicySelection } from '.'; interface Props { agentPolicies: AgentPolicy[]; - selectedPolicy?: AgentPolicy; + selectedPolicyId?: string; setSelectedPolicyId: (agentPolicyId?: string) => void; excludeFleetServer?: boolean; withKeySelection: boolean; @@ -34,7 +34,7 @@ export const SelectCreateAgentPolicy: React.FC = ({ agentPolicies, excludeFleetServer, setSelectedPolicyId, - selectedPolicy, + selectedPolicyId, withKeySelection, selectedApiKeyId, onKeyChange, @@ -111,7 +111,7 @@ export const SelectCreateAgentPolicy: React.FC = ({ onKeyChange={onKeyChange} excludeFleetServer={excludeFleetServer} onClickCreatePolicy={onClickCreatePolicy} - selectedPolicy={selectedPolicy} + selectedPolicyId={selectedPolicyId} setSelectedPolicyId={setSelectedPolicyId} isFleetServerPolicy={isFleetServerPolicy} /> diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_selection.test.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_selection.test.tsx index c5a6076308525..4c02ddeeaaf27 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_selection.test.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_selection.test.tsx @@ -23,7 +23,7 @@ describe('step select agent policy', () => { (renderResult = testRenderer.render( void; excludeFleetServer?: boolean; onClickCreatePolicy: () => void; @@ -50,23 +50,10 @@ type Props = { } ); -const resolveAgentId = ( - agentPolicies: AgentPolicy[], - selectedAgentPolicyId?: string -): undefined | string => { - if (agentPolicies.length && !selectedAgentPolicyId) { - if (agentPolicies.length === 1) { - return agentPolicies[0].id; - } - } - - return selectedAgentPolicyId; -}; - export const AgentPolicySelection: React.FC = (props) => { const { agentPolicies, - selectedPolicy, + selectedPolicyId, setSelectedPolicyId, excludeFleetServer, onClickCreatePolicy, @@ -75,17 +62,6 @@ export const AgentPolicySelection: React.FC = (props) => { const hasFleetAllPrivileges = useAuthz().fleet.all; - useEffect( - function useDefaultAgentPolicyEffect() { - const resolvedId = resolveAgentId(agentPolicies, selectedPolicy?.id); - // find AgentPolicy - if (resolvedId !== selectedPolicy?.id) { - setSelectedPolicyId(resolvedId); - } - }, - [agentPolicies, setSelectedPolicyId, selectedPolicy] - ); - const onChangeCallback = (event: React.ChangeEvent) => { const { value } = event.target; setSelectedPolicyId(value); @@ -144,7 +120,7 @@ export const AgentPolicySelection: React.FC = (props) => { value: agentPolicy.id, text: agentPolicy.name, }))} - value={selectedPolicy?.id} + value={selectedPolicyId} onChange={onChangeCallback} aria-label={i18n.translate( 'xpack.fleet.enrollmentStepAgentPolicy.policySelectAriaLabel', @@ -152,28 +128,28 @@ export const AgentPolicySelection: React.FC = (props) => { defaultMessage: 'Agent policy', } )} - hasNoInitialSelection={!selectedPolicy?.id} + hasNoInitialSelection={!selectedPolicyId} data-test-subj="agentPolicyDropdown" - isInvalid={!selectedPolicy?.id} + isInvalid={!selectedPolicyId} /> - {selectedPolicy?.id && !isFleetServerPolicy && ( + {selectedPolicyId && !isFleetServerPolicy && ( <> )} - {props.withKeySelection && props.onKeyChange && selectedPolicy?.id && ( + {props.withKeySelection && props.onKeyChange && selectedPolicyId && ( <> )} diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx index 5e6ea53261cd8..7dba93e5ddd3e 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx @@ -13,17 +13,19 @@ import { useFleetStatus, useGetAgents } from '../../hooks'; import { FleetServerRequirementPage } from '../../applications/fleet/sections/agents/agent_requirements_page'; -import { policyHasFleetServer } from '../../applications/fleet/sections/agents/services/has_fleet_server'; - import { FLEET_SERVER_PACKAGE } from '../../constants'; import { useFleetServerUnhealthy } from '../../applications/fleet/sections/agents/hooks/use_fleet_server_unhealthy'; import { Loading } from '..'; +import { policyHasFleetServer } from '../../services'; + +import { AdvancedTab } from '../../applications/fleet/components/fleet_server_instructions/advanced_tab'; + import type { InstructionProps } from './types'; -import { ManagedSteps, StandaloneSteps, FleetServerSteps } from './steps'; +import { ManagedSteps, StandaloneSteps } from './steps'; import { DefaultMissingRequirements } from './default_missing_requirements'; export const Instructions = (props: InstructionProps) => { @@ -35,6 +37,7 @@ export const Instructions = (props: InstructionProps) => { selectionType, setSelectionType, mode, + setMode, isIntegrationFlow, } = props; const fleetStatus = useFleetStatus(); @@ -86,7 +89,7 @@ export const Instructions = (props: InstructionProps) => { if (mode === 'managed') { if (showFleetServerEnrollment) { - return ; + return setMode('standalone')} />; } else if (showAgentEnrollment) { return ( <> @@ -101,11 +104,7 @@ export const Instructions = (props: InstructionProps) => { )} - {isFleetServerPolicySelected ? ( - - ) : ( - - )} + {isFleetServerPolicySelected ? : } ); } diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/agent_policy_selection_step.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/agent_policy_selection_step.tsx index c26bfd3f0e2b8..a0828bb72f489 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/agent_policy_selection_step.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/agent_policy_selection_step.tsx @@ -40,7 +40,7 @@ export const AgentPolicySelectionStep = ({ <> = ({ return ; }; - -export const FleetServerSteps: React.FunctionComponent = ({ - agentPolicy, - agentPolicies, - selectedPolicy, - setSelectedPolicyId, - refreshAgentPolicies, -}) => { - const [selectedApiKeyId, setSelectedAPIKeyId] = useState(); - - const apiKey = useGetOneEnrollmentAPIKey(selectedApiKeyId); - const apiKeyData = apiKey?.data; - const fleetServerInstructions = useFleetServerInstructions(apiKeyData?.item?.policy_id); - - const fleetServerSteps = useMemo(() => { - const { - serviceToken, - getServiceToken, - isLoadingServiceToken, - installCommand: managedInstallCommands, - platform, - setPlatform, - deploymentMode, - setDeploymentMode, - addFleetServerHost, - } = fleetServerInstructions; - - return [ - deploymentModeStep({ deploymentMode, setDeploymentMode }), - addFleetServerHostStep({ addFleetServerHost }), - ServiceTokenStep({ serviceToken, getServiceToken, isLoadingServiceToken }), - FleetServerCommandStep({ - serviceToken, - installCommand: managedInstallCommands, - platform, - setPlatform, - }), - ]; - }, [fleetServerInstructions]); - - const instructionsSteps = useMemo(() => { - const steps: EuiContainedStepProps[] = !agentPolicy - ? [ - AgentPolicySelectionStep({ - selectedPolicy, - agentPolicies, - selectedApiKeyId, - setSelectedAPIKeyId, - setSelectedPolicyId, - refreshAgentPolicies, - }), - ] - : [ - AgentEnrollmentKeySelectionStep({ - selectedPolicy, - selectedApiKeyId, - setSelectedAPIKeyId, - }), - ]; - - steps.push(...fleetServerSteps); - - return steps; - }, [ - agentPolicy, - selectedPolicy, - agentPolicies, - selectedApiKeyId, - setSelectedPolicyId, - refreshAgentPolicies, - fleetServerSteps, - ]); - - return ; -}; diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_managed_agent_step.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_managed_agent_step.tsx index 59f6fdeafe727..bbb3d8d4794c3 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_managed_agent_step.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_managed_agent_step.tsx @@ -14,7 +14,7 @@ import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/st import type { GetOneEnrollmentAPIKeyResponse } from '../../../../common/types/rest_spec/enrollment_api_key'; import { InstallSection } from '../../enrollment_instructions/install_section'; -import type { CommandsByPlatform } from '../../../applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils'; +import type { CommandsByPlatform } from '../../../applications/fleet/components/fleet_server_instructions/utils/install_command_utils'; import type { K8sMode } from '../types'; diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_standalone_agent_step.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_standalone_agent_step.tsx index fb6ddfd393dcc..74ce555f7c2e8 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_standalone_agent_step.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_standalone_agent_step.tsx @@ -10,7 +10,8 @@ import { i18n } from '@kbn/i18n'; import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; -import type { CommandsByPlatform } from '../../../applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils'; +import type { CommandsByPlatform } from '../../../applications/fleet/components/fleet_server_instructions/utils/install_command_utils'; + import { InstallSection } from '../../enrollment_instructions/install_section'; import type { K8sMode } from '../types'; diff --git a/x-pack/plugins/fleet/public/components/enrollment_instructions/install_section.tsx b/x-pack/plugins/fleet/public/components/enrollment_instructions/install_section.tsx index 0cf43902db7e2..1ebe68b8c5282 100644 --- a/x-pack/plugins/fleet/public/components/enrollment_instructions/install_section.tsx +++ b/x-pack/plugins/fleet/public/components/enrollment_instructions/install_section.tsx @@ -7,13 +7,12 @@ import React from 'react'; +import type { CommandsByPlatform } from '../../applications/fleet/components/fleet_server_instructions/utils'; + import { InstallationMessage } from '../agent_enrollment_flyout/installation_message'; import type { K8sMode } from '../agent_enrollment_flyout/types'; - -import type { CommandsByPlatform } from '../../applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils'; - -import { PlatformSelector } from './manual/platform_selector'; +import { PlatformSelector } from '../platform_selector'; interface Props { installCommand: CommandsByPlatform; diff --git a/x-pack/plugins/fleet/public/components/enrollment_instructions/standalone/index.tsx b/x-pack/plugins/fleet/public/components/enrollment_instructions/standalone/index.tsx index 89c1dfe3cac37..75378cdc86378 100644 --- a/x-pack/plugins/fleet/public/components/enrollment_instructions/standalone/index.tsx +++ b/x-pack/plugins/fleet/public/components/enrollment_instructions/standalone/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { CommandsByPlatform } from '../../../applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils'; +import type { CommandsByPlatform } from '../../../applications/fleet/components/fleet_server_instructions/utils/install_command_utils'; import type { K8sMode } from '../../agent_enrollment_flyout/types'; export const StandaloneInstructions = ( diff --git a/x-pack/plugins/fleet/public/components/index.ts b/x-pack/plugins/fleet/public/components/index.ts index 9df4182bc8a4e..723b376699e07 100644 --- a/x-pack/plugins/fleet/public/components/index.ts +++ b/x-pack/plugins/fleet/public/components/index.ts @@ -23,3 +23,4 @@ export { AddAgentHelpPopover } from './add_agent_help_popover'; export { EuiButtonWithTooltip } from './eui_button_with_tooltip'; export * from './link_and_revision'; export * from './agent_enrollment_flyout'; +export * from './platform_selector'; diff --git a/x-pack/plugins/fleet/public/components/enrollment_instructions/manual/platform_selector.tsx b/x-pack/plugins/fleet/public/components/platform_selector.tsx similarity index 96% rename from x-pack/plugins/fleet/public/components/enrollment_instructions/manual/platform_selector.tsx rename to x-pack/plugins/fleet/public/components/platform_selector.tsx index e03e43907f829..ae18f56b4b3ac 100644 --- a/x-pack/plugins/fleet/public/components/enrollment_instructions/manual/platform_selector.tsx +++ b/x-pack/plugins/fleet/public/components/platform_selector.tsx @@ -10,8 +10,8 @@ import styled from 'styled-components'; import { EuiSpacer, EuiCodeBlock, EuiButtonGroup, EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { PLATFORM_TYPE } from '../../../hooks'; -import { PLATFORM_OPTIONS, usePlatform } from '../../../hooks'; +import type { PLATFORM_TYPE } from '../hooks'; +import { PLATFORM_OPTIONS, usePlatform } from '../hooks'; interface Props { linuxCommand: string; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/has_fleet_server.ts b/x-pack/plugins/fleet/public/services/has_fleet_server.ts similarity index 79% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/has_fleet_server.ts rename to x-pack/plugins/fleet/public/services/has_fleet_server.ts index c10049303234c..e1100d6447aa2 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/has_fleet_server.ts +++ b/x-pack/plugins/fleet/public/services/has_fleet_server.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { AgentPolicy, PackagePolicy } from '../../../types'; -import { FLEET_SERVER_PACKAGE } from '../../../constants'; +import { FLEET_SERVER_PACKAGE } from '../constants'; +import type { AgentPolicy, PackagePolicy } from '../types'; export function policyHasFleetServer(agentPolicy: AgentPolicy) { return agentPolicy.package_policies?.some( diff --git a/x-pack/plugins/fleet/public/services/index.ts b/x-pack/plugins/fleet/public/services/index.ts index 306b081dce6c4..9de918d01d707 100644 --- a/x-pack/plugins/fleet/public/services/index.ts +++ b/x-pack/plugins/fleet/public/services/index.ts @@ -44,3 +44,4 @@ export { export * from './pkg_key_from_package_info'; export * from './ui_extensions'; export * from './increment_policy_name'; +export * from './has_fleet_server'; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 9840a808ee27b..3cdeff1d91709 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -12792,7 +12792,6 @@ "xpack.fleet.fleetServerSetup.cloudDeploymentLink": "Modifier le déploiement", "xpack.fleet.fleetServerSetup.cloudSetupText": "Un serveur Fleet est nécessaire pour enregistrer des agents avec Fleet. Le moyen le plus simple d’en obtenir un est d’ajouter un serveur d’intégration, qui prend en charge l’intégration du serveur Fleet. Vous pouvez l’ajouter à votre déploiement dans la console cloud. Pour en savoir plus, consultez {link}", "xpack.fleet.fleetServerSetup.cloudSetupTitle": "Activer un serveur Fleet", - "xpack.fleet.fleetServerSetup.continueButton": "Continuer", "xpack.fleet.fleetServerSetup.deploymentModeProductionOption": "{production} : fournissez vos propres certificats. Cette option demande aux agents de préciser une clé de certificat lors de leur enregistrement avec Fleet", "xpack.fleet.fleetServerSetup.deploymentModeQuickStartOption": "{quickStart} : le serveur Fleet génère un certificat autosigné. Les agents suivants doivent être enregistrés avec l'indicateur --insecure. Non recommandé pour les cas d'utilisation en production.", "xpack.fleet.fleetServerSetup.errorAddingFleetServerHostTitle": "Erreur lors de l'ajout de l'hôte du serveur Fleet", @@ -12801,23 +12800,16 @@ "xpack.fleet.fleetServerSetup.fleetSettingsLink": "Paramètres de Fleet", "xpack.fleet.fleetServerSetup.generateServiceTokenButton": "Générer un jeton de service", "xpack.fleet.fleetServerSetup.generateServiceTokenDescription": "Un jeton de service accorde au serveur Fleet les autorisations en écriture nécessaires dans Elasticsearch.", - "xpack.fleet.fleetServerSetup.installAgentDescription": "Depuis le répertoire de l'agent, copiez et exécutez la commande de démarrage rapide appropriée pour lancer un agent Elastic en tant que serveur Fleet à l'aide du jeton généré et d'un certificat autosigné. Reportez-vous au {userGuideLink} pour obtenir les instructions d'utilisation de vos propres certificats à des fins de déploiement de production. Toutes les commandes nécessitent des privilèges d'administrateur.", "xpack.fleet.fleetServerSetup.productionText": "Production", "xpack.fleet.fleetServerSetup.quickStartText": "Démarrage rapide", "xpack.fleet.fleetServerSetup.saveServiceTokenDescription": "Enregistrez les informations de votre jeton de service. Ce message s'affiche une seule fois.", "xpack.fleet.fleetServerSetup.serviceTokenLabel": "Jeton de service", "xpack.fleet.fleetServerSetup.setupGuideLink": "Guide de Fleet et d’Elastic Agent", - "xpack.fleet.fleetServerSetup.setupText": "Un serveur Fleet est nécessaire pour enregistrer des agents avec Fleet. Suivez les instructions ci-après pour configurer un serveur Fleet. Pour en savoir plus, consultez le {userGuideLink}.", - "xpack.fleet.fleetServerSetup.setupTitle": "Ajouter un serveur Fleet", "xpack.fleet.fleetServerSetup.stepCreateAgentPolicyTitle": "Créer une stratégie d’agent pour héberger le serveur Fleet", "xpack.fleet.fleetServerSetup.stepDeploymentModeDescriptionText": "Fleet utilise le protocole TLS (Transport Layer Security) pour chiffrer le trafic entre les agents Elastic et d'autres composants de la Suite Elastic. Sélectionnez un mode de déploiement pour définir comment vous souhaitez gérer les certificats. Votre sélection impactera la commande de configuration du serveur Fleet qui s'affichera à une étape ultérieure.", "xpack.fleet.fleetServerSetup.stepDeploymentModeTitle": "Sélectionner un mode de déploiement pour Security", - "xpack.fleet.fleetServerSetup.stepFleetServerCompleteDescription": "Vous pouvez désormais enregistrer des agents avec Fleet.", - "xpack.fleet.fleetServerSetup.stepFleetServerCompleteTitle": "Serveur Fleet connecté", "xpack.fleet.fleetServerSetup.stepGenerateServiceTokenTitle": "Générer un jeton de service", - "xpack.fleet.fleetServerSetup.stepInstallAgentTitle": "Lancer le serveur Fleet", "xpack.fleet.fleetServerSetup.stepSelectAgentPolicyTitle": "Sélectionner une stratégie d’agent pour héberger le serveur Fleet", - "xpack.fleet.fleetServerSetup.stepWaitingForFleetServerTitle": "En attente de connexion du serveur Fleet…", "xpack.fleet.fleetServerSetup.waitingText": "En attente de connexion d'un serveur Fleet…", "xpack.fleet.fleetServerSetupPermissionDeniedErrorMessage": "Le serveur Fleet doit être configuré. Pour cela, le privilège de cluster {roleName} est requis. Contactez votre administrateur.", "xpack.fleet.fleetServerSetupPermissionDeniedErrorTitle": "Autorisation refusée", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index ef4216a0ee22c..5adf5a0561ebe 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12912,7 +12912,6 @@ "xpack.fleet.fleetServerSetup.cloudDeploymentLink": "デプロイを編集", "xpack.fleet.fleetServerSetup.cloudSetupText": "Fleetにエージェントを登録する前に、Fleetサーバーが必要です。取得するための最も簡単な方法は、Fleetサーバー統合を実行する統合サーバーを追加することです。クラウドコンソールでデプロイに追加できます。詳細は{link}をご覧ください。", "xpack.fleet.fleetServerSetup.cloudSetupTitle": "Fleetサーバーを有効にする", - "xpack.fleet.fleetServerSetup.continueButton": "続行", "xpack.fleet.fleetServerSetup.deploymentModeProductionOption": "{production} – 独自の証明書を指定します。このオプションでは、Fleetに登録するときに、エージェントで証明書鍵を指定する必要があります。", "xpack.fleet.fleetServerSetup.deploymentModeQuickStartOption": "{quickStart} – Fleetサーバーは自己署名証明書を生成します。後続のエージェントは--insecureフラグを使用して登録する必要があります。本番ユースケースには推奨されません。", "xpack.fleet.fleetServerSetup.errorAddingFleetServerHostTitle": "Fleetサーバーホストの追加エラー", @@ -12921,23 +12920,16 @@ "xpack.fleet.fleetServerSetup.fleetSettingsLink": "Fleet設定", "xpack.fleet.fleetServerSetup.generateServiceTokenButton": "サービストークンを生成", "xpack.fleet.fleetServerSetup.generateServiceTokenDescription": "サービストークンは、Elasticsearchに書き込むためのFleetサーバーアクセス権を付与します。", - "xpack.fleet.fleetServerSetup.installAgentDescription": "エージェントディレクトリから、適切なクイックスタートコマンドをコピーして実行し、生成されたトークンと自己署名証明書を使用して、ElasticエージェントをFleetサーバーとして起動します。本番デプロイで独自の証明書を使用する手順については、{userGuideLink}を参照してください。すべてのコマンドには管理者権限が必要です。", "xpack.fleet.fleetServerSetup.productionText": "本番運用", "xpack.fleet.fleetServerSetup.quickStartText": "クイックスタート", "xpack.fleet.fleetServerSetup.saveServiceTokenDescription": "サービストークン情報を保存します。これは1回だけ表示されます。", "xpack.fleet.fleetServerSetup.serviceTokenLabel": "サービストークン", "xpack.fleet.fleetServerSetup.setupGuideLink": "FleetおよびElasticエージェントガイド", - "xpack.fleet.fleetServerSetup.setupText": "Fleetにエージェントを登録する前に、Fleetサーバーが必要です。Fleetサーバーのセットアップについては、次の手順に従ってください。詳細については、{userGuideLink}を参照してください。", - "xpack.fleet.fleetServerSetup.setupTitle": "Fleetサーバーを追加", "xpack.fleet.fleetServerSetup.stepCreateAgentPolicyTitle": "Fleetサーバーをホストするエージェントポリシーを作成", "xpack.fleet.fleetServerSetup.stepDeploymentModeDescriptionText": "FleetはTransport Layer Security(TLS)を使用して、ElasticエージェントとElastic Stackの他のコンポーネントとの間の通信を暗号化します。デプロイモードを選択し、証明書を処理する方法を決定します。選択内容は後続のステップに表示されるFleetサーバーセットアップコマンドに影響します。", "xpack.fleet.fleetServerSetup.stepDeploymentModeTitle": "セキュリティのデプロイモードを選択", - "xpack.fleet.fleetServerSetup.stepFleetServerCompleteDescription": "エージェントをFleetに登録できます。", - "xpack.fleet.fleetServerSetup.stepFleetServerCompleteTitle": "Fleetサーバーが接続されました", "xpack.fleet.fleetServerSetup.stepGenerateServiceTokenTitle": "サービストークンを生成", - "xpack.fleet.fleetServerSetup.stepInstallAgentTitle": "Fleetサーバーを起動", "xpack.fleet.fleetServerSetup.stepSelectAgentPolicyTitle": "Fleetサーバーをホストするエージェントポリシーを選択", - "xpack.fleet.fleetServerSetup.stepWaitingForFleetServerTitle": "Fleetサーバーの接続を待機しています...", "xpack.fleet.fleetServerSetup.waitingText": "Fleetサーバーの接続を待機しています...", "xpack.fleet.fleetServerSetupPermissionDeniedErrorMessage": "Fleetサーバーを設定する必要があります。これには{roleName}クラスター権限が必要です。管理者にお問い合わせください。", "xpack.fleet.fleetServerSetupPermissionDeniedErrorTitle": "パーミッションが拒否されました", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6f56727710d9d..a9e19a19413cb 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12936,7 +12936,6 @@ "xpack.fleet.fleetServerSetup.cloudDeploymentLink": "编辑部署", "xpack.fleet.fleetServerSetup.cloudSetupText": "需要提供 Fleet 服务器,才能使用 Fleet 注册代理。获取 Fleet 服务器的最简单方法是添加集成服务器,它会运行 Fleet 服务器集成。您可以在云控制台中将其添加到部署中。有关更多信息,请参阅{link}", "xpack.fleet.fleetServerSetup.cloudSetupTitle": "启用 Fleet 服务器", - "xpack.fleet.fleetServerSetup.continueButton": "继续", "xpack.fleet.fleetServerSetup.deploymentModeProductionOption": "{production} – 提供您自己的证书。注册到 Fleet 时,此选项将需要代理指定证书密钥", "xpack.fleet.fleetServerSetup.deploymentModeQuickStartOption": "{quickStart} – Fleet 服务器将生成自签名证书。必须使用 --insecure 标志注册后续代理。不推荐用于生产用例。", "xpack.fleet.fleetServerSetup.errorAddingFleetServerHostTitle": "添加 Fleet 服务器主机时出错", @@ -12945,23 +12944,16 @@ "xpack.fleet.fleetServerSetup.fleetSettingsLink": "Fleet 设置", "xpack.fleet.fleetServerSetup.generateServiceTokenButton": "生成服务令牌", "xpack.fleet.fleetServerSetup.generateServiceTokenDescription": "服务令牌授予 Fleet 服务器向 Elasticsearch 写入的权限。", - "xpack.fleet.fleetServerSetup.installAgentDescription": "从代理目录中,复制并运行适当的快速启动命令,以使用生成的令牌和自签名证书将 Elastic 代理启动为 Fleet 服务器。有关如何将自己的证书用于生产部署,请参阅 {userGuideLink}。所有命令都需要管理员权限。", "xpack.fleet.fleetServerSetup.productionText": "生产", "xpack.fleet.fleetServerSetup.quickStartText": "快速启动", "xpack.fleet.fleetServerSetup.saveServiceTokenDescription": "保存服务令牌信息。其仅显示一次。", "xpack.fleet.fleetServerSetup.serviceTokenLabel": "服务令牌", "xpack.fleet.fleetServerSetup.setupGuideLink": "Fleet 和 Elastic 代理指南", - "xpack.fleet.fleetServerSetup.setupText": "需要提供 Fleet 服务器,才能使用 Fleet 注册代理。按照下面的说明设置 Fleet 服务器。有关详细信息,请参阅{userGuideLink}。", - "xpack.fleet.fleetServerSetup.setupTitle": "添加 Fleet 服务器", "xpack.fleet.fleetServerSetup.stepCreateAgentPolicyTitle": "创建代理策略来托管 Fleet 服务器", "xpack.fleet.fleetServerSetup.stepDeploymentModeDescriptionText": "Fleet 使用传输层安全 (TLS) 加密 Elastic 代理和 Elastic Stack 中的其他组件之间的流量。选择部署模式来决定处理证书的方式。您的选择将影响后面步骤中显示的 Fleet 服务器设置命令。", "xpack.fleet.fleetServerSetup.stepDeploymentModeTitle": "为安全选择部署模式", - "xpack.fleet.fleetServerSetup.stepFleetServerCompleteDescription": "现在可以将代理注册到 Fleet。", - "xpack.fleet.fleetServerSetup.stepFleetServerCompleteTitle": "Fleet 服务器已连接", "xpack.fleet.fleetServerSetup.stepGenerateServiceTokenTitle": "生成服务令牌", - "xpack.fleet.fleetServerSetup.stepInstallAgentTitle": "启动 Fleet 服务器", "xpack.fleet.fleetServerSetup.stepSelectAgentPolicyTitle": "选择代理策略来托管 Fleet 服务器", - "xpack.fleet.fleetServerSetup.stepWaitingForFleetServerTitle": "正在等待 Fleet 服务器连接......", "xpack.fleet.fleetServerSetup.waitingText": "等候 Fleet 服务器连接......", "xpack.fleet.fleetServerSetupPermissionDeniedErrorMessage": "需要设置 Fleet 服务器。这需要 {roleName} 集群权限。请联系您的管理员。", "xpack.fleet.fleetServerSetupPermissionDeniedErrorTitle": "权限被拒绝",