From d8fa5d6831e71b8c8559ac7128f247b3ae98532c Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Thu, 20 Apr 2023 08:22:32 -0500 Subject: [PATCH 01/67] [Enterprise Search][Search Application] Rename API page to Connect (#155278) ## Summary Renamed the engine API page to Connect and introduced a tab for the API so we can add a Documentation tab next. ### Screenshots image --- .../engine_api_integration.tsx | 0 .../engine_api_logic.ts | 0 .../engine/engine_connect/engine_connect.tsx | 102 ++++++++++++++++++ ...enerate_engine_api_key_modal.logic.test.ts | 0 .../generate_engine_api_key_modal.logic.ts | 0 .../generate_engine_api_key_modal.test.tsx | 0 .../generate_engine_api_key_modal.tsx | 0 .../search_application_api.tsx} | 27 +---- .../components/engine/engine_error.tsx | 7 +- .../components/engine/engine_indices.tsx | 10 +- .../components/engine/engine_schema.tsx | 10 +- .../engine_search_preview/api_call_flyout.tsx | 2 +- .../engine_search_preview.tsx | 10 +- .../components/engine/engine_view.tsx | 53 ++++----- .../enterprise_search_content/routes.ts | 10 +- .../kibana_chrome/generate_breadcrumbs.ts | 6 +- .../shared/kibana_chrome/set_chrome.tsx | 7 +- .../applications/shared/layout/nav.test.tsx | 6 +- .../public/applications/shared/layout/nav.tsx | 15 ++- .../translations/translations/fr-FR.json | 2 - .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 22 files changed, 184 insertions(+), 87 deletions(-) rename x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/{engine_api => engine_connect}/engine_api_integration.tsx (100%) rename x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/{engine_api => engine_connect}/engine_api_logic.ts (100%) create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx rename x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/{engine_api => engine_connect}/generate_engine_api_key_modal/generate_engine_api_key_modal.logic.test.ts (100%) rename x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/{engine_api => engine_connect}/generate_engine_api_key_modal/generate_engine_api_key_modal.logic.ts (100%) rename x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/{engine_api => engine_connect}/generate_engine_api_key_modal/generate_engine_api_key_modal.test.tsx (100%) rename x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/{engine_api => engine_connect}/generate_engine_api_key_modal/generate_engine_api_key_modal.tsx (100%) rename x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/{engine_api/engine_api.tsx => engine_connect/search_application_api.tsx} (88%) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_api/engine_api_integration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_api/engine_api_integration.tsx rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_api/engine_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_logic.ts similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_api/engine_api_logic.ts rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_logic.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx new file mode 100644 index 0000000000000..2dd55304b6035 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx @@ -0,0 +1,102 @@ +/* + * 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 { useParams } from 'react-router-dom'; + +import { useValues } from 'kea'; + +import { i18n } from '@kbn/i18n'; + +import { generateEncodedPath } from '../../../../shared/encode_path_params'; +import { KibanaLogic } from '../../../../shared/kibana'; +import { + SEARCH_APPLICATION_CONNECT_PATH, + EngineViewTabs, + SearchApplicationConnectTabs, +} from '../../../routes'; +import { EnterpriseSearchEnginesPageTemplate } from '../../layout/engines_page_template'; + +import { EngineError } from '../engine_error'; +import { EngineViewLogic } from '../engine_view_logic'; + +import { SearchApplicationAPI } from './search_application_api'; + +const pageTitle = i18n.translate( + 'xpack.enterpriseSearch.content.searchApplications.connect.pageTitle', + { + defaultMessage: 'Connect', + } +); +const API_TAB_TITLE = i18n.translate( + 'xpack.enterpriseSearch.content.searchApplications.connect.apiTabTitle', + { + defaultMessage: 'API', + } +); +const ConnectTabs: string[] = Object.values(SearchApplicationConnectTabs); +const getTabBreadCrumb = (tabId: string) => { + switch (tabId) { + case SearchApplicationConnectTabs.API: + return API_TAB_TITLE; + default: + return tabId; + } +}; + +export const EngineConnect: React.FC = () => { + const { engineName, isLoadingEngine } = useValues(EngineViewLogic); + const { connectTabId = SearchApplicationConnectTabs.API } = useParams<{ + connectTabId?: string; + }>(); + const onTabClick = (tab: SearchApplicationConnectTabs) => () => { + KibanaLogic.values.navigateToUrl( + generateEncodedPath(SEARCH_APPLICATION_CONNECT_PATH, { + engineName, + connectTabId: tab, + }) + ); + }; + if (!ConnectTabs.includes(connectTabId)) { + return ( + + + + ); + } + + return ( + + {connectTabId === SearchApplicationConnectTabs.API && } + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_api/generate_engine_api_key_modal/generate_engine_api_key_modal.logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.logic.test.ts similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_api/generate_engine_api_key_modal/generate_engine_api_key_modal.logic.test.ts rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.logic.test.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_api/generate_engine_api_key_modal/generate_engine_api_key_modal.logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.logic.ts similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_api/generate_engine_api_key_modal/generate_engine_api_key_modal.logic.ts rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.logic.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_api/generate_engine_api_key_modal/generate_engine_api_key_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.test.tsx similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_api/generate_engine_api_key_modal/generate_engine_api_key_modal.test.tsx rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.test.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_api/generate_engine_api_key_modal/generate_engine_api_key_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.tsx similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_api/generate_engine_api_key_modal/generate_engine_api_key_modal.tsx rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_api/engine_api.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx similarity index 88% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_api/engine_api.tsx rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx index aad7e84d99084..c51654f0c557d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_api/engine_api.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx @@ -19,7 +19,6 @@ import { EuiSteps, EuiText, } from '@elastic/eui'; - import { i18n } from '@kbn/i18n'; import { ANALYTICS_PLUGIN } from '../../../../../../common/constants'; @@ -29,26 +28,19 @@ import { generateEncodedPath } from '../../../../shared/encode_path_params'; import { getEnterpriseSearchUrl } from '../../../../shared/enterprise_search_url'; import { KibanaLogic } from '../../../../shared/kibana'; -import { EngineViewTabs } from '../../../routes'; -import { EnterpriseSearchEnginesPageTemplate } from '../../layout/engines_page_template'; - -import { EngineIndicesLogic } from '../engine_indices_logic'; import { EngineViewLogic } from '../engine_view_logic'; import { EngineApiIntegrationStage } from './engine_api_integration'; import { EngineApiLogic } from './engine_api_logic'; import { GenerateEngineApiKeyModal } from './generate_engine_api_key_modal/generate_engine_api_key_modal'; -export const EngineAPI: React.FC = () => { - const { engineName, isLoadingEngine } = useValues(EngineViewLogic); - const { engineData } = useValues(EngineIndicesLogic); +export const SearchApplicationAPI = () => { + const { engineName } = useValues(EngineViewLogic); const { isGenerateModalOpen } = useValues(EngineApiLogic); const { openGenerateModal, closeGenerateModal } = useActions(EngineApiLogic); const enterpriseSearchUrl = getEnterpriseSearchUrl(); const { navigateToUrl } = useValues(KibanaLogic); - if (!engineData) return null; - const steps = [ { title: i18n.translate('xpack.enterpriseSearch.content.engine.api.step1.title', { @@ -186,22 +178,11 @@ export const EngineAPI: React.FC = () => { ]; return ( - + <> {isGenerateModalOpen ? ( ) : null} - + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.tsx index 4b620c7a3d505..b8be4c7652e1b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.tsx @@ -18,8 +18,11 @@ import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; import { ENGINES_PATH } from '../../routes'; -export const EngineError: React.FC<{ error: HttpError | undefined }> = ({ error }) => { - if (error?.body?.statusCode === 404) { +export const EngineError: React.FC<{ error?: HttpError; notFound?: boolean }> = ({ + error, + notFound, +}) => { + if (notFound || error?.body?.statusCode === 404) { return ( <> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx index f18fd66ff5f94..116430e4d0017 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx @@ -39,6 +39,10 @@ import { EnterpriseSearchEnginesPageTemplate } from '../layout/engines_page_temp import { AddIndicesFlyout } from './add_indices_flyout'; import { EngineIndicesLogic } from './engine_indices_logic'; +const pageTitle = i18n.translate('xpack.enterpriseSearch.content.engine.indices.pageTitle', { + defaultMessage: 'Indices', +}); + export const EngineIndices: React.FC = () => { const subduedBackground = useEuiBackgroundColor('subdued'); const { sendEnterpriseSearchTelemetry } = useActions(TelemetryLogic); @@ -174,13 +178,11 @@ export const EngineIndices: React.FC = () => { ]; return ( = ({ schemaFiel ); }; +const pageTitle = i18n.translate('xpack.enterpriseSearch.content.engine.schema.pageTitle', { + defaultMessage: 'Schema', +}); + export const EngineSchema: React.FC = () => { const { engineName } = useValues(EngineIndicesLogic); const [onlyShowConflicts, setOnlyShowConflicts] = useState(false); @@ -345,13 +349,11 @@ export const EngineSchema: React.FC = () => { return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/api_call_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/api_call_flyout.tsx index e4fa7d5c667dc..ca64d73bf1b64 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/api_call_flyout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/api_call_flyout.tsx @@ -79,7 +79,7 @@ export const APICallFlyout: React.FC = ({ { const { http } = useValues(HttpLogic); const [showAPICallFlyout, setShowAPICallFlyout] = useState(false); @@ -114,13 +118,11 @@ export const EngineSearchPreview: React.FC = () => { return ( { const { fetchEngine, closeDeleteEngineModal } = useActions(EngineViewLogic); - const { - engineName, - fetchEngineApiError, - fetchEngineApiStatus, - isDeleteModalVisible, - isLoadingEngine, - } = useValues(EngineViewLogic); + const { engineName, fetchEngineApiError, fetchEngineApiStatus, isDeleteModalVisible } = + useValues(EngineViewLogic); const { tabId = EngineViewTabs.PREVIEW } = useParams<{ tabId?: string; }>(); @@ -77,21 +76,25 @@ export const EngineView: React.FC = () => { /> - - ( - ], - }} - engineName={engineName} - isLoading={isLoadingEngine} - /> - )} + + + + + + + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts index 37504ef0ba960..ea5672222014f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts @@ -27,13 +27,17 @@ export const OLD_SEARCH_INDEX_CRAWLER_DOMAIN_DETAIL_PATH = `${SEARCH_INDEX_PATH} export const ENGINES_PATH = `${ROOT_PATH}engines`; -export const ENGINE_CREATION_PATH = `${ENGINES_PATH}/new`; -export const ENGINE_PATH = `${ENGINES_PATH}/:engineName`; -export const ENGINE_TAB_PATH = `${ENGINE_PATH}/:tabId`; export enum EngineViewTabs { PREVIEW = 'preview', INDICES = 'indices', SCHEMA = 'schema', + CONNECT = 'connect', +} +export const ENGINE_CREATION_PATH = `${ENGINES_PATH}/new`; +export const ENGINE_PATH = `${ENGINES_PATH}/:engineName`; +export const ENGINE_TAB_PATH = `${ENGINE_PATH}/:tabId`; +export const SEARCH_APPLICATION_CONNECT_PATH = `${ENGINE_PATH}/${EngineViewTabs.CONNECT}/:connectTabId`; +export enum SearchApplicationConnectTabs { API = 'api', } diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts index 87282007f5bbd..ff2c05c4c8566 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts @@ -10,7 +10,6 @@ import { useValues } from 'kea'; import { EuiBreadcrumb } from '@elastic/eui'; import { - ENGINES_PLUGIN, ENTERPRISE_SEARCH_OVERVIEW_PLUGIN, ANALYTICS_PLUGIN, APP_SEARCH_PLUGIN, @@ -139,7 +138,4 @@ export const useSearchExperiencesBreadcrumbs = (breadcrumbs: Breadcrumbs = []) = ]); export const useEnterpriseSearchEnginesBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => - useEnterpriseSearchBreadcrumbs([ - { text: ENGINES_PLUGIN.NAV_TITLE, path: '/engines' }, - ...breadcrumbs, - ]); + useEnterpriseSearchBreadcrumbs(breadcrumbs); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx index 9c22e64127326..bd042c4b6ac90 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx @@ -9,6 +9,8 @@ import React, { useEffect } from 'react'; import { useValues } from 'kea'; +import { ENGINES_PLUGIN } from '../../../../common/constants'; + import { KibanaLogic } from '../kibana'; import { @@ -176,8 +178,9 @@ export const SetEnterpriseSearchEnginesChrome: React.FC = ({ tra const title = reverseArray(trail); const docTitle = appSearchTitle(title); - const crumbs = useGenerateBreadcrumbs(trail); - const breadcrumbs = useEnterpriseSearchEnginesBreadcrumbs(crumbs); + const breadcrumbs = useEnterpriseSearchEnginesBreadcrumbs( + useGenerateBreadcrumbs([ENGINES_PLUGIN.NAV_TITLE, ...trail]) + ); useEffect(() => { setBreadcrumbs(breadcrumbs); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx index 299f803a6e131..11458565f781a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx @@ -300,9 +300,9 @@ describe('useEnterpriseSearchEngineNav', () => { name: 'Schema', }, { - href: `/app/enterprise_search/content/engines/${engineName}/api`, - id: 'enterpriseSearchEngineAPI', - name: 'API', + href: `/app/enterprise_search/content/engines/${engineName}/connect`, + id: 'enterpriseSearchApplicationConnect', + name: 'Connect', }, ], name: engineName, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index 0798de9870f94..f06e32b9e30c8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -189,6 +189,7 @@ export const useEnterpriseSearchEngineNav = (engineName?: string, isEmptyState?: name: engineName, ...generateNavLink({ shouldNotCreateHref: true, + shouldShowActiveForSubroutes: true, to: enginePath, }), items: [ @@ -223,13 +224,17 @@ export const useEnterpriseSearchEngineNav = (engineName?: string, isEmptyState?: }), }, { - id: 'enterpriseSearchEngineAPI', - name: i18n.translate('xpack.enterpriseSearch.nav.engine.apiTitle', { - defaultMessage: 'API', - }), + id: 'enterpriseSearchApplicationConnect', + name: i18n.translate( + 'xpack.enterpriseSearch.nav.applications.searchApplications.connectTitle', + { + defaultMessage: 'Connect', + } + ), ...generateNavLink({ shouldNotCreateHref: true, - to: `${enginePath}/${EngineViewTabs.API}`, + shouldShowActiveForSubroutes: true, + to: `${enginePath}/${EngineViewTabs.CONNECT}`, }), }, ], diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 6a0d6b7aa138a..969438e555138 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -12226,7 +12226,6 @@ "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.done": "Terminé", "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.generateButton": "Générer une clé en lecture seule", "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.title": "Créer une clé d'API en lecture seule pour le moteur", - "xpack.enterpriseSearch.content.engine.api.pageTitle": "API", "xpack.enterpriseSearch.content.engine.api.step1.apiKeyWarning": "Elastic ne stocke pas les clés d’API. Une fois la clé générée, vous ne pourrez la visualiser qu'une seule fois. Veillez à l'enregistrer dans un endroit sûr. Si vous n'y avez plus accès, vous devrez générer une nouvelle clé d’API à partir de cet écran.", "xpack.enterpriseSearch.content.engine.api.step1.createAPIKeyButton": "Créer une clé d'API", "xpack.enterpriseSearch.content.engine.api.step1.learnMoreLink": "Découvrez plus d'informations sur les clés d'API.", @@ -13130,7 +13129,6 @@ "xpack.enterpriseSearch.nav.contentSettingsTitle": "Paramètres", "xpack.enterpriseSearch.nav.contentTitle": "Contenu", "xpack.enterpriseSearch.nav.elasticsearchTitle": "Elasticsearch", - "xpack.enterpriseSearch.nav.engine.apiTitle": "API", "xpack.enterpriseSearch.nav.engine.indicesTitle": "Index", "xpack.enterpriseSearch.nav.engine.schemaTitle": "Schéma", "xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "Aperçu", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index f18a3f2f1916b..9bad4964ca71c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12225,7 +12225,6 @@ "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.done": "完了", "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.generateButton": "読み取り専用キーを生成", "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.title": "エンジン読み取り専用APIキーを作成", - "xpack.enterpriseSearch.content.engine.api.pageTitle": "API", "xpack.enterpriseSearch.content.engine.api.step1.apiKeyWarning": "ElasticはAPIキーを保存しません。生成後は、1回だけキーを表示できます。必ず安全に保管してください。アクセスできなくなった場合は、この画面から新しいAPIキーを生成する必要があります。", "xpack.enterpriseSearch.content.engine.api.step1.createAPIKeyButton": "APIキーを作成", "xpack.enterpriseSearch.content.engine.api.step1.learnMoreLink": "APIキーの詳細をご覧ください。", @@ -13129,7 +13128,6 @@ "xpack.enterpriseSearch.nav.contentSettingsTitle": "設定", "xpack.enterpriseSearch.nav.contentTitle": "コンテンツ", "xpack.enterpriseSearch.nav.elasticsearchTitle": "Elasticsearch", - "xpack.enterpriseSearch.nav.engine.apiTitle": "API", "xpack.enterpriseSearch.nav.engine.indicesTitle": "インデックス", "xpack.enterpriseSearch.nav.engine.schemaTitle": "スキーマ", "xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "概要", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 769dd6e40b48e..d800cf3c9c612 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12226,7 +12226,6 @@ "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.done": "完成", "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.generateButton": "生成只读密钥", "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.title": "创建引擎只读 API 密钥", - "xpack.enterpriseSearch.content.engine.api.pageTitle": "API", "xpack.enterpriseSearch.content.engine.api.step1.apiKeyWarning": "Elastic 不会存储 API 密钥。一旦生成,您只能查看密钥一次。请确保将其保存在某个安全位置。如果失去它的访问权限,您需要从此屏幕生成新的 API 密钥。", "xpack.enterpriseSearch.content.engine.api.step1.createAPIKeyButton": "创建 API 密钥", "xpack.enterpriseSearch.content.engine.api.step1.learnMoreLink": "详细了解 API 密钥。", @@ -13130,7 +13129,6 @@ "xpack.enterpriseSearch.nav.contentSettingsTitle": "设置", "xpack.enterpriseSearch.nav.contentTitle": "内容", "xpack.enterpriseSearch.nav.elasticsearchTitle": "Elasticsearch", - "xpack.enterpriseSearch.nav.engine.apiTitle": "API", "xpack.enterpriseSearch.nav.engine.indicesTitle": "索引", "xpack.enterpriseSearch.nav.engine.schemaTitle": "架构", "xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "概览", From 2d576c575ee632850692aa27b32de2866e3e0161 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Thu, 20 Apr 2023 14:36:27 +0100 Subject: [PATCH 02/67] [ML] Use two weeks before now for default start time in job start date picker (#155312) When starting an anomaly detection job, if you select "Specify start time", the time chosen is two weeks before now, rounded down to the start of the day. image Fixes https://github.com/elastic/enhancements/issues/16467 --- .../time_range_selector/time_range_selector.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js index e3c6458fabda9..b6166dd6cbd82 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js @@ -27,6 +27,7 @@ export class TimeRangeSelector extends Component { }; this.latestTimestamp = this.props.startTime; this.now = this.props.now; + this.twoWeeksAgo = moment(this.now).subtract(2, 'weeks').startOf('day'); } setStartTab = (tab) => { @@ -38,6 +39,9 @@ export class TimeRangeSelector extends Component { case 1: this.setStartTime(this.now); break; + case 2: + this.setStartTime(this.twoWeeksAgo); + break; default: break; } From f0964823deeaf11b5ac06e3dbed04d35f8054206 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Thu, 20 Apr 2023 16:13:23 +0200 Subject: [PATCH 03/67] [ML] AIOps: Add filter action for the Change point detection results (#155256) ## Summary Part of https://github.com/elastic/kibana/issues/151592 Adds actions to quickly filter for and filter out split field values. ![Apr-20-2023 12-41-17](https://user-images.githubusercontent.com/5236598/233342684-0ee7e27e-0ffc-444b-92b0-5bf1a732ed87.gif) ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [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 - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --- .../change_points_table.tsx | 99 ++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/change_points_table.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/change_points_table.tsx index 28ff26ad7645a..dd054b88076ec 100644 --- a/x-pack/plugins/aiops/public/components/change_point_detection/change_points_table.tsx +++ b/x-pack/plugins/aiops/public/components/change_point_detection/change_points_table.tsx @@ -12,11 +12,14 @@ import { EuiIcon, EuiInMemoryTable, EuiToolTip, + type DefaultItemAction, } from '@elastic/eui'; import React, { type FC, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types'; +import { type Filter, FilterStateStore } from '@kbn/es-query'; +import { useDataSource } from '../../hooks/use_data_source'; import { useCommonChartProps } from './use_common_chart_props'; import { type ChangePointAnnotation, @@ -33,13 +36,49 @@ export interface ChangePointsTableProps { onSelectionChange: (update: SelectedChangePoint[]) => void; } +function getFilterConfig( + index: string, + item: Required, + negate: boolean +): Filter { + return { + meta: { + disabled: false, + negate, + alias: null, + index, + key: `${item.group.name}_${item.group.value}`, + // @ts-ignore FilterMeta type definition misses the field property + field: item.group.name, + params: { + query: item.group.value, + }, + type: 'phrase', + }, + query: { + match_phrase: { + [item.group.name]: item.group.value, + }, + }, + $state: { + store: FilterStateStore.APP_STATE, + }, + }; +} + export const ChangePointsTable: FC = ({ isLoading, annotations, fieldConfig, onSelectionChange, }) => { - const { fieldFormats } = useAiopsAppContext(); + const { + fieldFormats, + data: { + query: { filterManager }, + }, + } = useAiopsAppContext(); + const { dataView } = useDataSource(); const dateFormatter = useMemo(() => fieldFormats.deserialize({ id: 'date' }), [fieldFormats]); @@ -51,6 +90,8 @@ export const ChangePointsTable: FC = ({ }, }; + const hasActions = fieldConfig.splitField !== undefined; + const columns: Array> = [ { field: 'timestamp', @@ -126,6 +167,61 @@ export const ChangePointsTable: FC = ({ truncateText: false, sortable: true, }, + { + name: i18n.translate('xpack.aiops.changePointDetection.actionsColumn', { + defaultMessage: 'Actions', + }), + actions: [ + { + name: i18n.translate( + 'xpack.aiops.changePointDetection.actions.filterForValueAction', + { + defaultMessage: 'Filter for value', + } + ), + description: i18n.translate( + 'xpack.aiops.changePointDetection.actions.filterForValueAction', + { + defaultMessage: 'Filter for value', + } + ), + icon: 'plusInCircle', + color: 'primary', + type: 'icon', + onClick: (item) => { + filterManager.addFilters( + getFilterConfig(dataView.id!, item as Required, false)! + ); + }, + isPrimary: true, + 'data-test-subj': 'aiopsChangePointFilterForValue', + }, + { + name: i18n.translate( + 'xpack.aiops.changePointDetection.actions.filterOutValueAction', + { + defaultMessage: 'Filter out value', + } + ), + description: i18n.translate( + 'xpack.aiops.changePointDetection.actions.filterOutValueAction', + { + defaultMessage: 'Filter out value', + } + ), + icon: 'minusInCircle', + color: 'primary', + type: 'icon', + onClick: (item) => { + filterManager.addFilters( + getFilterConfig(dataView.id!, item as Required, true)! + ); + }, + isPrimary: true, + 'data-test-subj': 'aiopsChangePointFilterForValue', + }, + ] as Array>, + }, ] : []), ]; @@ -155,6 +251,7 @@ export const ChangePointsTable: FC = ({ columns={columns} pagination={{ pageSizeOptions: [5, 10, 15] }} sorting={defaultSorting} + hasActions={hasActions} message={ isLoading ? ( Date: Thu, 20 Apr 2023 16:26:48 +0200 Subject: [PATCH 04/67] Enable APM for ES when running performance journeys (#155195) Enables APM instrumentation for Elasticsearch when running performance journeys, for a more complete understanding of where time is being spent: CleanShot 2023-04-18 at 20 30 32@2x --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-journeys/index.ts | 1 + .../journey/journey_apm_config.ts | 37 +++++++++++++++++++ .../journey/journey_ftr_config.ts | 36 ++++++------------ src/dev/performance/run_performance_cli.ts | 21 ++++++++++- src/dev/tsconfig.json | 1 + 5 files changed, 70 insertions(+), 26 deletions(-) create mode 100644 packages/kbn-journeys/journey/journey_apm_config.ts diff --git a/packages/kbn-journeys/index.ts b/packages/kbn-journeys/index.ts index def8e5f3b0d59..6c0df15667c27 100644 --- a/packages/kbn-journeys/index.ts +++ b/packages/kbn-journeys/index.ts @@ -7,6 +7,7 @@ */ export { JourneyConfig } from './journey/journey_config'; +export { JOURNEY_APM_CONFIG } from './journey/journey_apm_config'; export type { ScalabilityAction, ScalabilitySetup, diff --git a/packages/kbn-journeys/journey/journey_apm_config.ts b/packages/kbn-journeys/journey/journey_apm_config.ts new file mode 100644 index 0000000000000..c14a78a438418 --- /dev/null +++ b/packages/kbn-journeys/journey/journey_apm_config.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 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. + */ + +// These "secret" values are intentionally written in the source. We would make the APM server accept anonymous traffic if we could +const APM_SERVER_URL = 'https://kibana-ops-e2e-perf.apm.us-central1.gcp.cloud.es.io:443'; +const APM_PUBLIC_TOKEN = 'CTs9y3cvcfq13bQqsB'; + +export const JOURNEY_APM_CONFIG = { + serverUrl: APM_SERVER_URL, + secretToken: APM_PUBLIC_TOKEN, + active: 'true', + contextPropagationOnly: 'false', + environment: process.env.CI ? 'ci' : 'development', + transactionSampleRate: '1.0', + // capture request body for both errors and request transactions + // https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#capture-body + captureBody: 'all', + // capture request headers + // https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#capture-headers + captureRequestHeaders: true, + // request body with bigger size will be trimmed. + // 300_000 is the default of the APM server. + // for a body with larger size, we might need to reconfigure the APM server to increase the limit. + // https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#long-field-max-length + longFieldMaxLength: 300_000, + globalLabels: { + performancePhase: process.env.TEST_PERFORMANCE_PHASE, + branch: process.env.BUILDKITE_BRANCH, + gitRev: process.env.BUILDKITE_COMMIT, + ciBuildName: process.env.BUILDKITE_PIPELINE_SLUG, + }, +}; diff --git a/packages/kbn-journeys/journey/journey_ftr_config.ts b/packages/kbn-journeys/journey/journey_ftr_config.ts index 3933d0d7e195a..2d855ab8fb22c 100644 --- a/packages/kbn-journeys/journey/journey_ftr_config.ts +++ b/packages/kbn-journeys/journey/journey_ftr_config.ts @@ -15,10 +15,7 @@ import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; import { AnyStep } from './journey'; import { JourneyConfig } from './journey_config'; - -// These "secret" values are intentionally written in the source. We would make the APM server accept anonymous traffic if we could -const APM_SERVER_URL = 'https://kibana-ops-e2e-perf.apm.us-central1.gcp.cloud.es.io:443'; -const APM_PUBLIC_TOKEN = 'CTs9y3cvcfq13bQqsB'; +import { JOURNEY_APM_CONFIG } from './journey_apm_config'; export function makeFtrConfigProvider( config: JourneyConfig, @@ -92,33 +89,22 @@ export function makeFtrConfigProvider( ], env: { - ELASTIC_APM_ACTIVE: 'true', - ELASTIC_APM_CONTEXT_PROPAGATION_ONLY: 'false', - ELASTIC_APM_ENVIRONMENT: process.env.CI ? 'ci' : 'development', - ELASTIC_APM_TRANSACTION_SAMPLE_RATE: '1.0', - ELASTIC_APM_SERVER_URL: APM_SERVER_URL, - ELASTIC_APM_SECRET_TOKEN: APM_PUBLIC_TOKEN, - // capture request body for both errors and request transactions - // https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#capture-body - ELASTIC_APM_CAPTURE_BODY: 'all', - // capture request headers - // https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#capture-headers - ELASTIC_APM_CAPTURE_HEADERS: true, - // request body with bigger size will be trimmed. - // 300_000 is the default of the APM server. - // for a body with larger size, we might need to reconfigure the APM server to increase the limit. - // https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#long-field-max-length - ELASTIC_APM_LONG_FIELD_MAX_LENGTH: 300_000, + ELASTIC_APM_ACTIVE: JOURNEY_APM_CONFIG.active, + ELASTIC_APM_CONTEXT_PROPAGATION_ONLY: JOURNEY_APM_CONFIG.contextPropagationOnly, + ELASTIC_APM_ENVIRONMENT: JOURNEY_APM_CONFIG.environment, + ELASTIC_APM_TRANSACTION_SAMPLE_RATE: JOURNEY_APM_CONFIG.transactionSampleRate, + ELASTIC_APM_SERVER_URL: JOURNEY_APM_CONFIG.serverUrl, + ELASTIC_APM_SECRET_TOKEN: JOURNEY_APM_CONFIG.secretToken, + ELASTIC_APM_CAPTURE_BODY: JOURNEY_APM_CONFIG.captureBody, + ELASTIC_APM_CAPTURE_HEADERS: JOURNEY_APM_CONFIG.captureRequestHeaders, + ELASTIC_APM_LONG_FIELD_MAX_LENGTH: JOURNEY_APM_CONFIG.longFieldMaxLength, ELASTIC_APM_GLOBAL_LABELS: Object.entries({ ...config.getExtraApmLabels(), testJobId, testBuildId, journeyName: config.getName(), ftrConfig: config.getRepoRelPath(), - performancePhase: process.env.TEST_PERFORMANCE_PHASE, - branch: process.env.BUILDKITE_BRANCH, - gitRev: process.env.BUILDKITE_COMMIT, - ciBuildName: process.env.BUILDKITE_PIPELINE_SLUG, + ...JOURNEY_APM_CONFIG.globalLabels, }) .flatMap(([key, value]) => (value == null ? [] : `${key}=${value}`)) .join(','), diff --git a/src/dev/performance/run_performance_cli.ts b/src/dev/performance/run_performance_cli.ts index 3539ada07e405..3c5807e1c208b 100644 --- a/src/dev/performance/run_performance_cli.ts +++ b/src/dev/performance/run_performance_cli.ts @@ -11,6 +11,7 @@ import { run } from '@kbn/dev-cli-runner'; import { REPO_ROOT } from '@kbn/repo-info'; import fs from 'fs'; import path from 'path'; +import { JOURNEY_APM_CONFIG } from '@kbn/journeys'; const JOURNEY_BASE_PATH = 'x-pack/performance/journeys'; @@ -103,7 +104,25 @@ run( process.stdout.write(`--- Starting ES\n`); await procRunner.run('es', { cmd: 'node', - args: ['scripts/es', 'snapshot', '--license=trial'], + args: [ + 'scripts/es', + 'snapshot', + '--license=trial', + ...(JOURNEY_APM_CONFIG.active + ? [ + '-E', + 'tracing.apm.enabled=true', + '-E', + 'tracing.apm.agent.transaction_sample_rate=1.0', + '-E', + `tracing.apm.agent.server_url=${JOURNEY_APM_CONFIG.serverUrl}`, + '-E', + `tracing.apm.agent.secret_token=${JOURNEY_APM_CONFIG.secretToken}`, + '-E', + `tracing.apm.agent.environment=${JOURNEY_APM_CONFIG.environment}`, + ] + : []), + ], cwd: REPO_ROOT, wait: /kbn\/es setup complete/, }); diff --git a/src/dev/tsconfig.json b/src/dev/tsconfig.json index 7ceac215ee3f2..b4fb983d63e52 100644 --- a/src/dev/tsconfig.json +++ b/src/dev/tsconfig.json @@ -38,5 +38,6 @@ "@kbn/repo-file-maps", "@kbn/get-repo-files", "@kbn/import-locator", + "@kbn/journeys", ] } From c68dfd7f9a14d5409e065f5f0e5b62f8b9b36d09 Mon Sep 17 00:00:00 2001 From: Alexi Doak <109488926+doakalexi@users.noreply.github.com> Date: Thu, 20 Apr 2023 10:32:05 -0400 Subject: [PATCH 05/67] [ResponseOps][Window Maintenance] Add the edit action to the maintenance window table (#154669) Resolves https://github.com/elastic/kibana/issues/154559 ## Summary Adds an edit action to the maintance windows table. When a user clicks edit we retrieve the maintenance window and open the edit form. ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [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 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../application/maintenance_windows.tsx | 11 +- .../plugins/alerting/public/config/paths.ts | 2 + .../public/hooks/use_breadcrumbs.test.tsx | 15 + .../alerting/public/hooks/use_breadcrumbs.ts | 7 + .../hooks/use_find_maintenance_windows.ts | 1 + .../hooks/use_get_maintenance_window.test.tsx | 54 +++ .../hooks/use_get_maintenance_window.ts | 47 +++ .../public/hooks/use_navigation.test.tsx | 18 + .../alerting/public/hooks/use_navigation.ts | 20 ++ .../use_update_maintenance_window.test.tsx | 85 +++++ .../hooks/use_update_maintenance_window.ts | 53 +++ .../create_maintenance_windows_form.tsx | 36 +- .../components/maintenance_windows_list.tsx | 22 +- .../components/submit_button.tsx | 5 +- ...rt_from_maintenance_window_to_form.test.ts | 318 ++++++++++++++++++ ...convert_from_maintenance_window_to_form.ts | 94 ++++++ .../helpers/convert_to_rrule.ts | 58 ++-- .../maintenance_window_edit_page.tsx | 52 +++ .../pages/maintenance_windows/translations.ts | 18 + .../maintenance_windows_api/create.ts | 4 +- .../services/maintenance_windows_api/find.ts | 2 +- .../maintenance_windows_api/get.test.ts | 51 +++ .../services/maintenance_windows_api/get.ts | 32 ++ .../maintenance_windows_api/update.test.ts | 58 ++++ .../maintenance_windows_api/update.ts | 40 +++ 25 files changed, 1055 insertions(+), 48 deletions(-) create mode 100644 x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.test.tsx create mode 100644 x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.ts create mode 100644 x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.test.tsx create mode 100644 x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.ts create mode 100644 x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts create mode 100644 x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.ts create mode 100644 x-pack/plugins/alerting/public/pages/maintenance_windows/maintenance_window_edit_page.tsx create mode 100644 x-pack/plugins/alerting/public/services/maintenance_windows_api/get.test.ts create mode 100644 x-pack/plugins/alerting/public/services/maintenance_windows_api/get.ts create mode 100644 x-pack/plugins/alerting/public/services/maintenance_windows_api/update.test.ts create mode 100644 x-pack/plugins/alerting/public/services/maintenance_windows_api/update.ts diff --git a/x-pack/plugins/alerting/public/application/maintenance_windows.tsx b/x-pack/plugins/alerting/public/application/maintenance_windows.tsx index 88ac69cf82b66..4005ee739a12b 100644 --- a/x-pack/plugins/alerting/public/application/maintenance_windows.tsx +++ b/x-pack/plugins/alerting/public/application/maintenance_windows.tsx @@ -7,7 +7,7 @@ import React, { Suspense } from 'react'; import ReactDOM from 'react-dom'; -import { Router, Switch } from 'react-router-dom'; +import { Redirect, Router, Switch } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { Route } from '@kbn/shared-ux-router'; import { CoreStart } from '@kbn/core/public'; @@ -23,6 +23,9 @@ const MaintenanceWindowsLazy: React.FC = React.lazy(() => import('../pages/maint const MaintenanceWindowsCreateLazy: React.FC = React.lazy( () => import('../pages/maintenance_windows/maintenance_window_create_page') ); +const MaintenanceWindowsEditLazy: React.FC = React.lazy( + () => import('../pages/maintenance_windows/maintenance_window_edit_page') +); const App = React.memo(() => { return ( @@ -38,6 +41,12 @@ const App = React.memo(() => { + + }> + + + + ); diff --git a/x-pack/plugins/alerting/public/config/paths.ts b/x-pack/plugins/alerting/public/config/paths.ts index 1fdfa48078c0a..7dc8b1b643f2a 100644 --- a/x-pack/plugins/alerting/public/config/paths.ts +++ b/x-pack/plugins/alerting/public/config/paths.ts @@ -12,12 +12,14 @@ export const paths = { alerting: { maintenanceWindows: `/${MAINTENANCE_WINDOWS_APP_ID}`, maintenanceWindowsCreate: '/create', + maintenanceWindowsEdit: '/edit/:maintenanceWindowId', }, }; export const AlertingDeepLinkId = { maintenanceWindows: MAINTENANCE_WINDOWS_APP_ID, maintenanceWindowsCreate: 'create', + maintenanceWindowsEdit: 'edit', }; export type IAlertingDeepLinkId = typeof AlertingDeepLinkId[keyof typeof AlertingDeepLinkId]; diff --git a/x-pack/plugins/alerting/public/hooks/use_breadcrumbs.test.tsx b/x-pack/plugins/alerting/public/hooks/use_breadcrumbs.test.tsx index fb42a59db0fe2..def69ac94beb1 100644 --- a/x-pack/plugins/alerting/public/hooks/use_breadcrumbs.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_breadcrumbs.test.tsx @@ -71,4 +71,19 @@ describe('useBreadcrumbs', () => { { text: 'Create' }, ]); }); + + test('set edit maintenance windows breadcrumbs', () => { + renderHook(() => useBreadcrumbs(AlertingDeepLinkId.maintenanceWindowsEdit), { + wrapper: appMockRenderer.AppWrapper, + }); + expect(mockSetBreadcrumbs).toHaveBeenCalledWith([ + { href: '/test', onClick: expect.any(Function), text: 'Stack Management' }, + { + href: AlertingDeepLinkId.maintenanceWindows, + onClick: expect.any(Function), + text: 'Maintenance Windows', + }, + { text: 'Edit' }, + ]); + }); }); diff --git a/x-pack/plugins/alerting/public/hooks/use_breadcrumbs.ts b/x-pack/plugins/alerting/public/hooks/use_breadcrumbs.ts index d33ce68bd0b1b..1553fc59e9176 100644 --- a/x-pack/plugins/alerting/public/hooks/use_breadcrumbs.ts +++ b/x-pack/plugins/alerting/public/hooks/use_breadcrumbs.ts @@ -25,10 +25,17 @@ const breadcrumbTitle: Record = { defaultMessage: 'Create', } ), + [AlertingDeepLinkId.maintenanceWindowsEdit]: i18n.translate( + 'xpack.alerting.breadcrumbs.editMaintenanceWindowsLinkText', + { + defaultMessage: 'Edit', + } + ), }; const topLevelBreadcrumb: Record = { [AlertingDeepLinkId.maintenanceWindowsCreate]: AlertingDeepLinkId.maintenanceWindows, + [AlertingDeepLinkId.maintenanceWindowsEdit]: AlertingDeepLinkId.maintenanceWindows, }; function addClickHandlers( diff --git a/x-pack/plugins/alerting/public/hooks/use_find_maintenance_windows.ts b/x-pack/plugins/alerting/public/hooks/use_find_maintenance_windows.ts index dbbd39f523679..6a71bd9c64518 100644 --- a/x-pack/plugins/alerting/public/hooks/use_find_maintenance_windows.ts +++ b/x-pack/plugins/alerting/public/hooks/use_find_maintenance_windows.ts @@ -36,6 +36,7 @@ export const useFindMaintenanceWindows = () => { onError: onErrorFn, refetchOnWindowFocus: false, retry: false, + cacheTime: 0, }); return { diff --git a/x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.test.tsx b/x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.test.tsx new file mode 100644 index 0000000000000..eaef1f4fc4b99 --- /dev/null +++ b/x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.test.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { renderHook } from '@testing-library/react-hooks/dom'; +import { waitFor } from '@testing-library/dom'; + +import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; +import { useGetMaintenanceWindow } from './use_get_maintenance_window'; + +const mockAddDanger = jest.fn(); + +jest.mock('../utils/kibana_react', () => { + const originalModule = jest.requireActual('../utils/kibana_react'); + return { + ...originalModule, + useKibana: () => { + const { services } = originalModule.useKibana(); + return { + services: { + ...services, + notifications: { toasts: { addDanger: mockAddDanger } }, + }, + }; + }, + }; +}); +jest.mock('../services/maintenance_windows_api/get', () => ({ + getMaintenanceWindow: jest.fn(), +})); + +const { getMaintenanceWindow } = jest.requireMock('../services/maintenance_windows_api/get'); + +let appMockRenderer: AppMockRenderer; + +describe('useGetMaintenanceWindow', () => { + beforeEach(() => { + jest.clearAllMocks(); + + appMockRenderer = createAppMockRenderer(); + }); + + it('should call onError if api fails', async () => { + getMaintenanceWindow.mockRejectedValue(''); + + renderHook(() => useGetMaintenanceWindow('testId'), { + wrapper: appMockRenderer.AppWrapper, + }); + + await waitFor(() => expect(mockAddDanger).toBeCalledWith('Unable to get maintenance window.')); + }); +}); diff --git a/x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.ts b/x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.ts new file mode 100644 index 0000000000000..1657ac9fbb30e --- /dev/null +++ b/x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.ts @@ -0,0 +1,47 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import { useKibana } from '../utils/kibana_react'; +import { getMaintenanceWindow } from '../services/maintenance_windows_api/get'; +import { convertFromMaintenanceWindowToForm } from '../pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form'; + +export const useGetMaintenanceWindow = (maintenanceWindowId: string) => { + const { + http, + notifications: { toasts }, + } = useKibana().services; + + const queryFn = async () => { + const maintenanceWindow = await getMaintenanceWindow({ http, maintenanceWindowId }); + return convertFromMaintenanceWindowToForm(maintenanceWindow); + }; + + const onErrorFn = () => { + toasts.addDanger( + i18n.translate('xpack.alerting.getMaintenanceWindowFailure', { + defaultMessage: 'Unable to get maintenance window.', + }) + ); + }; + + const { isInitialLoading, isLoading, data, isError } = useQuery({ + queryKey: ['getMaintenanceWindow', maintenanceWindowId], + queryFn, + onError: onErrorFn, + refetchOnWindowFocus: false, + retry: false, + cacheTime: 0, + }); + + return { + maintenanceWindow: data, + isLoading: isLoading || isInitialLoading, + isError, + }; +}; diff --git a/x-pack/plugins/alerting/public/hooks/use_navigation.test.tsx b/x-pack/plugins/alerting/public/hooks/use_navigation.test.tsx index abf7a27f595a1..c4d2b7b71ef27 100644 --- a/x-pack/plugins/alerting/public/hooks/use_navigation.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_navigation.test.tsx @@ -9,6 +9,7 @@ import { act, renderHook } from '@testing-library/react-hooks'; import { useCreateMaintenanceWindowNavigation, + useEditMaintenanceWindowsNavigation, useMaintenanceWindowsNavigation, } from './use_navigation'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; @@ -90,4 +91,21 @@ describe('useNavigation', () => { }); }); }); + + describe('useEditMaintenanceWindowNavigation', () => { + it('it calls navigateToEditMaintenanceWindow with correct arguments', () => { + const { result } = renderHook(() => useEditMaintenanceWindowsNavigation(), { + wrapper: appMockRenderer.AppWrapper, + }); + + act(() => { + result.current.navigateToEditMaintenanceWindows('1234'); + }); + + expect(mockNavigateTo).toHaveBeenCalledWith(APP_ID, { + deepLinkId: MAINTENANCE_WINDOWS_APP_ID, + path: '/edit/1234', + }); + }); + }); }); diff --git a/x-pack/plugins/alerting/public/hooks/use_navigation.ts b/x-pack/plugins/alerting/public/hooks/use_navigation.ts index 12f64dbdcad30..d1c3673b60abc 100644 --- a/x-pack/plugins/alerting/public/hooks/use_navigation.ts +++ b/x-pack/plugins/alerting/public/hooks/use_navigation.ts @@ -6,6 +6,7 @@ */ import { useCallback } from 'react'; +import { generatePath } from 'react-router-dom'; import type { NavigateToAppOptions } from '@kbn/core/public'; import { useKibana } from '../utils/kibana_react'; import { paths, APP_ID, MAINTENANCE_WINDOWS_APP_ID } from '../config'; @@ -53,3 +54,22 @@ export const useMaintenanceWindowsNavigation = () => { }), }; }; + +export const useEditMaintenanceWindowsNavigation = () => { + const { navigateTo, getAppUrl } = useNavigation(APP_ID); + const deepLinkId = MAINTENANCE_WINDOWS_APP_ID; + + return { + navigateToEditMaintenanceWindows: (maintenanceWindowId: string) => + navigateTo({ + path: generatePath(paths.alerting.maintenanceWindowsEdit, { maintenanceWindowId }), + deepLinkId, + }), + getEditMaintenanceWindowsUrl: (maintenanceWindowId: string, absolute?: boolean) => + getAppUrl({ + path: generatePath(paths.alerting.maintenanceWindowsEdit, { maintenanceWindowId }), + deepLinkId, + absolute, + }), + }; +}; diff --git a/x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.test.tsx b/x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.test.tsx new file mode 100644 index 0000000000000..67545d83aba17 --- /dev/null +++ b/x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.test.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { act, renderHook } from '@testing-library/react-hooks/dom'; +import { waitFor } from '@testing-library/dom'; + +import { MaintenanceWindow } from '../pages/maintenance_windows/types'; +import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; +import { useUpdateMaintenanceWindow } from './use_update_maintenance_window'; + +const mockAddDanger = jest.fn(); +const mockAddSuccess = jest.fn(); + +jest.mock('../utils/kibana_react', () => { + const originalModule = jest.requireActual('../utils/kibana_react'); + return { + ...originalModule, + useKibana: () => { + const { services } = originalModule.useKibana(); + return { + services: { + ...services, + notifications: { toasts: { addSuccess: mockAddSuccess, addDanger: mockAddDanger } }, + }, + }; + }, + }; +}); +jest.mock('../services/maintenance_windows_api/update', () => ({ + updateMaintenanceWindow: jest.fn(), +})); + +const { updateMaintenanceWindow } = jest.requireMock('../services/maintenance_windows_api/update'); + +const maintenanceWindow: MaintenanceWindow = { + title: 'updated', + duration: 1, + rRule: { + dtstart: '2023-03-23T19:16:21.293Z', + tzid: 'America/New_York', + }, +}; + +let appMockRenderer: AppMockRenderer; + +describe('useUpdateMaintenanceWindow', () => { + beforeEach(() => { + jest.clearAllMocks(); + + appMockRenderer = createAppMockRenderer(); + updateMaintenanceWindow.mockResolvedValue(maintenanceWindow); + }); + + it('should call onSuccess if api succeeds', async () => { + const { result } = renderHook(() => useUpdateMaintenanceWindow(), { + wrapper: appMockRenderer.AppWrapper, + }); + + await act(async () => { + await result.current.mutate({ maintenanceWindowId: '123', maintenanceWindow }); + }); + await waitFor(() => + expect(mockAddSuccess).toBeCalledWith("Updated maintenance window 'updated'") + ); + }); + + it('should call onError if api fails', async () => { + updateMaintenanceWindow.mockRejectedValue(''); + + const { result } = renderHook(() => useUpdateMaintenanceWindow(), { + wrapper: appMockRenderer.AppWrapper, + }); + + await act(async () => { + await result.current.mutate({ maintenanceWindowId: '123', maintenanceWindow }); + }); + + await waitFor(() => + expect(mockAddDanger).toBeCalledWith("Failed to update maintenance window '123'") + ); + }); +}); diff --git a/x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.ts b/x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.ts new file mode 100644 index 0000000000000..de6596b1c766d --- /dev/null +++ b/x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { useMutation } from '@tanstack/react-query'; + +import { useKibana } from '../utils/kibana_react'; +import { MaintenanceWindow } from '../pages/maintenance_windows/types'; +import { updateMaintenanceWindow } from '../services/maintenance_windows_api/update'; + +export function useUpdateMaintenanceWindow() { + const { + http, + notifications: { toasts }, + } = useKibana().services; + + const mutationFn = ({ + maintenanceWindowId, + maintenanceWindow, + }: { + maintenanceWindowId: string; + maintenanceWindow: MaintenanceWindow; + }) => { + return updateMaintenanceWindow({ http, maintenanceWindowId, maintenanceWindow }); + }; + + return useMutation(mutationFn, { + onSuccess: (variables: MaintenanceWindow) => { + toasts.addSuccess( + i18n.translate('xpack.alerting.maintenanceWindowsUpdateSuccess', { + defaultMessage: "Updated maintenance window '{title}'", + values: { + title: variables.title, + }, + }) + ); + }, + onError: (error, variables) => { + toasts.addDanger( + i18n.translate('xpack.alerting.maintenanceWindowsUpdateFailure', { + defaultMessage: "Failed to update maintenance window '{id}'", + values: { + id: variables.maintenanceWindowId, + }, + }) + ); + }, + }); +} diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx index 41c6dc304d93c..bef99e1188051 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx @@ -22,6 +22,7 @@ import { RecurringSchedule } from './recurring_schedule_form/recurring_schedule' import { SubmitButton } from './submit_button'; import { convertToRRule } from '../helpers/convert_to_rrule'; import { useCreateMaintenanceWindow } from '../../../hooks/use_create_maintenance_window'; +import { useUpdateMaintenanceWindow } from '../../../hooks/use_update_maintenance_window'; import { useUiSetting } from '../../../utils/kibana_react'; import { DatePickerRangeField } from './fields/date_picker_range_field'; @@ -31,6 +32,7 @@ export interface CreateMaintenanceWindowFormProps { onCancel: () => void; onSuccess: () => void; initialValue?: FormProps; + maintenanceWindowId?: string; } export const useTimeZone = (): string => { @@ -39,30 +41,42 @@ export const useTimeZone = (): string => { }; export const CreateMaintenanceWindowForm = React.memo( - ({ onCancel, onSuccess, initialValue }) => { + ({ onCancel, onSuccess, initialValue, maintenanceWindowId }) => { const [defaultStartDateValue] = useState(moment().toISOString()); const [defaultEndDateValue] = useState(moment().add(30, 'minutes').toISOString()); const timezone = useTimeZone(); + const isEditMode = initialValue !== undefined && maintenanceWindowId !== undefined; const { mutate: createMaintenanceWindow, isLoading: isCreateLoading } = useCreateMaintenanceWindow(); + const { mutate: updateMaintenanceWindow, isLoading: isUpdateLoading } = + useUpdateMaintenanceWindow(); const submitMaintenanceWindow = useCallback( async (formData, isValid) => { if (isValid) { const startDate = moment(formData.startDate); const endDate = moment(formData.endDate); - await createMaintenanceWindow( - { - title: formData.title, - duration: endDate.diff(startDate), - rRule: convertToRRule(startDate, timezone, formData.recurringSchedule), - }, - { onSuccess } - ); + const maintenanceWindow = { + title: formData.title, + duration: endDate.diff(startDate), + rRule: convertToRRule(startDate, timezone, formData.recurringSchedule), + }; + if (isEditMode) { + updateMaintenanceWindow({ maintenanceWindowId, maintenanceWindow }, { onSuccess }); + } else { + createMaintenanceWindow(maintenanceWindow, { onSuccess }); + } } }, - [createMaintenanceWindow, onSuccess, timezone] + [ + isEditMode, + maintenanceWindowId, + updateMaintenanceWindow, + createMaintenanceWindow, + onSuccess, + timezone, + ] ); const { form } = useForm({ @@ -151,7 +165,7 @@ export const CreateMaintenanceWindowForm = React.memo - + diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/maintenance_windows_list.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/maintenance_windows_list.tsx index f7482e1ec78d3..33a36a2bc0dea 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/maintenance_windows_list.tsx +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/maintenance_windows_list.tsx @@ -17,6 +17,7 @@ import { import { css } from '@emotion/react'; import { MaintenanceWindowFindResponse, SortDirection } from '../types'; import * as i18n from '../translations'; +import { useEditMaintenanceWindowsNavigation } from '../../../hooks/use_navigation'; import { StatusColor, STATUS_DISPLAY, STATUS_SORT } from '../constants'; import { MaintenanceWindowStatus } from '../../../../common'; import { StatusFilter } from './status_filter'; @@ -94,6 +95,7 @@ const search: { filters: SearchFilterConfig[] } = { export const MaintenanceWindowsList = React.memo( ({ loading, items }) => { + const { navigateToEditMaintenanceWindows } = useEditMaintenanceWindowsNavigation(); const warningBackgroundColor = useEuiBackgroundColor('warning'); const subduedBackgroundColor = useEuiBackgroundColor('subdued'); const tableCss = useMemo(() => { @@ -110,6 +112,23 @@ export const MaintenanceWindowsList = React.memo( `; }, [warningBackgroundColor, subduedBackgroundColor]); + const actions: Array> = [ + { + name: '', + actions: [ + { + name: i18n.TABLE_ACTION_EDIT, + isPrimary: true, + description: 'Edit maintenance window', + icon: 'pencil', + type: 'icon', + onClick: (mw: MaintenanceWindowFindResponse) => navigateToEditMaintenanceWindows(mw.id), + 'data-test-subj': 'action-edit', + }, + ], + }, + ]; + return ( ( loading={loading} tableCaption="Maintenance Windows List" items={items} - columns={columns} + columns={columns.concat(actions)} pagination={true} sorting={sorting} rowProps={rowProps} search={search} + hasActions={true} /> ); } diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/submit_button.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/submit_button.tsx index d02e003347162..544c093539210 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/submit_button.tsx +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/submit_button.tsx @@ -13,9 +13,10 @@ import * as i18n from '../translations'; interface SubmitButtonProps { isLoading: boolean; + editMode?: boolean; } -export const SubmitButton: React.FC = React.memo(({ isLoading }) => { +export const SubmitButton: React.FC = React.memo(({ isLoading, editMode }) => { const { submit, isSubmitting } = useFormContext(); return ( @@ -26,7 +27,7 @@ export const SubmitButton: React.FC = React.memo(({ isLoading isLoading={isLoading || isSubmitting} onClick={submit} > - {i18n.CREATE_MAINTENANCE_WINDOW} + {editMode ? i18n.SAVE_MAINTENANCE_WINDOW : i18n.CREATE_MAINTENANCE_WINDOW} ); }); diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts new file mode 100644 index 0000000000000..9dc72ea30b1c1 --- /dev/null +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts @@ -0,0 +1,318 @@ +/* + * 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 moment from 'moment'; + +import { Frequency } from '../constants'; +import { RRuleFrequency } from '../types'; +import { convertFromMaintenanceWindowToForm } from './convert_from_maintenance_window_to_form'; + +describe('convertFromMaintenanceWindowToForm', () => { + const title = 'test'; + const today = '2023-03-22'; + const startDate = moment(today); + const endDate = moment(today).add(2, 'days'); + const duration = endDate.diff(startDate); + + test('should convert a maintenance window that is not recurring', () => { + const maintenanceWindow = convertFromMaintenanceWindowToForm({ + title, + duration, + rRule: { + dtstart: startDate.toISOString(), + tzid: 'UTC', + freq: RRuleFrequency.YEARLY, + count: 1, + }, + }); + + expect(maintenanceWindow).toEqual({ + title, + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + recurring: false, + }); + }); + + test('should convert a maintenance window that is recurring on a daily schedule', () => { + const maintenanceWindow = convertFromMaintenanceWindowToForm({ + title, + duration, + rRule: { + dtstart: startDate.toISOString(), + tzid: 'UTC', + freq: RRuleFrequency.DAILY, + interval: 1, + byweekday: ['WE'], + }, + }); + + expect(maintenanceWindow).toEqual({ + title, + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + recurring: true, + recurringSchedule: { + byweekday: { 1: false, 2: false, 3: true, 4: false, 5: false, 6: false, 7: false }, + ends: 'never', + frequency: Frequency.DAILY, + interval: 1, + }, + }); + }); + + test('should convert a maintenance window that is recurring on a daily schedule until', () => { + const until = moment(today).add(1, 'month').toISOString(); + + const maintenanceWindow = convertFromMaintenanceWindowToForm({ + title, + duration, + rRule: { + dtstart: startDate.toISOString(), + tzid: 'UTC', + freq: RRuleFrequency.DAILY, + interval: 1, + byweekday: ['WE'], + until, + }, + }); + + expect(maintenanceWindow).toEqual({ + title, + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + recurring: true, + recurringSchedule: { + byweekday: { 1: false, 2: false, 3: true, 4: false, 5: false, 6: false, 7: false }, + ends: 'ondate', + until, + frequency: Frequency.DAILY, + interval: 1, + }, + }); + }); + + test('should convert a maintenance window that is recurring on a daily schedule after x', () => { + const maintenanceWindow = convertFromMaintenanceWindowToForm({ + title, + duration, + rRule: { + dtstart: startDate.toISOString(), + tzid: 'UTC', + freq: RRuleFrequency.DAILY, + interval: 1, + byweekday: ['WE'], + count: 3, + }, + }); + + expect(maintenanceWindow).toEqual({ + title, + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + recurring: true, + recurringSchedule: { + byweekday: { 1: false, 2: false, 3: true, 4: false, 5: false, 6: false, 7: false }, + ends: 'afterx', + count: 3, + frequency: Frequency.DAILY, + interval: 1, + }, + }); + }); + + test('should convert a maintenance window that is recurring on a weekly schedule', () => { + const maintenanceWindow = convertFromMaintenanceWindowToForm({ + title, + duration, + rRule: { + dtstart: startDate.toISOString(), + tzid: 'UTC', + freq: RRuleFrequency.WEEKLY, + interval: 1, + byweekday: ['WE'], + }, + }); + + expect(maintenanceWindow).toEqual({ + title, + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + recurring: true, + recurringSchedule: { + ends: 'never', + frequency: Frequency.WEEKLY, + byweekday: { 1: false, 2: false, 3: true, 4: false, 5: false, 6: false, 7: false }, + interval: 1, + }, + }); + }); + + test('should convert a maintenance window that is recurring on a monthly schedule', () => { + const maintenanceWindow = convertFromMaintenanceWindowToForm({ + title, + duration, + rRule: { + dtstart: startDate.toISOString(), + tzid: 'UTC', + freq: RRuleFrequency.MONTHLY, + interval: 1, + byweekday: ['+4WE'], + }, + }); + + expect(maintenanceWindow).toEqual({ + title, + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + recurring: true, + recurringSchedule: { + ends: 'never', + frequency: Frequency.MONTHLY, + bymonth: 'weekday', + interval: 1, + }, + }); + }); + + test('should convert a maintenance window that is recurring on a yearly schedule', () => { + const maintenanceWindow = convertFromMaintenanceWindowToForm({ + title, + duration, + rRule: { + dtstart: startDate.toISOString(), + tzid: 'UTC', + freq: RRuleFrequency.YEARLY, + interval: 1, + bymonth: [2], + bymonthday: [22], + }, + }); + + expect(maintenanceWindow).toEqual({ + title, + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + recurring: true, + recurringSchedule: { + ends: 'never', + frequency: Frequency.YEARLY, + interval: 1, + }, + }); + }); + + test('should convert a maintenance window that is recurring on a custom daily schedule', () => { + const maintenanceWindow = convertFromMaintenanceWindowToForm({ + title, + duration, + rRule: { + dtstart: startDate.toISOString(), + tzid: 'UTC', + freq: RRuleFrequency.DAILY, + interval: 1, + }, + }); + + expect(maintenanceWindow).toEqual({ + title, + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + recurring: true, + recurringSchedule: { + customFrequency: Frequency.DAILY, + ends: 'never', + frequency: 'CUSTOM', + interval: 1, + }, + }); + }); + + test('should convert a maintenance window that is recurring on a custom weekly schedule', () => { + const maintenanceWindow = convertFromMaintenanceWindowToForm({ + title, + duration, + rRule: { + dtstart: startDate.toISOString(), + tzid: 'UTC', + freq: RRuleFrequency.WEEKLY, + interval: 1, + byweekday: ['WE', 'TH'], + }, + }); + + expect(maintenanceWindow).toEqual({ + title, + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + recurring: true, + recurringSchedule: { + byweekday: { 1: false, 2: false, 3: true, 4: true, 5: false, 6: false, 7: false }, + customFrequency: Frequency.WEEKLY, + ends: 'never', + frequency: 'CUSTOM', + interval: 1, + }, + }); + }); + + test('should convert a maintenance window that is recurring on a custom monthly by day schedule', () => { + const maintenanceWindow = convertFromMaintenanceWindowToForm({ + title, + duration, + rRule: { + dtstart: startDate.toISOString(), + tzid: 'UTC', + freq: RRuleFrequency.MONTHLY, + interval: 1, + bymonthday: [22], + }, + }); + + expect(maintenanceWindow).toEqual({ + title, + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + recurring: true, + recurringSchedule: { + bymonth: 'day', + customFrequency: Frequency.MONTHLY, + ends: 'never', + frequency: 'CUSTOM', + interval: 1, + }, + }); + }); + + test('should convert a maintenance window that is recurring on a custom yearly schedule', () => { + const maintenanceWindow = convertFromMaintenanceWindowToForm({ + title, + duration, + rRule: { + dtstart: startDate.toISOString(), + tzid: 'UTC', + freq: RRuleFrequency.YEARLY, + interval: 3, + bymonth: [2], + bymonthday: [22], + }, + }); + + expect(maintenanceWindow).toEqual({ + title, + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + recurring: true, + recurringSchedule: { + customFrequency: Frequency.YEARLY, + ends: 'never', + frequency: 'CUSTOM', + interval: 3, + }, + }); + }); +}); diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.ts new file mode 100644 index 0000000000000..c4b2c551de0cd --- /dev/null +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.ts @@ -0,0 +1,94 @@ +/* + * 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 moment from 'moment'; +import { has } from 'lodash'; +import { MaintenanceWindow } from '../types'; +import { EndsOptions, Frequency } from '../constants'; +import { FormProps, RecurringScheduleFormProps } from '../components/schema'; +import { getInitialByWeekday } from './get_initial_by_weekday'; +import { RRuleParams } from '../../../../common'; + +export const convertFromMaintenanceWindowToForm = ( + maintenanceWindow: MaintenanceWindow +): FormProps => { + const startDate = maintenanceWindow.rRule.dtstart; + const endDate = moment(startDate).add(maintenanceWindow.duration); + // maintenance window is considered recurring if interval is defined + const recurring = has(maintenanceWindow, 'rRule.interval'); + const form: FormProps = { + title: maintenanceWindow.title, + startDate, + endDate: endDate.toISOString(), + recurring, + }; + if (!recurring) return form; + + const rRule = maintenanceWindow.rRule; + const isCustomFrequency = isCustom(rRule); + const frequency = rRule.freq?.toString() as Frequency; + const ends = rRule.until + ? EndsOptions.ON_DATE + : rRule.count + ? EndsOptions.AFTER_X + : EndsOptions.NEVER; + + const recurringSchedule: RecurringScheduleFormProps = { + frequency: isCustomFrequency ? 'CUSTOM' : frequency, + interval: rRule.interval, + ends, + }; + + if (isCustomFrequency) { + recurringSchedule.customFrequency = frequency; + } + + if (rRule.until) { + recurringSchedule.until = rRule.until; + } + if (rRule.count) { + recurringSchedule.count = rRule.count; + } + if (frequency !== Frequency.MONTHLY && rRule.byweekday) { + recurringSchedule.byweekday = getInitialByWeekday( + rRule.byweekday as string[], + moment(startDate) + ); + } + if (frequency === Frequency.MONTHLY) { + if (rRule.byweekday) { + recurringSchedule.bymonth = 'weekday'; + } else if (rRule.bymonthday) { + recurringSchedule.bymonth = 'day'; + } + } + + form.recurringSchedule = recurringSchedule; + + return form; +}; + +const isCustom = (rRule: RRuleParams) => { + const freq = rRule.freq?.toString() as Frequency; + // interval is greater than 1 + if (rRule.interval && rRule.interval > 1) { + return true; + } + // frequency is daily and no weekdays are selected + if (freq && freq === Frequency.DAILY && !rRule.byweekday) { + return true; + } + // frequency is weekly and there are multiple weekdays selected + if (freq && freq === Frequency.WEEKLY && rRule.byweekday && rRule.byweekday.length > 1) { + return true; + } + // frequency is monthly and by month day is selected + if (freq && freq === Frequency.MONTHLY && rRule.bymonthday) { + return true; + } + return false; +}; diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.ts index 42935ae291934..b284e50579deb 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.ts @@ -34,45 +34,43 @@ export const convertToRRule = ( count: 1, }; - if (recurringForm) { - let form = recurringForm; - if (recurringForm.frequency !== 'CUSTOM') { - form = { ...recurringForm, ...presets[recurringForm.frequency] }; - } - - const frequency = form.customFrequency ? form.customFrequency : (form.frequency as Frequency); - rRule.freq = RRuleFrequencyMap[frequency]; + let form = recurringForm; + if (recurringForm.frequency !== 'CUSTOM') { + form = { ...recurringForm, ...presets[recurringForm.frequency] }; + } - rRule.interval = form.interval; + const frequency = form.customFrequency ? form.customFrequency : (form.frequency as Frequency); + rRule.freq = RRuleFrequencyMap[frequency]; - if (form.until) { - rRule.until = form.until; - } + rRule.interval = form.interval; - if (form.count) { - rRule.count = form.count; - } + if (form.until) { + rRule.until = form.until; + } - if (form.byweekday) { - const byweekday = form.byweekday; - rRule.byweekday = Object.keys(byweekday) - .filter((k) => byweekday[k] === true) - .map((n) => ISO_WEEKDAYS_TO_RRULE[Number(n)]); - } + if (form.count) { + rRule.count = form.count; + } - if (form.bymonth) { - if (form.bymonth === 'day') { - rRule.bymonthday = [startDate.date()]; - } else if (form.bymonth === 'weekday') { - rRule.byweekday = [getNthByWeekday(startDate)]; - } - } + if (form.byweekday) { + const byweekday = form.byweekday; + rRule.byweekday = Object.keys(byweekday) + .filter((k) => byweekday[k] === true) + .map((n) => ISO_WEEKDAYS_TO_RRULE[Number(n)]); + } - if (frequency === Frequency.YEARLY) { - rRule.bymonth = [startDate.month()]; + if (form.bymonth) { + if (form.bymonth === 'day') { rRule.bymonthday = [startDate.date()]; + } else if (form.bymonth === 'weekday') { + rRule.byweekday = [getNthByWeekday(startDate)]; } } + if (frequency === Frequency.YEARLY) { + rRule.bymonth = [startDate.month()]; + rRule.bymonthday = [startDate.date()]; + } + return rRule; }; diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/maintenance_window_edit_page.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/maintenance_window_edit_page.tsx new file mode 100644 index 0000000000000..d8969988ee7d9 --- /dev/null +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/maintenance_window_edit_page.tsx @@ -0,0 +1,52 @@ +/* + * 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 { useParams } from 'react-router-dom'; +import { EuiPageSection, EuiSpacer } from '@elastic/eui'; + +import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; +import { useMaintenanceWindowsNavigation } from '../../hooks/use_navigation'; +import * as i18n from './translations'; +import { PageHeader } from './components/page_header'; +import { CreateMaintenanceWindowForm } from './components/create_maintenance_windows_form'; +import { AlertingDeepLinkId } from '../../config'; +import { useGetMaintenanceWindow } from '../../hooks/use_get_maintenance_window'; +import { CenterJustifiedSpinner } from './components/center_justified_spinner'; + +export const MaintenanceWindowsEditPage = React.memo(() => { + const { navigateToMaintenanceWindows } = useMaintenanceWindowsNavigation(); + + useBreadcrumbs(AlertingDeepLinkId.maintenanceWindowsEdit); + + const { maintenanceWindowId } = useParams<{ maintenanceWindowId: string }>(); + const { maintenanceWindow, isLoading, isError } = useGetMaintenanceWindow(maintenanceWindowId); + + if (isError) { + navigateToMaintenanceWindows(); + } + + if (!maintenanceWindow || isLoading) { + return ; + } + + return ( + + + + + + ); +}); +MaintenanceWindowsEditPage.displayName = 'MaintenanceWindowsEditPage'; +// eslint-disable-next-line import/no-default-export +export { MaintenanceWindowsEditPage as default }; diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts index 64b0c0fae247f..7da8ad80a5932 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts @@ -428,3 +428,21 @@ export const TABLE_START_TIME = i18n.translate( export const TABLE_END_TIME = i18n.translate('xpack.alerting.maintenanceWindows.table.endTime', { defaultMessage: 'End time', }); + +export const TABLE_ACTION_EDIT = i18n.translate('xpack.alerting.maintenanceWindows.table.edit', { + defaultMessage: 'Edit', +}); + +export const EDIT_MAINTENANCE_WINDOW = i18n.translate( + 'xpack.alerting.maintenanceWindows.edit.maintenanceWindow', + { + defaultMessage: 'Edit maintenance window', + } +); + +export const SAVE_MAINTENANCE_WINDOW = i18n.translate( + 'xpack.alerting.maintenanceWindows.save.maintenanceWindow', + { + defaultMessage: 'Save maintenance window', + } +); diff --git a/x-pack/plugins/alerting/public/services/maintenance_windows_api/create.ts b/x-pack/plugins/alerting/public/services/maintenance_windows_api/create.ts index e4032edbef640..73323b257d3e2 100644 --- a/x-pack/plugins/alerting/public/services/maintenance_windows_api/create.ts +++ b/x-pack/plugins/alerting/public/services/maintenance_windows_api/create.ts @@ -12,12 +12,12 @@ import { INTERNAL_BASE_ALERTING_API_PATH } from '../../../common'; const rewriteBodyRequest: RewriteResponseCase = ({ rRule, ...res }) => ({ ...res, - r_rule: { ...rRule }, + r_rule: rRule, }); const rewriteBodyRes: RewriteRequestCase = ({ r_rule: rRule, ...rest }) => ({ ...rest, - rRule: { ...rRule }, + rRule, }); export async function createMaintenanceWindow({ diff --git a/x-pack/plugins/alerting/public/services/maintenance_windows_api/find.ts b/x-pack/plugins/alerting/public/services/maintenance_windows_api/find.ts index cc407c2eacb12..e54028d30dfa8 100644 --- a/x-pack/plugins/alerting/public/services/maintenance_windows_api/find.ts +++ b/x-pack/plugins/alerting/public/services/maintenance_windows_api/find.ts @@ -30,7 +30,7 @@ const transform: RewriteRequestCase = ({ }) => ({ ...rest, expirationDate, - rRule: { ...rRule }, + rRule, eventStartTime, eventEndTime, createdBy, diff --git a/x-pack/plugins/alerting/public/services/maintenance_windows_api/get.test.ts b/x-pack/plugins/alerting/public/services/maintenance_windows_api/get.test.ts new file mode 100644 index 0000000000000..f19d77e55b405 --- /dev/null +++ b/x-pack/plugins/alerting/public/services/maintenance_windows_api/get.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 { httpServiceMock } from '@kbn/core/public/mocks'; +import { MaintenanceWindow } from '../../pages/maintenance_windows/types'; +import { getMaintenanceWindow } from './get'; + +const http = httpServiceMock.createStartContract(); + +beforeEach(() => jest.resetAllMocks()); + +describe('getMaintenanceWindow', () => { + test('should call get maintenance windows api', async () => { + const apiResponse = { + title: 'test', + duration: 1, + r_rule: { + dtstart: '2023-03-23T19:16:21.293Z', + tzid: 'America/New_York', + freq: 3, + interval: 1, + byweekday: ['TH'], + }, + }; + http.get.mockResolvedValueOnce(apiResponse); + + const maintenanceWindow: MaintenanceWindow = { + title: 'test', + duration: 1, + rRule: { + dtstart: '2023-03-23T19:16:21.293Z', + tzid: 'America/New_York', + freq: 3, + interval: 1, + byweekday: ['TH'], + }, + }; + + const result = await getMaintenanceWindow({ http, maintenanceWindowId: '123' }); + expect(result).toEqual(maintenanceWindow); + expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/internal/alerting/rules/maintenance_window/123", + ] + `); + }); +}); diff --git a/x-pack/plugins/alerting/public/services/maintenance_windows_api/get.ts b/x-pack/plugins/alerting/public/services/maintenance_windows_api/get.ts new file mode 100644 index 0000000000000..d353f2b45124b --- /dev/null +++ b/x-pack/plugins/alerting/public/services/maintenance_windows_api/get.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { HttpSetup } from '@kbn/core/public'; +import { AsApiContract, RewriteRequestCase } from '@kbn/actions-plugin/common'; + +import { MaintenanceWindow } from '../../pages/maintenance_windows/types'; +import { INTERNAL_BASE_ALERTING_API_PATH } from '../../../common'; + +const rewriteBodyRes: RewriteRequestCase = ({ r_rule: rRule, ...rest }) => ({ + ...rest, + rRule, +}); + +export async function getMaintenanceWindow({ + http, + maintenanceWindowId, +}: { + http: HttpSetup; + maintenanceWindowId: string; +}): Promise { + const res = await http.get>( + `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/${encodeURIComponent( + maintenanceWindowId + )}` + ); + + return rewriteBodyRes(res); +} diff --git a/x-pack/plugins/alerting/public/services/maintenance_windows_api/update.test.ts b/x-pack/plugins/alerting/public/services/maintenance_windows_api/update.test.ts new file mode 100644 index 0000000000000..4c46ac4519928 --- /dev/null +++ b/x-pack/plugins/alerting/public/services/maintenance_windows_api/update.test.ts @@ -0,0 +1,58 @@ +/* + * 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 { httpServiceMock } from '@kbn/core/public/mocks'; +import { MaintenanceWindow } from '../../pages/maintenance_windows/types'; +import { updateMaintenanceWindow } from './update'; + +const http = httpServiceMock.createStartContract(); + +beforeEach(() => jest.resetAllMocks()); + +describe('updateMaintenanceWindow', () => { + test('should call update maintenance window api', async () => { + const apiResponse = { + title: 'test', + duration: 1, + r_rule: { + dtstart: '2023-03-23T19:16:21.293Z', + tzid: 'America/New_York', + freq: 3, + interval: 1, + byweekday: ['TH'], + }, + }; + http.post.mockResolvedValueOnce(apiResponse); + + const maintenanceWindow: MaintenanceWindow = { + title: 'test', + duration: 1, + rRule: { + dtstart: '2023-03-23T19:16:21.293Z', + tzid: 'America/New_York', + freq: 3, + interval: 1, + byweekday: ['TH'], + }, + }; + + const result = await updateMaintenanceWindow({ + http, + maintenanceWindowId: '123', + maintenanceWindow, + }); + expect(result).toEqual(maintenanceWindow); + expect(http.post.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/internal/alerting/rules/maintenance_window/123", + Object { + "body": "{\\"title\\":\\"test\\",\\"duration\\":1,\\"r_rule\\":{\\"dtstart\\":\\"2023-03-23T19:16:21.293Z\\",\\"tzid\\":\\"America/New_York\\",\\"freq\\":3,\\"interval\\":1,\\"byweekday\\":[\\"TH\\"]}}", + }, + ] + `); + }); +}); diff --git a/x-pack/plugins/alerting/public/services/maintenance_windows_api/update.ts b/x-pack/plugins/alerting/public/services/maintenance_windows_api/update.ts new file mode 100644 index 0000000000000..c8328c23e825d --- /dev/null +++ b/x-pack/plugins/alerting/public/services/maintenance_windows_api/update.ts @@ -0,0 +1,40 @@ +/* + * 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 { HttpSetup } from '@kbn/core/public'; +import { AsApiContract, RewriteRequestCase, RewriteResponseCase } from '@kbn/actions-plugin/common'; + +import { MaintenanceWindow } from '../../pages/maintenance_windows/types'; +import { INTERNAL_BASE_ALERTING_API_PATH } from '../../../common'; + +const rewriteBodyRequest: RewriteResponseCase = ({ rRule, ...res }) => ({ + ...res, + r_rule: rRule, +}); + +const rewriteBodyRes: RewriteRequestCase = ({ r_rule: rRule, ...rest }) => ({ + ...rest, + rRule, +}); + +export async function updateMaintenanceWindow({ + http, + maintenanceWindowId, + maintenanceWindow, +}: { + http: HttpSetup; + maintenanceWindowId: string; + maintenanceWindow: MaintenanceWindow; +}): Promise { + const res = await http.post>( + `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/${encodeURIComponent( + maintenanceWindowId + )}`, + { body: JSON.stringify(rewriteBodyRequest(maintenanceWindow)) } + ); + + return rewriteBodyRes(res); +} From 0b71c7458df0d78869668dbe38c7b11757da3193 Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Thu, 20 Apr 2023 10:51:04 -0400 Subject: [PATCH 06/67] [Synthetics] add default email recovery message (#154862) ## Summary Resolves https://github.com/elastic/kibana/issues/153891 Screen Shot 2023-04-18 at 7 50 20 PM Down email Recovered email Screen Shot 2023-04-18 at 7 50 30 PM Please note the `recoverd` typo in the screenshot has been addressed in commits [7c4040f](https://github.com/elastic/kibana/pull/154862/commits/7c4040fda316f0565eb1ea429417b0fa91f6424d) and [56f637b](https://github.com/elastic/kibana/pull/154862/commits/56f637b82edf81b7ab6dafd54c197f1b945c487b) ### Testing This PR is extremely tricky to test. I have deployed this branch to my own cloud account. This ensures that I can control the allowlist setting for emails on cloud. If you want, I can give you the cloud cluster where this branch is hosted and if you give me your email I can add you to my allowlist settings for you to test. The cloud cluster is current on commit [f889500](https://github.com/elastic/kibana/pull/154862/commits/f889500dc2b217fefb1a02a9e3c15b7cad8217cb) --- .../common/rules/alert_actions.test.ts | 140 ++++++++++++++- .../synthetics/common/rules/alert_actions.ts | 17 +- .../rules/legacy_uptime/translations.ts | 160 ++++++++++++++++++ .../common/rules/synthetics/translations.ts | 10 ++ .../plugins/synthetics/common/translations.ts | 143 ---------------- .../lib/alert_types/duration_anomaly.tsx | 2 +- .../lib/alert_types/monitor_status.tsx | 2 +- .../legacy_uptime/lib/alert_types/tls.tsx | 2 +- .../lib/alert_types/tls_legacy.tsx | 2 +- .../public/legacy_uptime/state/api/alerts.ts | 3 +- .../lib/alerts/duration_anomaly.ts | 2 +- .../server/legacy_uptime/lib/alerts/tls.ts | 2 +- .../default_alerts/status_alert_service.ts | 2 + .../uptime/simple_down_alert.ts | 2 +- 14 files changed, 333 insertions(+), 156 deletions(-) create mode 100644 x-pack/plugins/synthetics/common/rules/legacy_uptime/translations.ts diff --git a/x-pack/plugins/synthetics/common/rules/alert_actions.test.ts b/x-pack/plugins/synthetics/common/rules/alert_actions.test.ts index 219790bfe991c..d31e0cd74c360 100644 --- a/x-pack/plugins/synthetics/common/rules/alert_actions.test.ts +++ b/x-pack/plugins/synthetics/common/rules/alert_actions.test.ts @@ -9,7 +9,7 @@ import { populateAlertActions } from './alert_actions'; import { ActionConnector } from './types'; import { MONITOR_STATUS } from '../constants/uptime_alerts'; import { MONITOR_STATUS as SYNTHETICS_MONITOR_STATUS } from '../constants/synthetics_alerts'; -import { MonitorStatusTranslations } from '../translations'; +import { MonitorStatusTranslations } from './legacy_uptime/translations'; import { SyntheticsMonitorStatusTranslations } from './synthetics/translations'; describe('Legacy Alert Actions factory', () => { @@ -33,6 +33,7 @@ describe('Legacy Alert Actions factory', () => { defaultActionMessage: MonitorStatusTranslations.defaultActionMessage, defaultRecoveryMessage: MonitorStatusTranslations.defaultRecoveryMessage, defaultSubjectMessage: MonitorStatusTranslations.defaultSubjectMessage, + defaultRecoverySubjectMessage: MonitorStatusTranslations.defaultRecoverySubjectMessage, }, isLegacy: true, }); @@ -60,6 +61,70 @@ describe('Legacy Alert Actions factory', () => { ]); }); + it('generate expected action for email', async () => { + const resp = populateAlertActions({ + groupId: MONITOR_STATUS.id, + defaultActions: [ + { + actionTypeId: '.email', + group: 'xpack.uptime.alerts.actionGroups.monitorStatus', + params: { + dedupKey: 'always-downxpack.uptime.alerts.actionGroups.monitorStatus', + eventAction: 'trigger', + severity: 'error', + summary: MonitorStatusTranslations.defaultActionMessage, + }, + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + }, + ] as unknown as ActionConnector[], + translations: { + defaultActionMessage: MonitorStatusTranslations.defaultActionMessage, + defaultRecoveryMessage: MonitorStatusTranslations.defaultRecoveryMessage, + defaultSubjectMessage: MonitorStatusTranslations.defaultSubjectMessage, + defaultRecoverySubjectMessage: MonitorStatusTranslations.defaultRecoverySubjectMessage, + }, + isLegacy: true, + defaultEmail: { + to: ['test@email.com'], + }, + }); + expect(resp).toEqual([ + { + group: 'recovered', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + bcc: [], + cc: [], + kibanaFooterLink: { + path: '', + text: '', + }, + message: + 'Alert for monitor {{context.monitorName}} with url {{{context.monitorUrl}}} from {{context.observerLocation}} has recovered', + subject: + 'Monitor {{context.monitorName}} with url {{{context.monitorUrl}}} has recovered', + to: ['test@email.com'], + }, + }, + { + group: 'xpack.uptime.alerts.actionGroups.monitorStatus', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + bcc: [], + cc: [], + kibanaFooterLink: { + path: '', + text: '', + }, + message: + 'Monitor {{context.monitorName}} with url {{{context.monitorUrl}}} from {{context.observerLocation}} {{{context.statusMessage}}} The latest error message is {{{context.latestErrorMessage}}}, checked at {{context.checkedAt}}', + subject: 'Monitor {{context.monitorName}} with url {{{context.monitorUrl}}} is down', + to: ['test@email.com'], + }, + }, + ]); + }); + it('generate expected action for index', async () => { const resp = populateAlertActions({ groupId: MONITOR_STATUS.id, @@ -80,6 +145,7 @@ describe('Legacy Alert Actions factory', () => { defaultActionMessage: MonitorStatusTranslations.defaultActionMessage, defaultRecoveryMessage: MonitorStatusTranslations.defaultRecoveryMessage, defaultSubjectMessage: MonitorStatusTranslations.defaultSubjectMessage, + defaultRecoverySubjectMessage: MonitorStatusTranslations.defaultRecoverySubjectMessage, }, isLegacy: true, }); @@ -141,6 +207,7 @@ describe('Legacy Alert Actions factory', () => { defaultActionMessage: MonitorStatusTranslations.defaultActionMessage, defaultRecoveryMessage: MonitorStatusTranslations.defaultRecoveryMessage, defaultSubjectMessage: MonitorStatusTranslations.defaultSubjectMessage, + defaultRecoverySubjectMessage: MonitorStatusTranslations.defaultRecoverySubjectMessage, }, }); expect(resp).toEqual([ @@ -189,6 +256,8 @@ describe('Alert Actions factory', () => { defaultActionMessage: SyntheticsMonitorStatusTranslations.defaultActionMessage, defaultRecoveryMessage: SyntheticsMonitorStatusTranslations.defaultRecoveryMessage, defaultSubjectMessage: SyntheticsMonitorStatusTranslations.defaultSubjectMessage, + defaultRecoverySubjectMessage: + SyntheticsMonitorStatusTranslations.defaultRecoverySubjectMessage, }, }); expect(resp).toEqual([ @@ -235,6 +304,8 @@ describe('Alert Actions factory', () => { defaultActionMessage: SyntheticsMonitorStatusTranslations.defaultActionMessage, defaultRecoveryMessage: SyntheticsMonitorStatusTranslations.defaultRecoveryMessage, defaultSubjectMessage: SyntheticsMonitorStatusTranslations.defaultSubjectMessage, + defaultRecoverySubjectMessage: + SyntheticsMonitorStatusTranslations.defaultRecoverySubjectMessage, }, }); expect(resp).toEqual([ @@ -295,6 +366,8 @@ describe('Alert Actions factory', () => { defaultActionMessage: SyntheticsMonitorStatusTranslations.defaultActionMessage, defaultRecoveryMessage: SyntheticsMonitorStatusTranslations.defaultRecoveryMessage, defaultSubjectMessage: SyntheticsMonitorStatusTranslations.defaultSubjectMessage, + defaultRecoverySubjectMessage: + SyntheticsMonitorStatusTranslations.defaultRecoverySubjectMessage, }, }); expect(resp).toEqual([ @@ -320,4 +393,69 @@ describe('Alert Actions factory', () => { }, ]); }); + + it('generate expected action for email action connector', async () => { + const resp = populateAlertActions({ + groupId: SYNTHETICS_MONITOR_STATUS.id, + defaultActions: [ + { + actionTypeId: '.email', + group: 'xpack.synthetics.alerts.actionGroups.monitorStatus', + params: { + dedupKey: 'always-downxpack.uptime.alerts.actionGroups.monitorStatus', + eventAction: 'trigger', + severity: 'error', + summary: + 'Monitor {{context.monitorName}} with url {{{context.monitorUrl}}} from {{context.observerLocation}} {{{context.statusMessage}}} The latest error message is {{{context.latestErrorMessage}}}', + }, + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + }, + ] as unknown as ActionConnector[], + defaultEmail: { + to: ['test@email.com'], + }, + translations: { + defaultActionMessage: SyntheticsMonitorStatusTranslations.defaultActionMessage, + defaultRecoveryMessage: SyntheticsMonitorStatusTranslations.defaultRecoveryMessage, + defaultSubjectMessage: SyntheticsMonitorStatusTranslations.defaultSubjectMessage, + defaultRecoverySubjectMessage: + SyntheticsMonitorStatusTranslations.defaultRecoverySubjectMessage, + }, + }); + expect(resp).toEqual([ + { + group: 'recovered', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + bcc: [], + cc: [], + kibanaFooterLink: { + path: '', + text: '', + }, + message: + 'The alert for the monitor {{context.monitorName}} checking {{{context.monitorUrl}}} from {{context.locationName}} is no longer active: {{context.recoveryReason}}.', + subject: + 'The monitor {{context.monitorName}} checking {{{context.monitorUrl}}} has recovered.', + to: ['test@email.com'], + }, + }, + { + group: 'xpack.synthetics.alerts.actionGroups.monitorStatus', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + bcc: [], + cc: [], + kibanaFooterLink: { + path: '', + text: '', + }, + message: + 'The monitor {{context.monitorName}} checking {{{context.monitorUrl}}} from {{context.locationName}} last ran at {{context.checkedAt}} and is {{{context.status}}}. The last error received is: {{{context.lastErrorMessage}}}.', + subject: 'The monitor {{context.monitorName}} checking {{{context.monitorUrl}}} is down.', + to: ['test@email.com'], + }, + }, + ]); + }); }); diff --git a/x-pack/plugins/synthetics/common/rules/alert_actions.ts b/x-pack/plugins/synthetics/common/rules/alert_actions.ts index 3f8cedf715536..0c9782108743f 100644 --- a/x-pack/plugins/synthetics/common/rules/alert_actions.ts +++ b/x-pack/plugins/synthetics/common/rules/alert_actions.ts @@ -36,6 +36,7 @@ interface Translations { defaultActionMessage: string; defaultRecoveryMessage: string; defaultSubjectMessage: string; + defaultRecoverySubjectMessage: string; } export function populateAlertActions({ @@ -107,6 +108,8 @@ export function populateAlertActions({ case EMAIL_ACTION_ID: if (defaultEmail) { action.params = getEmailActionParams(translations, defaultEmail); + recoveredAction.params = getEmailActionParams(translations, defaultEmail, true); + actions.push(recoveredAction); } break; default: @@ -270,13 +273,19 @@ function getJiraActionParams({ defaultActionMessage }: Translations): JiraAction } function getEmailActionParams( - { defaultActionMessage, defaultSubjectMessage }: Translations, - defaultEmail: DefaultEmail + { + defaultActionMessage, + defaultSubjectMessage, + defaultRecoverySubjectMessage, + defaultRecoveryMessage, + }: Translations, + defaultEmail: DefaultEmail, + isRecovery?: boolean ): EmailActionParams { return { to: defaultEmail.to, - subject: defaultSubjectMessage, - message: defaultActionMessage, + subject: isRecovery ? defaultRecoverySubjectMessage : defaultSubjectMessage, + message: isRecovery ? defaultRecoveryMessage : defaultActionMessage, cc: defaultEmail.cc ?? [], bcc: defaultEmail.bcc ?? [], kibanaFooterLink: { diff --git a/x-pack/plugins/synthetics/common/rules/legacy_uptime/translations.ts b/x-pack/plugins/synthetics/common/rules/legacy_uptime/translations.ts new file mode 100644 index 0000000000000..fab705daeb0c0 --- /dev/null +++ b/x-pack/plugins/synthetics/common/rules/legacy_uptime/translations.ts @@ -0,0 +1,160 @@ +/* + * 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'; + +export const MonitorStatusTranslations = { + defaultActionMessage: i18n.translate( + 'xpack.synthetics.alerts.monitorStatus.defaultActionMessage', + { + defaultMessage: + 'Monitor {monitorName} with url {monitorUrl} from {observerLocation} {statusMessage} The latest error message is {latestErrorMessage}, checked at {checkedAt}', + values: { + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + statusMessage: '{{{context.statusMessage}}}', + latestErrorMessage: '{{{context.latestErrorMessage}}}', + observerLocation: '{{context.observerLocation}}', + checkedAt: '{{context.checkedAt}}', + }, + } + ), + defaultSubjectMessage: i18n.translate( + 'xpack.synthetics.alerts.monitorStatus.defaultSubjectMessage', + { + defaultMessage: 'Monitor {monitorName} with url {monitorUrl} is down', + values: { + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + }, + } + ), + defaultRecoverySubjectMessage: i18n.translate( + 'xpack.synthetics.alerts.monitorStatus.defaultRecoverySubjectMessage', + { + defaultMessage: 'Monitor {monitorName} with url {monitorUrl} has recovered', + values: { + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + }, + } + ), + defaultRecoveryMessage: i18n.translate( + 'xpack.synthetics.alerts.monitorStatus.defaultRecoveryMessage', + { + defaultMessage: + 'Alert for monitor {monitorName} with url {monitorUrl} from {observerLocation} has recovered', + values: { + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + observerLocation: '{{context.observerLocation}}', + }, + } + ), + name: i18n.translate('xpack.synthetics.alerts.monitorStatus.clientName', { + defaultMessage: 'Uptime monitor status', + }), + description: i18n.translate('xpack.synthetics.alerts.monitorStatus.description', { + defaultMessage: 'Alert when a monitor is down or an availability threshold is breached.', + }), +}; + +export const TlsTranslations = { + defaultActionMessage: i18n.translate('xpack.synthetics.alerts.tls.defaultActionMessage', { + defaultMessage: `Detected TLS certificate {commonName} from issuer {issuer} is {status}. Certificate {summary}`, + values: { + commonName: '{{context.commonName}}', + issuer: '{{context.issuer}}', + summary: '{{context.summary}}', + status: '{{context.status}}', + }, + }), + defaultRecoveryMessage: i18n.translate('xpack.synthetics.alerts.tls.defaultRecoveryMessage', { + defaultMessage: `Alert for TLS certificate {commonName} from issuer {issuer} has recovered`, + values: { + commonName: '{{context.commonName}}', + issuer: '{{context.issuer}}', + }, + }), + name: i18n.translate('xpack.synthetics.alerts.tls.clientName', { + defaultMessage: 'Uptime TLS', + }), + description: i18n.translate('xpack.synthetics.alerts.tls.description', { + defaultMessage: 'Alert when the TLS certificate of an Uptime monitor is about to expire.', + }), +}; + +export const TlsTranslationsLegacy = { + defaultActionMessage: i18n.translate('xpack.synthetics.alerts.tls.legacy.defaultActionMessage', { + defaultMessage: `Detected {count} TLS certificates expiring or becoming too old. +{expiringConditionalOpen} +Expiring cert count: {expiringCount} +Expiring Certificates: {expiringCommonNameAndDate} +{expiringConditionalClose} +{agingConditionalOpen} +Aging cert count: {agingCount} +Aging Certificates: {agingCommonNameAndDate} +{agingConditionalClose} +`, + values: { + count: '{{state.count}}', + expiringCount: '{{state.expiringCount}}', + expiringCommonNameAndDate: '{{state.expiringCommonNameAndDate}}', + expiringConditionalOpen: '{{#state.hasExpired}}', + expiringConditionalClose: '{{/state.hasExpired}}', + agingCount: '{{state.agingCount}}', + agingCommonNameAndDate: '{{state.agingCommonNameAndDate}}', + agingConditionalOpen: '{{#state.hasAging}}', + agingConditionalClose: '{{/state.hasAging}}', + }, + }), + name: i18n.translate('xpack.synthetics.alerts.tls.legacy.clientName', { + defaultMessage: 'Uptime TLS (Legacy)', + }), + description: i18n.translate('xpack.synthetics.alerts.tls.legacy.description', { + defaultMessage: + 'Alert when the TLS certificate of an Uptime monitor is about to expire. This alert will be deprecated in a future version.', + }), +}; + +export const DurationAnomalyTranslations = { + defaultActionMessage: i18n.translate( + 'xpack.synthetics.alerts.durationAnomaly.defaultActionMessage', + { + defaultMessage: `Abnormal ({severity} level) response time detected on {monitor} with url {monitorUrl} at {anomalyStartTimestamp}. Anomaly severity score is {severityScore}. +Response times as high as {slowestAnomalyResponse} have been detected from location {observerLocation}. Expected response time is {expectedResponseTime}.`, + values: { + severity: '{{context.severity}}', + anomalyStartTimestamp: '{{context.anomalyStartTimestamp}}', + monitor: '{{context.monitor}}', + monitorUrl: '{{{context.monitorUrl}}}', + slowestAnomalyResponse: '{{context.slowestAnomalyResponse}}', + expectedResponseTime: '{{context.expectedResponseTime}}', + severityScore: '{{context.severityScore}}', + observerLocation: '{{context.observerLocation}}', + }, + } + ), + defaultRecoveryMessage: i18n.translate( + 'xpack.synthetics.alerts.durationAnomaly.defaultRecoveryMessage', + { + defaultMessage: `Alert for abnormal ({severity} level) response time detected on monitor {monitor} with url {monitorUrl} from location {observerLocation} at {anomalyStartTimestamp} has recovered`, + values: { + severity: '{{context.severity}}', + anomalyStartTimestamp: '{{context.anomalyStartTimestamp}}', + monitor: '{{context.monitor}}', + monitorUrl: '{{{context.monitorUrl}}}', + observerLocation: '{{context.observerLocation}}', + }, + } + ), + name: i18n.translate('xpack.synthetics.alerts.durationAnomaly.clientName', { + defaultMessage: 'Uptime Duration Anomaly', + }), + description: i18n.translate('xpack.synthetics.alerts.durationAnomaly.description', { + defaultMessage: 'Alert when the Uptime monitor duration is anomalous.', + }), +}; diff --git a/x-pack/plugins/synthetics/common/rules/synthetics/translations.ts b/x-pack/plugins/synthetics/common/rules/synthetics/translations.ts index a6df24bfb5f8d..167c436dbed98 100644 --- a/x-pack/plugins/synthetics/common/rules/synthetics/translations.ts +++ b/x-pack/plugins/synthetics/common/rules/synthetics/translations.ts @@ -33,6 +33,16 @@ export const SyntheticsMonitorStatusTranslations = { }, } ), + defaultRecoverySubjectMessage: i18n.translate( + 'xpack.synthetics.alerts.syntheticsMonitorStatus.defaultRecoverySubjectMessage', + { + defaultMessage: 'The monitor {monitorName} checking {monitorUrl} has recovered.', + values: { + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + }, + } + ), defaultRecoveryMessage: i18n.translate( 'xpack.synthetics.alerts.syntheticsMonitorStatus.defaultRecoveryMessage', { diff --git a/x-pack/plugins/synthetics/common/translations.ts b/x-pack/plugins/synthetics/common/translations.ts index bd761456aa7b7..ac2bec7a5506b 100644 --- a/x-pack/plugins/synthetics/common/translations.ts +++ b/x-pack/plugins/synthetics/common/translations.ts @@ -20,146 +20,3 @@ export const VALUE_MUST_BE_AN_INTEGER = i18n.translate( defaultMessage: 'Value must be an integer.', } ); - -export const MonitorStatusTranslations = { - defaultActionMessage: i18n.translate( - 'xpack.synthetics.alerts.monitorStatus.defaultActionMessage', - { - defaultMessage: - 'Monitor {monitorName} with url {monitorUrl} from {observerLocation} {statusMessage} The latest error message is {latestErrorMessage}, checked at {checkedAt}', - values: { - monitorName: '{{context.monitorName}}', - monitorUrl: '{{{context.monitorUrl}}}', - statusMessage: '{{{context.statusMessage}}}', - latestErrorMessage: '{{{context.latestErrorMessage}}}', - observerLocation: '{{context.observerLocation}}', - checkedAt: '{{context.checkedAt}}', - }, - } - ), - defaultSubjectMessage: i18n.translate( - 'xpack.synthetics.alerts.monitorStatus.defaultSubjectMessage', - { - defaultMessage: 'Monitor {monitorName} with url {monitorUrl} is down', - values: { - monitorName: '{{context.monitorName}}', - monitorUrl: '{{{context.monitorUrl}}}', - }, - } - ), - defaultRecoveryMessage: i18n.translate( - 'xpack.synthetics.alerts.monitorStatus.defaultRecoveryMessage', - { - defaultMessage: - 'Alert for monitor {monitorName} with url {monitorUrl} from {observerLocation} has recovered', - values: { - monitorName: '{{context.monitorName}}', - monitorUrl: '{{{context.monitorUrl}}}', - observerLocation: '{{context.observerLocation}}', - }, - } - ), - name: i18n.translate('xpack.synthetics.alerts.monitorStatus.clientName', { - defaultMessage: 'Uptime monitor status', - }), - description: i18n.translate('xpack.synthetics.alerts.monitorStatus.description', { - defaultMessage: 'Alert when a monitor is down or an availability threshold is breached.', - }), -}; - -export const TlsTranslations = { - defaultActionMessage: i18n.translate('xpack.synthetics.alerts.tls.defaultActionMessage', { - defaultMessage: `Detected TLS certificate {commonName} from issuer {issuer} is {status}. Certificate {summary}`, - values: { - commonName: '{{context.commonName}}', - issuer: '{{context.issuer}}', - summary: '{{context.summary}}', - status: '{{context.status}}', - }, - }), - defaultRecoveryMessage: i18n.translate('xpack.synthetics.alerts.tls.defaultRecoveryMessage', { - defaultMessage: `Alert for TLS certificate {commonName} from issuer {issuer} has recovered`, - values: { - commonName: '{{context.commonName}}', - issuer: '{{context.issuer}}', - }, - }), - name: i18n.translate('xpack.synthetics.alerts.tls.clientName', { - defaultMessage: 'Uptime TLS', - }), - description: i18n.translate('xpack.synthetics.alerts.tls.description', { - defaultMessage: 'Alert when the TLS certificate of an Uptime monitor is about to expire.', - }), -}; - -export const TlsTranslationsLegacy = { - defaultActionMessage: i18n.translate('xpack.synthetics.alerts.tls.legacy.defaultActionMessage', { - defaultMessage: `Detected {count} TLS certificates expiring or becoming too old. -{expiringConditionalOpen} -Expiring cert count: {expiringCount} -Expiring Certificates: {expiringCommonNameAndDate} -{expiringConditionalClose} -{agingConditionalOpen} -Aging cert count: {agingCount} -Aging Certificates: {agingCommonNameAndDate} -{agingConditionalClose} -`, - values: { - count: '{{state.count}}', - expiringCount: '{{state.expiringCount}}', - expiringCommonNameAndDate: '{{state.expiringCommonNameAndDate}}', - expiringConditionalOpen: '{{#state.hasExpired}}', - expiringConditionalClose: '{{/state.hasExpired}}', - agingCount: '{{state.agingCount}}', - agingCommonNameAndDate: '{{state.agingCommonNameAndDate}}', - agingConditionalOpen: '{{#state.hasAging}}', - agingConditionalClose: '{{/state.hasAging}}', - }, - }), - name: i18n.translate('xpack.synthetics.alerts.tls.legacy.clientName', { - defaultMessage: 'Uptime TLS (Legacy)', - }), - description: i18n.translate('xpack.synthetics.alerts.tls.legacy.description', { - defaultMessage: - 'Alert when the TLS certificate of an Uptime monitor is about to expire. This alert will be deprecated in a future version.', - }), -}; - -export const DurationAnomalyTranslations = { - defaultActionMessage: i18n.translate( - 'xpack.synthetics.alerts.durationAnomaly.defaultActionMessage', - { - defaultMessage: `Abnormal ({severity} level) response time detected on {monitor} with url {monitorUrl} at {anomalyStartTimestamp}. Anomaly severity score is {severityScore}. -Response times as high as {slowestAnomalyResponse} have been detected from location {observerLocation}. Expected response time is {expectedResponseTime}.`, - values: { - severity: '{{context.severity}}', - anomalyStartTimestamp: '{{context.anomalyStartTimestamp}}', - monitor: '{{context.monitor}}', - monitorUrl: '{{{context.monitorUrl}}}', - slowestAnomalyResponse: '{{context.slowestAnomalyResponse}}', - expectedResponseTime: '{{context.expectedResponseTime}}', - severityScore: '{{context.severityScore}}', - observerLocation: '{{context.observerLocation}}', - }, - } - ), - defaultRecoveryMessage: i18n.translate( - 'xpack.synthetics.alerts.durationAnomaly.defaultRecoveryMessage', - { - defaultMessage: `Alert for abnormal ({severity} level) response time detected on monitor {monitor} with url {monitorUrl} from location {observerLocation} at {anomalyStartTimestamp} has recovered`, - values: { - severity: '{{context.severity}}', - anomalyStartTimestamp: '{{context.anomalyStartTimestamp}}', - monitor: '{{context.monitor}}', - monitorUrl: '{{{context.monitorUrl}}}', - observerLocation: '{{context.observerLocation}}', - }, - } - ), - name: i18n.translate('xpack.synthetics.alerts.durationAnomaly.clientName', { - defaultMessage: 'Uptime Duration Anomaly', - }), - description: i18n.translate('xpack.synthetics.alerts.durationAnomaly.description', { - defaultMessage: 'Alert when the Uptime monitor duration is anomalous.', - }), -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/duration_anomaly.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/duration_anomaly.tsx index 92406d65948f4..b6d5a63af7712 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/duration_anomaly.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/duration_anomaly.tsx @@ -14,7 +14,7 @@ import { ObservabilityRuleTypeModel } from '@kbn/observability-plugin/public'; import { AlertTypeInitializer } from '.'; import { getMonitorRouteFromMonitorId } from '../../../../common/utils/get_monitor_url'; import { CLIENT_ALERT_TYPES } from '../../../../common/constants/uptime_alerts'; -import { DurationAnomalyTranslations } from '../../../../common/translations'; +import { DurationAnomalyTranslations } from '../../../../common/rules/legacy_uptime/translations'; const { defaultActionMessage, defaultRecoveryMessage, description } = DurationAnomalyTranslations; const DurationAnomalyAlert = React.lazy(() => import('./lazy_wrapper/duration_anomaly')); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/monitor_status.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/monitor_status.tsx index 5910d3fb5093a..9af2ede31e42e 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/monitor_status.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/monitor_status.tsx @@ -20,7 +20,7 @@ import { ObservabilityRuleTypeModel } from '@kbn/observability-plugin/public'; import { ValidationResult } from '@kbn/triggers-actions-ui-plugin/public'; import { AlertTypeInitializer } from '.'; import { getMonitorRouteFromMonitorId } from '../../../../common/utils/get_monitor_url'; -import { MonitorStatusTranslations } from '../../../../common/translations'; +import { MonitorStatusTranslations } from '../../../../common/rules/legacy_uptime/translations'; import { CLIENT_ALERT_TYPES } from '../../../../common/constants/uptime_alerts'; const { defaultActionMessage, defaultRecoveryMessage, description } = MonitorStatusTranslations; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/tls.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/tls.tsx index f9d510243393b..c44949f930ae6 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/tls.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/tls.tsx @@ -12,7 +12,7 @@ import type { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plu import { ValidationResult } from '@kbn/triggers-actions-ui-plugin/public'; import { TLSParams } from '../../../../common/runtime_types/alerts/tls'; import { CLIENT_ALERT_TYPES } from '../../../../common/constants/uptime_alerts'; -import { TlsTranslations } from '../../../../common/translations'; +import { TlsTranslations } from '../../../../common/rules/legacy_uptime/translations'; import { AlertTypeInitializer } from '.'; import { CERTIFICATES_ROUTE } from '../../../../common/constants/ui'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/tls_legacy.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/tls_legacy.tsx index ed67a50ee08b7..38106cd6ce2f8 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/tls_legacy.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/tls_legacy.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { RuleTypeModel } from '@kbn/triggers-actions-ui-plugin/public'; import { CLIENT_ALERT_TYPES } from '../../../../common/constants/uptime_alerts'; -import { TlsTranslationsLegacy } from '../../../../common/translations'; +import { TlsTranslationsLegacy } from '../../../../common/rules/legacy_uptime/translations'; import { AlertTypeInitializer } from '.'; const { defaultActionMessage, description } = TlsTranslationsLegacy; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/alerts.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/alerts.ts index db335a1ef458b..aac00a0e8ed0e 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/alerts.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/alerts.ts @@ -7,7 +7,7 @@ import type { ActionType, AsApiContract, Rule } from '@kbn/triggers-actions-ui-plugin/public'; import { RuleTypeParams } from '@kbn/alerting-plugin/common'; -import { MonitorStatusTranslations } from '../../../../common/translations'; +import { MonitorStatusTranslations } from '../../../../common/rules/legacy_uptime/translations'; import { ActionConnector } from '../../../../common/rules/types'; import { CLIENT_ALERT_TYPES, MONITOR_STATUS } from '../../../../common/constants/uptime_alerts'; import { apiService } from './utils'; @@ -87,6 +87,7 @@ export const createAlert = async ({ defaultActionMessage: MonitorStatusTranslations.defaultActionMessage, defaultRecoveryMessage: MonitorStatusTranslations.defaultRecoveryMessage, defaultSubjectMessage: MonitorStatusTranslations.defaultSubjectMessage, + defaultRecoverySubjectMessage: MonitorStatusTranslations.defaultRecoverySubjectMessage, }, isLegacy: true, }); diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/duration_anomaly.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/duration_anomaly.ts index f70e3a96d1fb8..42d81f6e0d0d6 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/duration_anomaly.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/duration_anomaly.ts @@ -30,7 +30,7 @@ import { UptimeAlertTypeFactory } from './types'; import { Ping } from '../../../../common/runtime_types/ping'; import { getMLJobId } from '../../../../common/lib'; -import { DurationAnomalyTranslations as CommonDurationAnomalyTranslations } from '../../../../common/translations'; +import { DurationAnomalyTranslations as CommonDurationAnomalyTranslations } from '../../../../common/rules/legacy_uptime/translations'; import { getMonitorRouteFromMonitorId } from '../../../../common/utils/get_monitor_url'; import { ALERT_REASON_MSG, ACTION_VARIABLES, VIEW_IN_APP_URL } from './action_variables'; diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/tls.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/tls.ts index 6b23d90db0a7e..8bf4fc9de413e 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/tls.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/tls.ts @@ -22,7 +22,7 @@ import { CLIENT_ALERT_TYPES, TLS } from '../../../../common/constants/uptime_ale import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; import { Cert, CertResult } from '../../../../common/runtime_types'; import { commonStateTranslations, tlsTranslations } from './translations'; -import { TlsTranslations } from '../../../../common/translations'; +import { TlsTranslations } from '../../../../common/rules/legacy_uptime/translations'; import { savedObjectsAdapter } from '../saved_objects/saved_objects'; import { UptimeEsClient } from '../lib'; diff --git a/x-pack/plugins/synthetics/server/routes/default_alerts/status_alert_service.ts b/x-pack/plugins/synthetics/server/routes/default_alerts/status_alert_service.ts index 1de80b1a27c08..e175325d51f4c 100644 --- a/x-pack/plugins/synthetics/server/routes/default_alerts/status_alert_service.ts +++ b/x-pack/plugins/synthetics/server/routes/default_alerts/status_alert_service.ts @@ -114,6 +114,8 @@ export class StatusAlertService { defaultActionMessage: SyntheticsMonitorStatusTranslations.defaultActionMessage, defaultRecoveryMessage: SyntheticsMonitorStatusTranslations.defaultRecoveryMessage, defaultSubjectMessage: SyntheticsMonitorStatusTranslations.defaultSubjectMessage, + defaultRecoverySubjectMessage: + SyntheticsMonitorStatusTranslations.defaultRecoverySubjectMessage, }, }); } diff --git a/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/uptime/simple_down_alert.ts b/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/uptime/simple_down_alert.ts index 4064a8a6b7ae2..81a0a9b438af7 100644 --- a/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/uptime/simple_down_alert.ts +++ b/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/uptime/simple_down_alert.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { MonitorStatusTranslations } from '@kbn/synthetics-plugin/common/translations'; +import { MonitorStatusTranslations } from '@kbn/synthetics-plugin/common/rules/legacy_uptime/translations'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { deleteUptimeSettingsObject } from '../../../../functional/apps/uptime'; From 783d1c0092aea86e944f1b7110f94ea7eba8d8dd Mon Sep 17 00:00:00 2001 From: Elastic Machine Date: Fri, 21 Apr 2023 00:25:49 +0930 Subject: [PATCH 07/67] [main] Sync bundled packages with Package Storage (#155404) Automated by https://internal-ci.elastic.co/job/package_storage/job/sync-bundled-packages-job/job/main/3241/ Co-authored-by: apmmachine --- fleet_packages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fleet_packages.json b/fleet_packages.json index e78e1fbe6451f..f9734cdd91380 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -38,7 +38,7 @@ }, { "name": "fleet_server", - "version": "1.2.0" + "version": "1.3.0" }, { "name": "synthetics", From 4c79ef40095dac039f2da3fded26bdbdc09f8097 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Thu, 20 Apr 2023 09:02:52 -0600 Subject: [PATCH 08/67] [ML] Add search links for AIOps Labs pages (#155202) ## Summary Related meta issue: https://github.com/elastic/kibana/issues/146065 Added deep search links into the top search bar for the AIOps Labs pages - change point detection, log pattern analysis, and explain log rate spikes image Created deep link for 'Notifications'. image Moved 'Memory Usage' deep link out of model management into its own link. image image ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [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 - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --- .../search_deep_links.ts | 68 +++++++++++++++---- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/ml/public/register_helper/register_search_links/search_deep_links.ts b/x-pack/plugins/ml/public/register_helper/register_search_links/search_deep_links.ts index ab8b15c94e260..2b1cc16f131b6 100644 --- a/x-pack/plugins/ml/public/register_helper/register_search_links/search_deep_links.ts +++ b/x-pack/plugins/ml/public/register_helper/register_search_links/search_deep_links.ts @@ -34,30 +34,63 @@ const DATA_FRAME_ANALYTICS_DEEP_LINK: AppDeepLink = { path: `/${ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE}`, }; +const AIOPS_DEEP_LINK: AppDeepLink = { + id: 'aiOpsDeepLink', + title: i18n.translate('xpack.ml.deepLink.aiOps', { + defaultMessage: 'AIOps', + }), + // Default to the index select page for the explain log rate spikes since we don't have an AIops overview page + path: `/${ML_PAGES.AIOPS_EXPLAIN_LOG_RATE_SPIKES_INDEX_SELECT}`, + deepLinks: [ + { + id: 'explainLogRateSpikesDeepLink', + title: i18n.translate('xpack.ml.deepLink.explainLogRateSpikes', { + defaultMessage: 'Explain Log Rate Spikes', + }), + path: `/${ML_PAGES.AIOPS_EXPLAIN_LOG_RATE_SPIKES_INDEX_SELECT}`, + }, + { + id: 'logPatternAnalysisDeepLink', + title: i18n.translate('xpack.ml.deepLink.logPatternAnalysis', { + defaultMessage: 'Log Pattern Analysis', + }), + path: `/${ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT}`, + }, + { + id: 'changePointDetectionsDeepLink', + title: i18n.translate('xpack.ml.deepLink.changePointDetection', { + defaultMessage: 'Change Point Detection', + }), + path: `/${ML_PAGES.AIOPS_CHANGE_POINT_DETECTION_INDEX_SELECT}`, + }, + ], +}; + const MODEL_MANAGEMENT_DEEP_LINK: AppDeepLink = { id: 'mlModelManagementDeepLink', - title: i18n.translate('xpack.ml.deepLink.trainedModels', { - defaultMessage: 'Trained Models', + title: i18n.translate('xpack.ml.deepLink.modelManagement', { + defaultMessage: 'Model Management', }), path: `/${ML_PAGES.TRAINED_MODELS_MANAGE}`, deepLinks: [ { id: 'mlNodesOverviewDeepLink', - title: i18n.translate('xpack.ml.deepLink.modelManagement', { - defaultMessage: 'Model Management', + title: i18n.translate('xpack.ml.deepLink.trainedModels', { + defaultMessage: 'Trained Models', }), path: `/${ML_PAGES.TRAINED_MODELS_MANAGE}`, }, - { - id: 'mlMemoryUsageDeepLink', - title: i18n.translate('xpack.ml.deepLink.memoryUsage', { - defaultMessage: 'Memory usage', - }), - path: `/${ML_PAGES.MEMORY_USAGE}`, - }, ], }; +const MEMORY_USAGE_DEEP_LINK: AppDeepLink = { + id: 'mlMemoryUsageDeepLink', + title: i18n.translate('xpack.ml.deepLink.memoryUsage', { + defaultMessage: 'Memory Usage', + }), + path: `/${ML_PAGES.MEMORY_USAGE}`, +}; + const DATA_VISUALIZER_DEEP_LINK: AppDeepLink = { id: 'dataVisualizerDeepLink', title: i18n.translate('xpack.ml.deepLink.dataVisualizer', { @@ -107,6 +140,14 @@ const SETTINGS_DEEP_LINK: AppDeepLink = { ], }; +const NOTIFICATIONS_DEEP_LINK: AppDeepLink = { + id: 'mlNotificationsDeepLink', + title: i18n.translate('xpack.ml.deepLink.notifications', { + defaultMessage: 'Notifications', + }), + path: `/${ML_PAGES.NOTIFICATIONS}`, +}; + export function getDeepLinks(isFullLicense: boolean) { const deepLinks: AppDeepLink[] = [ DATA_VISUALIZER_DEEP_LINK, @@ -120,7 +161,10 @@ export function getDeepLinks(isFullLicense: boolean) { ANOMALY_DETECTION_DEEP_LINK, DATA_FRAME_ANALYTICS_DEEP_LINK, MODEL_MANAGEMENT_DEEP_LINK, - SETTINGS_DEEP_LINK + MEMORY_USAGE_DEEP_LINK, + SETTINGS_DEEP_LINK, + AIOPS_DEEP_LINK, + NOTIFICATIONS_DEEP_LINK ); } From 031bc369bdbdd073d35379575c6b51765326a134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Thu, 20 Apr 2023 17:22:31 +0200 Subject: [PATCH 09/67] [Cloud] Add `deploymentId` to the EBT context (#155182) --- ...rse_deployment_id_from_deployment_url.test.ts} | 2 +- .../parse_deployment_id_from_deployment_url.ts} | 0 ..._cloud_deployment_id_analytics_context.test.ts | 13 +++++++++++++ ...ister_cloud_deployment_id_analytics_context.ts | 15 +++++++++++++-- x-pack/plugins/cloud/server/plugin.ts | 2 +- 5 files changed, 28 insertions(+), 4 deletions(-) rename x-pack/plugins/cloud/{server/utils.test.ts => common/parse_deployment_id_from_deployment_url.test.ts} (87%) rename x-pack/plugins/cloud/{server/utils.ts => common/parse_deployment_id_from_deployment_url.ts} (100%) diff --git a/x-pack/plugins/cloud/server/utils.test.ts b/x-pack/plugins/cloud/common/parse_deployment_id_from_deployment_url.test.ts similarity index 87% rename from x-pack/plugins/cloud/server/utils.test.ts rename to x-pack/plugins/cloud/common/parse_deployment_id_from_deployment_url.test.ts index 00e7de7336c7a..de54561783285 100644 --- a/x-pack/plugins/cloud/server/utils.test.ts +++ b/x-pack/plugins/cloud/common/parse_deployment_id_from_deployment_url.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { parseDeploymentIdFromDeploymentUrl } from './utils'; +import { parseDeploymentIdFromDeploymentUrl } from './parse_deployment_id_from_deployment_url'; describe('parseDeploymentIdFromDeploymentUrl', () => { it('should return undefined if there is no deploymentUrl configured', () => { diff --git a/x-pack/plugins/cloud/server/utils.ts b/x-pack/plugins/cloud/common/parse_deployment_id_from_deployment_url.ts similarity index 100% rename from x-pack/plugins/cloud/server/utils.ts rename to x-pack/plugins/cloud/common/parse_deployment_id_from_deployment_url.ts diff --git a/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.test.ts b/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.test.ts index 4793bb1ac6af9..26f4acb9ae576 100644 --- a/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.test.ts +++ b/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.test.ts @@ -29,4 +29,17 @@ describe('registerCloudDeploymentIdAnalyticsContext', () => { cloudId: 'cloud_id', }); }); + + test('it registers the context provider and emits the cloudId and deploymentId', async () => { + registerCloudDeploymentMetadataAnalyticsContext(analytics, { + id: 'cloud_id', + deployment_url: 'deployments/uuid-of-my-deployment', + }); + expect(analytics.registerContextProvider).toHaveBeenCalledTimes(1); + const [{ context$ }] = analytics.registerContextProvider.mock.calls[0]; + await expect(firstValueFrom(context$)).resolves.toEqual({ + cloudId: 'cloud_id', + deploymentId: 'uuid-of-my-deployment', + }); + }); }); diff --git a/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts b/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts index 68130cdcda799..d3c8d0df8e553 100644 --- a/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts +++ b/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts @@ -7,11 +7,13 @@ import type { AnalyticsClient } from '@kbn/analytics-client'; import { of } from 'rxjs'; +import { parseDeploymentIdFromDeploymentUrl } from './parse_deployment_id_from_deployment_url'; export interface CloudDeploymentMetadata { id?: string; trial_end_date?: string; is_elastic_staff_owned?: boolean; + deployment_url?: string; } export function registerCloudDeploymentMetadataAnalyticsContext( @@ -29,11 +31,20 @@ export function registerCloudDeploymentMetadataAnalyticsContext( analytics.registerContextProvider({ name: 'Cloud Deployment Metadata', - context$: of({ cloudId, cloudTrialEndDate, cloudIsElasticStaffOwned }), + context$: of({ + cloudId, + deploymentId: parseDeploymentIdFromDeploymentUrl(cloudMetadata.deployment_url), + cloudTrialEndDate, + cloudIsElasticStaffOwned, + }), schema: { cloudId: { type: 'keyword', - _meta: { description: 'The Cloud Deployment ID' }, + _meta: { description: 'The Cloud ID' }, + }, + deploymentId: { + type: 'keyword', + _meta: { description: 'The Deployment ID', optional: true }, }, cloudTrialEndDate: { type: 'date', diff --git a/x-pack/plugins/cloud/server/plugin.ts b/x-pack/plugins/cloud/server/plugin.ts index c0f52645521fe..7d33ac99629b8 100644 --- a/x-pack/plugins/cloud/server/plugin.ts +++ b/x-pack/plugins/cloud/server/plugin.ts @@ -11,7 +11,7 @@ import { registerCloudDeploymentMetadataAnalyticsContext } from '../common/regis import type { CloudConfigType } from './config'; import { registerCloudUsageCollector } from './collectors'; import { getIsCloudEnabled } from '../common/is_cloud_enabled'; -import { parseDeploymentIdFromDeploymentUrl } from './utils'; +import { parseDeploymentIdFromDeploymentUrl } from '../common/parse_deployment_id_from_deployment_url'; import { readInstanceSizeMb } from './env'; interface PluginsSetup { From df8c29e64d319a2abc637aa52f84f2de38a58cfb Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Thu, 20 Apr 2023 17:30:41 +0200 Subject: [PATCH 10/67] [Security Solutions] Add EBT telemetry to ML jobs status updates (#155233) EPIC issue: https://github.com/elastic/kibana/issues/145276 ## Summary [Dashboard with events](https://telemetry-v2-staging.elastic.dev/s/securitysolution/app/dashboards#/view/d47a90f0-a6e4-11ed-a6e6-d32d2209b7b7?_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-90d%2Fd%2Cto%3Anow))) Add ML Job Update telemetry events. Jobs can be updated from the Entity analytics page, ML widget, and rules pages. ![Screenshot 2023-04-19 at 11 25 51](https://user-images.githubusercontent.com/1490444/233032320-be24b4a8-39a7-458e-8f32-89e639708b15.png) ![Screenshot 2023-04-19 at 11 26 15](https://user-images.githubusercontent.com/1490444/233032326-40c99750-ed86-4b80-a0b6-e0ac1944e340.png) Screenshot 2023-04-19 at 11 26 37 Screenshot 2023-04-19 at 11 26 55 Event: ```json { "timestamp": "2023-03-20T14:23:53.452Z", "event_type": "ML Job Update", "context": { ... }, "properties": { "jobId": "auth_rare_source_ip_for_a_user", "isElasticJob": true, "moduleId": "security_auth", "status": "module_installed" } } ``` The status could be one of the following options: ` | 'module_installed' | 'installation_error' | 'started' | 'start_error' | 'stopped' | 'stop_error' ` ### 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 --- .../hooks/use_enable_data_feed.test.tsx | 118 +++++++++++++++++- .../ml_popover/hooks/use_enable_data_feed.ts | 48 ++++++- .../lib/telemetry/telemetry_client.mock.ts | 1 + .../common/lib/telemetry/telemetry_client.ts | 5 + .../common/lib/telemetry/telemetry_events.ts | 42 +++++++ .../public/common/lib/telemetry/types.ts | 26 +++- 6 files changed, 236 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.test.tsx index 4afa7e1796de4..b4c8265d23242 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.test.tsx @@ -18,6 +18,8 @@ import { createStore } from '../../../store'; import type { State } from '../../../store'; import type { SecurityJob } from '../types'; +import { createTelemetryServiceMock } from '../../../lib/telemetry/telemetry_service.mock'; +import { ML_JOB_TELEMETRY_STATUS } from '../../../lib/telemetry'; const state: State = mockGlobalState; const { storage } = createSecuritySolutionStorageMock(); @@ -27,9 +29,15 @@ const wrapper = ({ children }: { children: React.ReactNode }) => ( {children} ); +const moduleId = 'test_module_id'; +const jobId = 'test_job_id'; + const TIMESTAMP = 99999999; const JOB = { + id: jobId, isInstalled: false, + isElasticJob: true, + moduleId, datafeedState: 'failed', jobState: 'failed', isCompatible: true, @@ -45,11 +53,26 @@ jest.mock('../api', () => ({ stopDatafeeds: () => mockStopDatafeeds(), })); +const mockedTelemetry = createTelemetryServiceMock(); +jest.mock('../../../lib/kibana', () => { + const original = jest.requireActual('../../../lib/kibana'); + + return { + ...original, + useKibana: () => ({ + services: { + telemetry: mockedTelemetry, + }, + }), + }; +}); + describe('useSecurityJobsHelpers', () => { afterEach(() => { mockSetupMlJob.mockReset(); mockStartDatafeeds.mockReset(); mockStopDatafeeds.mockReset(); + mockSetupMlJob.mockReset(); }); it('renders isLoading=true when installing job', async () => { @@ -132,8 +155,101 @@ describe('useSecurityJobsHelpers', () => { await result.current.enableDatafeed(JOB, TIMESTAMP, true); }); expect(mockStartDatafeeds).toBeCalledWith({ - datafeedIds: [`datafeed-undefined`], + datafeedIds: [`datafeed-test_job_id`], start: new Date('1989-02-21').getTime(), }); }); + + describe('telemetry', () => { + it('reports telemetry when installing and enabling a job', async () => { + mockSetupMlJob.mockReturnValue(new Promise((resolve) => resolve({}))); + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + + await act(async () => { + await result.current.enableDatafeed(JOB, TIMESTAMP, true); + }); + + expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ + status: ML_JOB_TELEMETRY_STATUS.moduleInstalled, + isElasticJob: true, + jobId, + moduleId, + }); + + expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ + status: ML_JOB_TELEMETRY_STATUS.started, + isElasticJob: true, + jobId, + }); + }); + + it('reports telemetry when stopping a job', async () => { + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + await result.current.enableDatafeed({ ...JOB, isInstalled: true }, TIMESTAMP, false); + }); + + expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ + status: ML_JOB_TELEMETRY_STATUS.stopped, + isElasticJob: true, + jobId, + }); + }); + + it('reports telemetry when stopping a job fails', async () => { + mockStopDatafeeds.mockReturnValue(Promise.reject(new Error('test_error'))); + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + await result.current.enableDatafeed({ ...JOB, isInstalled: true }, TIMESTAMP, false); + }); + + expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ + status: ML_JOB_TELEMETRY_STATUS.stopError, + errorMessage: 'Stop job failure - test_error', + isElasticJob: true, + jobId, + }); + }); + + it('reports telemetry when starting a job fails', async () => { + mockStartDatafeeds.mockReturnValue(Promise.reject(new Error('test_error'))); + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + await result.current.enableDatafeed({ ...JOB, isInstalled: true }, TIMESTAMP, true); + }); + + expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ + status: ML_JOB_TELEMETRY_STATUS.startError, + errorMessage: 'Start job failure - test_error', + isElasticJob: true, + jobId, + }); + }); + + it('reports telemetry when installing a module fails', async () => { + mockSetupMlJob.mockReturnValue(Promise.reject(new Error('test_error'))); + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + await result.current.enableDatafeed(JOB, TIMESTAMP, true); + }); + + expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ + status: ML_JOB_TELEMETRY_STATUS.installationError, + errorMessage: 'Create job failure - test_error', + isElasticJob: true, + jobId, + moduleId, + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.ts index 2d73d07c1787c..48b7a918af26c 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.ts @@ -7,13 +7,22 @@ import { useCallback, useState } from 'react'; import { useAppToasts } from '../../../hooks/use_app_toasts'; -import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../../lib/telemetry'; +import { useKibana } from '../../../lib/kibana'; +import { + METRIC_TYPE, + ML_JOB_TELEMETRY_STATUS, + TELEMETRY_EVENT, + track, +} from '../../../lib/telemetry'; + import { setupMlJob, startDatafeeds, stopDatafeeds } from '../api'; import type { SecurityJob } from '../types'; import * as i18n from './translations'; // Enable/Disable Job & Datafeed -- passed to JobsTable for use as callback on JobSwitch export const useEnableDataFeed = () => { + const { telemetry } = useKibana().services; + const { addError } = useAppToasts(); const [isLoading, setIsLoading] = useState(false); @@ -31,9 +40,22 @@ export const useEnableDataFeed = () => { groups: job.groups, }); setIsLoading(false); + telemetry.reportMLJobUpdate({ + jobId: job.id, + isElasticJob: job.isElasticJob, + moduleId: job.moduleId, + status: ML_JOB_TELEMETRY_STATUS.moduleInstalled, + }); } catch (error) { addError(error, { title: i18n.CREATE_JOB_FAILURE }); setIsLoading(false); + telemetry.reportMLJobUpdate({ + jobId: job.id, + isElasticJob: job.isElasticJob, + moduleId: job.moduleId, + status: ML_JOB_TELEMETRY_STATUS.installationError, + errorMessage: `${i18n.CREATE_JOB_FAILURE} - ${error.message}`, + }); return; } } @@ -47,21 +69,43 @@ export const useEnableDataFeed = () => { const startTime = Math.max(latestTimestampMs, maxStartTime); try { await startDatafeeds({ datafeedIds: [`datafeed-${job.id}`], start: startTime }); + telemetry.reportMLJobUpdate({ + jobId: job.id, + isElasticJob: job.isElasticJob, + status: ML_JOB_TELEMETRY_STATUS.started, + }); } catch (error) { track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_ENABLE_FAILURE); addError(error, { title: i18n.START_JOB_FAILURE }); + telemetry.reportMLJobUpdate({ + jobId: job.id, + isElasticJob: job.isElasticJob, + status: ML_JOB_TELEMETRY_STATUS.startError, + errorMessage: `${i18n.START_JOB_FAILURE} - ${error.message}`, + }); } } else { try { await stopDatafeeds({ datafeedIds: [`datafeed-${job.id}`] }); + telemetry.reportMLJobUpdate({ + jobId: job.id, + isElasticJob: job.isElasticJob, + status: ML_JOB_TELEMETRY_STATUS.stopped, + }); } catch (error) { track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_DISABLE_FAILURE); addError(error, { title: i18n.STOP_JOB_FAILURE }); + telemetry.reportMLJobUpdate({ + jobId: job.id, + isElasticJob: job.isElasticJob, + status: ML_JOB_TELEMETRY_STATUS.stopError, + errorMessage: `${i18n.STOP_JOB_FAILURE} - ${error.message}`, + }); } } setIsLoading(false); }, - [addError] + [addError, telemetry] ); return { enableDatafeed, isLoading }; diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts index a79f9c662ae23..4f2b2bdafe96f 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts @@ -14,4 +14,5 @@ export const createTelemetryClientMock = (): jest.Mocked = reportEntityDetailsClicked: jest.fn(), reportEntityAlertsClicked: jest.fn(), reportEntityRiskFiltered: jest.fn(), + reportMLJobUpdate: jest.fn(), }); diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts index e356b47f29c38..c95c8153261e1 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts @@ -14,6 +14,7 @@ import type { ReportEntityDetailsClickedParams, ReportEntityAlertsClickedParams, ReportEntityRiskFilteredParams, + ReportMLJobUpdateParams, } from './types'; import { TelemetryEventTypes } from './types'; @@ -83,4 +84,8 @@ export class TelemetryClient implements TelemetryClientStart { selectedSeverity, }); }; + + public reportMLJobUpdate = (params: ReportMLJobUpdateParams) => { + this.analytics.reportEvent(TelemetryEventTypes.MLJobUpdate, params); + }; } diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_events.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_events.ts index 4087d8df7b5d4..cb82b16658224 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_events.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_events.ts @@ -141,6 +141,47 @@ const entityRiskFilteredEvent: TelemetryEvent = { }, }; +const mlJobUpdateEvent: TelemetryEvent = { + eventType: TelemetryEventTypes.MLJobUpdate, + schema: { + jobId: { + type: 'keyword', + _meta: { + description: 'Job id', + optional: false, + }, + }, + isElasticJob: { + type: 'boolean', + _meta: { + description: 'If true the job is one of the pre-configure security solution modules', + optional: false, + }, + }, + moduleId: { + type: 'keyword', + _meta: { + description: 'Module id', + optional: true, + }, + }, + status: { + type: 'keyword', + _meta: { + description: 'It describes what has changed in the job.', + optional: false, + }, + }, + errorMessage: { + type: 'text', + _meta: { + description: 'Error message', + optional: true, + }, + }, + }, +}; + export const telemetryEvents = [ alertsGroupingToggledEvent, alertsGroupingChangedEvent, @@ -148,4 +189,5 @@ export const telemetryEvents = [ entityClickedEvent, entityAlertsClickedEvent, entityRiskFilteredEvent, + mlJobUpdateEvent, ]; diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts index 78ed0ee9d3fd3..ba084c49cc033 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts @@ -20,6 +20,7 @@ export enum TelemetryEventTypes { EntityDetailsClicked = 'Entity Details Clicked', EntityAlertsClicked = 'Entity Alerts Clicked', EntityRiskFiltered = 'Entity Risk Filtered', + MLJobUpdate = 'ML Job Update', } export interface ReportAlertsGroupingChangedParams { @@ -51,13 +52,31 @@ export interface ReportEntityRiskFilteredParams extends EntityParam { selectedSeverity: RiskSeverity; } +export enum ML_JOB_TELEMETRY_STATUS { + started = 'started', + startError = 'start_error', + stopped = 'stopped', + stopError = 'stop_error', + moduleInstalled = 'module_installed', + installationError = 'installationError', +} + +export interface ReportMLJobUpdateParams { + jobId: string; + isElasticJob: boolean; + status: ML_JOB_TELEMETRY_STATUS; + moduleId?: string; + errorMessage?: string; +} + export type TelemetryEventParams = | ReportAlertsGroupingChangedParams | ReportAlertsGroupingToggledParams | ReportAlertsTakeActionParams | ReportEntityDetailsClickedParams | ReportEntityAlertsClickedParams - | ReportEntityRiskFilteredParams; + | ReportEntityRiskFilteredParams + | ReportMLJobUpdateParams; export interface TelemetryClientStart { reportAlertsGroupingChanged(params: ReportAlertsGroupingChangedParams): void; @@ -67,6 +86,7 @@ export interface TelemetryClientStart { reportEntityDetailsClicked(params: ReportEntityDetailsClickedParams): void; reportEntityAlertsClicked(params: ReportEntityAlertsClickedParams): void; reportEntityRiskFiltered(params: ReportEntityRiskFilteredParams): void; + reportMLJobUpdate(params: ReportMLJobUpdateParams): void; } export type TelemetryEvent = @@ -93,4 +113,8 @@ export type TelemetryEvent = | { eventType: TelemetryEventTypes.EntityRiskFiltered; schema: RootSchema; + } + | { + eventType: TelemetryEventTypes.MLJobUpdate; + schema: RootSchema; }; From fd20b84ff17d100fb56972bae565672293eab775 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Thu, 20 Apr 2023 08:37:13 -0700 Subject: [PATCH 11/67] [ResponseOps][Window Maintenance] Edit button text (#155335) --- .../alerting/public/pages/maintenance_windows/translations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts index 7da8ad80a5932..ee02c5c2a9528 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts @@ -23,7 +23,7 @@ export const MAINTENANCE_WINDOWS_DESCRIPTION = i18n.translate( export const CREATE_NEW_BUTTON = i18n.translate( 'xpack.alerting.maintenanceWindows.createNewButton', { - defaultMessage: 'Create new', + defaultMessage: 'Create window', } ); From 92ce25d7c838ca5ac9dd21ebfebe57ac87fb7886 Mon Sep 17 00:00:00 2001 From: Kfir Peled <61654899+kfirpeled@users.noreply.github.com> Date: Thu, 20 Apr 2023 09:51:10 -0600 Subject: [PATCH 12/67] [Cloud Security] [CNVM] Showing the same prompt when not deployed and not installed (#155211) --- .../public/components/no_vulnerabilities_states.tsx | 4 ++-- .../public/pages/vulnerabilities/vulnerabilties.test.tsx | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx b/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx index 48f790bf4c332..a34b23b46bd64 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx @@ -206,10 +206,10 @@ export const NoVulnerabilitiesStates = () => { .sort((a, b) => a.localeCompare(b)); const render = () => { - if (status === 'not-deployed' || status === 'indexing' || status === 'waiting_for_results') + if (status === 'indexing' || status === 'waiting_for_results') return ; // integration installed, but no agents added if (status === 'index-timeout') return ; // agent added, index timeout has passed - if (status === 'not-installed') + if (status === 'not-deployed' || status === 'not-installed') return ( ', () => { renderVulnerabilitiesPage(); expectIdsInDoc({ - be: [NO_VULNERABILITIES_STATUS_TEST_SUBJ.SCANNING_VULNERABILITIES], + be: [VULN_MGMT_INTEGRATION_NOT_INSTALLED_TEST_SUBJECT], notToBe: [ VULNERABILITIES_CONTAINER_TEST_SUBJ, + NO_VULNERABILITIES_STATUS_TEST_SUBJ.SCANNING_VULNERABILITIES, NO_VULNERABILITIES_STATUS_TEST_SUBJ.INDEX_TIMEOUT, NO_VULNERABILITIES_STATUS_TEST_SUBJ.UNPRIVILEGED, ], From 241f71b346b4c3d81407622a212d66b2e74ded7e Mon Sep 17 00:00:00 2001 From: Ashokaditya <1849116+ashokaditya@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:56:31 +0200 Subject: [PATCH 13/67] [Security Solution][Endpoint][Response Actions] Add automated tests for execute response action test cases (#155128) --- .../common/endpoint/schema/actions.test.ts | 3 +- .../integration_tests/status_action.test.tsx | 148 ++++++++++++++++++ .../response_actions_log.test.tsx | 126 +++++++++------ 3 files changed, 228 insertions(+), 49 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/status_action.test.tsx diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts b/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts index 5dc5059d21262..c22dd615a5d89 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts @@ -7,6 +7,7 @@ import { v4 as uuidv4 } from 'uuid'; +import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../service/response_actions/constants'; import { EndpointActionListRequestSchema, NoParametersRequestSchema, @@ -185,7 +186,7 @@ describe('actions schemas', () => { }).not.toThrow(); }); - it.each(['isolate', 'unisolate', 'kill-process', 'suspend-process', 'running-processes'])( + it.each(RESPONSE_ACTION_API_COMMANDS_NAMES)( 'should work with commands query params with %s action', (command) => { expect(() => { diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/status_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/status_action.test.tsx new file mode 100644 index 0000000000000..d229b297f239f --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/status_action.test.tsx @@ -0,0 +1,148 @@ +/* + * 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 { AppContextTestRender } from '../../../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../../../common/mock/endpoint'; +import { + ConsoleManagerTestComponent, + getConsoleManagerMockRenderResultQueriesAndActions, +} from '../../../console/components/console_manager/mocks'; +import React from 'react'; +import { getEndpointConsoleCommands } from '../../lib/console_commands_definition'; +import { enterConsoleCommand } from '../../../console/mocks'; +import { getEndpointAuthzInitialState } from '../../../../../../common/endpoint/service/authz'; +import type { EndpointCapabilities } from '../../../../../../common/endpoint/service/response_actions/constants'; +import { ENDPOINT_CAPABILITIES } from '../../../../../../common/endpoint/service/response_actions/constants'; +import { useGetEndpointPendingActionsSummary } from '../../../../hooks/response_actions/use_get_endpoint_pending_actions_summary'; +import { useGetEndpointDetails } from '../../../../hooks'; +import { EndpointActionGenerator } from '../../../../../../common/endpoint/data_generators/endpoint_action_generator'; +import { EndpointMetadataGenerator } from '../../../../../../common/endpoint/data_generators/endpoint_metadata_generator'; + +jest.mock('../../../../hooks/response_actions/use_get_endpoint_pending_actions_summary'); +jest.mock('../../../../hooks'); + +const useGetEndpointPendingActionsSummaryMock = useGetEndpointPendingActionsSummary as jest.Mock; +const useGetEndpointDetailsMock = useGetEndpointDetails as jest.Mock; + +describe('When using processes action from response actions console', () => { + let render: ( + capabilities?: EndpointCapabilities[] + ) => Promise>; + let renderResult: ReturnType; + let consoleManagerMockAccess: ReturnType< + typeof getConsoleManagerMockRenderResultQueriesAndActions + >; + const agentId = 'a.b.c'; + + const pendingActionsMock = () => { + useGetEndpointPendingActionsSummaryMock.mockReturnValue({ + data: { + data: [ + new EndpointActionGenerator('seed').generateAgentPendingActionsSummary({ + agent_id: agentId, + pending_actions: { + isolate: 0, + }, + }), + ], + }, + }); + }; + + const endpointDetailsMock = () => { + const endpointMetadata = new EndpointMetadataGenerator('seed').generateHostInfo({ + metadata: { + '@timestamp': new Date('2023-04-20T09:37:40.309Z').getTime(), + agent: { + id: agentId, + version: '8.8.0', + }, + elastic: { + agent: { id: agentId }, + }, + Endpoint: { + state: { + isolation: false, + }, + }, + }, + }); + useGetEndpointDetailsMock.mockReturnValue({ + data: endpointMetadata, + isFetching: false, + isFetched: true, + }); + }; + + beforeEach(() => { + const mockedContext = createAppRootMockRenderer(); + + render = async (capabilities: EndpointCapabilities[] = [...ENDPOINT_CAPABILITIES]) => { + renderResult = mockedContext.render( + { + return { + consoleProps: { + 'data-test-subj': 'test', + commands: getEndpointConsoleCommands({ + endpointAgentId: 'a.b.c', + endpointCapabilities: [...capabilities], + endpointPrivileges: { + ...getEndpointAuthzInitialState(), + loading: false, + }, + }), + }, + }; + }} + /> + ); + + consoleManagerMockAccess = getConsoleManagerMockRenderResultQueriesAndActions(renderResult); + + await consoleManagerMockAccess.clickOnRegisterNewConsole(); + await consoleManagerMockAccess.openRunningConsole(); + + return renderResult; + }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should show expected status output', async () => { + pendingActionsMock(); + endpointDetailsMock(); + await render(); + enterConsoleCommand(renderResult, 'status'); + const statusResults = renderResult.getByTestId('agent-status-console-output'); + + expect( + Array.from(statusResults.querySelectorAll('dt')).map((term) => term.textContent) + ).toEqual([ + 'Agent status', + 'Platform', + 'Version', + 'Policy status', + 'Policy version', + 'Policy name', + 'Last active', + ]); + + expect( + Array.from(statusResults.querySelectorAll('dd')).map((detail) => detail.textContent) + ).toEqual([ + 'Healthy', + 'Windows Server 2012R2', + '8.8.0', + 'Success', + 'v3', + 'With Eventing', + 'Apr 20, 2023 @ 09:37:40.309', + ]); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx index 2ea008296b12e..6360b55b1c06f 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx @@ -796,6 +796,10 @@ describe('Response actions history', () => { }); describe('Action status ', () => { + beforeEach(() => { + apiMocks = responseActionsHttpMocks(mockedContext.coreStart.http); + }); + const expandRows = () => { const { getAllByTestId } = renderResult; @@ -805,58 +809,84 @@ describe('Response actions history', () => { return outputs; }; - it('shows completed status badge for successfully completed actions', async () => { - useGetEndpointActionListMock.mockReturnValue({ - ...getBaseMockedActionList(), - data: await getActionListMock({ actionCount: 2 }), - }); - render(); - - const outputs = expandRows(); - expect(outputs.map((n) => n.textContent)).toEqual([ - 'isolate completed successfully', - 'isolate completed successfully', - ]); - expect( - renderResult.getAllByTestId(`${testPrefix}-column-status`).map((n) => n.textContent) - ).toEqual(['Successful', 'Successful']); - }); + it.each(RESPONSE_ACTION_API_COMMANDS_NAMES)( + 'shows completed status badge for successfully completed %s actions', + async (command) => { + useGetEndpointActionListMock.mockReturnValue({ + ...getBaseMockedActionList(), + data: await getActionListMock({ actionCount: 2, commands: [command] }), + }); + if (command === 'get-file' || command === 'execute') { + mockUseGetFileInfo = { + isFetching: false, + error: null, + data: apiMocks.responseProvider.fileInfo(), + }; + } - it('shows Failed status badge for failed actions', async () => { - useGetEndpointActionListMock.mockReturnValue({ - ...getBaseMockedActionList(), - data: await getActionListMock({ actionCount: 2, wasSuccessful: false, status: 'failed' }), - }); - render(); + render(); - const outputs = expandRows(); - expect(outputs.map((n) => n.textContent)).toEqual(['isolate failed', 'isolate failed']); - expect( - renderResult.getAllByTestId(`${testPrefix}-column-status`).map((n) => n.textContent) - ).toEqual(['Failed', 'Failed']); - }); + const outputs = expandRows(); + expect(outputs.map((n) => n.textContent)).toEqual([ + expect.stringContaining(`${command} completed successfully`), + expect.stringContaining(`${command} completed successfully`), + ]); + expect( + renderResult.getAllByTestId(`${testPrefix}-column-status`).map((n) => n.textContent) + ).toEqual(['Successful', 'Successful']); + } + ); + + it.each(RESPONSE_ACTION_API_COMMANDS_NAMES)( + 'shows Failed status badge for failed %s actions', + async (command) => { + useGetEndpointActionListMock.mockReturnValue({ + ...getBaseMockedActionList(), + data: await getActionListMock({ + actionCount: 2, + commands: [command], + wasSuccessful: false, + status: 'failed', + }), + }); + render(); - it('shows Failed status badge for expired actions', async () => { - useGetEndpointActionListMock.mockReturnValue({ - ...getBaseMockedActionList(), - data: await getActionListMock({ - actionCount: 2, - isCompleted: false, - isExpired: true, - status: 'failed', - }), - }); - render(); + const outputs = expandRows(); + expect(outputs.map((n) => n.textContent)).toEqual([ + `${command} failed`, + `${command} failed`, + ]); + expect( + renderResult.getAllByTestId(`${testPrefix}-column-status`).map((n) => n.textContent) + ).toEqual(['Failed', 'Failed']); + } + ); + + it.each(RESPONSE_ACTION_API_COMMANDS_NAMES)( + 'shows Failed status badge for expired %s actions', + async (command) => { + useGetEndpointActionListMock.mockReturnValue({ + ...getBaseMockedActionList(), + data: await getActionListMock({ + actionCount: 2, + commands: [command], + isCompleted: false, + isExpired: true, + status: 'failed', + }), + }); + render(); - const outputs = expandRows(); - expect(outputs.map((n) => n.textContent)).toEqual([ - 'isolate failed: action expired', - 'isolate failed: action expired', - ]); - expect( - renderResult.getAllByTestId(`${testPrefix}-column-status`).map((n) => n.textContent) - ).toEqual(['Failed', 'Failed']); - }); + const outputs = expandRows(); + expect(outputs.map((n) => n.textContent)).toEqual([ + `${command} failed: action expired`, + `${command} failed: action expired`, + ]); + expect( + renderResult.getAllByTestId(`${testPrefix}-column-status`).map((n) => n.textContent) + ).toEqual(['Failed', 'Failed']); + } + ); it('shows Pending status badge for pending actions', async () => { useGetEndpointActionListMock.mockReturnValue({ From 5120d692c8c6a1545fbc40acff7b691523eb32d9 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Thu, 20 Apr 2023 09:12:09 -0700 Subject: [PATCH 14/67] [DOCS] Remove or move book-scoped attributes (#155210) --- .../development-functional-tests.asciidoc | 32 +++++++++---------- .../external-plugin-functional-tests.asciidoc | 2 +- .../external-plugin-localization.asciidoc | 4 +-- docs/gs-index.asciidoc | 4 --- docs/index.asciidoc | 14 -------- docs/maps/connect-to-ems.asciidoc | 4 ++- docs/maps/index.asciidoc | 4 --- docs/setup/docker.asciidoc | 5 +++ 8 files changed, 27 insertions(+), 42 deletions(-) diff --git a/docs/developer/contributing/development-functional-tests.asciidoc b/docs/developer/contributing/development-functional-tests.asciidoc index 95c6171aaecf5..55bc6f6b8c398 100644 --- a/docs/developer/contributing/development-functional-tests.asciidoc +++ b/docs/developer/contributing/development-functional-tests.asciidoc @@ -81,7 +81,7 @@ export TEST_BROWSER_HEADLESS=1 export TEST_THROTTLE_NETWORK=1 ---------- -** When running against a Cloud deployment, some tests are not applicable. To skip tests that do not apply, use --exclude-tag. An example shell file can be found at: {blob}test/scripts/jenkins_cloud.sh[test/scripts/jenkins_cloud.sh] +** When running against a Cloud deployment, some tests are not applicable. To skip tests that do not apply, use --exclude-tag. An example shell file can be found at: {kibana-blob}test/scripts/jenkins_cloud.sh[test/scripts/jenkins_cloud.sh] + ["source", "shell"] ---------- @@ -118,7 +118,7 @@ The tests are written in https://mochajs.org[mocha] using https://github.com/ela We use https://www.w3.org/TR/webdriver1/[WebDriver Protocol] to run tests in both Chrome and Firefox with the help of https://sites.google.com/a/chromium.org/chromedriver/[chromedriver] and https://firefox-source-docs.mozilla.org/testing/geckodriver/[geckodriver]. When the `FunctionalTestRunner` launches, remote service creates a new webdriver session, which starts the driver and a stripped-down browser instance. We use `browser` service and `webElementWrapper` class to wrap up https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/[Webdriver API]. -The `FunctionalTestRunner` automatically transpiles functional tests using babel, so that tests can use the same ECMAScript features that {kib} source code uses. See {blob}style_guides/js_style_guide.md[style_guides/js_style_guide.md]. +The `FunctionalTestRunner` automatically transpiles functional tests using babel, so that tests can use the same ECMAScript features that {kib} source code uses. See {kibana-blob}style_guides/js_style_guide.md[style_guides/js_style_guide.md]. [discrete] ==== Definitions @@ -270,7 +270,7 @@ The first and only argument to all providers is a Provider API Object. This obje Within config files the API has the following properties [horizontal] -`log`::: An instance of the {blob}packages/kbn-dev-utils/src/tooling_log/tooling_log.js[`ToolingLog`] that is ready for use +`log`::: An instance of the {kibana-blob}packages/kbn-dev-utils/src/tooling_log/tooling_log.js[`ToolingLog`] that is ready for use `readConfigFile(path)`::: Returns a promise that will resolve to a Config instance that provides the values from the config file at `path` Within service and PageObject Providers the API is: @@ -293,17 +293,17 @@ Within a test Provider the API is exactly the same as the service providers API The `FunctionalTestRunner` comes with three built-in services: **config:**::: -* Source: {blob}src/functional_test_runner/lib/config/config.ts[src/functional_test_runner/lib/config/config.ts] -* Schema: {blob}src/functional_test_runner/lib/config/schema.ts[src/functional_test_runner/lib/config/schema.ts] +* Source: {kibana-blob}src/functional_test_runner/lib/config/config.ts[src/functional_test_runner/lib/config/config.ts] +* Schema: {kibana-blob}src/functional_test_runner/lib/config/schema.ts[src/functional_test_runner/lib/config/schema.ts] * Use `config.get(path)` to read any value from the config file **log:**::: -* Source: {blob}packages/kbn-dev-utils/src/tooling_log/tooling_log.js[packages/kbn-dev-utils/src/tooling_log/tooling_log.js] +* Source: {kibana-blob}packages/kbn-dev-utils/src/tooling_log/tooling_log.js[packages/kbn-dev-utils/src/tooling_log/tooling_log.js] * `ToolingLog` instances are readable streams. The instance provided by this service is automatically piped to stdout by the `FunctionalTestRunner` CLI * `log.verbose()`, `log.debug()`, `log.info()`, `log.warning()` all work just like console.log but produce more organized output **lifecycle:**::: -* Source: {blob}src/functional_test_runner/lib/lifecycle.ts[src/functional_test_runner/lib/lifecycle.ts] +* Source: {kibana-blob}src/functional_test_runner/lib/lifecycle.ts[src/functional_test_runner/lib/lifecycle.ts] * Designed primary for use in services * Exposes lifecycle events for basic coordination. Handlers can return a promise and resolve/fail asynchronously * Phases include: `beforeLoadTests`, `beforeTests`, `beforeEachTest`, `cleanup` @@ -314,14 +314,14 @@ The `FunctionalTestRunner` comes with three built-in services: The {kib} functional tests define the vast majority of the actual functionality used by tests. **browser**::: -* Source: {blob}test/functional/services/browser.ts[test/functional/services/browser.ts] +* Source: {kibana-blob}test/functional/services/browser.ts[test/functional/services/browser.ts] * Higher level wrapper for `remote` service, which exposes available browser actions * Popular methods: ** `browser.getWindowSize()` ** `browser.refresh()` **testSubjects:**::: -* Source: {blob}test/functional/services/test_subjects.ts[test/functional/services/test_subjects.ts] +* Source: {kibana-blob}test/functional/services/test_subjects.ts[test/functional/services/test_subjects.ts] * Test subjects are elements that are tagged specifically for selecting from tests * Use `testSubjects` over CSS selectors when possible * Usage: @@ -346,21 +346,21 @@ await testSubjects.click(‘containerButton’); ** `testSubjects.click(testSubjectSelector)` - Click a test subject in the page; throw if it can't be found after some time **find:**::: -* Source: {blob}test/functional/services/find.ts[test/functional/services/find.ts] +* Source: {kibana-blob}test/functional/services/find.ts[test/functional/services/find.ts] * Helpers for `remote.findBy*` methods that log and manage timeouts * Popular methods: ** `find.byCssSelector()` ** `find.allByCssSelector()` **retry:**::: -* Source: {blob}test/common/services/retry/retry.ts[test/common/services/retry/retry.ts] +* Source: {kibana-blob}test/common/services/retry/retry.ts[test/common/services/retry/retry.ts] * Helpers for retrying operations * Popular methods: ** `retry.try(fn, onFailureBlock)` - Execute `fn` in a loop until it succeeds or the default timeout elapses. The optional `onFailureBlock` is executed before each retry attempt. ** `retry.tryForTime(ms, fn, onFailureBlock)` - Execute `fn` in a loop until it succeeds or `ms` milliseconds elapses. The optional `onFailureBlock` is executed before each retry attempt. **kibanaServer:**::: -* Source: {blob}test/common/services/kibana_server/kibana_server.js[test/common/services/kibana_server/kibana_server.js] +* Source: {kibana-blob}test/common/services/kibana_server/kibana_server.js[test/common/services/kibana_server/kibana_server.js] * Helpers for interacting with {kib}'s server * Commonly used methods: ** `kibanaServer.uiSettings.update()` @@ -368,23 +368,23 @@ await testSubjects.click(‘containerButton’); ** `kibanaServer.status.getOverallState()` **esArchiver:**::: -* Source: {blob}test/common/services/es_archiver.ts[test/common/services/es_archiver.ts] +* Source: {kibana-blob}test/common/services/es_archiver.ts[test/common/services/es_archiver.ts] * Load/unload archives created with the `esArchiver` * Popular methods: ** `esArchiver.load(path)` ** `esArchiver.loadIfNeeded(path)` ** `esArchiver.unload(path)` -Full list of services that are used in functional tests can be found here: {blob}test/functional/services[test/functional/services] +Full list of services that are used in functional tests can be found here: {kibana-blob}test/functional/services[test/functional/services] **Low-level utilities:**::: * es -** Source: {blob}test/common/services/es.ts[test/common/services/es.ts] +** Source: {kibana-blob}test/common/services/es.ts[test/common/services/es.ts] ** {es} client ** Higher level options: `kibanaServer.uiSettings` or `esArchiver` * remote -** Source: {blob}test/functional/services/remote/remote.ts[test/functional/services/remote/remote.ts] +** Source: {kibana-blob}test/functional/services/remote/remote.ts[test/functional/services/remote/remote.ts] ** Instance of https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html[WebDriver] class ** Responsible for all communication with the browser ** To perform browser actions, use `remote` service diff --git a/docs/developer/plugin/external-plugin-functional-tests.asciidoc b/docs/developer/plugin/external-plugin-functional-tests.asciidoc index 4a98ffcc5d08c..349602bb4f276 100644 --- a/docs/developer/plugin/external-plugin-functional-tests.asciidoc +++ b/docs/developer/plugin/external-plugin-functional-tests.asciidoc @@ -64,7 +64,7 @@ export default async function ({ readConfigFile }) { } // more settings, like timeouts, mochaOpts, etc are - // defined in the config schema. See {blob}src/functional_test_runner/lib/config/schema.js[src/functional_test_runner/lib/config/schema.js] + // defined in the config schema. See {kibana-blob}src/functional_test_runner/lib/config/schema.js[src/functional_test_runner/lib/config/schema.js] }; } diff --git a/docs/developer/plugin/external-plugin-localization.asciidoc b/docs/developer/plugin/external-plugin-localization.asciidoc index 1eb56a6787a62..cc43acc6ca4b3 100644 --- a/docs/developer/plugin/external-plugin-localization.asciidoc +++ b/docs/developer/plugin/external-plugin-localization.asciidoc @@ -47,9 +47,9 @@ To use {kib} i18n tooling, create a `.i18nrc.json` file with the following confi } ----------- -An example {kib} `.i18nrc.json` is {blob}.i18nrc.json[here]. +An example {kib} `.i18nrc.json` is {kibana-blob}.i18nrc.json[here]. -Full documentation about i18n tooling is {blob}src/dev/i18n/README.md[here]. +Full documentation about i18n tooling is {kibana-blob}src/dev/i18n/README.md[here]. [discrete] === Extracting default messages diff --git a/docs/gs-index.asciidoc b/docs/gs-index.asciidoc index 06996d382d90f..d4e6a102dddf0 100644 --- a/docs/gs-index.asciidoc +++ b/docs/gs-index.asciidoc @@ -9,10 +9,6 @@ release-state can be: released | prerelease | unreleased :major-version: 5.4 :branch: 5.4 -:docker-image: docker.elastic.co/kibana/kibana:{version} -:es-ref: https://www.elastic.co/guide/en/elasticsearch/reference/{branch}/ -:security: https://www.elastic.co/community/security/ - include::{docs-root}/shared/attributes.asciidoc[] include::introduction.asciidoc[] diff --git a/docs/index.asciidoc b/docs/index.asciidoc index 2823529df18d3..6de80b6e845de 100644 --- a/docs/index.asciidoc +++ b/docs/index.asciidoc @@ -1,26 +1,12 @@ [[kibana-guide]] = Kibana Guide -:include-xpack: true -:lang: en :kib-repo-dir: {kibana-root}/docs include::{docs-root}/shared/versions/stack/{source_branch}.asciidoc[] -:docker-repo: docker.elastic.co/kibana/kibana -:docker-image: {docker-repo}:{version} -:es-docker-repo: docker.elastic.co/elasticsearch/elasticsearch -:es-docker-image: {es-docker-repo}:{version} -:security-ref: https://www.elastic.co/community/security/ -:Data-source: Data view -:data-source: data view -:data-sources: data views -:a-data-source: a data view - include::{docs-root}/shared/attributes.asciidoc[] -:blob: {kib-repo}blob/{branch}/ - include::user/index.asciidoc[] include::accessibility.asciidoc[] diff --git a/docs/maps/connect-to-ems.asciidoc b/docs/maps/connect-to-ems.asciidoc index 5616e8755a881..8db34ee3f61bf 100644 --- a/docs/maps/connect-to-ems.asciidoc +++ b/docs/maps/connect-to-ems.asciidoc @@ -1,7 +1,9 @@ -[role="xpack"] [[maps-connect-to-ems]] == Connect to Elastic Maps Service +:ems-docker-repo: docker.elastic.co/elastic-maps-service/elastic-maps-server-ubi8 +:ems-docker-image: {ems-docker-repo}:{version} + https://www.elastic.co/elastic-maps-service[Elastic Maps Service (EMS)] is a service that hosts tile layers and vector shapes of administrative boundaries. If you are using Kibana's out-of-the-box settings, Maps is already configured to use EMS. diff --git a/docs/maps/index.asciidoc b/docs/maps/index.asciidoc index f1fdb48c248ab..e6446a2a30075 100644 --- a/docs/maps/index.asciidoc +++ b/docs/maps/index.asciidoc @@ -1,7 +1,3 @@ -:ems-docker-repo: docker.elastic.co/elastic-maps-service/elastic-maps-server-ubi8 -:ems-docker-image: {ems-docker-repo}:{version} - -[role="xpack"] [[maps]] = Maps diff --git a/docs/setup/docker.asciidoc b/docs/setup/docker.asciidoc index 3128ff98269fa..3f9cc2bca309f 100644 --- a/docs/setup/docker.asciidoc +++ b/docs/setup/docker.asciidoc @@ -4,6 +4,11 @@ Install with Docker ++++ +:docker-repo: docker.elastic.co/kibana/kibana +:docker-image: {docker-repo}:{version} +:es-docker-repo: docker.elastic.co/elasticsearch/elasticsearch +:es-docker-image: {es-docker-repo}:{version} + Docker images for {kib} are available from the Elastic Docker registry. The base image is https://hub.docker.com/_/ubuntu[ubuntu:20.04]. From 08a68dbe730d63a0f77c63e46da5581bbb842d41 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 20 Apr 2023 18:23:36 +0200 Subject: [PATCH 15/67] [Synthetics] Fix performance breakdown link from error details page (#155393) --- .../common/components/stderr_logs.tsx | 9 +++++++++ .../common/components/use_std_error_logs.ts | 4 ++-- .../error_details/error_details_page.tsx | 6 +++++- .../test_run_details/components/step_info.tsx | 2 +- .../components/test_run_error_info.tsx | 17 +++++++++-------- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx index 6c5e80c9f28e4..cbe375774ef50 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx @@ -53,6 +53,11 @@ export const StdErrorLogs = ({ sortable: true, render: (date: string) => formatDate(date, 'dateTime'), }, + { + field: 'synthetics.type', + name: TYPE_LABEL, + sortable: true, + }, { field: 'synthetics.payload.message', name: 'Message', @@ -146,6 +151,10 @@ export const TIMESTAMP_LABEL = i18n.translate('xpack.synthetics.monitorList.time defaultMessage: 'Timestamp', }); +export const TYPE_LABEL = i18n.translate('xpack.synthetics.monitorList.type', { + defaultMessage: 'Type', +}); + export const ERROR_SUMMARY_LABEL = i18n.translate('xpack.synthetics.monitorList.errorSummary', { defaultMessage: 'Error summary', }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/use_std_error_logs.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/use_std_error_logs.ts index a1317da94ccd2..ac26d594af5e1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/use_std_error_logs.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/use_std_error_logs.ts @@ -25,8 +25,8 @@ export const useStdErrorLogs = ({ bool: { filter: [ { - term: { - 'synthetics.type': 'stderr', + terms: { + 'synthetics.type': ['stderr', 'stdout'], }, }, ...(monitorId diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/error_details_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/error_details_page.tsx index 4a6ea2d3f34e1..a759273546005 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/error_details_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/error_details_page.tsx @@ -67,7 +67,11 @@ export function ErrorDetailsPage() { /> - + diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_info.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_info.tsx index 0e13c7efa251d..beda3ed42ead3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_info.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_info.tsx @@ -69,7 +69,7 @@ export const StepMetaInfo = ({ { const isDownMonitor = journeyDetails?.journey?.monitor?.status === 'down'; @@ -40,14 +42,13 @@ export const TestRunErrorInfo = ({ )} - {(hasNoSteps || isDownMonitor) && - errorMessage?.includes('journey did not finish executing') && ( - - )} + {isDownMonitor && (showErrorLogs || hasNoSteps) && ( + + )} ); }; From 00dfae43129cb9692c9151c3140f3177d347a6cd Mon Sep 17 00:00:00 2001 From: Ersin Erdal <92688503+ersin-erdal@users.noreply.github.com> Date: Thu, 20 Apr 2023 19:23:47 +0200 Subject: [PATCH 16/67] Make rule type param validation required (#154257) Resolves: #153755 This PR intends to make rule type param validation function required. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../server/alert_types/always_firing.ts | 13 ++++ .../server/alert_types/astros.ts | 8 +++ .../legacy_alerts_client.test.ts | 4 ++ .../alerting_authorization.test.ts | 4 ++ .../alerting_event_logger.test.ts | 4 ++ ...eate_alert_event_log_record_object.test.ts | 4 ++ .../alerting/server/lib/license_state.test.ts | 6 ++ .../server/lib/validate_rule_type_params.ts | 2 +- x-pack/plugins/alerting/server/plugin.test.ts | 3 + .../server/rule_type_registry.test.ts | 72 +++++++++++++++++++ .../migrate_legacy_actions.test.ts | 3 + .../rules_client/lib/validate_actions.test.ts | 3 + .../server/rules_client/methods/bulk_edit.ts | 4 +- .../server/rules_client/methods/create.ts | 2 +- .../server/rules_client/methods/update.ts | 2 +- .../rules_client/tests/bulk_delete.test.ts | 4 ++ .../rules_client/tests/bulk_edit.test.ts | 6 ++ .../server/rules_client/tests/create.test.ts | 33 +++++++++ .../server/rules_client/tests/find.test.ts | 13 ++++ .../server/rules_client/tests/get.test.ts | 6 ++ .../alerting/server/rules_client/tests/lib.ts | 3 + .../server/rules_client/tests/resolve.test.ts | 6 ++ .../server/rules_client/tests/update.test.ts | 9 +++ .../rules_client_conflict_retries.test.ts | 6 ++ .../saved_objects/is_rule_exportable.test.ts | 9 +++ .../task_runner/execution_handler.test.ts | 4 ++ .../alerting/server/task_runner/fixtures.ts | 3 + .../server/task_runner/task_runner.ts | 4 +- .../task_runner/task_runner_factory.test.ts | 4 ++ x-pack/plugins/alerting/server/types.ts | 6 +- .../monitoring/server/alerts/base_rule.ts | 7 ++ .../rule_types/es_query/rule_type.test.ts | 8 +-- .../index_threshold/rule_type.test.ts | 4 +- .../plugins/alerts/server/alert_types.ts | 43 +++++++++++ .../alerts_restricted/server/alert_types.ts | 7 ++ .../plugins/alerts/server/plugin.ts | 3 + .../plugins/alerts/server/plugin.ts | 21 +++++- 37 files changed, 325 insertions(+), 18 deletions(-) diff --git a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts index 9b653fc08576e..3c048702e4723 100644 --- a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts +++ b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts @@ -8,6 +8,7 @@ import { v4 as uuidv4 } from 'uuid'; import { range } from 'lodash'; import { RuleType } from '@kbn/alerting-plugin/server'; +import { schema } from '@kbn/config-schema'; import { DEFAULT_INSTANCES_TO_GENERATE, ALERTING_EXAMPLE_APP_ID, @@ -78,4 +79,16 @@ export const alertType: RuleType< }; }, producer: ALERTING_EXAMPLE_APP_ID, + validate: { + params: schema.object({ + instances: schema.maybe(schema.number()), + thresholds: schema.maybe( + schema.object({ + small: schema.maybe(schema.number()), + medium: schema.maybe(schema.number()), + large: schema.maybe(schema.number()), + }) + ), + }), + }, }; diff --git a/x-pack/examples/alerting_example/server/alert_types/astros.ts b/x-pack/examples/alerting_example/server/alert_types/astros.ts index c73ec8ecbc8bf..39cf751702dd7 100644 --- a/x-pack/examples/alerting_example/server/alert_types/astros.ts +++ b/x-pack/examples/alerting_example/server/alert_types/astros.ts @@ -7,6 +7,7 @@ import axios from 'axios'; import { RuleType } from '@kbn/alerting-plugin/server'; +import { schema } from '@kbn/config-schema'; import { Operator, Craft, ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; interface PeopleInSpace { @@ -84,4 +85,11 @@ export const alertType: RuleType< getViewInAppRelativeUrl({ rule }) { return `/app/${ALERTING_EXAMPLE_APP_ID}/astros/${rule.id}`; }, + validate: { + params: schema.object({ + outerSpaceCapacity: schema.number(), + craft: schema.string(), + op: schema.string(), + }), + }, }; diff --git a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts index 59668072632b4..e17a13e0b78d6 100644 --- a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts @@ -15,6 +15,7 @@ import { ruleRunMetricsStoreMock } from '../lib/rule_run_metrics_store.mock'; import { getAlertsForNotification, processAlerts } from '../lib'; import { logAlerts } from '../task_runner/log_alerts'; import { DEFAULT_FLAPPING_SETTINGS } from '../../common/rules_settings'; +import { schema } from '@kbn/config-schema'; const scheduleActions = jest.fn(); const replaceState = jest.fn(() => ({ scheduleActions })); @@ -86,6 +87,9 @@ const ruleType: jest.Mocked = { producer: 'alerts', cancelAlertsOnRuleTimeout: true, ruleTaskTimeout: '5m', + validate: { + params: schema.any(), + }, }; const testAlert1 = { diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts index 4949d958ce4f0..333623588aa64 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts @@ -24,6 +24,7 @@ import { v4 as uuidv4 } from 'uuid'; import { RecoveredActionGroup } from '../../common'; import { RegistryRuleType } from '../rule_type_registry'; import { AlertingAuthorizationFilterType } from './alerting_authorization_kuery'; +import { schema } from '@kbn/config-schema'; const ruleTypeRegistry = ruleTypeRegistryMock.create(); const features: jest.Mocked = featuresPluginMock.createStart(); @@ -196,6 +197,9 @@ beforeEach(() => { return { state: {} }; }, producer: 'myApp', + validate: { + params: schema.any(), + }, })); features.getKibanaFeatures.mockReturnValue([ myAppFeature, diff --git a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts index 0eba6610754f3..2711f921e81ec 100644 --- a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts +++ b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts @@ -27,6 +27,7 @@ import { import { RuleRunMetrics } from '../rule_run_metrics_store'; import { EVENT_LOG_ACTIONS } from '../../plugin'; import { TaskRunnerTimerSpan } from '../../task_runner/task_runner_timer'; +import { schema } from '@kbn/config-schema'; const mockNow = '2020-01-01T02:00:00.000Z'; const eventLogger = eventLoggerMock.create(); @@ -42,6 +43,9 @@ const ruleType: jest.Mocked = { executor: jest.fn(), producer: 'alerts', ruleTaskTimeout: '1m', + validate: { + params: schema.any(), + }, }; const context: RuleContextOpts = { diff --git a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts index e7d77e55b9e4a..e62ec213cba0f 100644 --- a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts +++ b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts @@ -8,6 +8,7 @@ import { createAlertEventLogRecordObject } from './create_alert_event_log_record_object'; import { UntypedNormalizedRuleType } from '../rule_type_registry'; import { RecoveredActionGroup } from '../types'; +import { schema } from '@kbn/config-schema'; const MAINTENANCE_WINDOW_IDS = ['test-1', 'test-2']; @@ -22,6 +23,9 @@ describe('createAlertEventLogRecordObject', () => { recoveryActionGroup: RecoveredActionGroup, executor: jest.fn(), producer: 'alerts', + validate: { + params: schema.any(), + }, }; test('created alert event "execute-start"', async () => { diff --git a/x-pack/plugins/alerting/server/lib/license_state.test.ts b/x-pack/plugins/alerting/server/lib/license_state.test.ts index 535033851b3e0..02b01bbd54f2d 100644 --- a/x-pack/plugins/alerting/server/lib/license_state.test.ts +++ b/x-pack/plugins/alerting/server/lib/license_state.test.ts @@ -72,6 +72,9 @@ describe('getLicenseCheckForRuleType', () => { minimumLicenseRequired: 'gold', isExportable: true, recoveryActionGroup: { id: 'recovered', name: 'Recovered' }, + validate: { + params: { validate: (params) => params }, + }, }; beforeEach(() => { @@ -207,6 +210,9 @@ describe('ensureLicenseForRuleType()', () => { minimumLicenseRequired: 'gold', isExportable: true, recoveryActionGroup: { id: 'recovered', name: 'Recovered' }, + validate: { + params: { validate: (params) => params }, + }, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/lib/validate_rule_type_params.ts b/x-pack/plugins/alerting/server/lib/validate_rule_type_params.ts index b791b05499263..0f152bb6f641b 100644 --- a/x-pack/plugins/alerting/server/lib/validate_rule_type_params.ts +++ b/x-pack/plugins/alerting/server/lib/validate_rule_type_params.ts @@ -17,7 +17,7 @@ export function validateRuleTypeParams( } try { - return validator.validate(params); + return validator.validate(params as Params); } catch (err) { throw Boom.badRequest(`params invalid: ${err.message}`); } diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index ed4b8575b9810..5101ec8609108 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -71,6 +71,9 @@ const sampleRuleType: RuleType = { async executor() { return { state: {} }; }, + validate: { + params: { validate: (params) => params }, + }, }; describe('Alerting Plugin', () => { diff --git a/x-pack/plugins/alerting/server/rule_type_registry.test.ts b/x-pack/plugins/alerting/server/rule_type_registry.test.ts index c30fd1d5a8ae4..428600f561ff7 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.test.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.test.ts @@ -15,6 +15,7 @@ import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import { inMemoryMetricsMock } from './monitoring/in_memory_metrics.mock'; import { alertsServiceMock } from './alerts_service/alerts_service.mock'; +import { schema } from '@kbn/config-schema'; const logger = loggingSystemMock.create().get(); let mockedLicenseState: jest.Mocked; @@ -62,6 +63,9 @@ describe('Create Lifecycle', () => { isExportable: true, executor: jest.fn(), producer: 'alerts', + validate: { + params: schema.any(), + }, }); expect(registry.has('foo')).toEqual(true); }); @@ -83,6 +87,9 @@ describe('Create Lifecycle', () => { isExportable: true, executor: jest.fn(), producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }; const registry = new RuleTypeRegistry(ruleTypeRegistryParams); @@ -116,6 +123,9 @@ describe('Create Lifecycle', () => { isExportable: true, executor: jest.fn(), producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }; const registry = new RuleTypeRegistry(ruleTypeRegistryParams); @@ -140,6 +150,9 @@ describe('Create Lifecycle', () => { isExportable: true, executor: jest.fn(), producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }; const registry = new RuleTypeRegistry(ruleTypeRegistryParams); @@ -167,6 +180,9 @@ describe('Create Lifecycle', () => { executor: jest.fn(), producer: 'alerts', defaultScheduleInterval: 'foobar', + validate: { + params: { validate: (params) => params }, + }, }; const registry = new RuleTypeRegistry(ruleTypeRegistryParams); @@ -193,6 +209,9 @@ describe('Create Lifecycle', () => { executor: jest.fn(), producer: 'alerts', defaultScheduleInterval: '10s', + validate: { + params: { validate: (params) => params }, + }, }; const registry = new RuleTypeRegistry(ruleTypeRegistryParams); registry.register(ruleType); @@ -219,6 +238,9 @@ describe('Create Lifecycle', () => { executor: jest.fn(), producer: 'alerts', defaultScheduleInterval: '10s', + validate: { + params: { validate: (params) => params }, + }, }; const registry = new RuleTypeRegistry({ ...ruleTypeRegistryParams, @@ -255,6 +277,9 @@ describe('Create Lifecycle', () => { isExportable: true, executor: jest.fn(), producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }; const registry = new RuleTypeRegistry(ruleTypeRegistryParams); @@ -284,6 +309,9 @@ describe('Create Lifecycle', () => { producer: 'alerts', minimumLicenseRequired: 'basic', isExportable: true, + validate: { + params: { validate: (params) => params }, + }, }; const registry = new RuleTypeRegistry(ruleTypeRegistryParams); registry.register(ruleType); @@ -317,6 +345,9 @@ describe('Create Lifecycle', () => { producer: 'alerts', minimumLicenseRequired: 'basic', isExportable: true, + validate: { + params: { validate: (params) => params }, + }, }; const registry = new RuleTypeRegistry(ruleTypeRegistryParams); registry.register(ruleType); @@ -354,6 +385,9 @@ describe('Create Lifecycle', () => { isExportable: true, executor: jest.fn(), producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }; const registry = new RuleTypeRegistry(ruleTypeRegistryParams); @@ -380,6 +414,9 @@ describe('Create Lifecycle', () => { executor: jest.fn(), producer: 'alerts', ruleTaskTimeout: '20m', + validate: { + params: { validate: (params) => params }, + }, }; const registry = new RuleTypeRegistry(ruleTypeRegistryParams); registry.register(ruleType); @@ -412,6 +449,9 @@ describe('Create Lifecycle', () => { isExportable: true, executor: jest.fn(), producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }; const registry = new RuleTypeRegistry(ruleTypeRegistryParams); registry.register(ruleType); @@ -435,6 +475,9 @@ describe('Create Lifecycle', () => { isExportable: true, executor: jest.fn(), producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }); expect(() => registry.register({ @@ -451,6 +494,9 @@ describe('Create Lifecycle', () => { isExportable: true, executor: jest.fn(), producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }) ).toThrowErrorMatchingInlineSnapshot(`"Rule type \\"test\\" is already registered."`); }); @@ -475,6 +521,9 @@ describe('Create Lifecycle', () => { context: 'test', mappings: { fieldMap: { field: { type: 'keyword', required: false } } }, }, + validate: { + params: { validate: (params) => params }, + }, }); expect(alertsService.register).toHaveBeenCalledWith({ @@ -499,6 +548,9 @@ describe('Create Lifecycle', () => { isExportable: true, executor: jest.fn(), producer: 'alerts', + validate: { + params: schema.any(), + }, }); expect(alertsService.register).not.toHaveBeenCalled(); @@ -522,6 +574,9 @@ describe('Create Lifecycle', () => { isExportable: true, executor: jest.fn(), producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }); const ruleType = registry.get('test'); expect(ruleType).toMatchInlineSnapshot(` @@ -552,6 +607,11 @@ describe('Create Lifecycle', () => { "id": "recovered", "name": "Recovered", }, + "validate": Object { + "params": Object { + "validate": [Function], + }, + }, } `); }); @@ -589,6 +649,9 @@ describe('Create Lifecycle', () => { minimumLicenseRequired: 'basic', executor: jest.fn(), producer: 'alerts', + validate: { + params: schema.any(), + }, }); const result = registry.list(); expect(result).toMatchInlineSnapshot(` @@ -689,6 +752,9 @@ describe('Create Lifecycle', () => { minimumLicenseRequired: 'basic', executor: jest.fn(), producer: 'alerts', + validate: { + params: schema.any(), + }, }); const result = registry.getAllTypes(); expect(result).toEqual(['test']); @@ -715,6 +781,9 @@ describe('Create Lifecycle', () => { isExportable: true, minimumLicenseRequired: 'basic', recoveryActionGroup: { id: 'recovered', name: 'Recovered' }, + validate: { + params: schema.any(), + }, }); }); @@ -750,6 +819,9 @@ function ruleTypeWithVariables( return { state: {} }; }, producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }; if (!context && !state) return baseAlert; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.test.ts index bf67bb5a97191..50848be43a73f 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.test.ts @@ -44,6 +44,9 @@ const ruleType: jest.Mocked = { cancelAlertsOnRuleTimeout: true, ruleTaskTimeout: '5m', getSummarizedAlerts: jest.fn(), + validate: { + params: { validate: (params) => params }, + }, }; const context = { diff --git a/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.test.ts index fbf59b2953d9d..679f79d477c86 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.test.ts @@ -26,6 +26,9 @@ describe('validateActions', () => { cancelAlertsOnRuleTimeout: true, ruleTaskTimeout: '5m', getSummarizedAlerts: jest.fn(), + validate: { + params: { validate: (params) => params }, + }, }; const data = { diff --git a/x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts b/x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts index dfe4ecc952bf6..ce11a0cc017e3 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts @@ -503,11 +503,11 @@ async function updateRuleAttributesAndParamsInMemory( // Throws an error if alert type isn't registered const ruleType = context.ruleTypeRegistry.get(data.alertTypeId); - const validatedAlertTypeParams = validateRuleTypeParams(data.params, ruleType.validate?.params); + const validatedAlertTypeParams = validateRuleTypeParams(data.params, ruleType.validate.params); const username = await context.getUserName(); let createdAPIKey = null; diff --git a/x-pack/plugins/alerting/server/rules_client/methods/update.ts b/x-pack/plugins/alerting/server/rules_client/methods/update.ts index 4703cd5b92b28..7c1fbedff37ce 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/update.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/update.ts @@ -198,7 +198,7 @@ async function updateAlert( } // Validate - const validatedAlertTypeParams = validateRuleTypeParams(data.params, ruleType.validate?.params); + const validatedAlertTypeParams = validateRuleTypeParams(data.params, ruleType.validate.params); await validateActions(context, ruleType, data, allowMissingConnectorSecrets); // Throw error if schedule interval is less than the minimum and we are enforcing it diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts index 74808613f869d..dbe04ec420000 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts @@ -27,6 +27,7 @@ import { returnedRule2, siemRule1, } from './test_helpers'; +import { schema } from '@kbn/config-schema'; import { migrateLegacyActions } from '../lib'; jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { @@ -174,6 +175,9 @@ describe('bulkDelete', () => { return { state: {} }; }, producer: 'alerts', + validate: { + params: schema.any(), + }, }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts index c9e9302fb0146..47b38cce094ba 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_edit.test.ts @@ -210,6 +210,9 @@ describe('bulkEdit()', () => { return { state: {} }; }, producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }); (migrateLegacyActions as jest.Mock).mockResolvedValue(migrateLegacyActionsMock); @@ -686,6 +689,9 @@ describe('bulkEdit()', () => { }, producer: 'alerts', getSummarizedAlerts: jest.fn().mockResolvedValue({}), + validate: { + params: { validate: (params) => params }, + }, }); const existingAction = { frequency: { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts index 2be00b0fc5352..d44d69a6e64bf 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts @@ -1233,6 +1233,9 @@ describe('create()', () => { extractReferences: extractReferencesFn, injectReferences: injectReferencesFn, }, + validate: { + params: { validate: (params) => params }, + }, })); const data = getMockData({ params: ruleParams, @@ -1414,6 +1417,9 @@ describe('create()', () => { extractReferences: extractReferencesFn, injectReferences: injectReferencesFn, }, + validate: { + params: { validate: (params) => params }, + }, })); const data = getMockData({ params: ruleParams, @@ -2679,6 +2685,9 @@ describe('create()', () => { extractReferences: jest.fn(), injectReferences: jest.fn(), }, + validate: { + params: { validate: (params) => params }, + }, })); const createdAttributes = { ...data, @@ -2747,6 +2756,9 @@ describe('create()', () => { extractReferences: jest.fn(), injectReferences: jest.fn(), }, + validate: { + params: { validate: (params) => params }, + }, })); const data = getMockData({ schedule: { interval: '1s' } }); @@ -2781,6 +2793,9 @@ describe('create()', () => { extractReferences: jest.fn(), injectReferences: jest.fn(), }, + validate: { + params: { validate: (params) => params }, + }, })); const data = getMockData({ @@ -2870,6 +2885,9 @@ describe('create()', () => { extractReferences: jest.fn(), injectReferences: jest.fn(), }, + validate: { + params: { validate: (params) => params }, + }, })); const data = getMockData({ @@ -2916,6 +2934,9 @@ describe('create()', () => { extractReferences: jest.fn(), injectReferences: jest.fn(), }, + validate: { + params: { validate: (params) => params }, + }, })); const data = getMockData({ @@ -2975,6 +2996,9 @@ describe('create()', () => { extractReferences: jest.fn(), injectReferences: jest.fn(), }, + validate: { + params: { validate: (params) => params }, + }, })); const data = getMockData({ @@ -3052,6 +3076,9 @@ describe('create()', () => { extractReferences: jest.fn(), injectReferences: jest.fn(), }, + validate: { + params: { validate: (params) => params }, + }, })); const data = getMockData({ @@ -3243,6 +3270,9 @@ describe('create()', () => { injectReferences: jest.fn(), }, getSummarizedAlerts: jest.fn().mockResolvedValue({}), + validate: { + params: { validate: (params) => params }, + }, })); const data = getMockData({ @@ -3292,6 +3322,9 @@ describe('create()', () => { extractReferences: jest.fn(), injectReferences: jest.fn(), }, + validate: { + params: { validate: (params) => params }, + }, })); const data = getMockData({ diff --git a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts index 3573508d891cd..dadfd3204204a 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts @@ -19,6 +19,7 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { RecoveredActionGroup } from '../../../common'; import { RegistryRuleType } from '../../rule_type_registry'; +import { schema } from '@kbn/config-schema'; import { enabledRule1, enabledRule2, siemRule1, siemRule2 } from './test_helpers'; import { formatLegacyActions } from '../lib'; @@ -370,6 +371,9 @@ describe('find()', () => { return { state: {} }; }, producer: 'myApp', + validate: { + params: schema.any(), + }, })); ruleTypeRegistry.get.mockImplementationOnce(() => ({ id: '123', @@ -387,6 +391,9 @@ describe('find()', () => { extractReferences: jest.fn(), injectReferences: injectReferencesFn, }, + validate: { + params: schema.any(), + }, })); unsecuredSavedObjectsClient.find.mockResolvedValue({ total: 2, @@ -570,6 +577,9 @@ describe('find()', () => { return { state: {} }; }, producer: 'myApp', + validate: { + params: schema.any(), + }, })); ruleTypeRegistry.get.mockImplementationOnce(() => ({ id: '123', @@ -587,6 +597,9 @@ describe('find()', () => { extractReferences: jest.fn(), injectReferences: injectReferencesFn, }, + validate: { + params: schema.any(), + }, })); unsecuredSavedObjectsClient.find.mockResolvedValue({ total: 2, diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts index 1ca6e664fc359..eb9a8a22523ee 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts @@ -232,6 +232,9 @@ describe('get()', () => { extractReferences: jest.fn(), injectReferences: injectReferencesFn, }, + validate: { + params: { validate: (params) => params }, + }, })); const rulesClient = new RulesClient(rulesClientParams); unsecuredSavedObjectsClient.get.mockResolvedValueOnce({ @@ -355,6 +358,9 @@ describe('get()', () => { extractReferences: jest.fn(), injectReferences: injectReferencesFn, }, + validate: { + params: { validate: (params) => params }, + }, })); const rulesClient = new RulesClient(rulesClientParams); unsecuredSavedObjectsClient.get.mockResolvedValueOnce({ 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 f036e2cb02298..337c09e087243 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/lib.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/lib.ts @@ -116,6 +116,9 @@ export function getBeforeSetup( return { state: {} }; }, producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, })); rulesClientParams.getEventLogClient.mockResolvedValue( eventLogClient ?? eventLogClientMock.create() diff --git a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts index bb22b5f6d0d53..37372752d53a3 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts @@ -284,6 +284,9 @@ describe('resolve()', () => { extractReferences: jest.fn(), injectReferences: injectReferencesFn, }, + validate: { + params: { validate: (params) => params }, + }, })); const rulesClient = new RulesClient(rulesClientParams); unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({ @@ -417,6 +420,9 @@ describe('resolve()', () => { extractReferences: jest.fn(), injectReferences: injectReferencesFn, }, + validate: { + params: { validate: (params) => params }, + }, })); const rulesClient = new RulesClient(rulesClientParams); unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({ 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 b76d0607f22a8..775df48e8fc02 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 @@ -171,6 +171,9 @@ describe('update()', () => { return { state: {} }; }, producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }); (migrateLegacyActions as jest.Mock).mockResolvedValue({ hasLegacyActions: false, @@ -749,6 +752,9 @@ describe('update()', () => { extractReferences: extractReferencesFn, injectReferences: injectReferencesFn, }, + validate: { + params: { validate: (params) => params }, + }, })); unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ id: '1', @@ -1643,6 +1649,9 @@ describe('update()', () => { return { state: {} }; }, producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }); encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: alertId, diff --git a/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts b/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts index 7f1a26b0e4940..fcde2f6e4f444 100644 --- a/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts @@ -359,6 +359,9 @@ beforeEach(() => { return { state: {} }; }, producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, })); ruleTypeRegistry.get.mockReturnValue({ @@ -373,6 +376,9 @@ beforeEach(() => { return { state: {} }; }, producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }); rulesClient = new RulesClient(rulesClientParams); diff --git a/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts b/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts index af57c644dd0aa..e517d37739337 100644 --- a/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts @@ -55,6 +55,9 @@ describe('isRuleExportable', () => { isExportable: true, executor: jest.fn(), producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }); expect( isRuleExportable( @@ -111,6 +114,9 @@ describe('isRuleExportable', () => { isExportable: false, executor: jest.fn(), producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }); expect( isRuleExportable( @@ -170,6 +176,9 @@ describe('isRuleExportable', () => { isExportable: false, executor: jest.fn(), producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }); expect( isRuleExportable( diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts index a83211aa9d040..3aba014226042 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts @@ -32,6 +32,7 @@ import { AlertInstanceState, AlertInstanceContext } from '../../common'; import { asSavedObjectExecutionSource } from '@kbn/actions-plugin/server'; import sinon from 'sinon'; import { mockAAD } from './fixtures'; +import { schema } from '@kbn/config-schema'; jest.mock('./inject_action_params', () => ({ injectActionParams: jest.fn(), @@ -70,6 +71,9 @@ const ruleType: NormalizedRuleType< executor: jest.fn(), producer: 'alerts', getSummarizedAlerts: getSummarizedAlertsMock, + validate: { + params: schema.any(), + }, }; const rule = { id: '1', diff --git a/x-pack/plugins/alerting/server/task_runner/fixtures.ts b/x-pack/plugins/alerting/server/task_runner/fixtures.ts index 64db706d824f7..0b5cd235185d0 100644 --- a/x-pack/plugins/alerting/server/task_runner/fixtures.ts +++ b/x-pack/plugins/alerting/server/task_runner/fixtures.ts @@ -156,6 +156,9 @@ export const ruleType: jest.Mocked = { cancelAlertsOnRuleTimeout: true, ruleTaskTimeout: '5m', autoRecoverAlerts: true, + validate: { + params: { validate: (params) => params }, + }, }; export const mockRunNowResponse = { 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 febe4a2eeacb7..41426724187e1 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -571,7 +571,7 @@ export class TaskRunner< this.alertingEventLogger.start(); return await loadRule({ - paramValidator: this.ruleType.validate?.params, + paramValidator: this.ruleType.validate.params, ruleId, spaceId, context: this.context, @@ -715,7 +715,7 @@ export class TaskRunner< schedule = asOk( ( await loadRule({ - paramValidator: this.ruleType.validate?.params, + paramValidator: this.ruleType.validate.params, ruleId, spaceId, context: this.context, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts index e9c9f244118ab..69480563c355e 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts @@ -32,6 +32,7 @@ import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { rulesSettingsClientMock } from '../rules_settings_client.mock'; import { maintenanceWindowClientMock } from '../maintenance_window_client.mock'; import { alertsServiceMock } from '../alerts_service/alerts_service.mock'; +import { schema } from '@kbn/config-schema'; const inMemoryMetrics = inMemoryMetricsMock.create(); const executionContext = executionContextServiceMock.createSetupContract(); @@ -58,6 +59,9 @@ const ruleType: UntypedNormalizedRuleType = { }, executor: jest.fn(), producer: 'alerts', + validate: { + params: schema.any(), + }, }; let fakeTimer: sinon.SinonFakeTimers; diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index ee9bf42e5716b..21ba86dc0f25f 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -137,7 +137,7 @@ export type ExecutorType< ) => Promise<{ state: State }>; export interface RuleTypeParamsValidator { - validate: (object: unknown) => Params; + validate: (object: Partial) => Params; validateMutatedParams?: (mutatedOject: Params, origObject?: Params) => Params; } @@ -235,8 +235,8 @@ export interface RuleType< > { id: string; name: string; - validate?: { - params?: RuleTypeParamsValidator; + validate: { + params: RuleTypeParamsValidator; }; actionGroups: Array>; defaultActionGroupId: ActionGroup['id']; diff --git a/x-pack/plugins/monitoring/server/alerts/base_rule.ts b/x-pack/plugins/monitoring/server/alerts/base_rule.ts index 3e08d370af56b..17491ffc760cb 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_rule.ts @@ -103,6 +103,13 @@ export class BaseRule { actionVariables: { context: actionVariables, }, + // As there is "[key: string]: unknown;" in CommonAlertParams, + // we couldn't figure out a schema for validation and created a follow on issue: + // https://github.com/elastic/kibana/issues/153754 + // Below validate function should be overwritten in each monitoring rule type + validate: { + params: { validate: (params) => params }, + }, }; } diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts index 66d96c36d2eb9..8b1679241d9c9 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.test.ts @@ -116,11 +116,11 @@ describe('ruleType', () => { groupBy: 'all', }; - expect(ruleType.validate?.params?.validate(params)).toBeTruthy(); + expect(ruleType.validate.params.validate(params)).toBeTruthy(); }); it('validator fails with invalid es query params - threshold', async () => { - const paramsSchema = ruleType.validate?.params; + const paramsSchema = ruleType.validate.params; if (!paramsSchema) throw new Error('params validator not set'); const params: Partial> = { @@ -556,11 +556,11 @@ describe('ruleType', () => { }); it('validator succeeds with valid search source params', async () => { - expect(ruleType.validate?.params?.validate(defaultParams)).toBeTruthy(); + expect(ruleType.validate.params.validate(defaultParams)).toBeTruthy(); }); it('validator fails with invalid search source params - esQuery provided', async () => { - const paramsSchema = ruleType.validate?.params!; + const paramsSchema = ruleType.validate.params; const params: Partial> = { size: 100, timeWindowSize: 5, diff --git a/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.test.ts index 11a02beddc96d..92664d05ff9ab 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.test.ts @@ -138,11 +138,11 @@ describe('ruleType', () => { threshold: [0], }; - expect(ruleType.validate?.params?.validate(params)).toBeTruthy(); + expect(ruleType.validate.params.validate(params)).toBeTruthy(); }); it('validator fails with invalid params', async () => { - const paramsSchema = ruleType.validate?.params; + const paramsSchema = ruleType.validate.params; if (!paramsSchema) throw new Error('params validator not set'); const params: Partial> = { diff --git a/x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts b/x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts index c2f6b5ca17d21..60e7e82966864 100644 --- a/x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts +++ b/x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts @@ -195,6 +195,9 @@ function getCumulativeFiringAlertType() { }, }; }, + validate: { + params: schema.any(), + }, }; return result; } @@ -531,6 +534,9 @@ function getPatternFiringAlertType() { }, }; }, + validate: { + params: paramsSchema, + }, }; return result; } @@ -572,6 +578,9 @@ function getPatternSuccessOrFailureAlertType() { }, }; }, + validate: { + params: paramsSchema, + }, }; return result; } @@ -646,6 +655,9 @@ function getPatternFiringAutoRecoverFalseAlertType() { }, }; }, + validate: { + params: paramsSchema, + }, }; return result; } @@ -692,6 +704,9 @@ function getLongRunningPatternRuleType(cancelAlertsOnRuleTimeout: boolean = true } return { state: {} }; }, + validate: { + params: paramsSchema, + }, }; return result; } @@ -752,6 +767,9 @@ function getCancellableRuleType() { return { state: {} }; }, + validate: { + params: paramsSchema, + }, }; return result; } @@ -851,6 +869,9 @@ export function defineAlertTypes( async executor() { return { state: {} }; }, + validate: { + params: schema.any(), + }, }; const goldNoopAlertType: RuleType<{}, {}, {}, {}, {}, 'default'> = { id: 'test.gold.noop', @@ -863,6 +884,9 @@ export function defineAlertTypes( async executor() { return { state: {} }; }, + validate: { + params: schema.any(), + }, }; const onlyContextVariablesAlertType: RuleType<{}, {}, {}, {}, {}, 'default'> = { id: 'test.onlyContextVariables', @@ -878,6 +902,9 @@ export function defineAlertTypes( async executor() { return { state: {} }; }, + validate: { + params: schema.any(), + }, }; const onlyStateVariablesAlertType: RuleType<{}, {}, {}, {}, {}, 'default'> = { id: 'test.onlyStateVariables', @@ -893,6 +920,9 @@ export function defineAlertTypes( async executor() { return { state: {} }; }, + validate: { + params: schema.any(), + }, }; const throwAlertType: RuleType<{}, {}, {}, {}, {}, 'default'> = { id: 'test.throw', @@ -910,6 +940,9 @@ export function defineAlertTypes( async executor() { throw new Error('this alert is intended to fail'); }, + validate: { + params: schema.any(), + }, }; function getLongRunningRuleType() { const paramsSchema = schema.object({ @@ -935,6 +968,9 @@ export function defineAlertTypes( await new Promise((resolve) => setTimeout(resolve, params.delay ?? 5000)); return { state: {} }; }, + validate: { + params: schema.any(), + }, }; return result; } @@ -953,7 +989,11 @@ export function defineAlertTypes( return { state: {} }; }, producer: 'alertsFixture', + validate: { + params: schema.any(), + }, }; + const multipleSearchesRuleType: RuleType< { numSearches: number; delay: string }, {}, @@ -1006,6 +1046,9 @@ export function defineAlertTypes( return { state: {} }; }, + validate: { + params: schema.object({ numSearches: schema.number(), delay: schema.string() }), + }, }; alerting.registerType(getAlwaysFiringAlertType()); diff --git a/x-pack/test/alerting_api_integration/common/plugins/alerts_restricted/server/alert_types.ts b/x-pack/test/alerting_api_integration/common/plugins/alerts_restricted/server/alert_types.ts index b4ac8a9fb259a..fd4dabf9c8cab 100644 --- a/x-pack/test/alerting_api_integration/common/plugins/alerts_restricted/server/alert_types.ts +++ b/x-pack/test/alerting_api_integration/common/plugins/alerts_restricted/server/alert_types.ts @@ -7,6 +7,7 @@ import { CoreSetup } from '@kbn/core/server'; import { RuleType } from '@kbn/alerting-plugin/server'; +import { schema } from '@kbn/config-schema'; import { FixtureStartDeps, FixtureSetupDeps } from './plugin'; export function defineAlertTypes( @@ -25,6 +26,9 @@ export function defineAlertTypes( async executor() { return { state: {} }; }, + validate: { + params: schema.any(), + }, }; const noopUnrestrictedAlertType: RuleType<{}, {}, {}, {}, {}, 'default'> = { id: 'test.unrestricted-noop', @@ -37,6 +41,9 @@ export function defineAlertTypes( async executor() { return { state: {} }; }, + validate: { + params: schema.any(), + }, }; alerting.registerType(noopRestrictedAlertType); alerting.registerType(noopUnrestrictedAlertType); diff --git a/x-pack/test/functional_execution_context/plugins/alerts/server/plugin.ts b/x-pack/test/functional_execution_context/plugins/alerts/server/plugin.ts index 068fd2356b360..436452c1fdbaf 100644 --- a/x-pack/test/functional_execution_context/plugins/alerts/server/plugin.ts +++ b/x-pack/test/functional_execution_context/plugins/alerts/server/plugin.ts @@ -82,6 +82,9 @@ export class FixturePlugin implements Plugin params }, + }, }); const router = core.http.createRouter(); diff --git a/x-pack/test/functional_with_es_ssl/plugins/alerts/server/plugin.ts b/x-pack/test/functional_with_es_ssl/plugins/alerts/server/plugin.ts index bbb52e6d98221..66a8890b3da0c 100644 --- a/x-pack/test/functional_with_es_ssl/plugins/alerts/server/plugin.ts +++ b/x-pack/test/functional_with_es_ssl/plugins/alerts/server/plugin.ts @@ -6,7 +6,11 @@ */ import { Plugin, CoreSetup } from '@kbn/core/server'; -import { PluginSetupContract as AlertingSetup, RuleType } from '@kbn/alerting-plugin/server'; +import { + PluginSetupContract as AlertingSetup, + RuleType, + RuleTypeParams, +} from '@kbn/alerting-plugin/server'; import { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; // this plugin's dependendencies @@ -26,10 +30,17 @@ export const noopAlertType: RuleType<{}, {}, {}, {}, {}, 'default'> = { return { state: {} }; }, producer: 'alerts', + validate: { + params: { validate: (params) => params }, + }, }; +interface AlwaysFiringParams extends RuleTypeParams { + instances: Array<{ id: string; state: any }>; +} + export const alwaysFiringAlertType: RuleType< - { instances: Array<{ id: string; state: any }> }, + AlwaysFiringParams, never, // Only use if defining useSavedObjectReferences hook { globalStateValue: boolean; @@ -66,6 +77,9 @@ export const alwaysFiringAlertType: RuleType< }, }; }, + validate: { + params: { validate: (params) => params as AlwaysFiringParams }, + }, }; export const failingAlertType: RuleType = { @@ -84,6 +98,9 @@ export const failingAlertType: RuleType params }, + }, }; export class AlertingFixturePlugin implements Plugin { From f315e808a40ac68d9d89007ea55aaf82261a6536 Mon Sep 17 00:00:00 2001 From: Alexi Doak <109488926+doakalexi@users.noreply.github.com> Date: Thu, 20 Apr 2023 13:54:05 -0400 Subject: [PATCH 17/67] [ResponseOps][Window Maintenance] Add technical preview (#155403) Resolves https://github.com/elastic/kibana/issues/153976 ## Summary Adds the technical preview pill and popover to the headings of the maintenance windows pages. Screen Shot 2023-04-20 at 9 29 51 AM Screen Shot 2023-04-20 at 9 30 04 AM ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) --- .../components/page_header.tsx | 24 +++++++-- .../pages/maintenance_windows/index.tsx | 51 +++++++++++++------ .../pages/maintenance_windows/translations.ts | 15 ++++++ 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/page_header.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/page_header.tsx index 6c2d85afecd71..97602e9f6c972 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/page_header.tsx +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/page_header.tsx @@ -6,7 +6,7 @@ */ import React, { useCallback } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; import { euiThemeVars } from '@kbn/ui-theme'; import { css } from '@emotion/react'; @@ -23,6 +23,15 @@ export const styles = { `, }; +export const ExperimentalBadge = React.memo(() => ( + +)); +ExperimentalBadge.displayName = 'ExperimentalBadge'; + interface TitleProps { title: string; description?: string; @@ -31,9 +40,16 @@ const Title = React.memo(({ title, description }) => { return ( - -

{}

-
+ + + +

{}

+
+
+ + + +
{description ? ( <> diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/index.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/index.tsx index 486c0538db8b5..ab5828f0dffa3 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/index.tsx +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/index.tsx @@ -6,7 +6,16 @@ */ import React, { useCallback } from 'react'; -import { EuiButton, EuiPageHeader, EuiSpacer } from '@elastic/eui'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiPageHeader, + EuiPageHeaderSection, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; import { useKibana } from '../../utils/kibana_react'; import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; import { EmptyPrompt } from './components/empty_prompt'; @@ -16,6 +25,7 @@ import { AlertingDeepLinkId } from '../../config'; import { MaintenanceWindowsList } from './components/maintenance_windows_list'; import { useFindMaintenanceWindows } from '../../hooks/use_find_maintenance_windows'; import { CenterJustifiedSpinner } from './components/center_justified_spinner'; +import { ExperimentalBadge } from './components/page_header'; export const MaintenanceWindowsPage = React.memo(() => { const { docLinks } = useKibana().services; @@ -37,20 +47,31 @@ export const MaintenanceWindowsPage = React.memo(() => { return ( <> - - {i18n.CREATE_NEW_BUTTON} - , - ] - : [] - } - /> + + + + + +

{i18n.MAINTENANCE_WINDOWS}

+
+
+ + + +
+ + +

{i18n.MAINTENANCE_WINDOWS_DESCRIPTION}

+
+
+ {!showEmptyPrompt ? ( + + + {i18n.CREATE_NEW_BUTTON} + + + ) : null} +
{showEmptyPrompt ? ( ) : ( diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts index ee02c5c2a9528..7dcb388f84967 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts @@ -446,3 +446,18 @@ export const SAVE_MAINTENANCE_WINDOW = i18n.translate( defaultMessage: 'Save maintenance window', } ); + +export const EXPERIMENTAL_LABEL = i18n.translate( + 'xpack.alerting.maintenanceWindows.badge.experimentalLabel', + { + defaultMessage: 'Technical preview', + } +); + +export const EXPERIMENTAL_DESCRIPTION = i18n.translate( + 'xpack.alerting.maintenanceWindows.badge.experimentalDescription', + { + defaultMessage: + 'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will take a best effort approach to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.', + } +); From a33b6d07319201e25c0485c1468a805d24894ac6 Mon Sep 17 00:00:00 2001 From: Jordan <51442161+JordanSh@users.noreply.github.com> Date: Thu, 20 Apr 2023 20:59:33 +0300 Subject: [PATCH 18/67] [Cloud Security] Fix broken dashboard UI and ComplianceScoreBar (#155428) --- .../components/compliance_score_bar.tsx | 17 +++++--- .../compliance_charts/risks_table.tsx | 4 +- .../dashboard_sections/benchmarks_section.tsx | 42 +++++++++++-------- .../findings_by_resource_table.tsx | 18 ++------ 4 files changed, 42 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/public/components/compliance_score_bar.tsx b/x-pack/plugins/cloud_security_posture/public/components/compliance_score_bar.tsx index c598faf19ba78..d70b43f810d8a 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/compliance_score_bar.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/compliance_score_bar.tsx @@ -6,11 +6,15 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { calculatePostureScore } from '../../common/utils/helpers'; import { statusColors } from '../common/constants'; +/** + * This component will take 100% of the width set by the parent + * */ export const ComplianceScoreBar = ({ totalPassed, totalFailed, @@ -23,7 +27,12 @@ export const ComplianceScoreBar = ({ return ( - - navToFailedFindingsByClusterAndSection(cluster, resourceTypeName) - } - viewAllButtonTitle={i18n.translate( - 'xpack.csp.dashboard.risksTable.clusterCardViewAllButtonTitle', - { - defaultMessage: 'View all failed findings for this {postureAsset}', - values: { - postureAsset: - dashboardType === CSPM_POLICY_TEMPLATE ? 'cloud account' : 'cluster', - }, +
+ + navToFailedFindingsByClusterAndSection(cluster, resourceTypeName) } - )} - onViewAllClick={() => navToFailedFindingsByCluster(cluster)} - /> + viewAllButtonTitle={i18n.translate( + 'xpack.csp.dashboard.risksTable.clusterCardViewAllButtonTitle', + { + defaultMessage: 'View all failed findings for this {postureAsset}', + values: { + postureAsset: + dashboardType === CSPM_POLICY_TEMPLATE ? 'cloud account' : 'cluster', + }, + } + )} + onViewAllClick={() => navToFailedFindingsByCluster(cluster)} + /> +
))} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/findings_by_resource_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/findings_by_resource_table.tsx index 2d11624317e47..ce3f55e03417d 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/findings_by_resource_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings_by_resource/findings_by_resource_table.tsx @@ -17,7 +17,6 @@ import { FormattedMessage } from '@kbn/i18n-react'; import numeral from '@elastic/numeral'; import { generatePath, Link } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; -import { css } from '@emotion/react'; import { ColumnNameWithTooltip } from '../../../components/column_name_with_tooltip'; import { ComplianceScoreBar } from '../../../components/compliance_score_bar'; import * as TEST_SUBJECTS from '../test_subjects'; @@ -179,19 +178,10 @@ const baseColumns: Array> = /> ), render: (complianceScore: FindingsByResourcePage['compliance_score'], data) => ( -
- -
+ ), dataType: 'number', }, From 1fe26f3fba1c73405c642399ffa4a57b5e9bc233 Mon Sep 17 00:00:00 2001 From: "Quynh Nguyen (Quinn)" <43350163+qn895@users.noreply.github.com> Date: Thu, 20 Apr 2023 13:07:42 -0500 Subject: [PATCH 19/67] [ML] Adds secondary authorization header to Transforms in Fleet (#154665) ## Summary The PR updates how credentials are created and managed for packages including Transforms. Previously, everything will be installed as `kibana_system` user, which has limited permissions to a specific set of indices defined internally. This PR changes so that a secondary authorization is passed to the creation of Transforms, making the permissions/privileges dependent on the logged-in user. ### Installing a package containing transforms - If the package has transforms assets to be installed, it will show warning/info call out message indicating that the transforms will be created and started with the current user's credentials/roles. Screen Shot 2023-04-11 at 17 45 58 Screen Shot 2023-04-11 at 17 46 03 -It will parse the authorization header (schema and credentials) from the Kibana request to the package handlers. - If the package contains transforms, and if **run_as_kibana_system: false in the any of the transform yml config** , then generate an API key from the above credential (as that Kibana user with the roles and permissions at the time of generation), and use it in `transform/_put` requests. - If user has **sufficient permissions**: - Transforms will be successfully created and started. They will be marked in the saved object reference with `deferred: false` - Transform `_meta` will have `installed_by: {username}` Screen Shot 2023-04-11 at 14 11 43 - Package will be successfully installed - If user has **insufficient permissions**: - Transforms will be successfully created, but fail to start. They will be marked in the saved object reference with `deferred: true` - Package will still be successfully installed. It will show warning that the package has some deferred installations. ### Deferred installations If a package has deferred installations (a.k.a assets that were included in the package, but require additional permissions to operate correctly), it will: - Show a warning on the `Installed integrations` page: Screen Shot 2023-04-06 at 15 59 46 - Show a warning badge with explanation on the tab: Screen Shot 2023-04-10 at 12 17 26 - Show a new `Deferred installations` section as well as call out message to prompt user to re-authorize inside the `Assets` tab: Screen Shot 2023-04-06 at 15 59 09 If the currently logged-in user has sufficient permissions (`manage_transform` ES cluster privilege/`transform_admin` Kibana role), the Reauthorize buttons will be enabled: Screen Shot 2023-04-10 at 12 24 18 ### Reauthorizing installations - For transforms: - Clicking the `Reauthorize` button will send an `_transform/_update` API request with a `headers: {es-secondary-authorization: 'ApiKey {encoded_api}'` and then a `_transform/_start` to start operations. - Transform `_meta` will be updated with addition of `last_authorized_by: {username}` Screen Shot 2023-04-11 at 14 12 38 - If `order` is specified in `_meta` of the transform, they will be updated and started sequentially. Else, they will be executed concurrently. ## Reviewers note: -For **kibana-core**: saved object for Fleet's EsAsset was extended with `deferred: boolean`, thus changing the hash. ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [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 - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### 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) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../group2/check_registered_types.test.ts | 2 +- x-pack/plugins/fleet/common/authz.test.ts | 29 ++ x-pack/plugins/fleet/common/authz.ts | 51 +++ .../plugins/fleet/common/constants/plugin.ts | 1 + .../plugins/fleet/common/constants/routes.ts | 2 + .../fleet/common/http_authorization_header.ts | 58 ++++ x-pack/plugins/fleet/common/mocks.ts | 8 + .../plugins/fleet/common/services/routes.ts | 6 + .../plugins/fleet/common/types/models/epm.ts | 2 + .../common/types/models/transform_api_key.ts | 19 ++ .../single_page_layout/index.test.tsx | 9 + .../single_page_layout/index.tsx | 16 + .../sections/epm/components/package_card.tsx | 30 +- .../epm/screens/detail/assets/assets.tsx | 80 +++-- .../detail/assets/assets_accordion.tsx | 24 +- .../epm/screens/detail/assets/constants.ts | 3 +- .../assets/deferred_assets_accordion.tsx | 66 ++++ .../detail/assets/deferred_assets_warning.tsx | 79 +++++ .../assets/deferred_transforms_accordion.tsx | 278 ++++++++++++++++ .../epm/screens/detail/assets/types.ts | 4 +- .../sections/epm/screens/detail/index.tsx | 23 +- .../epm/screens/detail/settings/settings.tsx | 22 +- .../sections/epm/screens/home/index.tsx | 5 + .../components/custom_assets_accordion.tsx | 6 +- ...nsform_install_as_current_user_callout.tsx | 37 +++ .../fleet/public/hooks/use_kibana_link.ts | 2 +- .../fleet/public/hooks/use_request/epm.ts | 12 + .../has_deferred_installations.test.ts | 99 ++++++ .../services/has_deferred_installations.ts | 17 + .../server/routes/agent_policy/handlers.ts | 5 + .../fleet/server/routes/epm/handlers.ts | 77 ++++- .../plugins/fleet/server/routes/epm/index.ts | 24 ++ .../server/routes/package_policy/handlers.ts | 6 + .../fleet/server/saved_objects/index.ts | 1 + .../fleet/server/services/agent_policy.ts | 16 +- .../server/services/agent_policy_create.ts | 15 +- .../services/api_keys/transform_api_keys.ts | 124 +++++++ .../elasticsearch/index/update_settings.ts | 7 +- .../epm/elasticsearch/transform/install.ts | 305 ++++++++++-------- .../elasticsearch/transform/reauthorize.ts | 174 ++++++++++ .../epm/elasticsearch/transform/remove.ts | 6 +- .../transform/transform_utils.test.ts | 54 ++++ .../transform/transform_utils.ts | 45 +++ .../transform/transforms.test.ts | 255 +++++++++------ .../services/epm/package_service.test.ts | 12 +- .../server/services/epm/package_service.ts | 24 +- .../services/epm/packages/_install_package.ts | 14 +- .../epm/packages/bulk_install_packages.ts | 5 + .../server/services/epm/packages/install.ts | 23 ++ .../fleet/server/services/package_policy.ts | 13 +- .../server/services/package_policy_service.ts | 4 + .../fleet/server/services/preconfiguration.ts | 1 - .../server/services/security/security.ts | 11 +- .../fleet/server/types/rest_spec/epm.ts | 13 + x-pack/plugins/fleet/tsconfig.json | 1 + 55 files changed, 1936 insertions(+), 289 deletions(-) create mode 100644 x-pack/plugins/fleet/common/http_authorization_header.ts create mode 100644 x-pack/plugins/fleet/common/types/models/transform_api_key.ts create mode 100644 x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/deferred_assets_accordion.tsx create mode 100644 x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/deferred_assets_warning.tsx create mode 100644 x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/deferred_transforms_accordion.tsx create mode 100644 x-pack/plugins/fleet/public/components/transform_install_as_current_user_callout.tsx create mode 100644 x-pack/plugins/fleet/public/services/has_deferred_installations.test.ts create mode 100644 x-pack/plugins/fleet/public/services/has_deferred_installations.ts create mode 100644 x-pack/plugins/fleet/server/services/api_keys/transform_api_keys.ts create mode 100644 x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/reauthorize.ts create mode 100644 x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform_utils.test.ts create mode 100644 x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform_utils.ts diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index 1da0a3c83c9bb..0adc6e5f084fd 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -84,7 +84,7 @@ describe('checking migration metadata changes on all registered SO types', () => "endpoint:user-artifact": "a5b154962fb6cdf5d9e7452e58690054c95cc72a", "endpoint:user-artifact-manifest": "5989989c0f84dd2d02da1eb46b6254e334bd2ccd", "enterprise_search_telemetry": "4b41830e3b28a16eb92dee0736b44ae6276ced9b", - "epm-packages": "83235af7c95fd9bfb1d70996a5511e05b3fcc9ef", + "epm-packages": "8755f947a00613f994b1bc5d5580e104043e27f6", "epm-packages-assets": "00c8b5e5bf059627ffc9fbde920e1ac75926c5f6", "event_loop_delays_daily": "ef49e7f15649b551b458c7ea170f3ed17f89abd0", "exception-list": "38181294f64fc406c15f20d85ca306c8a4feb3c0", diff --git a/x-pack/plugins/fleet/common/authz.test.ts b/x-pack/plugins/fleet/common/authz.test.ts index 9602fabdce7c1..22bfeb6c04cef 100644 --- a/x-pack/plugins/fleet/common/authz.test.ts +++ b/x-pack/plugins/fleet/common/authz.test.ts @@ -7,6 +7,8 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; +import { TRANSFORM_PLUGIN_ID } from './constants/plugin'; + import { calculatePackagePrivilegesFromCapabilities, calculatePackagePrivilegesFromKibanaPrivileges, @@ -39,16 +41,33 @@ describe('fleet authz', () => { writeHostIsolationExceptions: true, writeHostIsolation: false, }; + + const transformCapabilities = { + canCreateTransform: false, + canDeleteTransform: false, + canGetTransform: true, + canStartStopTransform: false, + }; + const expected = { endpoint: { actions: generateActions(ENDPOINT_PRIVILEGES, endpointCapabilities), }, + transform: { + actions: { + canCreateTransform: { executePackageAction: false }, + canDeleteTransform: { executePackageAction: false }, + canGetTransform: { executePackageAction: true }, + canStartStopTransform: { executePackageAction: false }, + }, + }, }; const actual = calculatePackagePrivilegesFromCapabilities({ navLinks: {}, management: {}, catalogue: {}, siem: endpointCapabilities, + transform: transformCapabilities, }); expect(actual).toEqual(expected); @@ -65,6 +84,8 @@ describe('fleet authz', () => { { privilege: `${SECURITY_SOLUTION_ID}-writeHostIsolationExceptions`, authorized: true }, { privilege: `${SECURITY_SOLUTION_ID}-writeHostIsolation`, authorized: false }, { privilege: `${SECURITY_SOLUTION_ID}-ignoreMe`, authorized: true }, + { privilege: `${TRANSFORM_PLUGIN_ID}-admin`, authorized: true }, + { privilege: `${TRANSFORM_PLUGIN_ID}-read`, authorized: true }, ]; const expected = { endpoint: { @@ -77,6 +98,14 @@ describe('fleet authz', () => { writeHostIsolation: false, }), }, + transform: { + actions: { + canCreateTransform: { executePackageAction: true }, + canDeleteTransform: { executePackageAction: true }, + canGetTransform: { executePackageAction: true }, + canStartStopTransform: { executePackageAction: true }, + }, + }, }; const actual = calculatePackagePrivilegesFromKibanaPrivileges(endpointPrivileges); expect(actual).toEqual(expected); diff --git a/x-pack/plugins/fleet/common/authz.ts b/x-pack/plugins/fleet/common/authz.ts index fa30f2b8f7f33..83d337c00368b 100644 --- a/x-pack/plugins/fleet/common/authz.ts +++ b/x-pack/plugins/fleet/common/authz.ts @@ -7,8 +7,16 @@ import type { Capabilities } from '@kbn/core-capabilities-common'; +import { TRANSFORM_PLUGIN_ID } from './constants/plugin'; + import { ENDPOINT_PRIVILEGES } from './constants'; +export type TransformPrivilege = + | 'canGetTransform' + | 'canCreateTransform' + | 'canDeleteTransform' + | 'canStartStopTransform'; + export interface FleetAuthz { fleet: { all: boolean; @@ -106,10 +114,22 @@ export function calculatePackagePrivilegesFromCapabilities( {} ); + const transformActions = Object.keys(capabilities.transform).reduce((acc, privilegeName) => { + return { + ...acc, + [privilegeName]: { + executePackageAction: capabilities.transform[privilegeName] || false, + }, + }; + }, {}); + return { endpoint: { actions: endpointActions, }, + transform: { + actions: transformActions, + }, }; } @@ -158,9 +178,40 @@ export function calculatePackagePrivilegesFromKibanaPrivileges( {} ); + const hasTransformAdmin = getAuthorizationFromPrivileges( + kibanaPrivileges, + `${TRANSFORM_PLUGIN_ID}-`, + `admin` + ); + const transformActions: { + [key in TransformPrivilege]: { + executePackageAction: boolean; + }; + } = { + canCreateTransform: { + executePackageAction: hasTransformAdmin, + }, + canDeleteTransform: { + executePackageAction: hasTransformAdmin, + }, + canStartStopTransform: { + executePackageAction: hasTransformAdmin, + }, + canGetTransform: { + executePackageAction: getAuthorizationFromPrivileges( + kibanaPrivileges, + `${TRANSFORM_PLUGIN_ID}-`, + `read` + ), + }, + }; + return { endpoint: { actions: endpointActions, }, + transform: { + actions: transformActions, + }, }; } diff --git a/x-pack/plugins/fleet/common/constants/plugin.ts b/x-pack/plugins/fleet/common/constants/plugin.ts index 86211ba3727eb..f9f71fb1608fa 100644 --- a/x-pack/plugins/fleet/common/constants/plugin.ts +++ b/x-pack/plugins/fleet/common/constants/plugin.ts @@ -7,3 +7,4 @@ export const PLUGIN_ID = 'fleet' as const; export const INTEGRATIONS_PLUGIN_ID = 'integrations' as const; +export const TRANSFORM_PLUGIN_ID = 'transform' as const; diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index 7eb00dd89ab0c..30e0862184a9f 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -40,6 +40,8 @@ export const EPM_API_ROUTES = { INFO_PATTERN_DEPRECATED: EPM_PACKAGES_ONE_DEPRECATED, INSTALL_FROM_REGISTRY_PATTERN_DEPRECATED: EPM_PACKAGES_ONE_DEPRECATED, DELETE_PATTERN_DEPRECATED: EPM_PACKAGES_ONE_DEPRECATED, + + REAUTHORIZE_TRANSFORMS: `${EPM_PACKAGES_ONE}/transforms/authorize`, }; // Data stream API routes diff --git a/x-pack/plugins/fleet/common/http_authorization_header.ts b/x-pack/plugins/fleet/common/http_authorization_header.ts new file mode 100644 index 0000000000000..0a209f5bc4eba --- /dev/null +++ b/x-pack/plugins/fleet/common/http_authorization_header.ts @@ -0,0 +1,58 @@ +/* + * 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 { KibanaRequest } from '@kbn/core/server'; + +// Extended version of x-pack/plugins/security/server/authentication/http_authentication/http_authorization_header.ts +// to prevent bundle being required in security_solution +export class HTTPAuthorizationHeader { + /** + * The authentication scheme. Should be consumed in a case-insensitive manner. + * https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes + */ + readonly scheme: string; + + /** + * The authentication credentials for the scheme. + */ + readonly credentials: string; + + /** + * The authentication credentials for the scheme. + */ + readonly username: string | undefined; + + constructor(scheme: string, credentials: string, username?: string) { + this.scheme = scheme; + this.credentials = credentials; + this.username = username; + } + + /** + * Parses request's `Authorization` HTTP header if present. + * @param request Request instance to extract the authorization header from. + */ + static parseFromRequest(request: KibanaRequest, username?: string) { + const authorizationHeaderValue = request.headers.authorization; + if (!authorizationHeaderValue || typeof authorizationHeaderValue !== 'string') { + return null; + } + + const [scheme] = authorizationHeaderValue.split(/\s+/); + const credentials = authorizationHeaderValue.substring(scheme.length + 1); + + return new HTTPAuthorizationHeader(scheme, credentials, username); + } + + toString() { + return `${this.scheme} ${this.credentials}`; + } + + getUsername() { + return this.username; + } +} diff --git a/x-pack/plugins/fleet/common/mocks.ts b/x-pack/plugins/fleet/common/mocks.ts index eb45689a73bb6..0784a5a0b36e8 100644 --- a/x-pack/plugins/fleet/common/mocks.ts +++ b/x-pack/plugins/fleet/common/mocks.ts @@ -94,6 +94,14 @@ export const createFleetAuthzMock = (): FleetAuthz => { endpoint: { actions: endpointActions, }, + transform: { + actions: { + canCreateTransform: { executePackageAction: true }, + canDeleteTransform: { executePackageAction: true }, + canGetTransform: { executePackageAction: true }, + canStartStopTransform: { executePackageAction: true }, + }, + }, }, }; }; diff --git a/x-pack/plugins/fleet/common/services/routes.ts b/x-pack/plugins/fleet/common/services/routes.ts index 310e53b616d50..70d98abdfac91 100644 --- a/x-pack/plugins/fleet/common/services/routes.ts +++ b/x-pack/plugins/fleet/common/services/routes.ts @@ -83,6 +83,12 @@ export const epmRouteService = { pkgVersion ); }, + + getReauthorizeTransformsPath: (pkgName: string, pkgVersion: string) => { + return EPM_API_ROUTES.REAUTHORIZE_TRANSFORMS.replace('{pkgName}', pkgName) + .replace('{pkgVersion}', pkgVersion) + .replace(/\/$/, ''); // trim trailing slash + }, }; export const packagePolicyRouteService = { diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 86dc732e9648e..b425eb11d9918 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -453,6 +453,7 @@ export interface IntegrationCardItem { id: string; categories: string[]; fromIntegrations?: string; + isReauthorizationRequired?: boolean; isUnverified?: boolean; isUpdateAvailable?: boolean; showLabels?: boolean; @@ -539,6 +540,7 @@ export type KibanaAssetReference = Pick & { }; export type EsAssetReference = Pick & { type: ElasticsearchAssetType; + deferred?: boolean; }; export type PackageAssetReference = Pick & { diff --git a/x-pack/plugins/fleet/common/types/models/transform_api_key.ts b/x-pack/plugins/fleet/common/types/models/transform_api_key.ts new file mode 100644 index 0000000000000..6a6a3635b4f68 --- /dev/null +++ b/x-pack/plugins/fleet/common/types/models/transform_api_key.ts @@ -0,0 +1,19 @@ +/* + * 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 { GrantAPIKeyResult } from '@kbn/security-plugin/server'; + +export interface TransformAPIKey extends GrantAPIKeyResult { + /** + * Generated encoded API key used for headers + */ + encoded: string; +} + +export interface SecondaryAuthorizationHeader { + headers?: { 'es-secondary-authorization': string | string[] }; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx index 57154ecb6b00a..e2f2ec5e908f7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx @@ -308,6 +308,15 @@ describe('when on the package policy create page', () => { fireEvent.click(renderResult.getByText(/Save and continue/).closest('button')!); }); + await waitFor( + async () => { + expect( + await renderResult.findByText(/Add Elastic Agent to your hosts/) + ).toBeInTheDocument(); + }, + { timeout: 10000 } + ); + await act(async () => { fireEvent.click( renderResult.getByText(/Add Elastic Agent to your hosts/).closest('button')! diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx index 9940f17d11492..81c1c518ccd4f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx @@ -22,6 +22,11 @@ import { } from '@elastic/eui'; import type { EuiStepProps } from '@elastic/eui/src/components/steps/step'; +import { + getNumTransformAssets, + TransformInstallWithCurrentUserPermissionCallout, +} from '../../../../../../components/transform_install_as_current_user_callout'; + import { useCancelAddPackagePolicy } from '../hooks'; import { splitPkgKey } from '../../../../../../../common/services'; @@ -266,6 +271,11 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ ] ); + const numTransformAssets = useMemo( + () => getNumTransformAssets(packageInfo?.assets), + [packageInfo?.assets] + ); + const extensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create'); const replaceDefineStepView = useUIExtension( packagePolicy.package?.name ?? '', @@ -406,6 +416,12 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ integration={integrationInfo?.name} /> )} + {numTransformAssets > 0 ? ( + <> + + + + ) : null} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx index bb5c99c9cb29b..d7db80f9b8c3f 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx @@ -7,12 +7,17 @@ import React from 'react'; import styled from 'styled-components'; -import { EuiBadge, EuiCard, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { EuiBadge, EuiCard, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiToolTip } from '@elastic/eui'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; +import { + DEFERRED_ASSETS_WARNING_LABEL, + DEFERRED_ASSETS_WARNING_MSG, +} from '../screens/detail/assets/deferred_assets_warning'; + import { CardIcon } from '../../../../../components/package_icon'; import type { IntegrationCardItem } from '../../../../../../common/types/models/epm'; @@ -39,6 +44,7 @@ export function PackageCard({ release, id, fromIntegrations, + isReauthorizationRequired, isUnverified, isUpdateAvailable, showLabels = true, @@ -74,6 +80,25 @@ export function PackageCard({ ); } + let hasDeferredInstallationsBadge: React.ReactNode | null = null; + + if (isReauthorizationRequired && showLabels) { + hasDeferredInstallationsBadge = ( + + + + + {DEFERRED_ASSETS_WARNING_LABEL} + + + + ); + } + let updateAvailableBadge: React.ReactNode | null = null; if (isUpdateAvailable && showLabels) { @@ -135,10 +160,11 @@ export function PackageCard({ } onClick={onCardClick} > - + {verifiedBadge} {updateAvailableBadge} {releaseBadge} + {hasDeferredInstallationsBadge} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx index 8d130c04bac5d..7f852ec15a5da 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx @@ -5,18 +5,20 @@ * 2.0. */ -import React, { useEffect, useState } from 'react'; +import React, { Fragment, useEffect, useState } from 'react'; import { Redirect } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer, EuiCallOut } from '@elastic/eui'; +import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; import { groupBy } from 'lodash'; import type { ResolvedSimpleSavedObject } from '@kbn/core/public'; -import { Loading, Error, ExtensionWrapper } from '../../../../../components'; +import type { EsAssetReference } from '../../../../../../../../common'; + +import { Error, ExtensionWrapper, Loading } from '../../../../../components'; import type { PackageInfo } from '../../../../../types'; -import { InstallStatus } from '../../../../../types'; +import { ElasticsearchAssetType, InstallStatus } from '../../../../../types'; import { useGetPackageInstallStatus, @@ -25,11 +27,14 @@ import { useUIExtension, } from '../../../../../hooks'; +import { DeferredAssetsSection } from './deferred_assets_accordion'; + import type { AssetSavedObject } from './types'; import { allowedAssetTypes } from './constants'; import { AssetsAccordion } from './assets_accordion'; const allowedAssetTypesLookup = new Set(allowedAssetTypes); + interface AssetsPanelProps { packageInfo: PackageInfo; } @@ -50,6 +55,8 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => { // assume assets are installed in this space until we find otherwise const [assetsInstalledInCurrentSpace, setAssetsInstalledInCurrentSpace] = useState(true); const [assetSavedObjects, setAssetsSavedObjects] = useState(); + const [deferredInstallations, setDeferredInstallations] = useState(); + const [fetchError, setFetchError] = useState(); const [isLoading, setIsLoading] = useState(true); const [hasPermissionError, setHasPermissionError] = useState(false); @@ -73,19 +80,33 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => { } = packageInfo; if ( - !packageAttributes.installed_kibana || - packageAttributes.installed_kibana.length === 0 + Array.isArray(packageAttributes.installed_es) && + packageAttributes.installed_es?.length > 0 + ) { + const deferredAssets = packageAttributes.installed_es.filter( + (asset) => asset.deferred === true + ); + setDeferredInstallations(deferredAssets); + } + + const authorizedTransforms = packageAttributes.installed_es.filter( + (asset) => asset.type === ElasticsearchAssetType.transform && !asset.deferred + ); + + if ( + authorizedTransforms.length === 0 && + (!packageAttributes.installed_kibana || packageAttributes.installed_kibana.length === 0) ) { setIsLoading(false); return; } - try { - const objectsToGet = packageAttributes.installed_kibana.map(({ id, type }) => ({ - id, - type, - })); - + const objectsToGet = [...authorizedTransforms, ...packageAttributes.installed_kibana].map( + ({ id, type }) => ({ + id, + type, + }) + ); // We don't have an API to know which SO types a user has access to, so instead we make a request for each // SO type and ignore the 403 errors const objectsByType = await Promise.all( @@ -118,7 +139,7 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => { ) ) ); - setAssetsSavedObjects(objectsByType.flat()); + setAssetsSavedObjects([...objectsByType.flat()]); } catch (e) { setFetchError(e); } finally { @@ -137,7 +158,10 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => { return ; } - let content: JSX.Element | Array; + const showDeferredInstallations = + Array.isArray(deferredInstallations) && deferredInstallations.length > 0; + + let content: JSX.Element | Array | null; if (isLoading) { content = ; } else if (fetchError) { @@ -190,7 +214,7 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => { ); } else { - content = ( + content = !showDeferredInstallations ? (

{ />

- ); + ) : null; } } else { content = [ @@ -211,10 +235,14 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => { } return ( - <> - + + - + ); }), // Ensure we add any custom assets provided via UI extension to the end of the list of other assets @@ -225,11 +253,23 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => { ) : null, ]; } + const deferredInstallationsContent = showDeferredInstallations ? ( + <> + + + + ) : null; return ( - {content} + + {deferredInstallationsContent} + {content} + ); }; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets_accordion.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets_accordion.tsx index 4a2d64fb84017..ade15c316d1fd 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets_accordion.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets_accordion.tsx @@ -5,26 +5,27 @@ * 2.0. */ -import React from 'react'; import type { FunctionComponent } from 'react'; +import { Fragment } from 'react'; +import React from 'react'; import { EuiAccordion, EuiFlexGroup, EuiFlexItem, - EuiSplitPanel, - EuiSpacer, - EuiText, - EuiLink, EuiHorizontalRule, + EuiLink, EuiNotificationBadge, + EuiSpacer, + EuiSplitPanel, + EuiText, } from '@elastic/eui'; import { AssetTitleMap } from '../../../constants'; import { getHrefToObjectInKibanaApp, useStartServices } from '../../../../../hooks'; -import { KibanaAssetType } from '../../../../../types'; +import { ElasticsearchAssetType, KibanaAssetType } from '../../../../../types'; import type { AllowedAssetType, AssetSavedObject } from './types'; @@ -60,8 +61,8 @@ export const AssetsAccordion: FunctionComponent = ({ savedObjects, type } <> - {savedObjects.map(({ id, attributes: { title, description } }, idx) => { - // Ignore custom asset views + {savedObjects.map(({ id, attributes: { title: soTitle, description } }, idx) => { + // Ignore custom asset views or if not a Kibana asset if (type === 'view') { return; } @@ -69,10 +70,11 @@ export const AssetsAccordion: FunctionComponent = ({ savedObjects, type } const pathToObjectInApp = getHrefToObjectInKibanaApp({ http, id, - type, + type: type === ElasticsearchAssetType.transform ? undefined : type, }); + const title = soTitle ?? id; return ( - <> +

@@ -93,7 +95,7 @@ export const AssetsAccordion: FunctionComponent = ({ savedObjects, type } )} {idx + 1 < savedObjects.length && } - + ); })} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/constants.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/constants.ts index d6d88f7935eb4..e4c773b445baa 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/constants.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/constants.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { KibanaAssetType } from '../../../../../types'; +import { ElasticsearchAssetType, KibanaAssetType } from '../../../../../types'; import type { AllowedAssetTypes } from './types'; @@ -13,4 +13,5 @@ export const allowedAssetTypes: AllowedAssetTypes = [ KibanaAssetType.dashboard, KibanaAssetType.search, KibanaAssetType.visualization, + ElasticsearchAssetType.transform, ]; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/deferred_assets_accordion.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/deferred_assets_accordion.tsx new file mode 100644 index 0000000000000..4a10a360f31de --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/deferred_assets_accordion.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 from 'react'; +import type { FunctionComponent } from 'react'; + +import { EuiSpacer, EuiCallOut, EuiTitle } from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +import { useAuthz } from '../../../../../../../hooks'; + +import type { EsAssetReference } from '../../../../../../../../common'; + +import type { PackageInfo } from '../../../../../types'; +import { ElasticsearchAssetType } from '../../../../../types'; + +import { getDeferredInstallationMsg } from './deferred_assets_warning'; + +import { DeferredTransformAccordion } from './deferred_transforms_accordion'; + +interface Props { + packageInfo: PackageInfo; + deferredInstallations: EsAssetReference[]; +} + +export const DeferredAssetsSection: FunctionComponent = ({ + deferredInstallations, + packageInfo, +}) => { + const authz = useAuthz(); + + const deferredTransforms = deferredInstallations.filter( + (asset) => asset.type === ElasticsearchAssetType.transform + ); + return ( + <> + +

+ +

+ + + + + + + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/deferred_assets_warning.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/deferred_assets_warning.tsx new file mode 100644 index 0000000000000..30fcab820f9b7 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/deferred_assets_warning.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiIcon, EuiToolTip } from '@elastic/eui'; + +import type { FleetAuthz } from '../../../../../../../../common'; + +import { useAuthz } from '../../../../../../../hooks'; + +export const DEFERRED_ASSETS_WARNING_LABEL = i18n.translate( + 'xpack.fleet.packageCard.reauthorizationRequiredLabel', + { + defaultMessage: 'Reauthorization required', + } +); + +export const DEFERRED_ASSETS_WARNING_MSG = i18n.translate( + 'xpack.fleet.epm.packageDetails.assets.deferredInstallationsMsg', + { + defaultMessage: + 'This package has at least one deferred installation which requires additional permissions to install and operate correctly.', + } +); + +export const getDeferredInstallationMsg = ( + numOfDeferredInstallations: number | undefined | null, + { authz }: { authz: FleetAuthz } +) => { + const canReauthorizeTransforms = + authz?.packagePrivileges?.transform?.actions?.canStartStopTransform?.executePackageAction ?? + false; + + if (!numOfDeferredInstallations) return DEFERRED_ASSETS_WARNING_MSG; + + if (canReauthorizeTransforms) { + return i18n.translate( + 'xpack.fleet.epm.packageDetails.assets.reauthorizeDeferredInstallationsMsg', + { + defaultMessage: + 'This package has {numOfDeferredInstallations, plural, one {one deferred installation} other {# deferred installations}}. Complete the installation to operate the package correctly.', + values: { numOfDeferredInstallations }, + } + ); + } + + return i18n.translate('xpack.fleet.epm.packageDetails.assets.deferredInstallationsWarning', { + defaultMessage: + 'This package has {numOfDeferredInstallations, plural, one {one deferred installation which requires} other {# deferred installations which require}} additional permissions to install and operate correctly.', + values: { numOfDeferredInstallations }, + }); +}; + +export const DeferredAssetsWarning = ({ + numOfDeferredInstallations, +}: { + numOfDeferredInstallations?: number; +}) => { + const authz = useAuthz(); + + const tooltipContent = useMemo( + () => getDeferredInstallationMsg(numOfDeferredInstallations, { authz }), + [numOfDeferredInstallations, authz] + ); + + return ( + + + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/deferred_transforms_accordion.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/deferred_transforms_accordion.tsx new file mode 100644 index 0000000000000..42b39f966e836 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/deferred_transforms_accordion.tsx @@ -0,0 +1,278 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { Fragment, useCallback, useMemo, useState } from 'react'; +import type { FunctionComponent, MouseEvent } from 'react'; + +import { + EuiAccordion, + EuiFlexGroup, + EuiFlexItem, + EuiSplitPanel, + EuiSpacer, + EuiText, + EuiHorizontalRule, + EuiNotificationBadge, + EuiButton, + EuiToolTip, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import type { ElasticsearchErrorDetails } from '@kbn/es-errors'; + +import type { EsAssetReference } from '../../../../../../../../common'; + +import { + sendRequestReauthorizeTransforms, + useAuthz, + useStartServices, +} from '../../../../../../../hooks'; + +import { AssetTitleMap } from '../../../constants'; + +import type { PackageInfo } from '../../../../../types'; +import { ElasticsearchAssetType } from '../../../../../types'; + +interface Props { + packageInfo: PackageInfo; + type: ElasticsearchAssetType.transform; + deferredInstallations: EsAssetReference[]; +} + +export const getDeferredAssetDescription = ( + assetType: string, + assetCount: number, + permissions: { canReauthorizeTransforms: boolean } +) => { + switch (assetType) { + case ElasticsearchAssetType.transform: + if (permissions.canReauthorizeTransforms) { + return i18n.translate( + 'xpack.fleet.epm.packageDetails.assets.deferredTransformReauthorizeDescription', + { + defaultMessage: + '{assetCount, plural, one {Transform was installed but requires} other {# transforms were installed but require}} additional permissions to run. Reauthorize the {assetCount, plural, one {transform} other {transforms}} to start operations.', + values: { assetCount: assetCount ?? 1 }, + } + ); + } + return i18n.translate( + 'xpack.fleet.epm.packageDetails.assets.deferredTransformRequestPermissionDescription', + { + defaultMessage: + '{assetCount, plural, one {Transform was installed but requires} other {# transforms were installed but require}} additional permissions to run. Contact your administrator to request the required privileges.', + values: { assetCount: assetCount ?? 1 }, + } + ); + default: + return i18n.translate( + 'xpack.fleet.epm.packageDetails.assets.deferredInstallationsDescription', + { + defaultMessage: 'Asset requires additional permissions.', + } + ); + } +}; + +export const DeferredTransformAccordion: FunctionComponent = ({ + packageInfo, + type, + deferredInstallations, +}) => { + const { notifications } = useStartServices(); + const [isLoading, setIsLoading] = useState(false); + const deferredTransforms = useMemo( + () => + deferredInstallations.map((i) => ({ + id: i.id, + attributes: { + title: i.id, + description: i.type, + }, + })), + [deferredInstallations] + ); + + const canReauthorizeTransforms = + useAuthz().packagePrivileges?.transform?.actions?.canStartStopTransform?.executePackageAction ?? + false; + + const authorizeTransforms = useCallback( + async (transformIds: Array<{ transformId: string }>) => { + setIsLoading(true); + notifications.toasts.addInfo( + i18n.translate('xpack.fleet.epm.packageDetails.assets.authorizeTransformsAcknowledged', { + defaultMessage: + 'Request to authorize {count, plural, one {# transform} other {# transforms}} acknowledged.', + values: { count: transformIds.length }, + }), + { toastLifeTimeMs: 500 } + ); + + try { + const reauthorizeTransformResp = await sendRequestReauthorizeTransforms( + packageInfo.name, + packageInfo.version, + transformIds + ); + if (reauthorizeTransformResp.error) { + throw reauthorizeTransformResp.error; + } + if (Array.isArray(reauthorizeTransformResp.data)) { + const error = reauthorizeTransformResp.data.find((d) => d.error)?.error; + + const cntAuthorized = reauthorizeTransformResp.data.filter((d) => d.success).length; + if (error) { + const errorBody = error.meta?.body as ElasticsearchErrorDetails; + const errorMsg = errorBody + ? `${errorBody.error?.type}: ${errorBody.error?.reason}` + : `${error.message}`; + + notifications.toasts.addError( + { name: errorMsg, message: errorMsg }, + { + title: i18n.translate( + 'xpack.fleet.epm.packageDetails.assets.authorizeTransformsUnsuccessful', + { + defaultMessage: + 'Unable to authorize {cntUnauthorized, plural, one {# transform} other {# transforms}}.', + values: { cntUnauthorized: transformIds.length - cntAuthorized }, + } + ), + toastLifeTimeMs: 1000, + } + ); + } else { + notifications.toasts.addSuccess( + i18n.translate( + 'xpack.fleet.epm.packageDetails.assets.authorizeTransformsSuccessful', + { + defaultMessage: + 'Successfully authorized {count, plural, one {# transform} other {# transforms}}.', + values: { count: cntAuthorized }, + } + ), + { toastLifeTimeMs: 1000 } + ); + } + } + } catch (e) { + if (e) { + notifications.toasts.addError(e, { + title: i18n.translate( + 'xpack.fleet.epm.packageDetails.assets.unableToAuthorizeAllTransformsError', + { + defaultMessage: 'An error occurred authorizing and starting transforms.', + } + ), + }); + } + } + setIsLoading(false); + }, + [notifications.toasts, packageInfo.name, packageInfo.version] + ); + if (deferredTransforms.length === 0) return null; + return ( + + + +

{AssetTitleMap[type]}

+
+
+ + +

{deferredTransforms.length}

+
+
+
+ } + id={type} + > + <> + + + + {getDeferredAssetDescription(type, deferredInstallations.length, { + canReauthorizeTransforms, + })}{' '} + + + + ) => { + e.preventDefault(); + authorizeTransforms(deferredTransforms.map((t) => ({ transformId: t.id }))); + }} + aria-label={getDeferredAssetDescription(type, deferredInstallations.length, { + canReauthorizeTransforms, + })} + > + {i18n.translate('xpack.fleet.epm.packageDetails.assets.reauthorizeAllButton', { + defaultMessage: 'Reauthorize all', + })} + + + + + + {deferredTransforms.map(({ id: transformId }, idx) => { + return ( + + + + + +

{transformId}

+
+
+ + + ) => { + e.preventDefault(); + authorizeTransforms([{ transformId }]); + }} + > + {i18n.translate( + 'xpack.fleet.epm.packageDetails.assets.reauthorizeButton', + { + defaultMessage: 'Reauthorize', + } + )} + + + +
+
+ {idx + 1 < deferredTransforms.length && } +
+ ); + })} +
+ + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/types.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/types.ts index 59ae6636aa8ff..03cf8be0b5c9b 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/types.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/types.ts @@ -8,13 +8,15 @@ import type { SimpleSavedObject } from '@kbn/core/public'; import type { KibanaAssetType } from '../../../../../types'; +import type { ElasticsearchAssetType } from '../../../../../types'; export type AssetSavedObject = SimpleSavedObject<{ title: string; description?: string }>; export type AllowedAssetTypes = [ KibanaAssetType.dashboard, KibanaAssetType.search, - KibanaAssetType.visualization + KibanaAssetType.visualization, + ElasticsearchAssetType.transform ]; export type AllowedAssetType = AllowedAssetTypes[number] | 'view'; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index 98e4d2c9cce52..0a5a39789a1ec 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -27,6 +27,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import semverLt from 'semver/functions/lt'; +import { getDeferredInstallationsCnt } from '../../../../../../services/has_deferred_installations'; + import { getPackageReleaseLabel, isPackagePrerelease, @@ -65,6 +67,7 @@ import { import type { WithHeaderLayoutProps } from '../../../../layouts'; import { WithHeaderLayout } from '../../../../layouts'; +import { DeferredAssetsWarning } from './assets/deferred_assets_warning'; import { useIsFirstTimeAgentUserQuery } from './hooks'; import { getInstallPkgRouteOptions } from './utils'; import { @@ -274,6 +277,11 @@ export function Detail() { ? getHref('integrations_installed') : getHref('integrations_all'); + const numOfDeferredInstallations = useMemo( + () => getDeferredInstallationsCnt(packageInfo), + [packageInfo] + ); + const headerLeftContent = useMemo( () => ( @@ -570,10 +578,16 @@ export function Detail() { tabs.push({ id: 'assets', name: ( - +
+ +   + {numOfDeferredInstallations > 0 ? ( + + ) : null} +
), isSelected: panel === 'assets', 'data-test-subj': `tab-assets`, @@ -645,6 +659,7 @@ export function Detail() { getHref, integration, canReadIntegrationPolicies, + numOfDeferredInstallations, isInstalled, CustomAssets, canReadPackageSettings, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx index f3a89b4e11038..b4eb7a035fe26 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx @@ -26,6 +26,11 @@ import { i18n } from '@kbn/i18n'; import type { Observable } from 'rxjs'; import type { CoreTheme } from '@kbn/core/public'; +import { + getNumTransformAssets, + TransformInstallWithCurrentUserPermissionCallout, +} from '../../../../../../../components/transform_install_as_current_user_callout'; + import type { PackageInfo } from '../../../../../types'; import { InstallStatus } from '../../../../../types'; import { @@ -227,9 +232,10 @@ export const SettingsPage: React.FC = memo(({ packageInfo, theme$ }: Prop const isUpdating = installationStatus === InstallStatus.installing && installedVersion; - const numOfAssets = useMemo( - () => - Object.entries(packageInfo.assets).reduce( + const { numOfAssets, numTransformAssets } = useMemo( + () => ({ + numTransformAssets: getNumTransformAssets(packageInfo.assets), + numOfAssets: Object.entries(packageInfo.assets).reduce( (acc, [serviceName, serviceNameValue]) => acc + Object.entries(serviceNameValue).reduce( @@ -238,6 +244,7 @@ export const SettingsPage: React.FC = memo(({ packageInfo, theme$ }: Prop ), 0 ), + }), [packageInfo.assets] ); @@ -351,6 +358,15 @@ export const SettingsPage: React.FC = memo(({ packageInfo, theme$ }: Prop + + {numTransformAssets > 0 ? ( + <> + + + + ) : null}

!!c), + isReauthorizationRequired, isUnverified, isUpdateAvailable, }; diff --git a/x-pack/plugins/fleet/public/components/custom_assets_accordion.tsx b/x-pack/plugins/fleet/public/components/custom_assets_accordion.tsx index 1194cd496c1dc..7c8ac417e066d 100644 --- a/x-pack/plugins/fleet/public/components/custom_assets_accordion.tsx +++ b/x-pack/plugins/fleet/public/components/custom_assets_accordion.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { Fragment } from 'react'; import type { FunctionComponent } from 'react'; import { EuiAccordion, @@ -62,7 +62,7 @@ export const CustomAssetsAccordion: FunctionComponent {views.map((view, index) => ( - <> +

@@ -78,7 +78,7 @@ export const CustomAssetsAccordion: FunctionComponent {index + 1 < views.length && } - + ))} diff --git a/x-pack/plugins/fleet/public/components/transform_install_as_current_user_callout.tsx b/x-pack/plugins/fleet/public/components/transform_install_as_current_user_callout.tsx new file mode 100644 index 0000000000000..bc2af74ccbb96 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/transform_install_as_current_user_callout.tsx @@ -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 { EuiCallOut } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { uniqBy } from 'lodash'; + +import type { PackageInfo } from '../../common'; + +export const getNumTransformAssets = (assets?: PackageInfo['assets']) => { + if ( + !assets || + !(Array.isArray(assets.elasticsearch?.transform) && assets.elasticsearch?.transform?.length > 0) + ) { + return 0; + } + + return uniqBy(assets.elasticsearch?.transform, 'file').length; +}; +export const TransformInstallWithCurrentUserPermissionCallout: React.FunctionComponent<{ + count: number; +}> = ({ count }) => { + return ( + + + + ); +}; diff --git a/x-pack/plugins/fleet/public/hooks/use_kibana_link.ts b/x-pack/plugins/fleet/public/hooks/use_kibana_link.ts index 7ab1b17b77caa..739fd078fe4db 100644 --- a/x-pack/plugins/fleet/public/hooks/use_kibana_link.ts +++ b/x-pack/plugins/fleet/public/hooks/use_kibana_link.ts @@ -32,7 +32,7 @@ export const getHrefToObjectInKibanaApp = ({ id, http, }: { - type: KibanaAssetType; + type: KibanaAssetType | undefined; id: string; http: HttpStart; }): undefined | string => { diff --git a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts index 1a50be67099a5..f050820508d98 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts @@ -224,6 +224,18 @@ export const sendRemovePackage = (pkgName: string, pkgVersion: string, force: bo }); }; +export const sendRequestReauthorizeTransforms = ( + pkgName: string, + pkgVersion: string, + transforms: Array<{ transformId: string }> +) => { + return sendRequest({ + path: epmRouteService.getReauthorizeTransformsPath(pkgName, pkgVersion), + method: 'post', + body: { transforms }, + }); +}; + interface UpdatePackageArgs { pkgName: string; pkgVersion: string; diff --git a/x-pack/plugins/fleet/public/services/has_deferred_installations.test.ts b/x-pack/plugins/fleet/public/services/has_deferred_installations.test.ts new file mode 100644 index 0000000000000..f4f39ed7115d1 --- /dev/null +++ b/x-pack/plugins/fleet/public/services/has_deferred_installations.test.ts @@ -0,0 +1,99 @@ +/* + * 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 { EsAssetReference } from '../../common/types'; +import type { PackageInfo } from '../types'; + +import { ElasticsearchAssetType } from '../../common/types'; + +import { hasDeferredInstallations } from './has_deferred_installations'; + +import { ExperimentalFeaturesService } from '.'; + +const mockGet = jest.spyOn(ExperimentalFeaturesService, 'get'); + +const createPackage = ({ + installedEs = [], +}: { + installedEs?: EsAssetReference[]; +} = {}): PackageInfo => ({ + name: 'test-package', + description: 'Test Package', + title: 'Test Package', + version: '0.0.1', + latestVersion: '0.0.1', + release: 'experimental', + format_version: '1.0.0', + owner: { github: 'elastic/fleet' }, + policy_templates: [], + // @ts-ignore + assets: {}, + savedObject: { + id: '1234', + type: 'epm-package', + references: [], + attributes: { + installed_kibana: [], + installed_es: installedEs ?? [], + es_index_patterns: {}, + name: 'test-package', + version: '0.0.1', + install_status: 'installed', + install_version: '0.0.1', + install_started_at: new Date().toString(), + install_source: 'registry', + verification_status: 'verified', + verification_key_id: '', + }, + }, +}); + +describe('isPackageUnverified', () => { + describe('When experimental feature is disabled', () => { + beforeEach(() => { + // @ts-ignore don't want to define all experimental features here + mockGet.mockReturnValue({ + packageVerification: false, + } as ReturnType); + }); + + it('Should return false for a package with no saved object', () => { + const noSoPkg = createPackage(); + // @ts-ignore we know pkg has savedObject but ts doesn't + delete noSoPkg.savedObject; + expect(hasDeferredInstallations(noSoPkg)).toEqual(false); + }); + + it('Should return true for a package with at least one asset deferred', () => { + const pkgWithDeferredInstallations = createPackage({ + installedEs: [ + { id: '', type: ElasticsearchAssetType.ingestPipeline }, + { id: '', type: ElasticsearchAssetType.transform, deferred: true }, + ], + }); + // @ts-ignore we know pkg has savedObject but ts doesn't + expect(hasDeferredInstallations(pkgWithDeferredInstallations)).toEqual(true); + }); + + it('Should return false for a package that has no asset deferred', () => { + const pkgWithoutDeferredInstallations = createPackage({ + installedEs: [ + { id: '', type: ElasticsearchAssetType.ingestPipeline }, + { id: '', type: ElasticsearchAssetType.transform, deferred: false }, + ], + }); + expect(hasDeferredInstallations(pkgWithoutDeferredInstallations)).toEqual(false); + }); + + it('Should return false for a package that has no asset', () => { + const pkgWithoutDeferredInstallations = createPackage({ + installedEs: [], + }); + expect(hasDeferredInstallations(pkgWithoutDeferredInstallations)).toEqual(false); + }); + }); +}); diff --git a/x-pack/plugins/fleet/public/services/has_deferred_installations.ts b/x-pack/plugins/fleet/public/services/has_deferred_installations.ts new file mode 100644 index 0000000000000..8026ca0ae39a0 --- /dev/null +++ b/x-pack/plugins/fleet/public/services/has_deferred_installations.ts @@ -0,0 +1,17 @@ +/* + * 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 { PackageInfo, PackageListItem } from '../../common'; + +export const getDeferredInstallationsCnt = (pkg?: PackageInfo | PackageListItem | null): number => { + return pkg && 'savedObject' in pkg && pkg.savedObject + ? pkg.savedObject.attributes?.installed_es?.filter((d) => d.deferred).length + : 0; +}; + +export const hasDeferredInstallations = (pkg?: PackageInfo | PackageListItem | null): boolean => + getDeferredInstallationsCnt(pkg) > 0; diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts index a31ba8fa5cb70..9601052fc415c 100644 --- a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts @@ -15,6 +15,8 @@ import type { import pMap from 'p-map'; import { safeDump } from 'js-yaml'; +import { HTTPAuthorizationHeader } from '../../../common/http_authorization_header'; + import { fullAgentPolicyToYaml } from '../../../common/services'; import { appContextService, agentPolicyService } from '../../services'; import { getAgentsByKuery } from '../../services/agents'; @@ -175,6 +177,8 @@ export const createAgentPolicyHandler: FleetRequestHandler< const monitoringEnabled = request.body.monitoring_enabled; const { has_fleet_server: hasFleetServer, ...newPolicy } = request.body; const spaceId = fleetContext.spaceId; + const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request, user?.username); + try { const body: CreateAgentPolicyResponse = { item: await createAgentPolicyWithPackages({ @@ -186,6 +190,7 @@ export const createAgentPolicyHandler: FleetRequestHandler< monitoringEnabled, spaceId, user, + authorizationHeader, }), }; diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index 8bacf87284271..0f2232593fd5e 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -12,6 +12,11 @@ import mime from 'mime-types'; import semverValid from 'semver/functions/valid'; import type { ResponseHeaders, KnownHeaders, HttpResponseOptions } from '@kbn/core/server'; +import { HTTPAuthorizationHeader } from '../../../common/http_authorization_header'; + +import { generateTransformSecondaryAuthHeaders } from '../../services/api_keys/transform_api_keys'; +import { handleTransformReauthorizeAndStart } from '../../services/epm/elasticsearch/transform/reauthorize'; + import type { GetInfoResponse, InstallPackageResponse, @@ -54,12 +59,13 @@ import { } from '../../services/epm/packages'; import type { BulkInstallResponse } from '../../services/epm/packages'; import { defaultFleetErrorHandler, fleetErrorToResponseOptions, FleetError } from '../../errors'; -import { checkAllowedPackages, licenseService } from '../../services'; +import { appContextService, checkAllowedPackages, licenseService } from '../../services'; import { getArchiveEntry } from '../../services/epm/archive/cache'; import { getAsset } from '../../services/epm/archive/storage'; import { getPackageUsageStats } from '../../services/epm/packages/get'; import { updatePackage } from '../../services/epm/packages/update'; import { getGpgKeyIdOrUndefined } from '../../services/epm/packages/package_verification'; +import type { ReauthorizeTransformRequestSchema } from '../../types'; const CACHE_CONTROL_10_MINUTES_HEADER: HttpResponseOptions['headers'] = { 'cache-control': 'max-age=600', @@ -282,8 +288,12 @@ export const installPackageFromRegistryHandler: FleetRequestHandler< const fleetContext = await context.fleet; const savedObjectsClient = fleetContext.internalSoClient; const esClient = coreContext.elasticsearch.client.asInternalUser; + const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; + const { pkgName, pkgVersion } = request.params; + const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request, user?.username); + const spaceId = fleetContext.spaceId; const res = await installPackage({ installSource: 'registry', @@ -294,6 +304,7 @@ export const installPackageFromRegistryHandler: FleetRequestHandler< force: request.body?.force, ignoreConstraints: request.body?.ignore_constraints, prerelease: request.query?.prerelease, + authorizationHeader, }); if (!res.error) { @@ -334,6 +345,7 @@ export const bulkInstallPackagesFromRegistryHandler: FleetRequestHandler< const savedObjectsClient = fleetContext.internalSoClient; const esClient = coreContext.elasticsearch.client.asInternalUser; const spaceId = fleetContext.spaceId; + const bulkInstalledResponses = await bulkInstallPackages({ savedObjectsClient, esClient, @@ -361,6 +373,7 @@ export const installPackageByUploadHandler: FleetRequestHandler< body: { message: 'Requires Enterprise license' }, }); } + const coreContext = await context.core; const fleetContext = await context.fleet; const savedObjectsClient = fleetContext.internalSoClient; @@ -368,6 +381,10 @@ export const installPackageByUploadHandler: FleetRequestHandler< const contentType = request.headers['content-type'] as string; // from types it could also be string[] or undefined but this is checked later const archiveBuffer = Buffer.from(request.body); const spaceId = fleetContext.spaceId; + const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; + + const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request, user?.username); + const res = await installPackage({ installSource: 'upload', savedObjectsClient, @@ -375,6 +392,7 @@ export const installPackageByUploadHandler: FleetRequestHandler< archiveBuffer, spaceId, contentType, + authorizationHeader, }); if (!res.error) { const body: InstallPackageResponse = { @@ -432,3 +450,60 @@ export const getVerificationKeyIdHandler: FleetRequestHandler = async ( return defaultFleetErrorHandler({ error, response }); } }; + +/** + * Create transform and optionally start transform + * Note that we want to add the current user's roles/permissions to the es-secondary-auth with a API Key. + * If API Key has insufficient permissions, it should still create the transforms but not start it + * Instead of failing, we need to allow package to continue installing other assets + * and prompt for users to authorize the transforms with the appropriate permissions after package is done installing + */ +export const reauthorizeTransformsHandler: FleetRequestHandler< + TypeOf, + TypeOf, + TypeOf +> = async (context, request, response) => { + const coreContext = await context.core; + const savedObjectsClient = (await context.fleet).internalSoClient; + + const esClient = coreContext.elasticsearch.client.asInternalUser; + const { pkgName, pkgVersion } = request.params; + const { transforms } = request.body; + + let username; + try { + const user = await appContextService.getSecurity()?.authc.getCurrentUser(request); + if (user) { + username = user.username; + } + } catch (e) { + // User might not have permission to get username, or security is not enabled, and that's okay. + } + + try { + const logger = appContextService.getLogger(); + const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request, username); + const secondaryAuth = await generateTransformSecondaryAuthHeaders({ + authorizationHeader, + logger, + username, + pkgName, + pkgVersion, + }); + + const resp = await handleTransformReauthorizeAndStart({ + esClient, + savedObjectsClient, + logger, + pkgName, + pkgVersion, + transforms, + secondaryAuth, + username, + }); + + return response.ok({ body: resp }); + } catch (error) { + return defaultFleetErrorHandler({ error, response }); + } +}; diff --git a/x-pack/plugins/fleet/server/routes/epm/index.ts b/x-pack/plugins/fleet/server/routes/epm/index.ts index 5c2a34cd90551..b707a8b80e582 100644 --- a/x-pack/plugins/fleet/server/routes/epm/index.ts +++ b/x-pack/plugins/fleet/server/routes/epm/index.ts @@ -39,6 +39,7 @@ import { GetStatsRequestSchema, UpdatePackageRequestSchema, UpdatePackageRequestSchemaDeprecated, + ReauthorizeTransformRequestSchema, } from '../../types'; import { @@ -54,6 +55,7 @@ import { getStatsHandler, updatePackageHandler, getVerificationKeyIdHandler, + reauthorizeTransformsHandler, } from './handlers'; const MAX_FILE_SIZE_BYTES = 104857600; // 100MB @@ -294,4 +296,26 @@ export const registerRoutes = (router: FleetAuthzRouter) => { return resp; } ); + + // Update transforms with es-secondary-authorization headers, + // append authorized_by to transform's _meta, and start transforms + router.post( + { + path: EPM_API_ROUTES.REAUTHORIZE_TRANSFORMS, + validate: ReauthorizeTransformRequestSchema, + fleetAuthz: { + integrations: { installPackages: true }, + packagePrivileges: { + transform: { + actions: { + canStartStopTransform: { + executePackageAction: true, + }, + }, + }, + }, + }, + }, + reauthorizeTransformsHandler + ); }; diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts index 51b4056843d25..9edfad74b7c5b 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts @@ -13,6 +13,8 @@ import type { RequestHandler } from '@kbn/core/server'; import { groupBy, keyBy } from 'lodash'; +import { HTTPAuthorizationHeader } from '../../../common/http_authorization_header'; + import { populatePackagePolicyAssignedAgentsCount } from '../../services/package_policies/populate_package_policy_assigned_agents_count'; import { @@ -219,6 +221,8 @@ export const createPackagePolicyHandler: FleetRequestHandler< const esClient = coreContext.elasticsearch.client.asInternalUser; const user = appContextService.getSecurity()?.authc.getCurrentUser(request) || undefined; const { force, package: pkg, ...newPolicy } = request.body; + const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request, user?.username); + if ('output_id' in newPolicy) { // TODO Remove deprecated APIs https://github.com/elastic/kibana/issues/121485 delete newPolicy.output_id; @@ -248,6 +252,7 @@ export const createPackagePolicyHandler: FleetRequestHandler< } // Create package policy + const packagePolicy = await fleetContext.packagePolicyService.asCurrentUser.create( soClient, esClient, @@ -256,6 +261,7 @@ export const createPackagePolicyHandler: FleetRequestHandler< user, force, spaceId, + authorizationHeader, }, context, request diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 8f0db94d1d31c..763c6d6c69136 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -243,6 +243,7 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ id: { type: 'keyword' }, type: { type: 'keyword' }, version: { type: 'keyword' }, + deferred: { type: 'boolean' }, }, }, installed_kibana: { diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 96b8fd55e3b84..aade7b382a7d3 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -22,6 +22,8 @@ import type { BulkResponseItem } from '@elastic/elasticsearch/lib/api/typesWithB import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; +import type { HTTPAuthorizationHeader } from '../../common/http_authorization_header'; + import { AGENT_POLICY_SAVED_OBJECT_TYPE, AGENTS_PREFIX, @@ -209,7 +211,11 @@ class AgentPolicyService { soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, agentPolicy: NewAgentPolicy, - options: { id?: string; user?: AuthenticatedUser } = {} + options: { + id?: string; + user?: AuthenticatedUser; + authorizationHeader?: HTTPAuthorizationHeader | null; + } = {} ): Promise { // Ensure an ID is provided, so we can include it in the audit logs below if (!options.id) { @@ -444,7 +450,12 @@ class AgentPolicyService { esClient: ElasticsearchClient, id: string, agentPolicy: Partial, - options?: { user?: AuthenticatedUser; force?: boolean; spaceId?: string } + options?: { + user?: AuthenticatedUser; + force?: boolean; + spaceId?: string; + authorizationHeader?: HTTPAuthorizationHeader | null; + } ): Promise { if (agentPolicy.name) { await this.requireUniqueName(soClient, { @@ -479,6 +490,7 @@ class AgentPolicyService { esClient, packagesToInstall, spaceId: options?.spaceId || DEFAULT_SPACE_ID, + authorizationHeader: options?.authorizationHeader, }); } diff --git a/x-pack/plugins/fleet/server/services/agent_policy_create.ts b/x-pack/plugins/fleet/server/services/agent_policy_create.ts index 1f7aeb2bc985f..11cb82123a347 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy_create.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy_create.ts @@ -9,6 +9,8 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/ import type { AuthenticatedUser } from '@kbn/security-plugin/common/model'; +import type { HTTPAuthorizationHeader } from '../../common/http_authorization_header'; + import { FLEET_ELASTIC_AGENT_PACKAGE, FLEET_SERVER_PACKAGE, @@ -48,7 +50,11 @@ async function createPackagePolicy( esClient: ElasticsearchClient, agentPolicy: AgentPolicy, packageToInstall: string, - options: { spaceId: string; user: AuthenticatedUser | undefined } + options: { + spaceId: string; + user: AuthenticatedUser | undefined; + authorizationHeader?: HTTPAuthorizationHeader | null; + } ) { const newPackagePolicy = await packagePolicyService .buildPackagePolicyFromPackage(soClient, packageToInstall) @@ -71,6 +77,7 @@ async function createPackagePolicy( spaceId: options.spaceId, user: options.user, bumpRevision: false, + authorizationHeader: options.authorizationHeader, }); } @@ -83,6 +90,7 @@ interface CreateAgentPolicyParams { monitoringEnabled?: string[]; spaceId: string; user?: AuthenticatedUser; + authorizationHeader?: HTTPAuthorizationHeader | null; } export async function createAgentPolicyWithPackages({ @@ -94,6 +102,7 @@ export async function createAgentPolicyWithPackages({ monitoringEnabled, spaceId, user, + authorizationHeader, }: CreateAgentPolicyParams) { let agentPolicyId = newPolicy.id; const packagesToInstall = []; @@ -118,6 +127,7 @@ export async function createAgentPolicyWithPackages({ esClient, packagesToInstall, spaceId, + authorizationHeader, }); } @@ -126,6 +136,7 @@ export async function createAgentPolicyWithPackages({ const agentPolicy = await agentPolicyService.create(soClient, esClient, policy, { user, id: agentPolicyId, + authorizationHeader, }); // Create the fleet server package policy and add it to agent policy. @@ -133,6 +144,7 @@ export async function createAgentPolicyWithPackages({ await createPackagePolicy(soClient, esClient, agentPolicy, FLEET_SERVER_PACKAGE, { spaceId, user, + authorizationHeader, }); } @@ -141,6 +153,7 @@ export async function createAgentPolicyWithPackages({ await createPackagePolicy(soClient, esClient, agentPolicy, FLEET_SYSTEM_PACKAGE, { spaceId, user, + authorizationHeader, }); } diff --git a/x-pack/plugins/fleet/server/services/api_keys/transform_api_keys.ts b/x-pack/plugins/fleet/server/services/api_keys/transform_api_keys.ts new file mode 100644 index 0000000000000..90d873289fa88 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/api_keys/transform_api_keys.ts @@ -0,0 +1,124 @@ +/* + * 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 { CreateAPIKeyParams } from '@kbn/security-plugin/server'; +import type { FakeRawRequest, Headers } from '@kbn/core-http-server'; +import { CoreKibanaRequest } from '@kbn/core-http-router-server-internal'; + +import type { Logger } from '@kbn/logging'; + +import { appContextService } from '..'; + +import type { HTTPAuthorizationHeader } from '../../../common/http_authorization_header'; + +import type { + TransformAPIKey, + SecondaryAuthorizationHeader, +} from '../../../common/types/models/transform_api_key'; + +export function isTransformApiKey(arg: any): arg is TransformAPIKey { + return ( + arg && + arg.hasOwnProperty('api_key') && + arg.hasOwnProperty('encoded') && + typeof arg.encoded === 'string' + ); +} + +function createKibanaRequestFromAuth(authorizationHeader: HTTPAuthorizationHeader) { + const requestHeaders: Headers = { + authorization: authorizationHeader.toString(), + }; + const fakeRawRequest: FakeRawRequest = { + headers: requestHeaders, + path: '/', + }; + + // Since we're using API keys and accessing elasticsearch can only be done + // via a request, we're faking one with the proper authorization headers. + const fakeRequest = CoreKibanaRequest.from(fakeRawRequest); + + return fakeRequest; +} + +/** This function generates a new API based on current Kibana's user request.headers.authorization + * then formats it into a es-secondary-authorization header object + * @param authorizationHeader: + * @param createParams + */ +export async function generateTransformSecondaryAuthHeaders({ + authorizationHeader, + createParams, + logger, + username, + pkgName, + pkgVersion, +}: { + authorizationHeader: HTTPAuthorizationHeader | null | undefined; + logger: Logger; + createParams?: CreateAPIKeyParams; + username?: string; + pkgName?: string; + pkgVersion?: string; +}): Promise { + if (!authorizationHeader) { + return; + } + + const fakeKibanaRequest = createKibanaRequestFromAuth(authorizationHeader); + + const user = username ?? authorizationHeader.getUsername(); + + const name = pkgName + ? `${pkgName}${pkgVersion ? '-' + pkgVersion : ''}-transform${user ? '-by-' + user : ''}` + : `fleet-transform-api-key`; + + const security = appContextService.getSecurity(); + + // If security is not enabled or available, we can't generate api key + // but that's ok, cause all the index and transform commands should work + if (!security) return; + + try { + const apiKeyWithCurrentUserPermission = await security?.authc.apiKeys.grantAsInternalUser( + fakeKibanaRequest, + createParams ?? { + name, + metadata: { + managed_by: 'fleet', + managed: true, + type: 'transform', + }, + role_descriptors: {}, + } + ); + + logger.debug(`Created api_key name: ${name}`); + let encodedApiKey: TransformAPIKey['encoded'] | null = null; + + // Property 'encoded' does exist in the resp coming back from request + // and is required to use in authentication headers + // It's just not defined in returned GrantAPIKeyResult type + if (isTransformApiKey(apiKeyWithCurrentUserPermission)) { + encodedApiKey = apiKeyWithCurrentUserPermission.encoded; + } + + const secondaryAuth = + encodedApiKey !== null + ? { + headers: { + 'es-secondary-authorization': `ApiKey ${encodedApiKey}`, + }, + } + : undefined; + + return secondaryAuth; + } catch (e) { + logger.debug(`Failed to create api_key: ${name} because ${e}`); + return undefined; + } +} diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/index/update_settings.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/index/update_settings.ts index a381f19a3d9fa..766d03f6a776c 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/index/update_settings.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/index/update_settings.ts @@ -9,6 +9,8 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { IndicesIndexSettings } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { appContextService } from '../../..'; + import { retryTransientEsErrors } from '../retry'; export async function updateIndexSettings( @@ -16,6 +18,8 @@ export async function updateIndexSettings( index: string, settings: IndicesIndexSettings ): Promise { + const logger = appContextService.getLogger(); + if (index) { try { await retryTransientEsErrors(() => @@ -25,7 +29,8 @@ export async function updateIndexSettings( }) ); } catch (err) { - throw new Error(`could not update index settings for ${index}`); + // No need to throw error and block installation process + logger.debug(`Could not update index settings for ${index} because ${err}`); } } } diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts index dc775d6f52e01..78469e7654504 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts @@ -11,6 +11,12 @@ import { safeLoad } from 'js-yaml'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { uniqBy } from 'lodash'; +import type { HTTPAuthorizationHeader } from '../../../../../common/http_authorization_header'; + +import type { SecondaryAuthorizationHeader } from '../../../../../common/types/models/transform_api_key'; + +import { generateTransformSecondaryAuthHeaders } from '../../../api_keys/transform_api_keys'; + import { PACKAGE_TEMPLATE_SUFFIX, USER_SETTINGS_TEMPLATE_SUFFIX, @@ -36,7 +42,8 @@ import { getInstallation } from '../../packages'; import { retryTransientEsErrors } from '../retry'; import { deleteTransforms } from './remove'; -import { getAsset, TRANSFORM_DEST_IDX_ALIAS_LATEST_SFX } from './common'; +import { getAsset } from './common'; +import { getDestinationIndexAliases } from './transform_utils'; const DEFAULT_TRANSFORM_TEMPLATES_PRIORITY = 250; enum TRANSFORM_SPECS_TYPES { @@ -58,6 +65,7 @@ interface TransformInstallation extends TransformModuleBase { content: any; transformVersion?: string; installationOrder?: number; + runAsKibanaSystem?: boolean; } const installLegacyTransformsAssets = async ( @@ -137,7 +145,8 @@ const processTransformAssetsPerModule = ( installablePackage: InstallablePackage, installNameSuffix: string, transformPaths: string[], - previousInstalledTransformEsAssets: EsAssetReference[] = [] + previousInstalledTransformEsAssets: EsAssetReference[] = [], + username?: string ) => { const transformsSpecifications = new Map(); const destinationIndexTemplates: DestinationIndexTemplateInstallation[] = []; @@ -195,10 +204,6 @@ const processTransformAssetsPerModule = ( const installationOrder = isFinite(content._meta?.order) && content._meta?.order >= 0 ? content._meta?.order : 0; const transformVersion = content._meta?.fleet_transform_version ?? '0.1.0'; - // The “all” alias for the transform destination indices will be adjusted to include the new transform destination index as well as everything it previously included - const allIndexAliasName = `${content.dest.index}.all`; - // The “latest” alias for the transform destination indices will point solely to the new transform destination index - const latestIndexAliasName = `${content.dest.index}.latest`; transformsSpecifications .get(transformModuleId) @@ -206,24 +211,30 @@ const processTransformAssetsPerModule = ( // Create two aliases associated with the destination index // for better handling during upgrades - const alias = { - [allIndexAliasName]: {}, - [latestIndexAliasName]: {}, - }; + const aliases = getDestinationIndexAliases(content.dest.aliases); + const aliasNames = aliases.map((a) => a.alias); + // Override yml settings with alia format for transform's dest.aliases + content.dest.aliases = aliases; - const versionedIndexName = `${content.dest.index}-${installNameSuffix}`; - content.dest.index = versionedIndexName; indicesToAddRefs.push({ - id: versionedIndexName, + id: content.dest.index, type: ElasticsearchAssetType.index, }); + + // If run_as_kibana_system is not set, or is set to true, then run as kibana_system user + // else, run with user's secondary credentials + const runAsKibanaSystem = content._meta?.run_as_kibana_system !== false; + transformsSpecifications.get(transformModuleId)?.set('destinationIndex', content.dest); - transformsSpecifications.get(transformModuleId)?.set('destinationIndexAlias', alias); + transformsSpecifications.get(transformModuleId)?.set('destinationIndexAlias', aliases); transformsSpecifications.get(transformModuleId)?.set('transform', content); transformsSpecifications.get(transformModuleId)?.set('transformVersion', transformVersion); + content._meta = { ...(content._meta ?? {}), ...getESAssetMetadata({ packageName: installablePackage.name }), + ...(username ? { installed_by: username } : {}), + run_as_kibana_system: runAsKibanaSystem, }; const installationName = getTransformAssetNameForInstallation( @@ -236,13 +247,14 @@ const processTransformAssetsPerModule = ( const currentTransformSameAsPrev = previousInstalledTransformEsAssets.find((t) => t.id === installationName) !== undefined; if (previousInstalledTransformEsAssets.length === 0) { - aliasesRefs.push(allIndexAliasName, latestIndexAliasName); + aliasesRefs.push(...aliasNames); transforms.push({ transformModuleId, installationName, installationOrder, transformVersion, content, + runAsKibanaSystem, }); transformsSpecifications.get(transformModuleId)?.set('transformVersionChanged', true); } else { @@ -277,9 +289,12 @@ const processTransformAssetsPerModule = ( installationOrder, transformVersion, content, + runAsKibanaSystem, }); transformsSpecifications.get(transformModuleId)?.set('transformVersionChanged', true); - aliasesRefs.push(allIndexAliasName, latestIndexAliasName); + if (aliasNames.length > 0) { + aliasesRefs.push(...aliasNames); + } } else { transformsSpecifications.get(transformModuleId)?.set('transformVersionChanged', false); } @@ -371,9 +386,12 @@ const installTransformsAssets = async ( savedObjectsClient: SavedObjectsClientContract, logger: Logger, esReferences: EsAssetReference[] = [], - previousInstalledTransformEsAssets: EsAssetReference[] = [] + previousInstalledTransformEsAssets: EsAssetReference[] = [], + authorizationHeader?: HTTPAuthorizationHeader | null ) => { let installedTransforms: EsAssetReference[] = []; + const username = authorizationHeader?.getUsername(); + if (transformPaths.length > 0) { const { indicesToAddRefs, @@ -383,23 +401,27 @@ const installTransformsAssets = async ( transforms, destinationIndexTemplates, transformsSpecifications, - aliasesRefs, transformsToRemove, transformsToRemoveWithDestIndex, } = processTransformAssetsPerModule( installablePackage, installNameSuffix, transformPaths, - previousInstalledTransformEsAssets + previousInstalledTransformEsAssets, + username ); - // ensure the .latest alias points to only the latest - // by removing any associate of old destination indices - await Promise.all( - aliasesRefs - .filter((a) => a.endsWith(TRANSFORM_DEST_IDX_ALIAS_LATEST_SFX)) - .map((alias) => deleteAliasFromIndices({ esClient, logger, alias })) - ); + // By default, for internal Elastic packages that touch system indices, we want to run as internal user + // so we set runAsKibanaSystem: true by default (e.g. when run_as_kibana_system set to true/not defined in yml file). + // If package should be installed as the logged in user, set run_as_kibana_system: false, + // and pass es-secondary-authorization in header when creating the transforms. + const secondaryAuth = await generateTransformSecondaryAuthHeaders({ + authorizationHeader, + logger, + pkgName: installablePackage.name, + pkgVersion: installablePackage.version, + username, + }); // delete all previous transform await Promise.all([ @@ -407,13 +429,15 @@ const installTransformsAssets = async ( esClient, transformsToRemoveWithDestIndex.map((asset) => asset.id), // Delete destination indices if specified or if from old json schema - true + true, + secondaryAuth ), deleteTransforms( esClient, transformsToRemove.map((asset) => asset.id), // Else, keep destination indices by default - false + false, + secondaryAuth ), ]); @@ -492,58 +516,6 @@ const installTransformsAssets = async ( .filter((p) => p !== undefined) ); - // create destination indices - await Promise.all( - transforms.map(async (transform) => { - const index = transform.content.dest.index; - - const aliases = transformsSpecifications - .get(transform.transformModuleId) - ?.get('destinationIndexAlias'); - try { - const resp = await retryTransientEsErrors( - () => - esClient.indices.create( - { - index, - aliases, - }, - { ignore: [400] } - ), - { logger } - ); - logger.debug(`Created destination index: ${index}`); - - // If index already exists, we still need to update the destination index alias - // to point '{destinationIndexName}.latest' to the versioned index - // @ts-ignore status is a valid field of resp - if (resp.status === 400 && aliases) { - await retryTransientEsErrors( - () => - esClient.indices.updateAliases({ - body: { - actions: Object.keys(aliases).map((alias) => ({ add: { index, alias } })), - }, - }), - { logger } - ); - logger.debug(`Created aliases for destination index: ${index}`); - } - } catch (err) { - logger.error( - `Error creating destination index: ${JSON.stringify({ - index, - aliases: transformsSpecifications - .get(transform.transformModuleId) - ?.get('destinationIndexAlias'), - })} with error ${err}` - ); - - throw new Error(err.message); - } - }) - ); - // If the transforms have specific installation order, install & optionally start transforms sequentially const shouldInstallSequentially = uniqBy(transforms, 'installationOrder').length === transforms.length; @@ -555,6 +527,7 @@ const installTransformsAssets = async ( logger, transform, startTransform: transformsSpecifications.get(transform.transformModuleId)?.get('start'), + secondaryAuth: transform.runAsKibanaSystem !== false ? undefined : secondaryAuth, }); installedTransforms.push(installTransform); } @@ -566,22 +539,42 @@ const installTransformsAssets = async ( logger, transform, startTransform: transformsSpecifications.get(transform.transformModuleId)?.get('start'), + secondaryAuth: transform.runAsKibanaSystem !== false ? undefined : secondaryAuth, }); }); installedTransforms = await Promise.all(transformsPromises).then((results) => results.flat()); } + + // If user does not have sufficient permissions to start the transforms, + // we need to mark them as deferred installations without blocking full package installation + // so that they can be updated/re-authorized later + + if (installedTransforms.length > 0) { + // get and save refs associated with the transforms before installing + esReferences = await updateEsAssetReferences( + savedObjectsClient, + installablePackage.name, + esReferences, + { + assetsToRemove: installedTransforms, + assetsToAdd: installedTransforms, + } + ); + } } return { installedTransforms, esReferences }; }; + export const installTransforms = async ( installablePackage: InstallablePackage, paths: string[], esClient: ElasticsearchClient, savedObjectsClient: SavedObjectsClientContract, logger: Logger, - esReferences?: EsAssetReference[] + esReferences?: EsAssetReference[], + authorizationHeader?: HTTPAuthorizationHeader | null ) => { const transformPaths = paths.filter((path) => isTransform(path)); @@ -628,7 +621,8 @@ export const installTransforms = async ( savedObjectsClient, logger, esReferences, - previousInstalledTransformEsAssets + previousInstalledTransformEsAssets, + authorizationHeader ); }; @@ -637,65 +631,59 @@ export const isTransform = (path: string) => { return !path.endsWith('/') && pathParts.type === ElasticsearchAssetType.transform; }; -async function deleteAliasFromIndices({ - esClient, - logger, - alias, -}: { - esClient: ElasticsearchClient; - logger: Logger; - alias: string; -}) { - try { - const resp = await esClient.indices.getAlias({ name: alias }); - const indicesMatchingAlias = Object.keys(resp); - logger.debug(`Deleting alias: '${alias}' matching indices ${indicesMatchingAlias}`); - - if (indicesMatchingAlias.length > 0) { - await retryTransientEsErrors( - () => - // defer validation on put if the source index is not available - esClient.indices.deleteAlias( - { index: indicesMatchingAlias, name: alias }, - { ignore: [404] } - ), - { logger } - ); - logger.debug(`Deleted alias: '${alias}' matching indices ${indicesMatchingAlias}`); - } - } catch (err) { - logger.error(`Error deleting alias: ${alias}`); - } +interface TransformEsAssetReference extends EsAssetReference { + version?: string; } +/** + * Create transform and optionally start transform + * Note that we want to add the current user's roles/permissions to the es-secondary-auth with a API Key. + * If API Key has insufficient permissions, it should still create the transforms but not start it + * Instead of failing, we need to allow package to continue installing other assets + * and prompt for users to authorize the transforms with the appropriate permissions after package is done installing + */ async function handleTransformInstall({ esClient, logger, transform, startTransform, + secondaryAuth, }: { esClient: ElasticsearchClient; logger: Logger; transform: TransformInstallation; startTransform?: boolean; -}): Promise { + secondaryAuth?: SecondaryAuthorizationHeader; +}): Promise { + let isUnauthorizedAPIKey = false; try { await retryTransientEsErrors( () => - // defer validation on put if the source index is not available - esClient.transform.putTransform({ - transform_id: transform.installationName, - defer_validation: true, - body: transform.content, - }), + // defer_validation: true on put if the source index is not available + // but will check if API Key has sufficient permission + esClient.transform.putTransform( + { + transform_id: transform.installationName, + defer_validation: true, + body: transform.content, + }, + // add '{ headers: { es-secondary-authorization: 'ApiKey {encodedApiKey}' } }' + secondaryAuth ? { ...secondaryAuth } : undefined + ), { logger } ); logger.debug(`Created transform: ${transform.installationName}`); } catch (err) { - // swallow the error if the transform already exists. + const isResponseError = err instanceof errors.ResponseError; + isUnauthorizedAPIKey = + isResponseError && + err?.body?.error?.type === 'security_exception' && + err?.body?.error?.reason?.includes('unauthorized for API key'); + const isAlreadyExistError = - err instanceof errors.ResponseError && - err?.body?.error?.type === 'resource_already_exists_exception'; - if (!isAlreadyExistError) { + isResponseError && err?.body?.error?.type === 'resource_already_exists_exception'; + + // swallow the error if the transform already exists or if API key has insufficient permissions + if (!isUnauthorizedAPIKey && !isAlreadyExistError) { throw err; } } @@ -703,18 +691,71 @@ async function handleTransformInstall({ // start transform by default if not set in yml file // else, respect the setting if (startTransform === undefined || startTransform === true) { - await retryTransientEsErrors( - () => - esClient.transform.startTransform( - { transform_id: transform.installationName }, - { ignore: [409] } - ), - { logger, additionalResponseStatuses: [400] } - ); - logger.debug(`Started transform: ${transform.installationName}`); + try { + await retryTransientEsErrors( + () => + esClient.transform.startTransform( + { transform_id: transform.installationName }, + { ignore: [409] } + ), + { logger, additionalResponseStatuses: [400] } + ); + logger.debug(`Started transform: ${transform.installationName}`); + } catch (err) { + const isResponseError = err instanceof errors.ResponseError; + isUnauthorizedAPIKey = + isResponseError && + // if transform was created with insufficient permission, + // _start will yield an error + err?.body?.error?.type === 'security_exception' && + err?.body?.error?.reason?.includes('lacks the required permissions'); + + // swallow the error if the transform can't be started if API key has insufficient permissions + if (!isUnauthorizedAPIKey) { + throw err; + } + } + } else { + // if transform was not set to start automatically in yml config, + // we need to check using _stats if the transform had insufficient permissions + try { + const transformStats = await retryTransientEsErrors( + () => + esClient.transform.getTransformStats( + { transform_id: transform.installationName }, + { ignore: [409] } + ), + { logger, additionalResponseStatuses: [400] } + ); + if (Array.isArray(transformStats.transforms) && transformStats.transforms.length === 1) { + // @ts-expect-error TransformGetTransformStatsTransformStats should have 'health' + const transformHealth = transformStats.transforms[0].health; + if ( + transformHealth.status === 'red' && + Array.isArray(transformHealth.issues) && + transformHealth.issues.find( + (i: { issue: string }) => i.issue === 'Privileges check failed' + ) + ) { + isUnauthorizedAPIKey = true; + } + } + } catch (err) { + logger.debug( + `Error getting transform stats for transform: ${transform.installationName} cause ${err}` + ); + } } - return { id: transform.installationName, type: ElasticsearchAssetType.transform }; + return { + id: transform.installationName, + type: ElasticsearchAssetType.transform, + // If isUnauthorizedAPIKey: true (due to insufficient user permission at transform creation) + // that means the transform is created but not started. + // Note in saved object this is a deferred installation so user can later reauthorize + deferred: isUnauthorizedAPIKey, + version: transform.transformVersion, + }; } const getLegacyTransformNameForInstallation = ( diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/reauthorize.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/reauthorize.ts new file mode 100644 index 0000000000000..50308554ce5ac --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/reauthorize.ts @@ -0,0 +1,174 @@ +/* + * 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 { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { Logger } from '@kbn/logging'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; + +import { sortBy, uniqBy } from 'lodash'; + +import type { SecondaryAuthorizationHeader } from '../../../../../common/types/models/transform_api_key'; +import { updateEsAssetReferences } from '../../packages/install'; +import type { Installation } from '../../../../../common'; +import { ElasticsearchAssetType, PACKAGES_SAVED_OBJECT_TYPE } from '../../../../../common'; + +import { retryTransientEsErrors } from '../retry'; + +interface FleetTransformMetadata { + fleet_transform_version?: string; + order?: number; + package?: { name: string }; + managed?: boolean; + managed_by?: string; + installed_by?: string; + last_authorized_by?: string; + transformId: string; +} + +async function reauthorizeAndStartTransform({ + esClient, + logger, + transformId, + secondaryAuth, + meta, +}: { + esClient: ElasticsearchClient; + logger: Logger; + transformId: string; + secondaryAuth?: SecondaryAuthorizationHeader; + shouldInstallSequentially?: boolean; + meta?: object; +}): Promise<{ transformId: string; success: boolean; error: null | any }> { + try { + await retryTransientEsErrors( + () => + esClient.transform.updateTransform( + { + transform_id: transformId, + body: { _meta: meta }, + }, + { ...(secondaryAuth ? secondaryAuth : {}) } + ), + { logger, additionalResponseStatuses: [400] } + ); + + logger.debug(`Updated transform: ${transformId}`); + } catch (err) { + logger.error(`Failed to update transform: ${transformId} because ${err}`); + return { transformId, success: false, error: err }; + } + + try { + const startedTransform = await retryTransientEsErrors( + () => esClient.transform.startTransform({ transform_id: transformId }, { ignore: [409] }), + { logger, additionalResponseStatuses: [400] } + ); + logger.debug(`Started transform: ${transformId}`); + return { transformId, success: startedTransform.acknowledged, error: null }; + } catch (err) { + logger.error(`Failed to start transform: ${transformId} because ${err}`); + return { transformId, success: false, error: err }; + } +} +export async function handleTransformReauthorizeAndStart({ + esClient, + savedObjectsClient, + logger, + pkgName, + pkgVersion, + transforms, + secondaryAuth, + username, +}: { + esClient: ElasticsearchClient; + savedObjectsClient: SavedObjectsClientContract; + logger: Logger; + transforms: Array<{ transformId: string }>; + pkgName: string; + pkgVersion?: string; + secondaryAuth?: SecondaryAuthorizationHeader; + username?: string; +}) { + if (!secondaryAuth) { + throw Error( + 'A valid secondary authorization with sufficient `manage_transform` permission is needed to re-authorize and start transforms. ' + + 'This could be because security is not enabled, or API key cannot be generated.' + ); + } + + const transformInfos = await Promise.all( + transforms.map(({ transformId }) => + retryTransientEsErrors( + () => + esClient.transform.getTransform( + { + transform_id: transformId, + }, + { ...(secondaryAuth ? secondaryAuth : {}) } + ), + { logger, additionalResponseStatuses: [400] } + ) + ) + ); + const transformsMetadata: FleetTransformMetadata[] = transformInfos.flat().map((t) => { + const transform = t.transforms?.[0]; + return { ...transform._meta, transformId: transform?.id }; + }); + + const shouldInstallSequentially = + uniqBy(transformsMetadata, 'order').length === transforms.length; + + let authorizedTransforms = []; + + if (shouldInstallSequentially) { + const sortedTransformsMetadata = sortBy(transformsMetadata, [ + (t) => t.package?.name, + (t) => t.fleet_transform_version, + (t) => t.order, + ]); + + for (const { transformId, ...meta } of sortedTransformsMetadata) { + const authorizedTransform = await reauthorizeAndStartTransform({ + esClient, + logger, + transformId, + secondaryAuth, + meta: { ...meta, last_authorized_by: username }, + }); + + authorizedTransforms.push(authorizedTransform); + } + } else { + // Else, create & start all the transforms at once for speed + const transformsPromises = transformsMetadata.map(async ({ transformId, ...meta }) => { + return await reauthorizeAndStartTransform({ + esClient, + logger, + transformId, + secondaryAuth, + meta: { ...meta, last_authorized_by: username }, + }); + }); + + authorizedTransforms = await Promise.all(transformsPromises).then((results) => results.flat()); + } + + const so = await savedObjectsClient.get(PACKAGES_SAVED_OBJECT_TYPE, pkgName); + const esReferences = so.attributes.installed_es ?? []; + + const successfullyAuthorizedTransforms = authorizedTransforms.filter((t) => t.success); + const authorizedTransformsRefs = successfullyAuthorizedTransforms.map((t) => ({ + type: ElasticsearchAssetType.transform, + id: t.transformId, + version: pkgVersion, + })); + await updateEsAssetReferences(savedObjectsClient, pkgName, esReferences, { + assetsToRemove: authorizedTransformsRefs, + assetsToAdd: authorizedTransformsRefs, + }); + return authorizedTransforms; +} diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts index 77996674f402e..5de22e050483a 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts @@ -7,6 +7,7 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; +import type { SecondaryAuthorizationHeader } from '../../../../../common/types/models/transform_api_key'; import { ElasticsearchAssetType } from '../../../../types'; import type { EsAssetReference } from '../../../../types'; import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../../../common/constants'; @@ -24,7 +25,8 @@ export const stopTransforms = async (transformIds: string[], esClient: Elasticse export const deleteTransforms = async ( esClient: ElasticsearchClient, transformIds: string[], - deleteDestinationIndices = false + deleteDestinationIndices = false, + secondaryAuth?: SecondaryAuthorizationHeader ) => { const logger = appContextService.getLogger(); if (transformIds.length) { @@ -41,7 +43,7 @@ export const deleteTransforms = async ( await stopTransforms([transformId], esClient); await esClient.transform.deleteTransform( { force: true, transform_id: transformId }, - { ignore: [404] } + { ...(secondaryAuth ? secondaryAuth : {}), ignore: [404] } ); logger.info(`Deleted: ${transformId}`); if (deleteDestinationIndices && transformResponse?.transforms) { diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform_utils.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform_utils.test.ts new file mode 100644 index 0000000000000..3449af4da8eaf --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform_utils.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getDestinationIndexAliases } from './transform_utils'; + +describe('test transform_utils', () => { + describe('getDestinationIndexAliases()', function () { + test('return transform alias settings when input is an object', () => { + const aliasSettings = { + '.alerts-security.host-risk-score-latest.latest': { move_on_creation: true }, + '.alerts-security.host-risk-score-latest.all': { move_on_creation: false }, + }; + expect(getDestinationIndexAliases(aliasSettings)).toStrictEqual([ + { alias: '.alerts-security.host-risk-score-latest.latest', move_on_creation: true }, + { alias: '.alerts-security.host-risk-score-latest.all', move_on_creation: false }, + ]); + }); + + test('return transform alias settings when input is an array', () => { + const aliasSettings = [ + '.alerts-security.host-risk-score-latest.latest', + '.alerts-security.host-risk-score-latest.all', + ]; + expect(getDestinationIndexAliases(aliasSettings)).toStrictEqual([ + { alias: '.alerts-security.host-risk-score-latest.latest', move_on_creation: true }, + { alias: '.alerts-security.host-risk-score-latest.all', move_on_creation: false }, + ]); + }); + + test('return transform alias settings when input is a string', () => { + expect( + getDestinationIndexAliases('.alerts-security.host-risk-score-latest.latest') + ).toStrictEqual([ + { alias: '.alerts-security.host-risk-score-latest.latest', move_on_creation: true }, + ]); + + expect( + getDestinationIndexAliases('.alerts-security.host-risk-score-latest.all') + ).toStrictEqual([ + { alias: '.alerts-security.host-risk-score-latest.all', move_on_creation: false }, + ]); + }); + + test('return empty array when input is invalid', () => { + expect(getDestinationIndexAliases(undefined)).toStrictEqual([]); + + expect(getDestinationIndexAliases({})).toStrictEqual([]); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform_utils.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform_utils.ts new file mode 100644 index 0000000000000..eacb8cfbf3c94 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform_utils.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 { isPopulatedObject } from '@kbn/ml-is-populated-object'; + +interface TransformAliasSetting { + alias: string; + // When move_on_creation: true, all the other indices are removed from the alias, + // ensuring that the alias points at only one index (i.e.: the destination index of the current transform). + move_on_creation?: boolean; +} + +export const getDestinationIndexAliases = (aliasSettings: unknown): TransformAliasSetting[] => { + let aliases: TransformAliasSetting[] = []; + + if (!aliasSettings) return aliases; + + // If in form of + if (isPopulatedObject(aliasSettings)) { + Object.keys(aliasSettings).forEach((alias) => { + if (aliasSettings.hasOwnProperty(alias) && typeof alias === 'string') { + const moveOnCreation = aliasSettings[alias].move_on_creation === true; + aliases.push({ alias, move_on_creation: moveOnCreation }); + } + }); + } + if (Array.isArray(aliasSettings)) { + aliases = aliasSettings.reduce((acc, alias) => { + if (typeof alias === 'string') { + acc.push({ alias, move_on_creation: alias.endsWith('.latest') ? true : false }); + } + return acc; + }, []); + } + if (typeof aliasSettings === 'string') { + aliases = [ + { alias: aliasSettings, move_on_creation: aliasSettings.endsWith('.latest') ? true : false }, + ]; + } + return aliases; +}; diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transforms.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transforms.test.ts index 77537f200628d..6180e37c4373d 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transforms.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transforms.test.ts @@ -5,25 +5,14 @@ * 2.0. */ -// eslint-disable-next-line import/order -import { createAppContextStartContractMock } from '../../../../mocks'; - -jest.mock('../../packages/get', () => { - return { getInstallation: jest.fn(), getInstallationObject: jest.fn() }; -}); - -jest.mock('./common', () => { - return { - getAsset: jest.fn(), - }; -}); - import type { SavedObject, SavedObjectsClientContract } from '@kbn/core/server'; import { loggerMock } from '@kbn/logging-mocks'; import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { HTTPAuthorizationHeader } from '../../../../../common/http_authorization_header'; + import { getInstallation, getInstallationObject } from '../../packages'; import type { Installation, RegistryPackage } from '../../../../types'; import { ElasticsearchAssetType } from '../../../../types'; @@ -33,15 +22,31 @@ import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../../constants'; import { getESAssetMetadata } from '../meta'; +import { createAppContextStartContractMock } from '../../../../mocks'; + import { installTransforms } from './install'; import { getAsset } from './common'; +jest.mock('../../packages/get', () => { + return { getInstallation: jest.fn(), getInstallationObject: jest.fn() }; +}); + +jest.mock('./common', () => { + return { + getAsset: jest.fn(), + }; +}); + const meta = getESAssetMetadata({ packageName: 'endpoint' }); describe('test transform install', () => { let esClient: ReturnType; let savedObjectsClient: jest.Mocked; + const authorizationHeader = new HTTPAuthorizationHeader( + 'Basic', + 'bW9uaXRvcmluZ191c2VyOm1scWFfYWRtaW4=' + ); const getYamlTestData = ( autoStart: boolean | undefined = undefined, transformVersion: string = '0.1.0' @@ -113,7 +118,8 @@ _meta: body: { description: 'Merges latest endpoint and Agent metadata documents.', dest: { - index: '.metrics-endpoint.metadata_united_default-0.16.0-dev.0', + index: '.metrics-endpoint.metadata_united_default', + aliases: [], }, frequency: '1s', pivot: { @@ -145,7 +151,7 @@ _meta: field: 'updated_at', }, }, - _meta: { fleet_transform_version: transformVersion, ...meta }, + _meta: { fleet_transform_version: transformVersion, ...meta, run_as_kibana_system: true }, }, }, }; @@ -336,7 +342,7 @@ _meta: 'logs-endpoint.metadata_current-template@package', 'logs-endpoint.metadata_current-template@custom', ], - index_patterns: ['.metrics-endpoint.metadata_united_default-0.16.0-dev.0'], + index_patterns: ['.metrics-endpoint.metadata_united_default'], priority: 250, template: { mappings: undefined, settings: undefined }, }, @@ -346,19 +352,8 @@ _meta: ], ]); - // Destination index is created before transform is created - expect(esClient.indices.create.mock.calls).toEqual([ - [ - { - aliases: { - '.metrics-endpoint.metadata_united_default.all': {}, - '.metrics-endpoint.metadata_united_default.latest': {}, - }, - index: '.metrics-endpoint.metadata_united_default-0.16.0-dev.0', - }, - { ignore: [400] }, - ], - ]); + // Destination index is not created before transform is created + expect(esClient.indices.create.mock.calls).toEqual([]); expect(esClient.transform.putTransform.mock.calls).toEqual([[expectedData.TRANSFORM]]); expect(esClient.transform.startTransform.mock.calls).toEqual([ @@ -382,7 +377,47 @@ _meta: type: ElasticsearchAssetType.ingestPipeline, }, { - id: '.metrics-endpoint.metadata_united_default-0.16.0-dev.0', + id: '.metrics-endpoint.metadata_united_default', + type: ElasticsearchAssetType.index, + }, + { + id: 'logs-endpoint.metadata_current-template', + type: ElasticsearchAssetType.indexTemplate, + version: '0.2.0', + }, + { + id: 'logs-endpoint.metadata_current-template@custom', + type: ElasticsearchAssetType.componentTemplate, + version: '0.2.0', + }, + { + id: 'logs-endpoint.metadata_current-template@package', + type: ElasticsearchAssetType.componentTemplate, + version: '0.2.0', + }, + { + id: 'logs-endpoint.metadata_current-default-0.2.0', + type: ElasticsearchAssetType.transform, + version: '0.2.0', + }, + ], + }, + { + refresh: false, + }, + ], + // After transforms are installed, es asset reference needs to be updated if they are deferred or not + [ + 'epm-packages', + 'endpoint', + { + installed_es: [ + { + id: 'metrics-endpoint.policy-0.16.0-dev.0', + type: ElasticsearchAssetType.ingestPipeline, + }, + { + id: '.metrics-endpoint.metadata_united_default', type: ElasticsearchAssetType.index, }, { @@ -401,6 +436,8 @@ _meta: version: '0.2.0', }, { + // After transforms are installed, es asset reference needs to be updated if they are deferred or not + deferred: false, id: 'logs-endpoint.metadata_current-default-0.2.0', type: ElasticsearchAssetType.transform, version: '0.2.0', @@ -588,7 +625,7 @@ _meta: 'logs-endpoint.metadata_current-template@package', 'logs-endpoint.metadata_current-template@custom', ], - index_patterns: ['.metrics-endpoint.metadata_united_default-0.16.0-dev.0'], + index_patterns: ['.metrics-endpoint.metadata_united_default'], priority: 250, template: { mappings: undefined, settings: undefined }, }, @@ -598,19 +635,8 @@ _meta: ], ]); - // Destination index is created before transform is created - expect(esClient.indices.create.mock.calls).toEqual([ - [ - { - aliases: { - '.metrics-endpoint.metadata_united_default.all': {}, - '.metrics-endpoint.metadata_united_default.latest': {}, - }, - index: '.metrics-endpoint.metadata_united_default-0.16.0-dev.0', - }, - { ignore: [400] }, - ], - ]); + // Destination index is not created before transform is created + expect(esClient.indices.create.mock.calls).toEqual([]); expect(esClient.transform.putTransform.mock.calls).toEqual([[expectedData.TRANSFORM]]); expect(esClient.transform.startTransform.mock.calls).toEqual([ @@ -634,7 +660,46 @@ _meta: type: ElasticsearchAssetType.ingestPipeline, }, { - id: '.metrics-endpoint.metadata_united_default-0.16.0-dev.0', + id: '.metrics-endpoint.metadata_united_default', + type: ElasticsearchAssetType.index, + }, + { + id: 'logs-endpoint.metadata_current-template', + type: ElasticsearchAssetType.indexTemplate, + version: '0.2.0', + }, + { + id: 'logs-endpoint.metadata_current-template@custom', + type: ElasticsearchAssetType.componentTemplate, + version: '0.2.0', + }, + { + id: 'logs-endpoint.metadata_current-template@package', + type: ElasticsearchAssetType.componentTemplate, + version: '0.2.0', + }, + { + id: 'logs-endpoint.metadata_current-default-0.2.0', + type: ElasticsearchAssetType.transform, + version: '0.2.0', + }, + ], + }, + { + refresh: false, + }, + ], + [ + 'epm-packages', + 'endpoint', + { + installed_es: [ + { + id: 'metrics-endpoint.policy-0.1.0-dev.0', + type: ElasticsearchAssetType.ingestPipeline, + }, + { + id: '.metrics-endpoint.metadata_united_default', type: ElasticsearchAssetType.index, }, { @@ -653,6 +718,8 @@ _meta: version: '0.2.0', }, { + // After transforms are installed, es asset reference needs to be updated if they are deferred or not + deferred: false, id: 'logs-endpoint.metadata_current-default-0.2.0', type: ElasticsearchAssetType.transform, version: '0.2.0', @@ -809,7 +876,7 @@ _meta: 'logs-endpoint.metadata_current-template@package', 'logs-endpoint.metadata_current-template@custom', ], - index_patterns: ['.metrics-endpoint.metadata_united_default-0.16.0-dev.0'], + index_patterns: ['.metrics-endpoint.metadata_united_default'], priority: 250, template: { mappings: undefined, settings: undefined }, }, @@ -819,19 +886,8 @@ _meta: ], ]); - // Destination index is created before transform is created - expect(esClient.indices.create.mock.calls).toEqual([ - [ - { - aliases: { - '.metrics-endpoint.metadata_united_default.all': {}, - '.metrics-endpoint.metadata_united_default.latest': {}, - }, - index: '.metrics-endpoint.metadata_united_default-0.16.0-dev.0', - }, - { ignore: [400] }, - ], - ]); + // Destination index is not created before transform is created + expect(esClient.indices.create.mock.calls).toEqual([]); expect(esClient.transform.putTransform.mock.calls).toEqual([[expectedData.TRANSFORM]]); expect(esClient.transform.startTransform.mock.calls).toEqual([ @@ -855,7 +911,46 @@ _meta: type: ElasticsearchAssetType.ingestPipeline, }, { - id: '.metrics-endpoint.metadata_united_default-0.16.0-dev.0', + id: '.metrics-endpoint.metadata_united_default', + type: ElasticsearchAssetType.index, + }, + { + id: 'logs-endpoint.metadata_current-template', + type: ElasticsearchAssetType.indexTemplate, + version: '0.2.0', + }, + { + id: 'logs-endpoint.metadata_current-template@custom', + type: ElasticsearchAssetType.componentTemplate, + version: '0.2.0', + }, + { + id: 'logs-endpoint.metadata_current-template@package', + type: ElasticsearchAssetType.componentTemplate, + version: '0.2.0', + }, + { + id: 'logs-endpoint.metadata_current-default-0.2.0', + type: ElasticsearchAssetType.transform, + version: '0.2.0', + }, + ], + }, + { + refresh: false, + }, + ], + [ + 'epm-packages', + 'endpoint', + { + installed_es: [ + { + id: 'metrics-endpoint.policy-0.16.0-dev.0', + type: ElasticsearchAssetType.ingestPipeline, + }, + { + id: '.metrics-endpoint.metadata_united_default', type: ElasticsearchAssetType.index, }, { @@ -874,6 +969,7 @@ _meta: version: '0.2.0', }, { + deferred: false, id: 'logs-endpoint.metadata_current-default-0.2.0', type: ElasticsearchAssetType.transform, version: '0.2.0', @@ -931,7 +1027,8 @@ _meta: esClient, savedObjectsClient, loggerMock.create(), - previousInstallation.installed_es + previousInstallation.installed_es, + authorizationHeader ); expect(esClient.transform.putTransform.mock.calls).toEqual([[expectedData.TRANSFORM]]); @@ -1026,43 +1123,11 @@ _meta: previousInstallation.installed_es ); - expect(esClient.indices.create.mock.calls).toEqual([ - [ - { - index: '.metrics-endpoint.metadata_united_default-0.16.0-dev.0', - aliases: { - '.metrics-endpoint.metadata_united_default.all': {}, - '.metrics-endpoint.metadata_united_default.latest': {}, - }, - }, - { ignore: [400] }, - ], - ]); + expect(esClient.indices.create.mock.calls).toEqual([]); // If downgrading to and older version, and destination index already exists // aliases should still be updated to point .latest to this index - expect(esClient.indices.updateAliases.mock.calls).toEqual([ - [ - { - body: { - actions: [ - { - add: { - index: '.metrics-endpoint.metadata_united_default-0.16.0-dev.0', - alias: '.metrics-endpoint.metadata_united_default.all', - }, - }, - { - add: { - index: '.metrics-endpoint.metadata_united_default-0.16.0-dev.0', - alias: '.metrics-endpoint.metadata_united_default.latest', - }, - }, - ], - }, - }, - ], - ]); + expect(esClient.indices.updateAliases.mock.calls).toEqual([]); expect(esClient.transform.deleteTransform.mock.calls).toEqual([ [ diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.test.ts b/x-pack/plugins/fleet/server/services/epm/package_service.test.ts index aa6f8c81111f5..7f0493d1ee66f 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.test.ts @@ -131,7 +131,17 @@ function getTest( method: mocks.packageClient.reinstallEsAssets.bind(mocks.packageClient), args: [pkg, paths], spy: jest.spyOn(epmTransformsInstall, 'installTransforms'), - spyArgs: [pkg, paths, mocks.esClient, mocks.soClient, mocks.logger], + spyArgs: [ + pkg, + paths, + mocks.esClient, + mocks.soClient, + mocks.logger, + // Undefined es references + undefined, + // Undefined secondary authorization + undefined, + ], spyResponse: { installedTransforms: [ { diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.ts b/x-pack/plugins/fleet/server/services/epm/package_service.ts index 4e820df7a99fc..02a12dd9e77aa 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.ts @@ -14,6 +14,8 @@ import type { Logger, } from '@kbn/core/server'; +import { HTTPAuthorizationHeader } from '../../../common/http_authorization_header'; + import type { PackageList } from '../../../common'; import type { @@ -96,7 +98,8 @@ export class PackageServiceImpl implements PackageService { this.internalEsClient, this.internalSoClient, this.logger, - preflightCheck + preflightCheck, + request ); } @@ -106,13 +109,23 @@ export class PackageServiceImpl implements PackageService { } class PackageClientImpl implements PackageClient { + private authorizationHeader?: HTTPAuthorizationHeader | null = undefined; + constructor( private readonly internalEsClient: ElasticsearchClient, private readonly internalSoClient: SavedObjectsClientContract, private readonly logger: Logger, - private readonly preflightCheck?: () => void | Promise + private readonly preflightCheck?: () => void | Promise, + private readonly request?: KibanaRequest ) {} + private getAuthorizationHeader() { + if (this.request) { + this.authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(this.request); + return this.authorizationHeader; + } + } + public async getInstallation(pkgName: string) { await this.#runPreflight(); return getInstallation({ @@ -127,6 +140,7 @@ class PackageClientImpl implements PackageClient { spaceId?: string; }): Promise { await this.#runPreflight(); + return ensureInstalledPackage({ ...options, esClient: this.internalEsClient, @@ -193,12 +207,16 @@ class PackageClientImpl implements PackageClient { } async #reinstallTransforms(packageInfo: InstallablePackage, paths: string[]) { + const authorizationHeader = await this.getAuthorizationHeader(); + const { installedTransforms } = await installTransforms( packageInfo, paths, this.internalEsClient, this.internalSoClient, - this.logger + this.logger, + undefined, + authorizationHeader ); return installedTransforms; } diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts index 68c981a308f82..6ea2749b0823a 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts @@ -16,6 +16,8 @@ import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import type { IAssignmentService, ITagsClient } from '@kbn/saved-objects-tagging-plugin/server'; +import type { HTTPAuthorizationHeader } from '../../../../common/http_authorization_header'; + import { getNormalizedDataStreams } from '../../../../common/services'; import { @@ -74,6 +76,7 @@ export async function _installPackage({ installSource, spaceId, verificationResult, + authorizationHeader, }: { savedObjectsClient: SavedObjectsClientContract; savedObjectsImporter: Pick; @@ -88,6 +91,7 @@ export async function _installPackage({ installSource: InstallSource; spaceId: string; verificationResult?: PackageVerificationResult; + authorizationHeader?: HTTPAuthorizationHeader | null; }): Promise { const { name: pkgName, version: pkgVersion, title: pkgTitle } = packageInfo; @@ -245,7 +249,15 @@ export async function _installPackage({ ); ({ esReferences } = await withPackageSpan('Install transforms', () => - installTransforms(packageInfo, paths, esClient, savedObjectsClient, logger, esReferences) + installTransforms( + packageInfo, + paths, + esClient, + savedObjectsClient, + logger, + esReferences, + authorizationHeader + ) )); // If this is an update or retrying an update, delete the previous version's pipelines diff --git a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts index 659d8d1a1c5db..a95560fbd9cbb 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts @@ -7,6 +7,8 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; +import type { HTTPAuthorizationHeader } from '../../../../common/http_authorization_header'; + import { appContextService } from '../../app_context'; import * as Registry from '../registry'; @@ -23,6 +25,7 @@ interface BulkInstallPackagesParams { spaceId: string; preferredSource?: 'registry' | 'bundled'; prerelease?: boolean; + authorizationHeader?: HTTPAuthorizationHeader | null; } export async function bulkInstallPackages({ @@ -32,6 +35,7 @@ export async function bulkInstallPackages({ spaceId, force, prerelease, + authorizationHeader, }: BulkInstallPackagesParams): Promise { const logger = appContextService.getLogger(); @@ -94,6 +98,7 @@ export async function bulkInstallPackages({ spaceId, force, prerelease, + authorizationHeader, }); if (installResult.error) { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index af56737f7f8b5..866a24f74ec25 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -25,6 +25,8 @@ import { uniqBy } from 'lodash'; import type { LicenseType } from '@kbn/licensing-plugin/server'; +import type { HTTPAuthorizationHeader } from '../../../../common/http_authorization_header'; + import { isPackagePrerelease, getNormalizedDataStreams } from '../../../../common/services'; import { FLEET_INSTALL_FORMAT_VERSION } from '../../../constants/fleet_es_assets'; @@ -120,6 +122,7 @@ export async function ensureInstalledPackage(options: { pkgVersion?: string; spaceId?: string; force?: boolean; + authorizationHeader?: HTTPAuthorizationHeader | null; }): Promise { const { savedObjectsClient, @@ -128,6 +131,7 @@ export async function ensureInstalledPackage(options: { pkgVersion, force = false, spaceId = DEFAULT_SPACE_ID, + authorizationHeader, } = options; // If pkgVersion isn't specified, find the latest package version @@ -152,6 +156,7 @@ export async function ensureInstalledPackage(options: { esClient, neverIgnoreVerificationError: !force, force: true, // Always force outdated packages to be installed if a later version isn't installed + authorizationHeader, }); if (installResult.error) { @@ -188,6 +193,7 @@ export async function handleInstallPackageFailure({ installedPkg, esClient, spaceId, + authorizationHeader, }: { savedObjectsClient: SavedObjectsClientContract; error: FleetError | Boom.Boom | Error; @@ -196,6 +202,7 @@ export async function handleInstallPackageFailure({ installedPkg: SavedObject | undefined; esClient: ElasticsearchClient; spaceId: string; + authorizationHeader?: HTTPAuthorizationHeader | null; }) { if (error instanceof FleetError) { return; @@ -232,6 +239,7 @@ export async function handleInstallPackageFailure({ esClient, spaceId, force: true, + authorizationHeader, }); } } catch (e) { @@ -255,6 +263,7 @@ interface InstallRegistryPackageParams { neverIgnoreVerificationError?: boolean; ignoreConstraints?: boolean; prerelease?: boolean; + authorizationHeader?: HTTPAuthorizationHeader | null; } interface InstallUploadedArchiveParams { savedObjectsClient: SavedObjectsClientContract; @@ -263,6 +272,7 @@ interface InstallUploadedArchiveParams { contentType: string; spaceId: string; version?: string; + authorizationHeader?: HTTPAuthorizationHeader | null; } function getTelemetryEvent(pkgName: string, pkgVersion: string): PackageUpdateEvent { @@ -290,6 +300,7 @@ async function installPackageFromRegistry({ pkgkey, esClient, spaceId, + authorizationHeader, force = false, ignoreConstraints = false, neverIgnoreVerificationError = false, @@ -366,6 +377,7 @@ async function installPackageFromRegistry({ packageInfo, paths, verificationResult, + authorizationHeader, }); } catch (e) { sendEvent({ @@ -400,6 +412,7 @@ async function installPackageCommon(options: { paths: string[]; verificationResult?: PackageVerificationResult; telemetryEvent?: PackageUpdateEvent; + authorizationHeader?: HTTPAuthorizationHeader | null; }): Promise { const { pkgName, @@ -414,6 +427,7 @@ async function installPackageCommon(options: { packageInfo, paths, verificationResult, + authorizationHeader, } = options; let { telemetryEvent } = options; const logger = appContextService.getLogger(); @@ -496,6 +510,7 @@ async function installPackageCommon(options: { spaceId, verificationResult, installSource, + authorizationHeader, }) .then(async (assets) => { await removeOldAssets({ @@ -519,6 +534,7 @@ async function installPackageCommon(options: { installedPkg, spaceId, esClient, + authorizationHeader, }); sendEvent({ ...telemetryEvent!, @@ -548,6 +564,7 @@ async function installPackageByUpload({ contentType, spaceId, version, + authorizationHeader, }: InstallUploadedArchiveParams): Promise { // if an error happens during getInstallType, report that we don't know let installType: InstallType = 'unknown'; @@ -595,6 +612,7 @@ async function installPackageByUpload({ force: true, // upload has implicit force packageInfo, paths, + authorizationHeader, }); } catch (e) { return { @@ -622,6 +640,8 @@ export async function installPackage(args: InstallPackageParams): Promise; diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index 06c206ee77dc8..350909d3de0a7 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -80,7 +80,6 @@ export async function ensurePreconfiguredPackagesAndPolicies( const packagesToInstall = packages.map((pkg) => pkg.version === PRECONFIGURATION_LATEST_KEYWORD ? pkg.name : pkg ); - // Preinstall packages specified in Kibana config const preconfiguredPackages = await bulkInstallPackages({ savedObjectsClient: soClient, diff --git a/x-pack/plugins/fleet/server/services/security/security.ts b/x-pack/plugins/fleet/server/services/security/security.ts index 0f72aeabfd17e..715d8d966484f 100644 --- a/x-pack/plugins/fleet/server/services/security/security.ts +++ b/x-pack/plugins/fleet/server/services/security/security.ts @@ -9,6 +9,8 @@ import { pick } from 'lodash'; import type { KibanaRequest } from '@kbn/core/server'; +import { TRANSFORM_PLUGIN_ID } from '../../../common/constants/plugin'; + import type { FleetAuthz } from '../../../common'; import { INTEGRATIONS_PLUGIN_ID } from '../../../common'; import { @@ -36,6 +38,7 @@ export function checkSuperuser(req: KibanaRequest) { const security = appContextService.getSecurity(); const user = security.authc.getCurrentUser(req); + if (!user) { return false; } @@ -79,6 +82,10 @@ export async function getAuthzFromRequest(req: KibanaRequest): Promise Date: Thu, 20 Apr 2023 14:13:51 -0400 Subject: [PATCH 20/67] feat(slo): Add overview and alerts tab on slo details page (#155413) --- .../slo_details/components/header_title.tsx | 7 -- .../slo_details/components/slo_details.tsx | 116 +++++++++++++++--- .../pages/slo_details/slo_details.test.tsx | 27 ++-- 3 files changed, 112 insertions(+), 38 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/header_title.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/header_title.tsx index 94e7bcc8acf2c..e7d19c8b7f18a 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/header_title.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/header_title.tsx @@ -9,9 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import React from 'react'; -import { useFetchActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts'; import { SloStatusBadge } from '../../../components/slo/slo_status_badge'; -import { SloActiveAlertsBadge } from '../../../components/slo/slo_status_badge/slo_active_alerts_badge'; export interface Props { slo: SLOWithSummaryResponse | undefined; @@ -21,10 +19,6 @@ export interface Props { export function HeaderTitle(props: Props) { const { isLoading, slo } = props; - const { data: activeAlerts } = useFetchActiveAlerts({ - sloIds: !!slo ? [slo.id] : [], - }); - if (isLoading) { return ; } @@ -38,7 +32,6 @@ export function HeaderTitle(props: Props) { {slo.name} - ); diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx index cb0e9d37c9d5c..97a7a2915b11a 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx @@ -5,10 +5,21 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiNotificationBadge, + EuiSpacer, + EuiTabbedContent, + EuiTabbedContentTab, +} from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; -import React from 'react'; +import React, { Fragment } from 'react'; +import { AlertConsumers } from '@kbn/rule-data-utils'; +import { i18n } from '@kbn/i18n'; +import { useFetchActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts'; +import { useKibana } from '../../../utils/kibana_react'; import { formatHistoricalData } from '../../../utils/slo/chart_data_formatter'; import { useFetchHistoricalSummary } from '../../../hooks/slo/use_fetch_historical_summary'; import { ErrorBudgetChartPanel } from './error_budget_chart_panel'; @@ -19,8 +30,18 @@ export interface Props { slo: SLOWithSummaryResponse; isAutoRefreshing: boolean; } +const ALERTS_TABLE_ID = 'xpack.observability.slo.sloDetails.alertTable'; +const OVERVIEW_TAB = 'overview'; +const ALERTS_TAB = 'alerts'; export function SloDetails({ slo, isAutoRefreshing }: Props) { + const { + triggersActionsUi: { alertsTableConfigurationRegistry, getAlertsStateTable: AlertsStateTable }, + } = useKibana().services; + + const { data: activeAlerts } = useFetchActiveAlerts({ + sloIds: [slo.id], + }); const { isLoading: historicalSummaryLoading, sloHistoricalSummaryResponse = {} } = useFetchHistoricalSummary({ sloIds: [slo.id], shouldRefetch: isAutoRefreshing }); @@ -30,23 +51,80 @@ export function SloDetails({ slo, isAutoRefreshing }: Props) { ); const historicalSliData = formatHistoricalData(sloHistoricalSummaryResponse[slo.id], 'sli_value'); + const tabs: EuiTabbedContentTab[] = [ + { + id: OVERVIEW_TAB, + name: i18n.translate('xpack.observability.slo.sloDetails.tab.overviewLabel', { + defaultMessage: 'Overview', + }), + 'data-test-subj': 'overviewTab', + content: ( + + + + + + + + + + + + + + + + + ), + }, + { + id: ALERTS_TAB, + name: i18n.translate('xpack.observability.slo.sloDetails.tab.alertsLabel', { + defaultMessage: 'Alerts', + }), + 'data-test-subj': 'alertsTab', + append: ( + + {(activeAlerts && activeAlerts[slo.id]?.count) ?? 0} + + ), + content: ( + + + + + + + + + ), + }, + ]; + return ( - - - - - - - - - - - - - + ); } diff --git a/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx b/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx index a606edc900438..ebffcefafd6e1 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx @@ -158,22 +158,19 @@ describe('SLO Details Page', () => { expect(screen.queryAllByTestId('wideChartLoading').length).toBe(0); }); - it('renders the active alerts badge', async () => { + it("renders a 'Edit' button under actions menu", async () => { const slo = buildSlo(); - useLicenseMock.mockReturnValue({ hasAtLeast: () => true }); useParamsMock.mockReturnValue(slo.id); useFetchSloDetailsMock.mockReturnValue({ isLoading: false, slo }); - useFetchActiveAlertsMock.mockReturnValue({ - isLoading: false, - data: { [slo.id]: { count: 2, ruleIds: ['rule-1', 'rule-2'] } }, - }); + useLicenseMock.mockReturnValue({ hasAtLeast: () => true }); render(); - expect(screen.getByTestId('o11ySloActiveAlertsBadge')).toBeTruthy(); + fireEvent.click(screen.getByTestId('o11yHeaderControlActionsButton')); + expect(screen.queryByTestId('sloDetailsHeaderControlPopoverEdit')).toBeTruthy(); }); - it("renders a 'Edit' button under actions menu", async () => { + it("renders a 'Create alert rule' button under actions menu", async () => { const slo = buildSlo(); useParamsMock.mockReturnValue(slo.id); useFetchSloDetailsMock.mockReturnValue({ isLoading: false, slo }); @@ -182,19 +179,25 @@ describe('SLO Details Page', () => { render(); fireEvent.click(screen.getByTestId('o11yHeaderControlActionsButton')); - expect(screen.queryByTestId('sloDetailsHeaderControlPopoverEdit')).toBeTruthy(); + expect(screen.queryByTestId('sloDetailsHeaderControlPopoverCreateRule')).toBeTruthy(); }); - it("renders a 'Create alert rule' button under actions menu", async () => { + it('renders the Overview tab by default', async () => { const slo = buildSlo(); useParamsMock.mockReturnValue(slo.id); useFetchSloDetailsMock.mockReturnValue({ isLoading: false, slo }); useLicenseMock.mockReturnValue({ hasAtLeast: () => true }); + useFetchActiveAlertsMock.mockReturnValue({ + isLoading: false, + data: { [slo.id]: { count: 2, ruleIds: ['rule-1', 'rule-2'] } }, + }); render(); - fireEvent.click(screen.getByTestId('o11yHeaderControlActionsButton')); - expect(screen.queryByTestId('sloDetailsHeaderControlPopoverCreateRule')).toBeTruthy(); + expect(screen.queryByTestId('overviewTab')).toBeTruthy(); + expect(screen.queryByTestId('overviewTab')?.getAttribute('aria-selected')).toBe('true'); + expect(screen.queryByTestId('alertsTab')).toBeTruthy(); + expect(screen.queryByTestId('alertsTab')?.getAttribute('aria-selected')).toBe('false'); }); describe('when an APM SLO is loaded', () => { From dd46350cac23a8738452c6893a981d7d33abfcc0 Mon Sep 17 00:00:00 2001 From: "Quynh Nguyen (Quinn)" <43350163+qn895@users.noreply.github.com> Date: Thu, 20 Apr 2023 13:16:18 -0500 Subject: [PATCH 21/67] [ML] Add option to Reauthorize transform in Management page (#154736) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../api_schemas/reauthorize_transforms.ts | 14 + .../privilege/has_privilege_factory.test.ts | 3 + .../common/privilege/has_privilege_factory.ts | 14 + .../transform/common/types/transform_stats.ts | 1 + .../common/utils/transform_api_key.ts | 41 +++ .../app/common/reauthorization_utils.ts | 22 ++ .../transform/public/app/hooks/use_api.ts | 16 + .../app/hooks/use_reauthorize_transform.tsx | 81 ++++++ .../components/action_reauthorize/index.ts | 10 + .../reauthorize_action_modal.tsx | 64 ++++ .../reauthorize_action_name.tsx | 84 ++++++ .../sort_transforms_to_reauthorize.test.ts | 108 +++++++ .../sort_transforms_to_reauthorize.ts | 42 +++ .../use_reauthorize_action.tsx | 76 +++++ .../transform_list/transform_list.tsx | 24 ++ .../transform_list/use_actions.test.tsx | 1 + .../components/transform_list/use_actions.tsx | 5 +- .../components/transform_list/use_columns.tsx | 44 ++- .../transform_management_section.tsx | 50 +++- x-pack/plugins/transform/server/plugin.ts | 3 +- .../transform/server/routes/api/transforms.ts | 106 ++++++- x-pack/plugins/transform/server/types.ts | 1 + .../api_integration/apis/transform/index.ts | 1 + .../apis/transform/reauthorize_transforms.ts | 274 ++++++++++++++++++ .../transform/start_reset_delete/starting.ts | 8 +- .../test/functional/services/transform/api.ts | 40 ++- .../services/transform/security_common.ts | 47 +++ 27 files changed, 1160 insertions(+), 20 deletions(-) create mode 100644 x-pack/plugins/transform/common/api_schemas/reauthorize_transforms.ts create mode 100644 x-pack/plugins/transform/common/utils/transform_api_key.ts create mode 100644 x-pack/plugins/transform/public/app/common/reauthorization_utils.ts create mode 100644 x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/index.ts create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_modal.tsx create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_name.tsx create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/sort_transforms_to_reauthorize.test.ts create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/sort_transforms_to_reauthorize.ts create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/use_reauthorize_action.tsx create mode 100644 x-pack/test/api_integration/apis/transform/reauthorize_transforms.ts diff --git a/x-pack/plugins/transform/common/api_schemas/reauthorize_transforms.ts b/x-pack/plugins/transform/common/api_schemas/reauthorize_transforms.ts new file mode 100644 index 0000000000000..463366aa06826 --- /dev/null +++ b/x-pack/plugins/transform/common/api_schemas/reauthorize_transforms.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 { TypeOf } from '@kbn/config-schema'; + +import { transformIdsSchema, CommonResponseStatusSchema } from './common'; + +export const reauthorizeTransformsRequestSchema = transformIdsSchema; +export type ReauthorizeTransformsRequestSchema = TypeOf; +export type ReauthorizeTransformsResponseSchema = CommonResponseStatusSchema; diff --git a/x-pack/plugins/transform/common/privilege/has_privilege_factory.test.ts b/x-pack/plugins/transform/common/privilege/has_privilege_factory.test.ts index c00e79220e5b0..e9fa6cd7bc6d7 100644 --- a/x-pack/plugins/transform/common/privilege/has_privilege_factory.test.ts +++ b/x-pack/plugins/transform/common/privilege/has_privilege_factory.test.ts @@ -71,6 +71,7 @@ describe('has_privilege_factory', () => { canDeleteTransform: true, canGetTransform: true, canPreviewTransform: true, + canReauthorizeTransform: true, canResetTransform: true, canScheduleNowTransform: true, canStartStopTransform: true, @@ -96,6 +97,7 @@ describe('has_privilege_factory', () => { canDeleteTransform: false, canGetTransform: true, canPreviewTransform: false, + canReauthorizeTransform: false, canResetTransform: false, canScheduleNowTransform: false, canStartStopTransform: false, @@ -124,6 +126,7 @@ describe('has_privilege_factory', () => { canGetTransform: false, canPreviewTransform: false, canResetTransform: false, + canReauthorizeTransform: false, canScheduleNowTransform: false, canStartStopTransform: false, canUseTransformAlerts: false, diff --git a/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts b/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts index f9afe59355c01..972f8e727f50d 100644 --- a/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts +++ b/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts @@ -17,6 +17,7 @@ export interface TransformCapabilities { canDeleteTransform: boolean; canPreviewTransform: boolean; canCreateTransform: boolean; + canReauthorizeTransform: boolean; canScheduleNowTransform: boolean; canStartStopTransform: boolean; canCreateTransformAlerts: boolean; @@ -30,6 +31,7 @@ export const INITIAL_CAPABILITIES = Object.freeze({ canDeleteTransform: false, canPreviewTransform: false, canCreateTransform: false, + canReauthorizeTransform: false, canScheduleNowTransform: false, canStartStopTransform: false, canCreateTransformAlerts: false, @@ -130,6 +132,8 @@ export const getPrivilegesAndCapabilities = ( capabilities.canScheduleNowTransform = capabilities.canStartStopTransform; + capabilities.canReauthorizeTransform = capabilities.canStartStopTransform; + return { privileges: privilegesResult, capabilities }; }; // create the text for button's tooltips if the user @@ -170,6 +174,16 @@ export function createCapabilityFailureMessage( } ); break; + + case 'canReauthorizeTransform': + message = i18n.translate( + 'xpack.transform.capability.noPermission.reauthorizeTransformTooltip', + { + defaultMessage: 'You do not have permission to reauthorize transforms.', + } + ); + break; + case 'canDeleteTransform': message = i18n.translate('xpack.transform.capability.noPermission.deleteTransformTooltip', { defaultMessage: 'You do not have permission to delete transforms.', diff --git a/x-pack/plugins/transform/common/types/transform_stats.ts b/x-pack/plugins/transform/common/types/transform_stats.ts index c1194ea8f1018..4134662552a90 100644 --- a/x-pack/plugins/transform/common/types/transform_stats.ts +++ b/x-pack/plugins/transform/common/types/transform_stats.ts @@ -11,6 +11,7 @@ import { type TransformHealth, type TransformState, TRANSFORM_STATE } from '../c import { TransformId } from './transform'; export interface TransformHealthIssue { + type: string; issue: string; details?: string; count: number; diff --git a/x-pack/plugins/transform/common/utils/transform_api_key.ts b/x-pack/plugins/transform/common/utils/transform_api_key.ts new file mode 100644 index 0000000000000..66187ef318ced --- /dev/null +++ b/x-pack/plugins/transform/common/utils/transform_api_key.ts @@ -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 type { GrantAPIKeyResult } from '@kbn/security-plugin/server'; +import type { SecurityCreateApiKeyResponse } from '@elastic/elasticsearch/lib/api/types'; +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; + +export interface TransformAPIKey extends GrantAPIKeyResult { + /** + * Generated encoded API key used for headers + */ + encoded: string; +} + +export interface SecondaryAuthorizationHeader { + headers?: { 'es-secondary-authorization': string | string[] }; +} + +export function isTransformApiKey(arg: any): arg is TransformAPIKey { + return isPopulatedObject(arg, ['api_key', 'encoded']) && typeof arg.encoded === 'string'; +} + +export function generateTransformSecondaryAuthHeaders( + apiKeyWithCurrentUserPermission: + | GrantAPIKeyResult + | null + | undefined + | SecurityCreateApiKeyResponse +): SecondaryAuthorizationHeader | undefined { + return isTransformApiKey(apiKeyWithCurrentUserPermission) + ? { + headers: { + 'es-secondary-authorization': `ApiKey ${apiKeyWithCurrentUserPermission.encoded}`, + }, + } + : undefined; +} diff --git a/x-pack/plugins/transform/public/app/common/reauthorization_utils.ts b/x-pack/plugins/transform/public/app/common/reauthorization_utils.ts new file mode 100644 index 0000000000000..d32383ccd683e --- /dev/null +++ b/x-pack/plugins/transform/public/app/common/reauthorization_utils.ts @@ -0,0 +1,22 @@ +/* + * 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 { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import type { TransformHealthIssue } from '../../../common/types/transform_stats'; +import { TRANSFORM_HEALTH } from '../../../common/constants'; +import type { TransformListRow } from './transform_list'; + +export const needsReauthorization = (transform: Partial) => { + return ( + isPopulatedObject(transform.config?.authorization, ['api_key']) && + isPopulatedObject(transform.stats) && + transform.stats.health.status === TRANSFORM_HEALTH.red && + transform.stats.health.issues?.find( + (issue) => (issue as TransformHealthIssue).issue === 'Privileges check failed' + ) !== undefined + ); +}; diff --git a/x-pack/plugins/transform/public/app/hooks/use_api.ts b/x-pack/plugins/transform/public/app/hooks/use_api.ts index 44df1941d8d9f..3364ed58d5af6 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_api.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_api.ts @@ -13,6 +13,10 @@ import type { IHttpFetchError } from '@kbn/core-http-browser'; import { KBN_FIELD_TYPES } from '@kbn/field-types'; +import { + ReauthorizeTransformsRequestSchema, + ReauthorizeTransformsResponseSchema, +} from '../../../common/api_schemas/reauthorize_transforms'; import type { GetTransformsAuditMessagesResponseSchema } from '../../../common/api_schemas/audit_messages'; import type { DeleteTransformsRequestSchema, @@ -166,6 +170,18 @@ export const useApi = () => { return e; } }, + async reauthorizeTransforms( + reqBody: ReauthorizeTransformsRequestSchema + ): Promise { + try { + return await http.post(`${API_BASE_PATH}reauthorize_transforms`, { + body: JSON.stringify(reqBody), + }); + } catch (e) { + return e; + } + }, + async resetTransforms( reqBody: ResetTransformsRequestSchema ): Promise { diff --git a/x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx new file mode 100644 index 0000000000000..af6018c35cecc --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.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 React from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; + +import type { StartTransformsRequestSchema } from '../../../common/api_schemas/start_transforms'; +import { isStartTransformsResponseSchema } from '../../../common/api_schemas/type_guards'; + +import { getErrorMessage } from '../../../common/utils/errors'; + +import { useAppDependencies, useToastNotifications } from '../app_dependencies'; +import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common'; +import { ToastNotificationText } from '../components'; + +import { useApi } from './use_api'; + +export const useReauthorizeTransforms = () => { + const { overlays, theme } = useAppDependencies(); + const toastNotifications = useToastNotifications(); + const api = useApi(); + + return async (transformsInfo: StartTransformsRequestSchema) => { + const results = await api.reauthorizeTransforms(transformsInfo); + + if (!isStartTransformsResponseSchema(results)) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.transform.stepCreateForm.reauthorizeTransformResponseSchemaErrorMessage', + { + defaultMessage: 'An error occurred calling the reauthorize transforms request.', + } + ), + text: toMountPoint( + , + { theme$: theme.theme$ } + ), + }); + return; + } + + for (const transformId in results) { + // hasOwnProperty check to ensure only properties on object itself, and not its prototypes + if (results.hasOwnProperty(transformId)) { + const result = results[transformId]; + if (result.success === true) { + toastNotifications.addSuccess( + i18n.translate('xpack.transform.transformList.reauthorizeTransformSuccessMessage', { + defaultMessage: 'Request to reauthorize transform {transformId} acknowledged.', + values: { transformId }, + }) + ); + } else { + toastNotifications.addError(new Error(JSON.stringify(result.error!.caused_by, null, 2)), { + title: i18n.translate( + 'xpack.transform.transformList.reauthorizeTransformErrorMessage', + { + defaultMessage: 'An error occurred reauthorizing the transform {transformId}', + values: { transformId }, + } + ), + toastMessage: result.error!.reason, + }); + } + } + } + + refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); + }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/index.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/index.ts new file mode 100644 index 0000000000000..bbbe18d9f6acf --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/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 { useReauthorizeAction } from './use_reauthorize_action'; +export { ReauthorizeActionModal } from './reauthorize_action_modal'; +export { isReauthorizeActionDisabled, ReauthorizeActionName } from './reauthorize_action_name'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_modal.tsx new file mode 100644 index 0000000000000..9e848fdd8323e --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_modal.tsx @@ -0,0 +1,64 @@ +/* + * 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, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EUI_MODAL_CONFIRM_BUTTON, EuiConfirmModal } from '@elastic/eui'; +import type { ReauthorizeAction } from './use_reauthorize_action'; + +export const ReauthorizeActionModal: FC = ({ + closeModal, + items, + reauthorizeAndCloseModal, +}) => { + const isBulkAction = items.length > 1; + + const bulkReauthorizeModalTitle = i18n.translate( + 'xpack.transform.transformList.bulkReauthorizeModalTitle', + { + defaultMessage: 'Reauthorize {count} {count, plural, one {transform} other {transforms}}?', + values: { count: items && items.length }, + } + ); + const reauthorizeModalTitle = i18n.translate( + 'xpack.transform.transformList.reauthorizeModalTitle', + { + defaultMessage: 'Reauthorize {transformId}?', + values: { transformId: items[0] && items[0].config.id }, + } + ); + + return ( + +

+ {i18n.translate('xpack.transform.transformList.reauthorizeModalBody', { + defaultMessage: + 'Your current roles are used to update and start the transform. Starting a transform increases search and indexing load in your cluster. If excessive load is experienced, stop the transform.', + })} +

+ + ); +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_name.tsx new file mode 100644 index 0000000000000..e07ae03ec46b0 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/reauthorize_action_name.tsx @@ -0,0 +1,84 @@ +/* + * 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, { FC, useContext } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiToolTip, EuiText } from '@elastic/eui'; +import { needsReauthorization } from '../../../../common/reauthorization_utils'; +import { + AuthorizationContext, + createCapabilityFailureMessage, +} from '../../../../lib/authorization'; +import { TransformListRow } from '../../../../common'; + +export const reauthorizeActionNameText = i18n.translate( + 'xpack.transform.transformList.reauthorizeActionNameText', + { + defaultMessage: 'Reauthorize', + } +); + +export const isReauthorizeActionDisabled = ( + items: TransformListRow[], + canStartStopTransform: boolean, + transformNodes: number +) => { + return ( + !canStartStopTransform || + items.length === 0 || + transformNodes === 0 || + !items.some(needsReauthorization) + ); +}; + +export interface ReauthorizeActionNameProps { + items: TransformListRow[]; + forceDisable?: boolean; + transformNodes: number; +} +export const ReauthorizeActionName: FC = ({ + items, + forceDisable, + transformNodes, +}) => { + const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + + // Disable start for batch transforms which have completed. + const someNeedsReauthorization = items.some(needsReauthorization); + + const actionIsDisabled = isReauthorizeActionDisabled( + items, + canStartStopTransform, + transformNodes + ); + + let content: string | undefined; + if (actionIsDisabled && items.length > 0) { + if (!canStartStopTransform && someNeedsReauthorization) { + content = createCapabilityFailureMessage('canReauthorizeTransform'); + } + if (!someNeedsReauthorization) { + content = i18n.translate( + 'xpack.transform.transformList.reauthorizeBulkActionDisabledToolTipContent', + { + defaultMessage: 'One or more selected transforms must require reauthorization.', + } + ); + } + } + + const text = {reauthorizeActionNameText}; + if ((forceDisable === true || actionIsDisabled) && content !== undefined) { + return ( + + {text} + + ); + } + + return <>{text}; +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/sort_transforms_to_reauthorize.test.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/sort_transforms_to_reauthorize.test.ts new file mode 100644 index 0000000000000..3c0d1b7ed7bde --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/sort_transforms_to_reauthorize.test.ts @@ -0,0 +1,108 @@ +/* + * 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 { sortTransformsToReauthorize } from './sort_transforms_to_reauthorize'; + +describe('sortTransformsToReauthorize', () => { + test('should work with multiple transforms with or without config or _meta defined', () => { + const transforms = [ + { + id: 'transform0', + config: {}, + }, + { + id: 'transformA-2', + config: { + _meta: { + run_as_kibana_system: false, + managed_by: 'fleet', + managed: true, + order: 2, + package: { + name: 'packageA', + }, + installed_by: 'transform_user', + }, + }, + }, + { + id: 'transformA-0', + config: { + _meta: { + run_as_kibana_system: false, + managed_by: 'fleet', + managed: true, + package: { + name: 'packageA', + }, + installed_by: 'transform_user', + }, + }, + }, + { + id: 'transformB-2', + config: { + _meta: { + run_as_kibana_system: false, + managed_by: 'fleet', + managed: true, + order: 2, + package: { + name: 'packageB', + }, + installed_by: 'transform_user', + }, + }, + }, + { + id: 'transformB-1', + config: { + _meta: { + run_as_kibana_system: false, + managed_by: 'fleet', + managed: true, + order: 1, + package: { + name: 'packageB', + }, + installed_by: 'transform_user', + }, + }, + }, + ]; + // @ts-ignore transforms is partial of TransformListRow + const { transformIds, shouldInstallSequentially } = sortTransformsToReauthorize(transforms); + expect(transformIds.map((t) => t.id)).toEqual([ + 'transform0', + 'transformA-0', + 'transformA-2', + 'transformB-1', + 'transformB-2', + ]); + expect(shouldInstallSequentially).toEqual(true); + }); + + test('should return shouldInstallSequentially: false if none of the transforms have order specified', () => { + const transforms = [ + { + id: 'transform3', + config: {}, + }, + { + id: 'transform2', + config: {}, + }, + { + id: 'transform1', + config: {}, + }, + ]; + // @ts-ignore transforms is partial of TransformListRow + const { transformIds, shouldInstallSequentially } = sortTransformsToReauthorize(transforms); + expect(transformIds.map((t) => t.id)).toEqual(['transform3', 'transform2', 'transform1']); + expect(shouldInstallSequentially).toEqual(false); + }); +}); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/sort_transforms_to_reauthorize.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/sort_transforms_to_reauthorize.ts new file mode 100644 index 0000000000000..00803929d2952 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/sort_transforms_to_reauthorize.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 { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { sortBy } from 'lodash'; +import { TransformListRow } from '../../../../common'; + +// For transforms, some might be part of a integration package +// A transform might be dependent on the results of another transform +// If transforms have _meta.order specified, install sequentially (sort by package and install order) +export const sortTransformsToReauthorize = (transforms: TransformListRow[]) => { + let shouldInstallSequentially = false; + const mappedTransforms = transforms.map((t) => { + let packageName = ''; + let order = Number.MIN_VALUE; + if (isPopulatedObject(t?.config?._meta, ['order'])) { + if (typeof t.config._meta.order === 'number') { + shouldInstallSequentially = true; + order = t?.config?._meta.order; + } + if ( + isPopulatedObject(t.config._meta.package, ['name']) && + typeof t.config._meta.package.name === 'string' + ) { + packageName = t.config._meta.package.name; + } + } + + return { id: t.id, order, packageName }; + }); + + return { + transformIds: shouldInstallSequentially + ? sortBy(mappedTransforms, ['packageName', 'order']).map((t) => ({ id: t.id })) + : mappedTransforms.map((t) => ({ id: t.id })), + shouldInstallSequentially, + }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/use_reauthorize_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/use_reauthorize_action.tsx new file mode 100644 index 0000000000000..086f5451d53be --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reauthorize/use_reauthorize_action.tsx @@ -0,0 +1,76 @@ +/* + * 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, useMemo, useState } from 'react'; + +import { sortTransformsToReauthorize } from './sort_transforms_to_reauthorize'; +import { needsReauthorization } from '../../../../common/reauthorization_utils'; +import { useReauthorizeTransforms } from '../../../../hooks/use_reauthorize_transform'; +import { + isReauthorizeActionDisabled, + ReauthorizeActionName, + reauthorizeActionNameText, +} from './reauthorize_action_name'; + +import { TransformListAction, TransformListRow } from '../../../../common'; +import { AuthorizationContext } from '../../../../lib/authorization'; + +export type ReauthorizeAction = ReturnType; +export const useReauthorizeAction = (forceDisable: boolean, transformNodes: number) => { + const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + + const reauthorizeTransforms = useReauthorizeTransforms(); + + const [isModalVisible, setModalVisible] = useState(false); + const [items, setItems] = useState([]); + + const closeModal = () => setModalVisible(false); + + const reauthorizeAndCloseModal = () => { + setModalVisible(false); + const { transformIds } = sortTransformsToReauthorize(items); + reauthorizeTransforms(transformIds); + }; + + const openModal = (newItems: TransformListRow[]) => { + if (Array.isArray(newItems)) { + setItems(newItems); + setModalVisible(true); + } + }; + + const action: TransformListAction = useMemo( + () => ({ + name: (item: TransformListRow) => ( + + ), + available: (item: TransformListRow) => needsReauthorization(item), + enabled: (item: TransformListRow) => + !isReauthorizeActionDisabled([item], canStartStopTransform, transformNodes), + description: reauthorizeActionNameText, + icon: 'alert', + type: 'icon', + color: 'warning', + onClick: (item: TransformListRow) => openModal([item]), + 'data-test-subj': 'transformActionReauthorize', + }), + [canStartStopTransform, forceDisable, transformNodes] + ); + + return { + action, + closeModal, + isModalVisible, + items, + openModal, + reauthorizeAndCloseModal, + }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx index 7843516bc5a6e..e6c279e56f3c3 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx @@ -24,6 +24,12 @@ import { EuiSearchBarProps, } from '@elastic/eui'; +import { + isReauthorizeActionDisabled, + ReauthorizeActionModal, + ReauthorizeActionName, + useReauthorizeAction, +} from '../action_reauthorize'; import type { TransformId } from '../../../../../../common/types/transform'; import { @@ -108,6 +114,7 @@ export const TransformList: FC = ({ const [isActionsMenuOpen, setIsActionsMenuOpen] = useState(false); const bulkStartAction = useStartAction(false, transformNodes); const bulkDeleteAction = useDeleteAction(false); + const bulkReauthorizeAction = useReauthorizeAction(false, transformNodes); const bulkResetAction = useResetAction(false); const bulkStopAction = useStopAction(false); const bulkScheduleNowAction = useScheduleNowAction(false, transformNodes); @@ -221,6 +228,20 @@ export const TransformList: FC = ({ , +
+ { + bulkReauthorizeAction.openModal(transformSelection); + }} + disabled={isReauthorizeActionDisabled( + transformSelection, + capabilities.canStartStopTransform, + transformNodes + )} + > + + +
,
{ @@ -325,6 +346,9 @@ export const TransformList: FC = ({ {/* Bulk Action Modals */} {bulkStartAction.isModalVisible && } {bulkDeleteAction.isModalVisible && } + {bulkReauthorizeAction.isModalVisible && ( + + )} {bulkResetAction.isModalVisible && } {bulkStopAction.isModalVisible && } diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx index 4c1e34583ce5a..d620a60e7a861 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx @@ -34,6 +34,7 @@ describe('Transform: Transform List Actions', () => { 'transformActionEdit', 'transformActionClone', 'transformActionDelete', + 'transformActionReauthorize', 'transformActionReset', ]); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx index 26ee8b9fd9111..b46628aeb4eff 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { EuiTableActionsColumnType } from '@elastic/eui'; +import { ReauthorizeActionModal, useReauthorizeAction } from '../action_reauthorize'; import { TransformListRow } from '../../../../common'; import { useCloneAction } from '../action_clone'; @@ -37,6 +38,7 @@ export const useActions = ({ const deleteAction = useDeleteAction(forceDisable); const discoverAction = useDiscoverAction(forceDisable); const editAction = useEditAction(forceDisable, transformNodes); + const reauthorizeAction = useReauthorizeAction(forceDisable, transformNodes); const resetAction = useResetAction(forceDisable); const scheduleNowAction = useScheduleNowAction(forceDisable, transformNodes); const startAction = useStartAction(forceDisable, transformNodes); @@ -49,7 +51,7 @@ export const useActions = ({ {resetAction.isModalVisible && } {startAction.isModalVisible && } {stopAction.isModalVisible && } - + {reauthorizeAction.isModalVisible && } {deleteAction.isModalVisible && } @@ -63,6 +65,7 @@ export const useActions = ({ editAction.action, cloneAction.action, deleteAction.action, + reauthorizeAction.action, resetAction.action, ], }; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx index cf3ffa98b0334..db77b3e306da2 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useContext } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { @@ -24,6 +24,8 @@ import { EuiIcon, } from '@elastic/eui'; +import { AuthorizationContext } from '../../../../lib/authorization'; +import { needsReauthorization } from '../../../../common/reauthorization_utils'; import { isLatestTransform, isPivotTransform, @@ -38,12 +40,20 @@ import { isManagedTransform } from '../../../../common/managed_transforms_utils' import { TransformHealthColoredDot } from './transform_health_colored_dot'; import { TransformTaskStateBadge } from './transform_task_state_badge'; +const TRANSFORM_INSUFFICIENT_PERMISSIONS_MSG = i18n.translate( + 'xpack.transform.transformList.needsReauthorizationBadge.insufficientPermissions', + { + defaultMessage: 'This transform was created with insufficient permissions.', + } +); export const useColumns = ( expandedRowItemIds: TransformId[], setExpandedRowItemIds: React.Dispatch>, transformNodes: number, transformSelection: TransformListRow[] ) => { + const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const { actions, modals } = useActions({ forceDisable: transformSelection.length > 0, transformNodes, @@ -150,7 +160,31 @@ export const useColumns = ( ), width: '30px', render: (item) => { - return Array.isArray(item.alerting_rules) ? ( + const needsReauth = needsReauthorization(item); + + const actionMsg = canStartStopTransform + ? i18n.translate( + 'xpack.transform.transformList.needsReauthorizationBadge.reauthorizeTooltip', + { + defaultMessage: 'Reauthorize to start transforms.', + } + ) + : i18n.translate( + 'xpack.transform.transformList.needsReauthorizationBadge.contactAdminTooltip', + { + defaultMessage: 'Contact your administrator to request the required permissions.', + } + ); + const needsReauthTooltipIcon = needsReauth ? ( + <> + + + +   + + ) : null; + + const alertingRulesTooltipIcon = Array.isArray(item.alerting_rules) ? ( ); + return ( + <> + {needsReauthTooltipIcon} + {alertingRulesTooltipIcon} + + ); }, }, { diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx index 6e2c1cc612235..3961f83ecffd0 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC, useEffect, useState } from 'react'; +import React, { FC, useContext, useEffect, useMemo, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -23,6 +23,8 @@ import { EuiCallOut, EuiButton, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { needsReauthorization } from '../../common/reauthorization_utils'; import { APP_GET_TRANSFORM_CLUSTER_PRIVILEGES, TRANSFORM_STATE, @@ -32,7 +34,7 @@ import { useRefreshTransformList, TransformListRow } from '../../common'; import { useDocumentationLinks } from '../../hooks/use_documentation_links'; import { useDeleteTransforms, useGetTransforms } from '../../hooks'; import { RedirectToCreateTransform } from '../../common/navigation'; -import { PrivilegesWrapper } from '../../lib/authorization'; +import { AuthorizationContext, PrivilegesWrapper } from '../../lib/authorization'; import { breadcrumbService, docTitleService, BREADCRUMB_SECTION } from '../../services/navigation'; import { useRefreshInterval } from './components/transform_list/use_refresh_interval'; @@ -77,6 +79,48 @@ export const TransformManagement: FC = () => { // Call useRefreshInterval() after the subscription above is set up. useRefreshInterval(setBlockRefresh); + const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + + const unauthorizedTransformsWarning = useMemo(() => { + const unauthorizedCnt = transforms.filter((t) => needsReauthorization(t)).length; + + if (!unauthorizedCnt) return null; + + const insufficientPermissionsMsg = i18n.translate( + 'xpack.transform.transformList.unauthorizedTransformsCallout.insufficientPermissionsMsg', + { + defaultMessage: + '{unauthorizedCnt, plural, one {A transform was created with insufficient permissions.} other {# transforms were created with insufficient permissions.}}', + values: { unauthorizedCnt }, + } + ); + const actionMsg = canStartStopTransform + ? i18n.translate( + 'xpack.transform.transformList.unauthorizedTransformsCallout.reauthorizeMsg', + { + defaultMessage: + 'Reauthorize to start {unauthorizedCnt, plural, one {transform} other {# transforms}}.', + values: { unauthorizedCnt }, + } + ) + : i18n.translate( + 'xpack.transform.transformList.unauthorizedTransformsCallout.contactAdminMsg', + { + defaultMessage: 'Contact your administrator to request the required permissions.', + } + ); + return ( + <> + + + + ); + }, [transforms, canStartStopTransform]); + const [isSearchSelectionVisible, setIsSearchSelectionVisible] = useState(false); const [savedObjectId, setSavedObjectId] = useState(null); @@ -131,6 +175,8 @@ export const TransformManagement: FC = () => { {!isInitialized && } {isInitialized && ( <> + {unauthorizedTransformsWarning} + {typeof errorMessage !== 'undefined' && ( diff --git a/x-pack/plugins/transform/server/plugin.ts b/x-pack/plugins/transform/server/plugin.ts index ab2bb0769e002..95d80f3545c42 100644 --- a/x-pack/plugins/transform/server/plugin.ts +++ b/x-pack/plugins/transform/server/plugin.ts @@ -58,7 +58,7 @@ export class TransformServerPlugin implements Plugin<{}, void, any, any> { ], }); - getStartServices().then(([coreStart, { dataViews }]) => { + getStartServices().then(([coreStart, { dataViews, security: securityStart }]) => { const license = new License({ pluginId: PLUGIN.id, minimumLicenseType: PLUGIN.minimumLicenseType, @@ -75,6 +75,7 @@ export class TransformServerPlugin implements Plugin<{}, void, any, any> { license, dataViews, coreStart, + security: securityStart, }); }); diff --git a/x-pack/plugins/transform/server/routes/api/transforms.ts b/x-pack/plugins/transform/server/routes/api/transforms.ts index c4aee67bf4d45..d19d9eb1a2963 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms.ts @@ -16,6 +16,13 @@ import { } from '@kbn/core/server'; import { DataViewsService } from '@kbn/data-views-plugin/common'; +import type { TransportRequestOptions } from '@elastic/elasticsearch'; +import { generateTransformSecondaryAuthHeaders } from '../../../common/utils/transform_api_key'; +import { + reauthorizeTransformsRequestSchema, + ReauthorizeTransformsRequestSchema, + ReauthorizeTransformsResponseSchema, +} from '../../../common/api_schemas/reauthorize_transforms'; import { TRANSFORM_STATE } from '../../../common/constants'; import { transformIdParamSchema, @@ -72,6 +79,7 @@ import { transformHealthServiceProvider } from '../../lib/alerting/transform_hea enum TRANSFORM_ACTIONS { DELETE = 'delete', + REAUTHORIZE = 'reauthorize', RESET = 'reset', SCHEDULE_NOW = 'schedule_now', STOP = 'stop', @@ -79,7 +87,7 @@ enum TRANSFORM_ACTIONS { } export function registerTransformsRoutes(routeDependencies: RouteDependencies) { - const { router, license, coreStart, dataViews } = routeDependencies; + const { router, license, coreStart, dataViews, security: securityStart } = routeDependencies; /** * @apiGroup Transforms * @@ -295,6 +303,61 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) { ) ); + /** + * @apiGroup Reauthorize transforms with API key generated from currently logged in user + * @api {post} /api/transform/reauthorize_transforms Post reauthorize transforms + * @apiName Reauthorize Transforms + * @apiDescription Reauthorize transforms by generating an API Key for current user + * and update transform's es-secondary-authorization headers with the generated key, + * then start the transform. + * @apiSchema (body) reauthorizeTransformsRequestSchema + */ + router.post( + { + path: addBasePath('reauthorize_transforms'), + validate: { + body: reauthorizeTransformsRequestSchema, + }, + }, + license.guardApiRoute( + async (ctx, req, res) => { + try { + const transformsInfo = req.body; + const { elasticsearch } = coreStart; + const esClient = elasticsearch.client.asScoped(req).asCurrentUser; + + let apiKeyWithCurrentUserPermission; + + // If security is not enabled or available, user should not have the need to reauthorize + // in that case, start anyway + if (securityStart) { + apiKeyWithCurrentUserPermission = await securityStart.authc.apiKeys.grantAsInternalUser( + req, + { + name: `auto-generated-transform-api-key`, + role_descriptors: {}, + } + ); + } + const secondaryAuth = generateTransformSecondaryAuthHeaders( + apiKeyWithCurrentUserPermission + ); + + const authorizedTransforms = await reauthorizeAndStartTransforms( + transformsInfo, + esClient, + { + ...(secondaryAuth ? secondaryAuth : {}), + } + ); + return res.ok({ body: authorizedTransforms }); + } catch (e) { + return res.customError(wrapError(wrapEsError(e))); + } + } + ) + ); + /** * @apiGroup Transforms * @@ -860,3 +923,44 @@ async function scheduleNowTransforms( } return results; } + +async function reauthorizeAndStartTransforms( + transformsInfo: ReauthorizeTransformsRequestSchema, + esClient: ElasticsearchClient, + options?: TransportRequestOptions +) { + const results: ReauthorizeTransformsResponseSchema = {}; + + for (const transformInfo of transformsInfo) { + const transformId = transformInfo.id; + try { + await esClient.transform.updateTransform( + { + body: {}, + transform_id: transformId, + }, + options ?? {} + ); + + await esClient.transform.startTransform( + { + transform_id: transformId, + }, + { ignore: [409] } + ); + + results[transformId] = { success: true }; + } catch (e) { + if (isRequestTimeout(e)) { + return fillResultsWithTimeouts({ + results, + id: transformId, + items: transformsInfo, + action: TRANSFORM_ACTIONS.REAUTHORIZE, + }); + } + results[transformId] = { success: false, error: e.meta.body.error }; + } + } + return results; +} diff --git a/x-pack/plugins/transform/server/types.ts b/x-pack/plugins/transform/server/types.ts index 012f819fce88f..6aa17d59db353 100644 --- a/x-pack/plugins/transform/server/types.ts +++ b/x-pack/plugins/transform/server/types.ts @@ -33,4 +33,5 @@ export interface RouteDependencies { license: License; coreStart: CoreStart; dataViews: DataViewsServerPluginStart; + security?: SecurityPluginStart; } diff --git a/x-pack/test/api_integration/apis/transform/index.ts b/x-pack/test/api_integration/apis/transform/index.ts index c665665b527d3..ad44dc1249e8e 100644 --- a/x-pack/test/api_integration/apis/transform/index.ts +++ b/x-pack/test/api_integration/apis/transform/index.ts @@ -29,6 +29,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { }); loadTestFile(require.resolve('./delete_transforms')); + loadTestFile(require.resolve('./reauthorize_transforms')); loadTestFile(require.resolve('./reset_transforms')); loadTestFile(require.resolve('./start_transforms')); loadTestFile(require.resolve('./stop_transforms')); diff --git a/x-pack/test/api_integration/apis/transform/reauthorize_transforms.ts b/x-pack/test/api_integration/apis/transform/reauthorize_transforms.ts new file mode 100644 index 0000000000000..274c1b42a4d07 --- /dev/null +++ b/x-pack/test/api_integration/apis/transform/reauthorize_transforms.ts @@ -0,0 +1,274 @@ +/* + * 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 { ReauthorizeTransformsRequestSchema } from '@kbn/transform-plugin/common/api_schemas/reauthorize_transforms'; +import expect from '@kbn/expect'; +import { TRANSFORM_STATE } from '@kbn/transform-plugin/common/constants'; +import type { SecurityCreateApiKeyResponse } from '@elastic/elasticsearch/lib/api/types'; +import { COMMON_REQUEST_HEADERS } from '../../../functional/services/ml/common_api'; +import { USER } from '../../../functional/services/transform/security_common'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +import { asyncForEach, generateDestIndex, generateTransformConfig } from './common'; + +export default ({ getService }: FtrProviderContext) => { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertestWithoutAuth'); + const transform = getService('transform'); + + // If transform was created with sufficient -> should still authorize and start + // If transform was created with insufficient -> should still authorize and start + + function getTransformIdByUser(username: USER) { + return `transform-by-${username}`; + } + + function generateHeaders(apiKey: SecurityCreateApiKeyResponse) { + return { + ...COMMON_REQUEST_HEADERS, + 'es-secondary-authorization': `ApiKey ${apiKey.encoded}`, + }; + } + + async function createTransform(transformId: string, headers: object) { + const config = generateTransformConfig(transformId, true); + await transform.api.createTransform(transformId, config, { + headers, + deferValidation: true, + }); + } + + async function cleanUpTransform(transformId: string) { + const destinationIndex = generateDestIndex(transformId); + + await transform.api.stopTransform(transformId); + await transform.api.cleanTransformIndices(); + await transform.api.deleteIndices(destinationIndex); + } + + describe('/api/transform/reauthorize_transforms', function () { + const apiKeysForTransformUsers = new Map(); + + async function expectUnauthorizedTransform(transformId: string, createdByUser: USER) { + const user = createdByUser; + const { body: getTransformBody } = await transform.api.getTransform(transformId); + const transformInfo = getTransformBody.transforms[0]; + const expectedApiKeyId = apiKeysForTransformUsers?.get(user)?.id; + + expect(typeof transformInfo.authorization.api_key).to.be('object'); + expect(transformInfo.authorization.api_key.id).to.eql( + expectedApiKeyId, + `Expected authorization api_key for ${transformId} to be ${expectedApiKeyId} (got ${JSON.stringify( + transformInfo.authorization.api_key + )})` + ); + + const stats = await transform.api.getTransformStats(transformId); + expect(stats.state).to.eql( + TRANSFORM_STATE.STOPPED, + `Expected transform state of ${transformId} to be '${TRANSFORM_STATE.STOPPED}' (got ${stats.state})` + ); + expect(stats.health.status).to.eql( + 'red', + `Expected transform health status of ${transformId} to be 'red' (got ${stats.health.status})` + ); + expect(stats.health.issues![0].type).to.eql( + 'privileges_check_failed', + `Expected transform health issue of ${transformId} to be 'privileges_check_failed' (got ${stats.health.status})` + ); + } + + async function expectAuthorizedTransform(transformId: string, createdByUser: USER) { + const { body: getTransformBody } = await transform.api.getTransform(transformId); + const transformInfo = getTransformBody.transforms[0]; + + const expectedApiKeyId = apiKeysForTransformUsers?.get(createdByUser)?.id; + expect(transformInfo.authorization.api_key.id).to.not.eql( + expectedApiKeyId, + `Expected authorization api_key for ${transformId} to not be ${expectedApiKeyId} (got ${JSON.stringify( + transformInfo.authorization.api_key + )})` + ); + const stats = await transform.api.getTransformStats(transformId); + expect(stats.health.status).to.eql( + 'green', + `Expected transform health status of ${transformId} to be 'green' (got ${stats.health.status})` + ); + } + + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); + await transform.testResources.setKibanaTimeZoneToUTC(); + + const apiKeyForTransformUsers = + await transform.securityCommon.createApiKeyForTransformUsers(); + + apiKeyForTransformUsers.forEach(({ user, apiKey }) => + apiKeysForTransformUsers.set(user.name as USER, apiKey) + ); + }); + + after(async () => { + await transform.securityCommon.clearAllTransformApiKeys(); + }); + + describe('single transform reauthorize_transforms', function () { + const transformCreatedByViewerId = getTransformIdByUser(USER.TRANSFORM_VIEWER); + + beforeEach(async () => { + await createTransform( + transformCreatedByViewerId, + generateHeaders(apiKeysForTransformUsers.get(USER.TRANSFORM_VIEWER)!) + ); + }); + afterEach(async () => { + await cleanUpTransform(transformCreatedByViewerId); + }); + + it('should not reauthorize transform created by transform_viewer for transform_unauthorized', async () => { + const reqBody: ReauthorizeTransformsRequestSchema = [{ id: transformCreatedByViewerId }]; + const { body, status } = await supertest + .post(`/api/transform/reauthorize_transforms`) + .auth( + USER.TRANSFORM_UNAUTHORIZED, + transform.securityCommon.getPasswordForUser(USER.TRANSFORM_UNAUTHORIZED) + ) + .set(COMMON_REQUEST_HEADERS) + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); + expect(body[transformCreatedByViewerId].success).to.eql( + false, + `Expected ${transformCreatedByViewerId} not to be authorized` + ); + expect(typeof body[transformCreatedByViewerId].error).to.be('object'); + + await expectUnauthorizedTransform(transformCreatedByViewerId, USER.TRANSFORM_VIEWER); + }); + + it('should not reauthorize transform created by transform_viewer for transform_viewer', async () => { + const reqBody: ReauthorizeTransformsRequestSchema = [{ id: transformCreatedByViewerId }]; + const { body, status } = await supertest + .post(`/api/transform/reauthorize_transforms`) + .auth( + USER.TRANSFORM_VIEWER, + transform.securityCommon.getPasswordForUser(USER.TRANSFORM_VIEWER) + ) + .set(COMMON_REQUEST_HEADERS) + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); + expect(body[transformCreatedByViewerId].success).to.eql( + false, + `Expected ${transformCreatedByViewerId} not to be rauthorized` + ); + expect(typeof body[transformCreatedByViewerId].error).to.be('object'); + + await expectUnauthorizedTransform(transformCreatedByViewerId, USER.TRANSFORM_VIEWER); + }); + + it('should reauthorize transform created by transform_viewer with new api key of poweruser and start the transform', async () => { + const reqBody: ReauthorizeTransformsRequestSchema = [{ id: transformCreatedByViewerId }]; + const { body, status } = await supertest + .post(`/api/transform/reauthorize_transforms`) + .auth( + USER.TRANSFORM_POWERUSER, + transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) + ) + .set(COMMON_REQUEST_HEADERS) + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); + expect(body[transformCreatedByViewerId].success).to.eql( + true, + `Expected ${transformCreatedByViewerId} to be reauthorized` + ); + expect(typeof body[transformCreatedByViewerId].error).to.eql( + 'undefined', + `Expected ${transformCreatedByViewerId} to be reauthorized without error` + ); + await transform.api.waitForTransformState( + transformCreatedByViewerId, + TRANSFORM_STATE.STARTED + ); + + await expectAuthorizedTransform(transformCreatedByViewerId, USER.TRANSFORM_VIEWER); + }); + }); + + describe('single transform reauthorize_transforms with invalid transformId', function () { + it('should return 200 with error in response if invalid transformId', async () => { + const reqBody: ReauthorizeTransformsRequestSchema = [{ id: 'invalid_transform_id' }]; + const { body, status } = await supertest + .post(`/api/transform/reauthorize_transforms`) + .auth( + USER.TRANSFORM_POWERUSER, + transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) + ) + .set(COMMON_REQUEST_HEADERS) + .send(reqBody); + transform.api.assertResponseStatusCode(200, status, body); + + expect(body.invalid_transform_id.success).to.eql(false); + expect(body.invalid_transform_id).to.have.property('error'); + }); + }); + + describe('bulk reauthorize_transforms', function () { + const reqBody: ReauthorizeTransformsRequestSchema = [ + USER.TRANSFORM_VIEWER, + USER.TRANSFORM_POWERUSER, + ].map((user) => ({ id: getTransformIdByUser(user) })); + const destinationIndices = reqBody.map((d) => generateDestIndex(d.id)); + + beforeEach(async () => { + await Promise.all( + [USER.TRANSFORM_VIEWER, USER.TRANSFORM_POWERUSER].map((user) => + createTransform( + getTransformIdByUser(user), + generateHeaders(apiKeysForTransformUsers.get(user)!) + ) + ) + ); + }); + + afterEach(async () => { + await asyncForEach(reqBody, async ({ id }: { id: string }, idx: number) => { + await transform.api.stopTransform(id); + }); + await transform.api.cleanTransformIndices(); + await asyncForEach(destinationIndices, async (destinationIndex: string) => { + await transform.api.deleteIndices(destinationIndex); + }); + }); + + it('should reauthorize multiple transforms for transform_poweruser, even if one of the transformIds is invalid', async () => { + const invalidTransformId = 'invalid_transform_id'; + + const { body, status } = await supertest + .post(`/api/transform/reauthorize_transforms`) + .auth( + USER.TRANSFORM_POWERUSER, + transform.securityCommon.getPasswordForUser(USER.TRANSFORM_POWERUSER) + ) + .set(COMMON_REQUEST_HEADERS) + .send([...reqBody, { id: invalidTransformId }]); + transform.api.assertResponseStatusCode(200, status, body); + + await expectAuthorizedTransform( + getTransformIdByUser(USER.TRANSFORM_VIEWER), + USER.TRANSFORM_VIEWER + ); + await expectAuthorizedTransform( + getTransformIdByUser(USER.TRANSFORM_POWERUSER), + USER.TRANSFORM_POWERUSER + ); + + expect(body[invalidTransformId].success).to.eql(false); + expect(body[invalidTransformId]).to.have.property('error'); + }); + }); + }); +}; diff --git a/x-pack/test/functional/apps/transform/start_reset_delete/starting.ts b/x-pack/test/functional/apps/transform/start_reset_delete/starting.ts index dc86cf7489530..2cccd4525856d 100644 --- a/x-pack/test/functional/apps/transform/start_reset_delete/starting.ts +++ b/x-pack/test/functional/apps/transform/start_reset_delete/starting.ts @@ -125,11 +125,9 @@ export default function ({ getService }: FtrProviderContext) { }, }; } - await transform.api.createTransform( - testData.originalConfig.id, - testData.originalConfig, - testData.expected.healthStatus === TRANSFORM_HEALTH.yellow - ); + await transform.api.createTransform(testData.originalConfig.id, testData.originalConfig, { + deferValidation: testData.expected.healthStatus === TRANSFORM_HEALTH.yellow, + }); } await transform.testResources.setKibanaTimeZoneToUTC(); await transform.securityUI.loginAsTransformPowerUser(); diff --git a/x-pack/test/functional/services/transform/api.ts b/x-pack/test/functional/services/transform/api.ts index dcb76bb23eaf0..4c6944b64a91d 100644 --- a/x-pack/test/functional/services/transform/api.ts +++ b/x-pack/test/functional/services/transform/api.ts @@ -218,13 +218,33 @@ export function TransformAPIProvider({ getService }: FtrProviderContext) { async createTransform( transformId: string, transformConfig: PutTransformsRequestSchema, - deferValidation?: boolean + options: { + deferValidation?: boolean; + headers?: object; + } = {} ) { - log.debug(`Creating transform with id '${transformId}'...`); - const { body, status } = await esSupertest - .put(`/_transform/${transformId}${deferValidation ? '?defer_validation=true' : ''}`) - .send(transformConfig); - this.assertResponseStatusCode(200, status, body); + const { deferValidation, headers } = options; + + if (headers) { + log.debug( + `Creating transform with id '${transformId}' with headers ${JSON.stringify( + headers + )} and defer_validation:${deferValidation}...` + ); + const { body, status } = await esSupertest + .put(`/_transform/${transformId}${deferValidation ? '?defer_validation=true' : ''}`) + .set(headers) + .send(transformConfig); + this.assertResponseStatusCode(200, status, body); + } else { + log.debug( + `Creating transform with id '${transformId}' and defer_validation:${deferValidation}...` + ); + const { body, status } = await esSupertest + .put(`/_transform/${transformId}${deferValidation ? '?defer_validation=true' : ''}`) + .send(transformConfig); + this.assertResponseStatusCode(200, status, body); + } await this.waitForTransformToExist( transformId, @@ -264,8 +284,12 @@ export function TransformAPIProvider({ getService }: FtrProviderContext) { this.assertResponseStatusCode(200, status, body); }, - async createAndRunTransform(transformId: string, transformConfig: PutTransformsRequestSchema) { - await this.createTransform(transformId, transformConfig); + async createAndRunTransform( + transformId: string, + transformConfig: PutTransformsRequestSchema, + options: { headers?: object } = {} + ) { + await this.createTransform(transformId, transformConfig, { headers: options.headers }); await this.startTransform(transformId); if (transformConfig.sync === undefined) { // batch mode diff --git a/x-pack/test/functional/services/transform/security_common.ts b/x-pack/test/functional/services/transform/security_common.ts index 36670a65211b3..94c059be0fae6 100644 --- a/x-pack/test/functional/services/transform/security_common.ts +++ b/x-pack/test/functional/services/transform/security_common.ts @@ -7,6 +7,7 @@ import { ProvidedType } from '@kbn/test'; +import { Client } from '@elastic/elasticsearch'; import { FtrProviderContext } from '../../ftr_provider_context'; export type TransformSecurityCommon = ProvidedType; @@ -19,6 +20,7 @@ export enum USER { export function TransformSecurityCommonProvider({ getService }: FtrProviderContext) { const security = getService('security'); + const esClient: Client = getService('es'); const roles = [ { @@ -98,6 +100,51 @@ export function TransformSecurityCommonProvider({ getService }: FtrProviderConte } }, + async createApiKeyForTransformUser(username: string) { + const user = users.find((u) => u.name === username); + + if (user === undefined) { + throw new Error(`Can't create api key for user ${user} - user not defined`); + } + + const roleDescriptors = user.roles.reduce>((map, roleName) => { + const userRole = roles.find((r) => r.name === roleName); + + if (userRole) { + map[roleName] = userRole.elasticsearch; + } + return map; + }, {}); + const apiKey = await esClient.security.createApiKey({ + body: { + name: `Transform API Key ${user.full_name}`, + role_descriptors: roleDescriptors, + metadata: user, + }, + }); + return { user: { name: user.name as USER, roles: user.roles }, apiKey }; + }, + + async createApiKeyForTransformUsers() { + const apiKeyForTransformUsers = await Promise.all( + users.map((user) => this.createApiKeyForTransformUser(user.name)) + ); + + return apiKeyForTransformUsers; + }, + + async clearAllTransformApiKeys() { + const existingKeys = await esClient.security.queryApiKeys(); + + if (existingKeys.count > 0) { + await Promise.all( + existingKeys.api_keys.map(async (key) => { + esClient.security.invalidateApiKey({ ids: [key.id] }); + }) + ); + } + }, + async cleanTransformRoles() { for (const role of roles) { await security.role.delete(role.name); From ada91f9a5e7cf1fc9d5609fbcd533591542b3d4b Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Thu, 20 Apr 2023 11:23:15 -0700 Subject: [PATCH 22/67] [Security Solution][Exceptions] - Fix stale linked rules count on manage rules save (#155108) ## Summary Addresses https://github.com/elastic/kibana/issues/153195 --- .../alerts_table_flow/add_exception.cy.ts | 6 ++-- .../manage_shared_exception_list.cy.ts | 16 +++++++-- .../cypress/screens/exceptions.ts | 10 ++++++ .../cypress/tasks/exceptions_table.ts | 33 ++++++++++++++++--- .../components/exceptions_list_card/index.tsx | 9 +++-- .../components/manage_rules/index.tsx | 1 + .../hooks/use_list_detail_view/index.ts | 8 +++-- 7 files changed, 68 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/alerts_table_flow/add_exception.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/alerts_table_flow/add_exception.cy.ts index 80ec42d3ab408..5964c8303ce1d 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/alerts_table_flow/add_exception.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/alerts_table_flow/add_exception.cy.ts @@ -14,7 +14,7 @@ import { login, visitWithoutDateRange, waitForPageWithoutDateRange } from '../.. import { EXCEPTIONS_URL } from '../../../urls/navigation'; import { - deleteExceptionListWithRuleReference, + deleteExceptionListWithRuleReferenceByListId, deleteExceptionListWithoutRuleReference, exportExceptionList, searchForExceptionList, @@ -79,7 +79,7 @@ describe('Exceptions Table', () => { visitWithoutDateRange(EXCEPTIONS_URL); waitForExceptionsTableToBeLoaded(); - exportExceptionList(); + exportExceptionList(getExceptionList1().list_id); cy.wait('@export').then(({ response }) => { cy.wrap(response?.body).should( @@ -168,7 +168,7 @@ describe('Exceptions Table', () => { // just checking number of lists shown cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '2'); - deleteExceptionListWithRuleReference(); + deleteExceptionListWithRuleReferenceByListId(getExceptionList2().list_id); // Using cy.contains because we do not care about the exact text, // just checking number of lists shown diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/manage_shared_exception_list.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/manage_shared_exception_list.cy.ts index 1888ee78b7223..f7610c3f4b5d9 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/manage_shared_exception_list.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/manage_shared_exception_list.cy.ts @@ -14,11 +14,13 @@ import { login, visitWithoutDateRange, waitForPageWithoutDateRange } from '../.. import { EXCEPTIONS_URL } from '../../../urls/navigation'; import { - deleteExceptionListWithRuleReference, + deleteExceptionListWithRuleReferenceByListId, deleteExceptionListWithoutRuleReference, exportExceptionList, waitForExceptionsTableToBeLoaded, createSharedExceptionList, + linkRulesToExceptionList, + assertNumberLinkedRules, } from '../../../tasks/exceptions_table'; import { EXCEPTIONS_LIST_MANAGEMENT_NAME, @@ -46,6 +48,8 @@ describe('Manage shared exception list', () => { esArchiverResetKibana(); login(); + createRule(getNewRule({ name: 'Another rule' })); + // Create exception list associated with a rule createExceptionList(getExceptionList2(), getExceptionList2().list_id).then((response) => createRule( @@ -76,7 +80,7 @@ describe('Manage shared exception list', () => { it('Export exception list', function () { cy.intercept(/(\/api\/exception_lists\/_export)/).as('export'); - exportExceptionList(); + exportExceptionList(getExceptionList1().list_id); cy.wait('@export').then(({ response }) => { cy.wrap(response?.body).should( @@ -91,6 +95,12 @@ describe('Manage shared exception list', () => { }); }); + it('Link rules to shared exception list', function () { + assertNumberLinkedRules(getExceptionList2().list_id, '1'); + linkRulesToExceptionList(getExceptionList2().list_id, 1); + assertNumberLinkedRules(getExceptionList2().list_id, '2'); + }); + it('Create exception list', function () { createSharedExceptionList({ name: EXCEPTION_LIST_NAME, description: 'This is my list.' }, true); @@ -118,7 +128,7 @@ describe('Manage shared exception list', () => { // just checking number of lists shown cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '3'); - deleteExceptionListWithRuleReference(); + deleteExceptionListWithRuleReferenceByListId(getExceptionList2().list_id); // Using cy.contains because we do not care about the exact text, // just checking number of lists shown diff --git a/x-pack/plugins/security_solution/cypress/screens/exceptions.ts b/x-pack/plugins/security_solution/cypress/screens/exceptions.ts index b650c59364423..100ab6f91ab1d 100644 --- a/x-pack/plugins/security_solution/cypress/screens/exceptions.ts +++ b/x-pack/plugins/security_solution/cypress/screens/exceptions.ts @@ -49,6 +49,9 @@ export const EXCEPTIONS_TABLE_SHOWING_LISTS = '[data-test-subj="showingException export const EXCEPTIONS_TABLE_DELETE_BTN = '[data-test-subj="sharedListOverflowCardActionItemDelete"]'; +export const EXCEPTIONS_TABLE_LINK_RULES_BTN = + '[data-test-subj="sharedListOverflowCardActionItemLinkRules"]'; + export const EXCEPTIONS_TABLE_EXPORT_MODAL_BTN = '[data-test-subj="sharedListOverflowCardActionItemExport"]'; @@ -149,6 +152,13 @@ export const CREATE_SHARED_EXCEPTION_LIST_DESCRIPTION_INPUT = export const CREATE_SHARED_EXCEPTION_LIST_BTN = 'button[data-test-subj="exception-lists-form-create-shared"]'; +export const exceptionsTableListManagementListContainerByListId = (listId: string) => + `[data-test-subj="exceptionsManagementListCard-${listId}"]`; + +export const LINKED_RULES_BADGE = '[data-test-subj="exceptionListCardLinkedRulesBadge"]'; + +export const MANAGE_RULES_SAVE = '[data-test-subj="manageListRulesSaveButton"]'; + // Exception list management export const EXCEPTIONS_LIST_MANAGEMENT_NAME = '[data-test-subj="exceptionListManagementTitleText"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts b/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts index 9be5d281903c8..0f7a8e60e8c45 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts @@ -26,6 +26,11 @@ import { EXCEPTIONS_LIST_MANAGEMENT_EDIT_MODAL_DESCRIPTION_INPUT, EXCEPTIONS_LIST_EDIT_DETAILS_SAVE_BTN, EXCEPTIONS_LIST_DETAILS_HEADER, + exceptionsTableListManagementListContainerByListId, + EXCEPTIONS_TABLE_LINK_RULES_BTN, + RULE_ACTION_LINK_RULE_SWITCH, + LINKED_RULES_BADGE, + MANAGE_RULES_SAVE, } from '../screens/exceptions'; export const clearSearchSelection = () => { @@ -36,12 +41,30 @@ export const expandExceptionActions = () => { cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click(); }; -export const exportExceptionList = () => { - cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click(); +export const exportExceptionList = (listId: string) => { + cy.get(exceptionsTableListManagementListContainerByListId(listId)) + .find(EXCEPTIONS_OVERFLOW_ACTIONS_BTN) + .click(); cy.get(EXCEPTIONS_TABLE_EXPORT_MODAL_BTN).first().click(); cy.get(EXCEPTIONS_TABLE_EXPORT_CONFIRM_BTN).first().click(); }; +export const assertNumberLinkedRules = (listId: string, numberOfRulesAsString: string) => { + cy.get(exceptionsTableListManagementListContainerByListId(listId)) + .find(LINKED_RULES_BADGE) + .contains(numberOfRulesAsString); +}; + +export const linkRulesToExceptionList = (listId: string, ruleSwitch: number = 0) => { + cy.log(`Open link rules flyout for list_id: '${listId}'`); + cy.get(exceptionsTableListManagementListContainerByListId(listId)) + .find(EXCEPTIONS_OVERFLOW_ACTIONS_BTN) + .click(); + cy.get(EXCEPTIONS_TABLE_LINK_RULES_BTN).first().click(); + cy.get(RULE_ACTION_LINK_RULE_SWITCH).eq(ruleSwitch).find('button').click(); + cy.get(MANAGE_RULES_SAVE).first().click(); +}; + export const deleteExceptionListWithoutRuleReference = () => { cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click(); cy.get(EXCEPTIONS_TABLE_DELETE_BTN).first().click(); @@ -50,8 +73,10 @@ export const deleteExceptionListWithoutRuleReference = () => { cy.get(EXCEPTIONS_TABLE_MODAL).should('not.exist'); }; -export const deleteExceptionListWithRuleReference = () => { - cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).last().click(); +export const deleteExceptionListWithRuleReferenceByListId = (listId: string) => { + cy.get(exceptionsTableListManagementListContainerByListId(listId)) + .find(EXCEPTIONS_OVERFLOW_ACTIONS_BTN) + .click(); cy.get(EXCEPTIONS_TABLE_DELETE_BTN).last().click(); cy.get(EXCEPTIONS_TABLE_MODAL).should('exist'); cy.get(EXCEPTIONS_TABLE_MODAL_CONFIRM_BTN).first().click(); diff --git a/x-pack/plugins/security_solution/public/exceptions/components/exceptions_list_card/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/exceptions_list_card/index.tsx index eb945e18e7761..95dde7239b605 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/exceptions_list_card/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/components/exceptions_list_card/index.tsx @@ -102,7 +102,6 @@ export const ExceptionsListCard = memo( toggleAccordion, openAccordionId, menuActionItems, - listRulesCount, listDescription, exceptionItemsCount, onEditExceptionItem, @@ -184,8 +183,11 @@ export const ExceptionsListCard = memo( - - + + ( } + data-test-subj={`exceptionsManagementListCard-${listId}`} > = memo( { i18n.EXCEPTION_MANAGE_RULES_ERROR_DESCRIPTION ); setShowManageButtonLoader(false); + }) + .finally(() => { + initializeList(); }); } catch (err) { handleErrorStatus(err); @@ -348,10 +351,11 @@ export const useListDetailsView = (exceptionListId: string) => { list, getRulesToAdd, getRulesToRemove, - exceptionListId, resetManageRulesAfterSaving, - handleErrorStatus, + exceptionListId, invalidateFetchRuleByIdQuery, + handleErrorStatus, + initializeList, ]); const onCancelManageRules = useCallback(() => { setShowManageRulesFlyout(false); From afced9d47fe425dfb47a1eb543f592649519f497 Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Thu, 20 Apr 2023 11:24:26 -0700 Subject: [PATCH 23/67] [Security Solution][Exceptions] - Fix empty selection showing for exception item field selection (#155221) ## Summary Addresses https://github.com/elastic/kibana/issues/145540 - blank space no longer shows as selection option for exception item field dropdown --- .../src/field/__tests__/use_field.test.ts | 51 ++++++++++++++++++- .../src/field/use_field.tsx | 3 +- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/packages/kbn-securitysolution-autocomplete/src/field/__tests__/use_field.test.ts b/packages/kbn-securitysolution-autocomplete/src/field/__tests__/use_field.test.ts index 84bd78a4d8fe4..221ef23fd13e5 100644 --- a/packages/kbn-securitysolution-autocomplete/src/field/__tests__/use_field.test.ts +++ b/packages/kbn-securitysolution-autocomplete/src/field/__tests__/use_field.test.ts @@ -84,7 +84,56 @@ describe('useField', () => { expect(comboOptions).toEqual([{ label: 'bytes' }, { label: 'ssl' }, { label: '@timestamp' }]); expect(selectedComboOptions).toEqual([]); }); - it('should not return an empty field as a combo option', () => { + it('should not return a selected field when empty string as a combo option', () => { + const newIndexPattern = { + ...indexPattern, + fields: [ + { + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'ssl', + type: 'boolean', + esTypes: ['boolean'], + count: 20, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: '@timestamp', + type: 'date', + esTypes: ['date'], + count: 30, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + ] as unknown as DataViewFieldBase[], + title: 'title1', + }; + + const { result } = renderHook(() => + useField({ + indexPattern: newIndexPattern, + onChange: onChangeMock, + selectedField: { name: '', type: 'keyword' }, + }) + ); + const { comboOptions, selectedComboOptions } = result.current; + expect(comboOptions).toEqual([{ label: 'bytes' }, { label: 'ssl' }, { label: '@timestamp' }]); + expect(selectedComboOptions).toEqual([]); + }); + it('should not return a selected field when string with spaces is written as a combo option', () => { const newIndexPattern = { ...indexPattern, fields: [ diff --git a/packages/kbn-securitysolution-autocomplete/src/field/use_field.tsx b/packages/kbn-securitysolution-autocomplete/src/field/use_field.tsx index 0e9e01bbec315..ac5286f0f038f 100644 --- a/packages/kbn-securitysolution-autocomplete/src/field/use_field.tsx +++ b/packages/kbn-securitysolution-autocomplete/src/field/use_field.tsx @@ -26,14 +26,13 @@ import { GetFieldComboBoxPropsReturn, } from './types'; import { disabledTypesWithTooltipText } from './disabled_types_with_tooltip_text'; -import { paramContainsSpace } from '../param_contains_space'; const getExistingFields = (indexPattern: DataViewBase | undefined): DataViewFieldBase[] => { return indexPattern != null ? indexPattern.fields : []; }; const getSelectedFields = (selectedField: DataViewField | undefined): DataViewFieldBase[] => { - return selectedField && !paramContainsSpace(selectedField.name) ? [selectedField] : []; + return selectedField && selectedField.name.trim() !== '' ? [selectedField] : []; }; const getAvailableFields = ( From 48aa064268ae45a8414b00f1543771154812d1b8 Mon Sep 17 00:00:00 2001 From: Jordan <51442161+JordanSh@users.noreply.github.com> Date: Thu, 20 Apr 2023 21:24:40 +0300 Subject: [PATCH 24/67] [Cloud Security] Vuln Mgmt - Copy and UX changes (#155376) ## Summary Copy changes according to [this issue](https://github.com/elastic/security-team/issues/6410) Section 1: - empty state component text changes - new learn more button instead of text link Section 5: - scanning in progress prompt text changes --------- Co-authored-by: Paulo Henrique --- .../fleet_extensions/policy_template_form.tsx | 4 --- .../components/no_vulnerabilities_states.tsx | 29 +++++++++---------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx index 0c5e237bfccb1..762367673cc6e 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx @@ -182,7 +182,6 @@ export const CspPolicyTemplateForm = memo {isEditPage && } - {/* Defines the enabled policy template */} {!integration && ( <> @@ -195,11 +194,9 @@ export const CspPolicyTemplateForm = memo )} - {/* Shows info on the active policy template */} - {/* Defines the single enabled input of the active policy template */} - {/* Defines the name/description */} (

} @@ -51,7 +52,7 @@ const ScanningVulnerabilitiesEmptyPrompt = () => (

} @@ -72,7 +73,7 @@ const VulnerabilitiesFindingsInstalledEmptyPrompt = ({ } @@ -82,17 +83,7 @@ const VulnerabilitiesFindingsInstalledEmptyPrompt = ({

- - - ), - }} + defaultMessage="Add the Cloud Native Vulnerability Management integration to begin" />

} @@ -102,10 +93,18 @@ const VulnerabilitiesFindingsInstalledEmptyPrompt = ({
+ + + + + } /> From de697825311a5ec5bbc86ce3dfee55d361f4dfb7 Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Thu, 20 Apr 2023 12:44:49 -0600 Subject: [PATCH 25/67] [RAM] Fix maintenance window update not updating removal of fields (#155431) ## Summary Fix a bug where when we remove fields from `rRule` during a maintenance window update, the removed fields are not removed because of how ES does partial updates. Due to the complexity of the `rRule` schema, we decided to simply delete and re-create the maintenance window with the same ID, otherwise, we would have to diff and null each of the removed fields specifically. ### 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 --- .../methods/update.test.ts | 25 ++++------ .../methods/update.ts | 50 +++++++++---------- .../update_maintenance_window.ts | 50 +++++++++++++++++++ 3 files changed, 86 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/alerting/server/maintenance_window_client/methods/update.test.ts b/x-pack/plugins/alerting/server/maintenance_window_client/methods/update.test.ts index 47991a11f2bb1..481557d9d47a6 100644 --- a/x-pack/plugins/alerting/server/maintenance_window_client/methods/update.test.ts +++ b/x-pack/plugins/alerting/server/maintenance_window_client/methods/update.test.ts @@ -9,7 +9,7 @@ import moment from 'moment-timezone'; import { RRule } from 'rrule'; import { update } from './update'; import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; -import { SavedObjectsUpdateResponse, SavedObject } from '@kbn/core/server'; +import { SavedObject } from '@kbn/core/server'; import { MaintenanceWindowClientContext, MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, @@ -72,14 +72,14 @@ describe('MaintenanceWindowClient - update', () => { id: 'test-id', } as unknown as SavedObject); - savedObjectsClient.update.mockResolvedValueOnce({ + savedObjectsClient.create.mockResolvedValueOnce({ attributes: { ...mockMaintenanceWindow, ...updatedAttributes, ...updatedMetadata, }, id: 'test-id', - } as unknown as SavedObjectsUpdateResponse); + } as unknown as SavedObject); jest.useFakeTimers().setSystemTime(new Date(secondTimestamp)); @@ -92,9 +92,8 @@ describe('MaintenanceWindowClient - update', () => { MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, 'test-id' ); - expect(savedObjectsClient.update).toHaveBeenLastCalledWith( + expect(savedObjectsClient.create).toHaveBeenLastCalledWith( MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, - 'test-id', { ...updatedAttributes, events: [ @@ -104,7 +103,7 @@ describe('MaintenanceWindowClient - update', () => { expirationDate: moment(new Date(secondTimestamp)).tz('UTC').add(1, 'year').toISOString(), ...updatedMetadata, }, - { version: '123' } + { id: 'test-id' } ); // Only these 3 properties are worth asserting since the rest come from mocks expect(result).toEqual( @@ -141,25 +140,24 @@ describe('MaintenanceWindowClient - update', () => { id: 'test-id', } as unknown as SavedObject); - savedObjectsClient.update.mockResolvedValue({ + savedObjectsClient.create.mockResolvedValue({ attributes: { ...mockMaintenanceWindow, ...updatedAttributes, ...updatedMetadata, }, id: 'test-id', - } as unknown as SavedObjectsUpdateResponse); + } as unknown as SavedObject); // Update without changing duration or rrule await update(mockContext, { id: 'test-id' }); // Events keep the previous modified events, but adds on the new events - expect(savedObjectsClient.update).toHaveBeenLastCalledWith( + expect(savedObjectsClient.create).toHaveBeenLastCalledWith( MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, - 'test-id', expect.objectContaining({ events: [...modifiedEvents, expect.any(Object), expect.any(Object), expect.any(Object)], }), - { version: '123' } + { id: 'test-id' } ); // Update with changing rrule @@ -173,16 +171,15 @@ describe('MaintenanceWindowClient - update', () => { }, }); // All events are regenerated - expect(savedObjectsClient.update).toHaveBeenLastCalledWith( + expect(savedObjectsClient.create).toHaveBeenLastCalledWith( MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, - 'test-id', expect.objectContaining({ events: [ { gte: '2023-03-26T00:00:00.000Z', lte: '2023-03-26T01:00:00.000Z' }, { gte: '2023-04-01T23:00:00.000Z', lte: '2023-04-02T00:00:00.000Z' }, ], }), - { version: '123' } + { id: 'test-id' } ); }); diff --git a/x-pack/plugins/alerting/server/maintenance_window_client/methods/update.ts b/x-pack/plugins/alerting/server/maintenance_window_client/methods/update.ts index 4722e820867d4..17a5d2f573ee9 100644 --- a/x-pack/plugins/alerting/server/maintenance_window_client/methods/update.ts +++ b/x-pack/plugins/alerting/server/maintenance_window_client/methods/update.ts @@ -51,14 +51,11 @@ async function updateWithOCC( const { id, title, enabled, duration, rRule } = params; try { - const { - attributes, - version, - id: fetchedId, - } = await savedObjectsClient.get( - MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, - id - ); + const { attributes, id: fetchedId } = + await savedObjectsClient.get( + MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, + id + ); if (moment.utc(attributes.expirationDate).isBefore(new Date())) { throw Boom.badRequest('Cannot edit archived maintenance windows'); @@ -77,29 +74,32 @@ async function updateWithOCC( events = mergeEvents({ oldEvents: attributes.events, newEvents: events }); } - const result = await savedObjectsClient.update( + await savedObjectsClient.delete(MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, fetchedId); + + const updatedAttributes = { + ...attributes, + ...(title ? { title } : {}), + ...(rRule ? { rRule } : {}), + ...(typeof duration === 'number' ? { duration } : {}), + ...(typeof enabled === 'boolean' ? { enabled } : {}), + expirationDate, + events, + ...modificationMetadata, + }; + + // We are deleting and then creating rather than updating because SO.update + // performs a partial update on the rRule, we would need to null out all of the fields + // that are removed from a new rRule if that were the case. + const result = await savedObjectsClient.create( MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, - fetchedId, - { - ...attributes, - title, - enabled: typeof enabled === 'boolean' ? enabled : attributes.enabled, - expirationDate, - duration, - rRule, - events, - ...modificationMetadata, - }, + updatedAttributes, { - version, + id: fetchedId, } ); return getMaintenanceWindowFromRaw({ - attributes: { - ...attributes, - ...result.attributes, - }, + attributes: result.attributes, id: result.id, }); } catch (e) { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/update_maintenance_window.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/update_maintenance_window.ts index 2fa8fa83a5532..40550997eed06 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/update_maintenance_window.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/update_maintenance_window.ts @@ -5,6 +5,7 @@ * 2.0. */ +import moment from 'moment'; import expect from '@kbn/expect'; import { UserAtSpaceScenarios } from '../../../scenarios'; import { getUrlPrefix, ObjectRemover } from '../../../../common/lib'; @@ -88,5 +89,54 @@ export default function updateMaintenanceWindowTests({ getService }: FtrProvider }); }); } + + it('should update RRule correctly when removing fields', async () => { + const { body: createdMaintenanceWindow } = await supertest + .post(`${getUrlPrefix('space1')}/internal/alerting/rules/maintenance_window`) + .set('kbn-xsrf', 'foo') + .send({ + ...createParams, + r_rule: { + ...createParams.r_rule, + count: 1, + until: moment.utc().add(1, 'week').toISOString(), + }, + }) + .expect(200); + + objectRemover.add( + 'space1', + createdMaintenanceWindow.id, + 'rules/maintenance_window', + 'alerting', + true + ); + + const updatedRRule = { + ...createParams.r_rule, + count: 2, + }; + + await supertest + .post( + `${getUrlPrefix('space1')}/internal/alerting/rules/maintenance_window/${ + createdMaintenanceWindow.id + }` + ) + .set('kbn-xsrf', 'foo') + .send({ + ...createParams, + r_rule: updatedRRule, + }) + .expect(200); + + const response = await supertest + .get(`${getUrlPrefix('space1')}/internal/alerting/rules/maintenance_window/_find`) + .set('kbn-xsrf', 'foo') + .send({}); + + expect(response.body.data[0].id).to.eql(createdMaintenanceWindow.id); + expect(response.body.data[0].r_rule).to.eql(updatedRRule); + }); }); } From d6aba14b26810be8410c3f532366229235d5fd74 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Thu, 20 Apr 2023 13:07:26 -0600 Subject: [PATCH 26/67] [ML] Data Frame Analytics/Anomaly Detection: Custom URLs - entity dropdown reflects Data View update (#155096) ## Summary Related meta issue: https://github.com/elastic/kibana/issues/150375 This PR ensures that when data view is changed, the query entity dropdown values update to reflect the fields for the newly chosen data view. This makes it easier for example to build a link to Discover to view documents in a data view which contains useful contextual data, and has fields of the same name as those used in the job but which was not used to create the job e.g. data views which share a common `host.name` field. image ### 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 --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__snapshots__/editor.test.tsx.snap | 1083 ----------------- .../custom_url_editor/editor.test.tsx | 163 --- .../custom_urls/custom_url_editor/editor.tsx | 126 +- .../custom_url_editor/get_dropdown_options.ts | 27 + .../custom_urls/custom_url_editor/utils.ts | 29 +- .../components/custom_urls/custom_urls.tsx | 34 +- .../custom_urls/custom_urls_wrapper.tsx | 49 +- 7 files changed, 135 insertions(+), 1376 deletions(-) delete mode 100644 x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/__snapshots__/editor.test.tsx.snap delete mode 100644 x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.test.tsx create mode 100644 x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/get_dropdown_options.ts diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/__snapshots__/editor.test.tsx.snap b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/__snapshots__/editor.test.tsx.snap deleted file mode 100644 index 163bad28d8cc2..0000000000000 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/__snapshots__/editor.test.tsx.snap +++ /dev/null @@ -1,1083 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CustomUrlEditor renders the editor for a dashboard type URL with a label 1`] = ` - - -

- -

-
- - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - - - - - } - labelType="label" - > - - - - - -
-`; - -exports[`CustomUrlEditor renders the editor for a discover type URL with an entity and empty time range interval 1`] = ` - - -

- -

-
- - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - - - - - } - labelType="label" - > - - - - - - } - labelType="label" - > - - - - - -
-`; - -exports[`CustomUrlEditor renders the editor for a discover type URL with valid time range interval 1`] = ` - - -

- -

-
- - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - - - - - } - labelType="label" - > - - - - - - } - labelType="label" - > - - - - - -
-`; - -exports[`CustomUrlEditor renders the editor for a new dashboard type URL with no label 1`] = ` - - -

- -

-
- - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - - - - - } - labelType="label" - > - - - - - -
-`; - -exports[`CustomUrlEditor renders the editor for other type of URL with duplicate label 1`] = ` - - -

- -

-
- - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - -
-`; - -exports[`CustomUrlEditor renders the editor for other type of URL with unique label 1`] = ` - - -

- -

-
- - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - - } - labelType="label" - > - - - -
-`; diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.test.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.test.tsx deleted file mode 100644 index ce7f31df4e86c..0000000000000 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.test.tsx +++ /dev/null @@ -1,163 +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. - */ - -// Mock the mlJobService that is used for testing custom URLs. -import { shallow } from 'enzyme'; - -jest.mock('../../../services/job_service', () => 'mlJobService'); - -import React from 'react'; - -import { CustomUrlEditor } from './editor'; -import { TIME_RANGE_TYPE, URL_TYPE } from './constants'; -import { CustomUrlSettings } from './utils'; -import { DataViewListItem } from '@kbn/data-views-plugin/common'; - -function prepareTest( - customUrl: CustomUrlSettings, - setEditCustomUrlFn: (url: CustomUrlSettings) => void -) { - const savedCustomUrls = [ - { - url_name: 'Show data', - time_range: 'auto', - url_value: - "discover#/?_g=(time:(from:'$earliest$',mode:absolute,to:'$latest$'))&_a=" + - '(index:e532ba80-b76f-11e8-a9dc-37914a458883,query:(language:lucene,query:\'airline:"$airline$"\'))', - }, - { - url_name: 'Show dashboard', - time_range: '1h', - url_value: - 'dashboards#/view/52ea8840-bbef-11e8-a04d-b1701b2b977e?_g=' + - "(time:(from:'$earliest$',mode:absolute,to:'$latest$'))&" + - '_a=(filters:!(),query:(language:lucene,query:\'airline:"$airline$"\'))', - }, - { - url_name: 'Show airline', - time_range: 'auto', - url_value: 'http://airlinecodes.info/airline-code-$airline$', - }, - ]; - - const dashboards = [ - { id: 'dash1', title: 'Dashboard 1' }, - { id: 'dash2', title: 'Dashboard 2' }, - ]; - - const dataViewListItems = [ - { id: 'pattern1', title: 'Data view 1' }, - { id: 'pattern2', title: 'Data view 2' }, - ] as DataViewListItem[]; - - const queryEntityFieldNames = ['airline']; - - const props = { - customUrl, - setEditCustomUrl: setEditCustomUrlFn, - savedCustomUrls, - dashboards, - dataViewListItems, - queryEntityFieldNames, - }; - - return shallow(); -} - -describe('CustomUrlEditor', () => { - const setEditCustomUrl = jest.fn(() => {}); - const dashboardUrl = { - label: '', - timeRange: { - type: TIME_RANGE_TYPE.AUTO, - interval: '', - }, - type: URL_TYPE.KIBANA_DASHBOARD, - kibanaSettings: { - queryFieldNames: [], - dashboardId: 'dash1', - }, - }; - - const discoverUrl = { - label: 'Open Discover', - timeRange: { - type: TIME_RANGE_TYPE.INTERVAL, - interval: '', - }, - type: URL_TYPE.KIBANA_DISCOVER, - kibanaSettings: { - queryFieldNames: ['airline'], - discoverIndexPatternId: 'pattern1', - }, - }; - - const otherUrl = { - label: 'Show airline', - timeRange: { - type: TIME_RANGE_TYPE.AUTO, - interval: '', - }, - type: URL_TYPE.OTHER, - otherUrlSettings: { - urlValue: 'https://www.google.co.uk/search?q=airline+code+$airline$', - }, - }; - - test('renders the editor for a new dashboard type URL with no label', () => { - const wrapper = prepareTest(dashboardUrl, setEditCustomUrl); - expect(wrapper).toMatchSnapshot(); - }); - - test('renders the editor for a dashboard type URL with a label', () => { - const dashboardUrlEdit = { - ...dashboardUrl, - label: 'Open Dashboard 1', - }; - const wrapper = prepareTest(dashboardUrlEdit, setEditCustomUrl); - expect(wrapper).toMatchSnapshot(); - }); - - test('renders the editor for a discover type URL with an entity and empty time range interval', () => { - const wrapper = prepareTest(discoverUrl, setEditCustomUrl); - expect(wrapper).toMatchSnapshot(); - }); - - test('renders the editor for a discover type URL with valid time range interval', () => { - const discoverUrlEdit = { - ...discoverUrl, - timeRange: { - type: TIME_RANGE_TYPE.INTERVAL, - interval: '1h', - }, - }; - const wrapper = prepareTest(discoverUrlEdit, setEditCustomUrl); - expect(wrapper).toMatchSnapshot(); - }); - - test('renders the editor for other type of URL with duplicate label', () => { - const wrapper = prepareTest(otherUrl, setEditCustomUrl); - expect(wrapper).toMatchSnapshot(); - }); - - test('renders the editor for other type of URL with unique label', () => { - const otherUrlEdit = { - ...otherUrl, - label: 'View airline', - }; - const wrapper = prepareTest(otherUrlEdit, setEditCustomUrl); - expect(wrapper).toMatchSnapshot(); - }); - - test('calls setEditCustomUrl on updating a custom URL field', () => { - const wrapper = prepareTest(dashboardUrl, setEditCustomUrl); - const labelInput = wrapper.find('EuiFieldText').first(); - labelInput.simulate('change', { target: { value: 'Edit' } }); - wrapper.update(); - expect(setEditCustomUrl).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx index 9eddc02b5e1a4..315c60fab6a6f 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { ChangeEvent, FC } from 'react'; +import React, { ChangeEvent, useMemo, useState, useRef, useEffect, FC } from 'react'; import { EuiComboBox, @@ -25,11 +25,16 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { DataViewListItem } from '@kbn/data-views-plugin/common'; +import { DataView } from '@kbn/data-views-plugin/public'; import { CustomUrlSettings, isValidCustomUrlSettingsTimeRange } from './utils'; import { isValidLabel } from '../../../util/custom_url_utils'; +import { type DataFrameAnalyticsConfig } from '../../../../../common/types/data_frame_analytics'; +import { Job, isAnomalyDetectionJob } from '../../../../../common/types/anomaly_detection_jobs'; import { TIME_RANGE_TYPE, TimeRangeType, URL_TYPE } from './constants'; import { UrlConfig } from '../../../../../common/types/custom_urls'; +import { useMlKibana } from '../../../contexts/kibana'; +import { getDropDownOptions } from './get_dropdown_options'; function getLinkToOptions() { return [ @@ -60,8 +65,8 @@ interface CustomUrlEditorProps { savedCustomUrls: UrlConfig[]; dashboards: Array<{ id: string; title: string }>; dataViewListItems: DataViewListItem[]; - queryEntityFieldNames: string[]; showTimeRangeSelector?: boolean; + job: Job | DataFrameAnalyticsConfig; } /* @@ -73,9 +78,42 @@ export const CustomUrlEditor: FC = ({ savedCustomUrls, dashboards, dataViewListItems, - queryEntityFieldNames, - showTimeRangeSelector = true, + job, }) => { + const [queryEntityFieldNames, setQueryEntityFieldNames] = useState([]); + const isAnomalyJob = useMemo(() => isAnomalyDetectionJob(job), [job]); + + const { + services: { + data: { dataViews }, + }, + } = useMlKibana(); + + const isFirst = useRef(true); + + useEffect(() => { + async function getQueryEntityDropdownOptions() { + let dataViewToUse: DataView | undefined; + const dataViewId = customUrl?.kibanaSettings?.discoverIndexPatternId; + + try { + dataViewToUse = await dataViews.get(dataViewId ?? ''); + } catch (e) { + dataViewToUse = undefined; + } + const dropDownOptions = await getDropDownOptions(isFirst.current, job, dataViewToUse); + setQueryEntityFieldNames(dropDownOptions); + + if (isFirst.current) { + isFirst.current = false; + } + } + + if (job !== undefined) { + getQueryEntityDropdownOptions(); + } + }, [dataViews, job, customUrl?.kibanaSettings?.discoverIndexPatternId]); + if (customUrl === undefined) { return null; } @@ -112,6 +150,7 @@ export const CustomUrlEditor: FC = ({ kibanaSettings: { ...kibanaSettings, discoverIndexPatternId: e.target.value, + queryFieldNames: [], }, }); }; @@ -307,58 +346,57 @@ export const CustomUrlEditor: FC = ({ )} - {(type === URL_TYPE.KIBANA_DASHBOARD || type === URL_TYPE.KIBANA_DISCOVER) && - showTimeRangeSelector && ( - <> - - - + {(type === URL_TYPE.KIBANA_DASHBOARD || type === URL_TYPE.KIBANA_DISCOVER) && isAnomalyJob && ( + <> + + + + + } + className="url-time-range" + display="rowCompressed" + > + + + + {timeRange.type === TIME_RANGE_TYPE.INTERVAL && ( + } className="url-time-range" + error={invalidIntervalError} + isInvalid={isInvalidTimeRange} display="rowCompressed" > - - {timeRange.type === TIME_RANGE_TYPE.INTERVAL && ( - - - } - className="url-time-range" - error={invalidIntervalError} - isInvalid={isInvalidTimeRange} - display="rowCompressed" - > - - - - )} - - - )} + )} + + + )} {type === URL_TYPE.OTHER && ( 0) { indicesName = job.dest.index; + backupIndicesName = job.source.index[0]; query = job.source?.query ?? {}; jobId = job.id; } const defaultDataViewId = dataViews.find((dv) => dv.title === indicesName)?.id; - kibanaSettings.discoverIndexPatternId = defaultDataViewId; + if (defaultDataViewId === undefined && backupIndicesName !== undefined) { + backupDataViewId = dataViews.find((dv) => dv.title === backupIndicesName)?.id; + } + kibanaSettings.discoverIndexPatternId = defaultDataViewId ?? backupDataViewId ?? ''; kibanaSettings.filters = defaultDataViewId === null ? [] : getFiltersForDSLQuery(query, defaultDataViewId, jobId); @@ -134,17 +141,23 @@ export function getNewCustomUrlDefaults( // Returns the list of supported field names that can be used // to add to the query used when linking to a Kibana dashboard or Discover. export function getSupportedFieldNames( - job: DataFrameAnalyticsConfig, + job: DataFrameAnalyticsConfig | Job, dataView: DataView ): string[] { - const resultsField = job.dest.results_field; const sortedFields = dataView.fields.getAll().sort((a, b) => a.name.localeCompare(b.name)) ?? []; - const categoryFields = sortedFields.filter( - (f) => + let filterFunction: (field: DataViewField) => boolean = (field: DataViewField) => + categoryFieldTypes.some((type) => { + return field.esTypes?.includes(type); + }); + + if (isDataFrameAnalyticsConfigs(job)) { + const resultsField = job.dest.results_field; + filterFunction = (f) => categoryFieldTypes.some((type) => { return f.esTypes?.includes(type); - }) && !f.name.startsWith(resultsField ?? DEFAULT_RESULTS_FIELD) - ); + }) && !f.name.startsWith(resultsField ?? DEFAULT_RESULTS_FIELD); + } + const categoryFields = sortedFields.filter(filterFunction); return categoryFields.map((field) => field.name); } diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx index 7ebab7b3a1359..9d3db04fa40de 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx @@ -22,7 +22,6 @@ import { EuiModalFooter, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { DataView } from '@kbn/data-views-plugin/public'; import { i18n } from '@kbn/i18n'; import { withKibana } from '@kbn/kibana-react-plugin/public'; @@ -31,8 +30,6 @@ import { MlKibanaReactContextValue } from '../../contexts/kibana'; import { CustomUrlEditor, CustomUrlList } from './custom_url_editor'; import { getNewCustomUrlDefaults, - getQueryEntityFieldNames, - getSupportedFieldNames, isValidCustomUrlSettings, buildCustomUrlFromSettings, getTestUrl, @@ -43,22 +40,8 @@ import { loadDataViewListItems, } from '../../jobs/jobs_list/components/edit_job_flyout/edit_utils'; import { openCustomUrlWindow } from '../../util/custom_url_utils'; -import { Job, isAnomalyDetectionJob } from '../../../../common/types/anomaly_detection_jobs'; import { UrlConfig } from '../../../../common/types/custom_urls'; import type { CustomUrlsWrapperProps } from './custom_urls_wrapper'; -import { - isDataFrameAnalyticsConfigs, - type DataFrameAnalyticsConfig, -} from '../../../../common/types/data_frame_analytics'; - -function getDropDownOptions(job: Job | DataFrameAnalyticsConfig, dataView?: DataView) { - if (isAnomalyDetectionJob(job)) { - return getQueryEntityFieldNames(job); - } else if (isDataFrameAnalyticsConfigs(job) && dataView !== undefined) { - return getSupportedFieldNames(job, dataView); - } - return []; -} const MAX_NUMBER_DASHBOARDS = 1000; @@ -66,14 +49,12 @@ interface CustomUrlsState { customUrls: UrlConfig[]; dashboards: Array<{ id: string; title: string }>; dataViewListItems: DataViewListItem[]; - queryEntityFieldNames: string[]; editorOpen: boolean; editorSettings?: CustomUrlSettings; supportedFilterFields: string[]; } interface CustomUrlsProps extends CustomUrlsWrapperProps { kibana: MlKibanaReactContextValue; - dataView?: DataView; currentTimeFilter?: EsQueryTimeRange; } @@ -85,7 +66,6 @@ class CustomUrlsUI extends Component { customUrls: [], dashboards: [], dataViewListItems: [], - queryEntityFieldNames: [], editorOpen: false, supportedFilterFields: [], }; @@ -95,8 +75,6 @@ class CustomUrlsUI extends Component { return { job: props.job, customUrls: props.jobCustomUrls, - // For DFA uses the destination index Data View to get the query entities and falls back to source index Data View. - queryEntityFieldNames: getDropDownOptions(props.job, props.dataView), }; } @@ -223,25 +201,17 @@ class CustomUrlsUI extends Component { }; renderEditor() { - const { - customUrls, - editorOpen, - editorSettings, - dashboards, - dataViewListItems, - queryEntityFieldNames, - } = this.state; + const { customUrls, editorOpen, editorSettings, dashboards, dataViewListItems } = this.state; const editMode = this.props.editMode ?? 'inline'; const editor = ( ); diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls_wrapper.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls_wrapper.tsx index a4a125d7cb52f..175b16dca74ea 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls_wrapper.tsx +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls_wrapper.tsx @@ -5,16 +5,11 @@ * 2.0. */ -import React, { useEffect, useState, FC } from 'react'; -import { DataView } from '@kbn/data-views-plugin/public'; +import React, { FC } from 'react'; import { useMlKibana } from '../../contexts/kibana'; import { Job } from '../../../../common/types/anomaly_detection_jobs'; import { UrlConfig } from '../../../../common/types/custom_urls'; -import { getDataViewIdFromName } from '../../util/index_utils'; -import { - isDataFrameAnalyticsConfigs, - type DataFrameAnalyticsConfig, -} from '../../../../common/types/data_frame_analytics'; +import { type DataFrameAnalyticsConfig } from '../../../../common/types/data_frame_analytics'; import { CustomUrls } from './custom_urls'; export interface CustomUrlsWrapperProps { @@ -25,12 +20,9 @@ export interface CustomUrlsWrapperProps { } export const CustomUrlsWrapper: FC = (props) => { - const [dataView, setDataView] = useState(); - const { services: { data: { - dataViews, query: { timefilter: { timefilter }, }, @@ -38,40 +30,5 @@ export const CustomUrlsWrapper: FC = (props) => { }, } = useMlKibana(); - useEffect(() => { - let active = true; - - async function loadDataView() { - if (isDataFrameAnalyticsConfigs(props.job)) { - const destIndex = props.job.dest.index; - const sourceIndex = props.job.source.index[0]; - let dataViewIdSource: string | null; - let dataViewIdDest: string | null; - let dv: DataView | undefined; - - try { - dataViewIdSource = await getDataViewIdFromName(sourceIndex); - dataViewIdDest = await getDataViewIdFromName(destIndex); - dv = await dataViews.get(dataViewIdDest ?? dataViewIdSource ?? ''); - - if (dv === undefined) { - dv = await dataViews.get(dataViewIdSource ?? ''); - } - if (!active) return; - setDataView(dv); - } catch (e) { - dv = undefined; - } - - return dv; - } - } - - loadDataView(); - return () => { - active = false; - }; - }, [dataViews, props.job]); - - return ; + return ; }; From 45449acc0136d9b5bed320476cec6ede838d3d84 Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Thu, 20 Apr 2023 12:14:48 -0700 Subject: [PATCH 27/67] [Saved Objects] Add a root level managed property to all saved object documents (#154515) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../src/apis/create.ts | 8 + .../src/simple_saved_object.ts | 8 + .../src/lib/included_fields.test.ts | 1 + .../src/lib/included_fields.ts | 1 + .../src/lib/internal_utils.test.ts | 27 ++ .../src/lib/internal_utils.ts | 29 ++ .../src/lib/repository.test.ts | 247 +++++++++++++++++- .../src/lib/repository.ts | 18 +- .../test_helpers/repository.test.common.ts | 19 +- .../src/apis/bulk_create.ts | 8 + .../src/apis/create.ts | 8 + .../src/apis/increment_counter.ts | 10 + .../src/serialization/serializer.test.ts | 40 +++ .../src/serialization/serializer.ts | 5 +- .../src/validation/schema.ts | 1 + .../src/saved_objects_client.ts | 3 +- .../src/simple_saved_object.ts | 4 + .../src/simple_saved_object.mock.ts | 2 + .../src/server_types.ts | 8 + .../kibana_migrator.test.ts.snap | 4 + .../build_active_mappings.test.ts.snap | 8 + .../src/core/build_active_mappings.ts | 3 + .../document_migrator.test.ts | 10 + .../src/document_migrator/migrations/index.ts | 10 +- .../transform_set_managed_default.test.ts | 40 +++ .../transform_set_managed_default.ts | 14 + .../src/routes/bulk_create.ts | 1 + .../lib/import_dashboards.test.ts | 7 +- .../lib/import_dashboards.ts | 4 +- .../src/serialization.ts | 2 + .../group2/batch_size_bytes.test.ts | 4 +- .../migrations/group3/rewriting_id.test.ts | 4 + .../data_sets/ecommerce/saved_objects.ts | 2 + .../data_sets/flights/saved_objects.ts | 3 + .../data_sets/logs/saved_objects.ts | 5 + .../apis/saved_objects/bulk_create.ts | 1 + .../apis/saved_objects/bulk_get.ts | 2 + .../apis/saved_objects/create.ts | 2 + .../apis/saved_objects/export.ts | 6 + .../api_integration/apis/saved_objects/get.ts | 2 + .../apis/saved_objects/resolve.ts | 2 + 41 files changed, 565 insertions(+), 18 deletions(-) create mode 100644 packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/migrations/transform_set_managed_default.test.ts create mode 100644 packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/migrations/transform_set_managed_default.ts diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts index a58b4b4a2868d..b9db75e204119 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts @@ -35,4 +35,12 @@ export interface SavedObjectsCreateOptions { typeMigrationVersion?: string; /** Array of referenced saved objects. */ references?: SavedObjectReference[]; + /** + * Flag indicating if a saved object is managed by Kibana (default=false) + * + * This can be leveraged by applications to e.g. prevent edits to a managed + * saved object. Instead, users can be guided to create a copy first and + * make their edits to the copy. + */ + managed?: boolean; } diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts index 12486a32a8c03..2f3cb8dc8fc08 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts @@ -49,6 +49,14 @@ export interface SimpleSavedObject { * `namespaceType: 'agnostic'`. */ namespaces: SavedObjectType['namespaces']; + /** + * Flag indicating if a saved object is managed by Kibana (default=false) + * + * This can be leveraged by applications to e.g. prevent edits to a managed + * saved object. Instead, users can be guided to create a copy first and + * make their edits to the copy. + */ + managed: SavedObjectType['managed']; /** * Gets an attribute of this object diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/included_fields.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/included_fields.test.ts index 13dacc4f07a64..3ccfd5ca6f2a9 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/included_fields.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/included_fields.test.ts @@ -20,6 +20,7 @@ describe('getRootFields', () => { "migrationVersion", "coreMigrationVersion", "typeMigrationVersion", + "managed", "updated_at", "created_at", "originId", diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/included_fields.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/included_fields.ts index 8ee3f70585452..20e980f4707a1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/included_fields.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/included_fields.ts @@ -18,6 +18,7 @@ const ROOT_FIELDS = [ 'migrationVersion', 'coreMigrationVersion', 'typeMigrationVersion', + 'managed', 'updated_at', 'created_at', 'originId', diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.test.ts index 569142ef8dc5d..cba31cbd02162 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.test.ts @@ -18,6 +18,7 @@ import { normalizeNamespace, rawDocExistsInNamespace, rawDocExistsInNamespaces, + setManaged, } from './internal_utils'; describe('#getBulkOperationError', () => { @@ -99,6 +100,7 @@ describe('#getSavedObjectFromSource', () => { const originId = 'originId'; // eslint-disable-next-line @typescript-eslint/naming-convention const updated_at = 'updatedAt'; + const managed = false; function createRawDoc( type: string, @@ -115,6 +117,7 @@ describe('#getSavedObjectFromSource', () => { migrationVersion, coreMigrationVersion, typeMigrationVersion, + managed, originId, updated_at, ...namespaceAttrs, @@ -131,6 +134,7 @@ describe('#getSavedObjectFromSource', () => { attributes, coreMigrationVersion, typeMigrationVersion, + managed, id, migrationVersion, namespaces: expect.anything(), // see specific test cases below @@ -406,3 +410,26 @@ describe('#getCurrentTime', () => { expect(getCurrentTime()).toEqual('2021-09-10T21:00:00.000Z'); }); }); + +describe('#setManaged', () => { + it('returns false if no arguments are provided', () => { + expect(setManaged({})).toEqual(false); + }); + + it('returns false if only one argument is provided as false', () => { + expect(setManaged({ optionsManaged: false })).toEqual(false); + expect(setManaged({ objectManaged: false })).toEqual(false); + }); + + it('returns true if only one argument is provided as true', () => { + expect(setManaged({ optionsManaged: true })).toEqual(true); + expect(setManaged({ objectManaged: true })).toEqual(true); + }); + + it('overrides objectManaged with optionsManaged', () => { + expect(setManaged({ optionsManaged: false, objectManaged: true })).toEqual(false); + expect(setManaged({ optionsManaged: true, objectManaged: false })).toEqual(true); + expect(setManaged({ optionsManaged: false, objectManaged: false })).toEqual(false); + expect(setManaged({ optionsManaged: true, objectManaged: true })).toEqual(true); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts index 03f3c2c698c3e..afafd67fcab3a 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts @@ -144,6 +144,7 @@ export function getSavedObjectFromSource( created_at: createdAt, coreMigrationVersion, typeMigrationVersion, + managed, migrationVersion = migrationVersionCompatibility === 'compatible' && typeMigrationVersion ? { [type]: typeMigrationVersion } : undefined, @@ -169,6 +170,7 @@ export function getSavedObjectFromSource( version: encodeHitVersion(doc), attributes: doc._source[type], references: doc._source.references || [], + managed, }; } @@ -272,3 +274,30 @@ export function normalizeNamespace(namespace?: string) { export function getCurrentTime() { return new Date(Date.now()).toISOString(); } + +/** + * Returns the managed boolean to apply to a document as it's managed value. + * For use by applications to modify behavior for managed saved objects. + * The behavior is as follows: + * If `optionsManaged` is set, it will override any existing `managed` value in all the documents being created + * If `optionsManaged` is not provided, then the documents are created with whatever may be assigned to their `managed` property + * or default to `false`. + * + * @internal + */ + +export function setManaged({ + optionsManaged, + objectManaged, +}: { + optionsManaged?: boolean; + objectManaged?: boolean; +}): boolean { + if (optionsManaged !== undefined) { + return optionsManaged; + } else if (optionsManaged === undefined && objectManaged !== undefined) { + return objectManaged; + } else { + return false; + } +} diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts index ccbe414bf82f4..f0aea1afaf5fd 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts @@ -189,6 +189,7 @@ describe('SavedObjectsRepository', () => { ...(doc.attributes?.title && { title: `${doc.attributes.title}!!` }), }, migrationVersion: mockMigrationVersion, + managed: doc.managed ?? false, references: [{ name: 'search_0', type: 'search', id: '123' }], }); @@ -205,12 +206,14 @@ describe('SavedObjectsRepository', () => { id: '6.0.0-alpha1', attributes: { title: 'Test One' }, references: [{ name: 'ref_0', type: 'test', id: '1' }], + managed: false, }; const obj2 = { type: 'index-pattern', id: 'logstash-*', attributes: { title: 'Test Two' }, references: [{ name: 'ref_0', type: 'test', id: '2' }], + managed: false, }; const namespace = 'foo-namespace'; @@ -259,7 +262,6 @@ describe('SavedObjectsRepository', () => { ...mockTimestampFields, }), ]; - describe('client calls', () => { it(`should use the ES bulk action by default`, async () => { await bulkCreateSuccess(client, repository, [obj1, obj2]); @@ -318,6 +320,7 @@ describe('SavedObjectsRepository', () => { const obj1WithSeq = { ...obj1, + managed: obj1.managed, if_seq_no: mockVersionProps._seq_no, if_primary_term: mockVersionProps._primary_term, }; @@ -330,6 +333,19 @@ describe('SavedObjectsRepository', () => { expectClientCallArgsAction([obj1, obj2], { method: 'create' }); }); + it(`should use the ES index method if ID is defined, overwrite=true and managed=true in a document`, async () => { + await bulkCreateSuccess(client, repository, [obj1, obj2], { + overwrite: true, + managed: true, + }); + expectClientCallArgsAction([obj1, obj2], { method: 'index' }); + }); + + it(`should use the ES create method if ID is defined, overwrite=false and managed=true in a document`, async () => { + await bulkCreateSuccess(client, repository, [obj1, obj2], { managed: true }); + expectClientCallArgsAction([obj1, obj2], { method: 'create' }); + }); + it(`formats the ES request`, async () => { await bulkCreateSuccess(client, repository, [obj1, obj2]); const body = [...expectObjArgs(obj1), ...expectObjArgs(obj2)]; @@ -338,6 +354,17 @@ describe('SavedObjectsRepository', () => { expect.anything() ); }); + // this test only ensures that the client accepts the managed field in a document + it(`formats the ES request with managed=true in a document`, async () => { + const obj1WithManagedTrue = { ...obj1, managed: true }; + const obj2WithManagedTrue = { ...obj2, managed: true }; + await bulkCreateSuccess(client, repository, [obj1WithManagedTrue, obj2WithManagedTrue]); + const body = [...expectObjArgs(obj1WithManagedTrue), ...expectObjArgs(obj2WithManagedTrue)]; + expect(client.bulk).toHaveBeenCalledWith( + expect.objectContaining({ body }), + expect.anything() + ); + }); describe('originId', () => { it(`returns error if originId is set for non-multi-namespace type`, async () => { @@ -439,6 +466,27 @@ describe('SavedObjectsRepository', () => { ); }); + // this only ensures we don't override any other options + it(`adds managed=false to request body if declared for any types that are single-namespace`, async () => { + await bulkCreateSuccess(client, repository, [obj1, obj2], { namespace, managed: false }); + const expected = expect.objectContaining({ namespace, managed: false }); + const body = [expect.any(Object), expected, expect.any(Object), expected]; + expect(client.bulk).toHaveBeenCalledWith( + expect.objectContaining({ body }), + expect.anything() + ); + }); + // this only ensures we don't override any other options + it(`adds managed=true to request body if declared for any types that are single-namespace`, async () => { + await bulkCreateSuccess(client, repository, [obj1, obj2], { namespace, managed: true }); + const expected = expect.objectContaining({ namespace, managed: true }); + const body = [expect.any(Object), expected, expect.any(Object), expected]; + expect(client.bulk).toHaveBeenCalledWith( + expect.objectContaining({ body }), + expect.anything() + ); + }); + it(`normalizes options.namespace from 'default' to undefined`, async () => { await bulkCreateSuccess(client, repository, [obj1, obj2], { namespace: 'default' }); const expected = expect.not.objectContaining({ namespace: 'default' }); @@ -825,10 +873,8 @@ describe('SavedObjectsRepository', () => { it(`migrates the docs and serializes the migrated docs`, async () => { migrator.migrateDocument.mockImplementation(mockMigrateDocument); const modifiedObj1 = { ...obj1, coreMigrationVersion: '8.0.0' }; - await bulkCreateSuccess(client, repository, [modifiedObj1, obj2]); const docs = [modifiedObj1, obj2].map((x) => ({ ...x, ...mockTimestampFieldsWithCreated })); - expectMigrationArgs(docs[0], true, 1); expectMigrationArgs(docs[1], true, 2); @@ -964,6 +1010,96 @@ describe('SavedObjectsRepository', () => { expect.stringMatching(/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/) ); expect(result.saved_objects[1].id).toEqual(obj2.id); + + // Assert that managed is not changed + expect(result.saved_objects[0].managed).toBeFalsy(); + expect(result.saved_objects[1].managed).toEqual(obj2.managed); + }); + + it(`sets managed=false if not already set`, async () => { + const obj1WithoutManaged = { + type: 'config', + id: '6.0.0-alpha1', + attributes: { title: 'Test One' }, + references: [{ name: 'ref_0', type: 'test', id: '1' }], + }; + const obj2WithoutManaged = { + type: 'index-pattern', + id: 'logstash-*', + attributes: { title: 'Test Two' }, + references: [{ name: 'ref_0', type: 'test', id: '2' }], + }; + const result = await bulkCreateSuccess(client, repository, [ + obj1WithoutManaged, + obj2WithoutManaged, + ]); + expect(result).toEqual({ + saved_objects: [obj1, obj2].map((x) => expectCreateResult(x)), + }); + }); + + it(`sets managed=false only on documents without managed already set`, async () => { + const objWithoutManaged = { + type: 'config', + id: '6.0.0-alpha1', + attributes: { title: 'Test One' }, + references: [{ name: 'ref_0', type: 'test', id: '1' }], + }; + const result = await bulkCreateSuccess(client, repository, [objWithoutManaged, obj2]); + expect(result).toEqual({ + saved_objects: [obj1, obj2].map((x) => expectCreateResult(x)), + }); + }); + + it(`sets managed=true if provided as an override`, async () => { + const obj1WithoutManaged = { + type: 'config', + id: '6.0.0-alpha1', + attributes: { title: 'Test One' }, + references: [{ name: 'ref_0', type: 'test', id: '1' }], + }; + const obj2WithoutManaged = { + type: 'index-pattern', + id: 'logstash-*', + attributes: { title: 'Test Two' }, + references: [{ name: 'ref_0', type: 'test', id: '2' }], + }; + const result = await bulkCreateSuccess( + client, + repository, + [obj1WithoutManaged, obj2WithoutManaged], + { managed: true } + ); + expect(result).toEqual({ + saved_objects: [ + { ...obj1WithoutManaged, managed: true }, + { ...obj2WithoutManaged, managed: true }, + ].map((x) => expectCreateResult(x)), + }); + }); + + it(`sets managed=false if provided as an override`, async () => { + const obj1WithoutManaged = { + type: 'config', + id: '6.0.0-alpha1', + attributes: { title: 'Test One' }, + references: [{ name: 'ref_0', type: 'test', id: '1' }], + }; + const obj2WithoutManaged = { + type: 'index-pattern', + id: 'logstash-*', + attributes: { title: 'Test Two' }, + references: [{ name: 'ref_0', type: 'test', id: '2' }], + }; + const result = await bulkCreateSuccess( + client, + repository, + [obj1WithoutManaged, obj2WithoutManaged], + { managed: false } + ); + expect(result).toEqual({ + saved_objects: [obj1, obj2].map((x) => expectCreateResult(x)), + }); }); }); }); @@ -2392,18 +2528,42 @@ describe('SavedObjectsRepository', () => { }; describe('client calls', () => { - it(`should use the ES index action if ID is not defined and overwrite=true`, async () => { + it(`should use the ES index action if ID is not defined`, async () => { await createSuccess(type, attributes, { overwrite: true }); expect(mockPreflightCheckForCreate).not.toHaveBeenCalled(); expect(client.index).toHaveBeenCalled(); }); + it(`should use the ES index action if ID is not defined and a doc has managed=true`, async () => { + await createSuccess(type, attributes, { overwrite: true, managed: true }); + expect(mockPreflightCheckForCreate).not.toHaveBeenCalled(); + expect(client.index).toHaveBeenCalled(); + }); + + it(`should use the ES index action if ID is not defined and a doc has managed=false`, async () => { + await createSuccess(type, attributes, { overwrite: true, managed: false }); + expect(mockPreflightCheckForCreate).not.toHaveBeenCalled(); + expect(client.index).toHaveBeenCalled(); + }); + it(`should use the ES create action if ID is not defined and overwrite=false`, async () => { await createSuccess(type, attributes); expect(mockPreflightCheckForCreate).not.toHaveBeenCalled(); expect(client.create).toHaveBeenCalled(); }); + it(`should use the ES create action if ID is not defined, overwrite=false and a doc has managed=true`, async () => { + await createSuccess(type, attributes, { managed: true }); + expect(mockPreflightCheckForCreate).not.toHaveBeenCalled(); + expect(client.create).toHaveBeenCalled(); + }); + + it(`should use the ES create action if ID is not defined, overwrite=false and a doc has managed=false`, async () => { + await createSuccess(type, attributes, { managed: false }); + expect(mockPreflightCheckForCreate).not.toHaveBeenCalled(); + expect(client.create).toHaveBeenCalled(); + }); + it(`should use the ES index with version if ID and version are defined and overwrite=true`, async () => { await createSuccess(type, attributes, { id, overwrite: true, version: mockVersion }); expect(mockPreflightCheckForCreate).not.toHaveBeenCalled(); @@ -2885,17 +3045,20 @@ describe('SavedObjectsRepository', () => { it(`migrates a document and serializes the migrated doc`, async () => { const migrationVersion = mockMigrationVersion; const coreMigrationVersion = '8.0.0'; + const managed = false; await createSuccess(type, attributes, { id, references, migrationVersion, coreMigrationVersion, + managed, }); const doc = { type, id, attributes, references, + managed, migrationVersion, coreMigrationVersion, ...mockTimestampFieldsWithCreated, @@ -2906,6 +3069,60 @@ describe('SavedObjectsRepository', () => { expect(serializer.savedObjectToRaw).toHaveBeenLastCalledWith(migratedDoc); }); + it(`migrates a document, adds managed=false and serializes the migrated doc`, async () => { + const migrationVersion = mockMigrationVersion; + const coreMigrationVersion = '8.0.0'; + await createSuccess(type, attributes, { + id, + references, + migrationVersion, + coreMigrationVersion, + managed: undefined, + }); + const doc = { + type, + id, + attributes, + references, + managed: undefined, + migrationVersion, + coreMigrationVersion, + ...mockTimestampFieldsWithCreated, + }; + expectMigrationArgs({ ...doc, managed: false }); + + const migratedDoc = migrator.migrateDocument(doc); + expect(migratedDoc.managed).toBe(false); + expect(serializer.savedObjectToRaw).toHaveBeenLastCalledWith(migratedDoc); + }); + + it(`migrates a document, does not change managed=true to managed=false and serializes the migrated doc`, async () => { + const migrationVersion = mockMigrationVersion; + const coreMigrationVersion = '8.0.0'; + await createSuccess(type, attributes, { + id, + references, + migrationVersion, + coreMigrationVersion, + managed: true, + }); + const doc = { + type, + id, + attributes, + references, + managed: true, + migrationVersion, + coreMigrationVersion, + ...mockTimestampFieldsWithCreated, + }; + expectMigrationArgs(doc); + + const migratedDoc = migrator.migrateDocument(doc); + expect(migratedDoc.managed).toBe(true); + expect(serializer.savedObjectToRaw).toHaveBeenLastCalledWith(migratedDoc); + }); + it(`adds namespace to body when providing namespace for single-namespace type`, async () => { await createSuccess(type, attributes, { id, namespace }); expectMigrationArgs({ namespace }); @@ -2962,6 +3179,27 @@ describe('SavedObjectsRepository', () => { namespaces: [namespace ?? 'default'], coreMigrationVersion: expect.any(String), typeMigrationVersion: '1.1.1', + managed: false, + }); + }); + it(`allows setting 'managed' to true`, async () => { + const result = await createSuccess(MULTI_NAMESPACE_TYPE, attributes, { + id, + namespace, + references, + managed: true, + }); + expect(result).toEqual({ + type: MULTI_NAMESPACE_TYPE, + id, + ...mockTimestampFieldsWithCreated, + version: mockVersion, + attributes, + references, + namespaces: [namespace ?? 'default'], + coreMigrationVersion: expect.any(String), + typeMigrationVersion: '1.1.1', + managed: true, }); }); }); @@ -3549,6 +3787,7 @@ describe('SavedObjectsRepository', () => { 'migrationVersion', 'coreMigrationVersion', 'typeMigrationVersion', + 'managed', 'updated_at', 'created_at', 'originId', diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index a84f6313d462c..0bde80ea0f32f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -120,6 +120,7 @@ import { type Either, isLeft, isRight, + setManaged, } from './internal_utils'; import { collectMultiNamespaceReferences } from './collect_multi_namespace_references'; import { updateObjectsSpaces } from './update_objects_spaces'; @@ -309,6 +310,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { migrationVersion, coreMigrationVersion, typeMigrationVersion, + managed, overwrite = false, references = [], refresh = DEFAULT_REFRESH_SETTING, @@ -389,6 +391,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { migrationVersion, coreMigrationVersion, typeMigrationVersion, + managed: setManaged({ optionsManaged: managed }), created_at: time, updated_at: time, ...(Array.isArray(references) && { references }), @@ -442,6 +445,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { migrationVersionCompatibility, overwrite = false, refresh = DEFAULT_REFRESH_SETTING, + managed: optionsManaged, } = options; const time = getCurrentTime(); @@ -455,9 +459,10 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { } >; const expectedResults = objects.map((object) => { - const { type, id: requestId, initialNamespaces, version } = object; + const { type, id: requestId, initialNamespaces, version, managed } = object; let error: DecoratedError | undefined; let id: string = ''; // Assign to make TS happy, the ID will be validated (or randomly generated if needed) during getValidId below + const objectManaged = managed; if (!this._allowedTypes.includes(type)) { error = SavedObjectsErrorHelpers.createUnsupportedTypeError(type); } else { @@ -484,7 +489,11 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { tag: 'Right', value: { method, - object: { ...object, id }, + object: { + ...object, + id, + managed: setManaged({ optionsManaged, objectManaged }), + }, ...(requiresNamespacesCheck && { preflightCheckIndex: preflightCheckIndexCounter++ }), }, }; @@ -606,6 +615,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { typeMigrationVersion: object.typeMigrationVersion, ...(savedObjectNamespace && { namespace: savedObjectNamespace }), ...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }), + managed: setManaged({ optionsManaged, objectManaged: object.managed }), updated_at: time, created_at: time, references: object.references || [], @@ -686,7 +696,6 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { ); }), }; - return this.optionallyDecryptAndRedactBulkResult(result, authorizationResult?.typeMap, objects); } @@ -2338,6 +2347,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { refresh = DEFAULT_REFRESH_SETTING, initialize = false, upsertAttributes, + managed, } = options; if (!id) { @@ -2409,6 +2419,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { }, migrationVersion, typeMigrationVersion, + managed, updated_at: time, }); @@ -2462,6 +2473,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { references: body.get?._source.references ?? [], version: encodeHitVersion(body), attributes: body.get?._source[type], + ...(managed && { managed }), }; } diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/test_helpers/repository.test.common.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/test_helpers/repository.test.common.ts index 570a1704ab854..43892f6719c19 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/test_helpers/repository.test.common.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/test_helpers/repository.test.common.ts @@ -583,13 +583,23 @@ export const expectBulkGetResult = ( export const getMockBulkCreateResponse = ( objects: SavedObjectsBulkCreateObject[], - namespace?: string + namespace?: string, + managed?: boolean ) => { return { errors: false, took: 1, items: objects.map( - ({ type, id, originId, attributes, references, migrationVersion, typeMigrationVersion }) => ({ + ({ + type, + id, + originId, + attributes, + references, + migrationVersion, + typeMigrationVersion, + managed: docManaged, + }) => ({ create: { // status: 1, // _index: '.kibana', @@ -602,6 +612,7 @@ export const getMockBulkCreateResponse = ( references, ...mockTimestampFieldsWithCreated, typeMigrationVersion: typeMigrationVersion || migrationVersion?.[type] || '1.1.1', + managed: managed ?? docManaged ?? false, }, ...mockVersionProps, }, @@ -616,7 +627,7 @@ export const bulkCreateSuccess = async ( objects: SavedObjectsBulkCreateObject[], options?: SavedObjectsCreateOptions ) => { - const mockResponse = getMockBulkCreateResponse(objects, options?.namespace); + const mockResponse = getMockBulkCreateResponse(objects, options?.namespace, options?.managed); client.bulk.mockResponse(mockResponse); const result = await repository.bulkCreate(objects, options); return result; @@ -626,10 +637,12 @@ export const expectCreateResult = (obj: { type: string; namespace?: string; namespaces?: string[]; + managed?: boolean; }) => ({ ...obj, coreMigrationVersion: expect.any(String), typeMigrationVersion: '1.1.1', + managed: obj.managed ?? false, version: mockVersion, namespaces: obj.namespaces ?? [obj.namespace ?? 'default'], ...mockTimestampFieldsWithCreated, diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts index a933c1be438e9..276682995a4f1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts @@ -55,4 +55,12 @@ export interface SavedObjectsBulkCreateObject { * * For global object types (registered with `namespaceType: 'agnostic'`): this option cannot be used. */ initialNamespaces?: string[]; + /** + * Flag indicating if a saved object is managed by Kibana (default=false) + * + * This can be leveraged by applications to e.g. prevent edits to a managed + * saved object. Instead, users can be guided to create a copy first and + * make their edits to the copy. + */ + managed?: boolean; } diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts index 60b219056f935..fe509b65252da 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts @@ -61,6 +61,14 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { * * For global object types (registered with `namespaceType: 'agnostic'`): this option cannot be used. */ initialNamespaces?: string[]; + /** + * Flag indicating if a saved object is managed by Kibana (default=false) + * + * This can be leveraged by applications to e.g. prevent edits to a managed + * saved object. Instead, users can be guided to create a copy first and + * make their edits to the copy. + */ + managed?: boolean; /** {@link SavedObjectsRawDocParseOptions.migrationVersionCompatibility} */ migrationVersionCompatibility?: 'compatible' | 'raw'; } diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts index 3e186f38916de..7edb0fc97ae13 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts @@ -39,6 +39,16 @@ export interface SavedObjectsIncrementCounterOptions * Attributes to use when upserting the document if it doesn't exist. */ upsertAttributes?: Attributes; + /** + * Flag indicating if a saved object is managed by Kibana (default=false). + * Only used when upserting a saved object. If the saved object already + * exist this option has no effect. + * + * This can be leveraged by applications to e.g. prevent edits to a managed + * saved object. Instead, users can be guided to create a copy first and + * make their edits to the copy. + */ + managed?: boolean; } /** diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.test.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.test.ts index c3ac7222e20c8..85035bfc38610 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.test.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.test.ts @@ -218,6 +218,27 @@ describe('#rawToSavedObject', () => { expect(actual).not.toHaveProperty('coreMigrationVersion'); }); + test('if specified it copies the _source.managed property to managed', () => { + const actual = singleNamespaceSerializer.rawToSavedObject({ + _id: 'foo:bar', + _source: { + type: 'foo', + managed: false, + }, + }); + expect(actual).toHaveProperty('managed', false); + }); + + test(`if _source.managed is unspecified it doesn't set managed`, () => { + const actual = singleNamespaceSerializer.rawToSavedObject({ + _id: 'foo:bar', + _source: { + type: 'foo', + }, + }); + expect(actual).not.toHaveProperty('managed'); + }); + test(`if version is unspecified it doesn't set version`, () => { const actual = singleNamespaceSerializer.rawToSavedObject({ _id: 'foo:bar', @@ -756,6 +777,25 @@ describe('#savedObjectToRaw', () => { expect(actual._source).not.toHaveProperty('coreMigrationVersion'); }); + test('if specified, copies managed property to _source.managed', () => { + const actual = singleNamespaceSerializer.savedObjectToRaw({ + type: '', + attributes: {}, + managed: false, + } as any); + + expect(actual._source).toHaveProperty('managed', false); + }); + + test(`if unspecified it doesn't add managed property to _source`, () => { + const actual = singleNamespaceSerializer.savedObjectToRaw({ + type: '', + attributes: {}, + } as any); + + expect(actual._source.managed).toBe(undefined); + }); + test('it decodes the version property to _seq_no and _primary_term', () => { const actual = singleNamespaceSerializer.savedObjectToRaw({ type: '', diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.ts index f18cc1e832ada..96eded287975c 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.ts @@ -94,6 +94,7 @@ export class SavedObjectsSerializer implements ISavedObjectsSerializer { references, coreMigrationVersion, typeMigrationVersion, + managed, migrationVersion = migrationVersionCompatibility === 'compatible' && typeMigrationVersion ? { [type]: typeMigrationVersion } : undefined, @@ -116,6 +117,7 @@ export class SavedObjectsSerializer implements ISavedObjectsSerializer { ...(originId && { originId }), attributes: _source[type], references: references || [], + ...(managed != null ? { managed } : {}), ...(migrationVersion && { migrationVersion }), ...(coreMigrationVersion && { coreMigrationVersion }), ...(typeMigrationVersion != null ? { typeMigrationVersion } : {}), @@ -146,11 +148,13 @@ export class SavedObjectsSerializer implements ISavedObjectsSerializer { references, coreMigrationVersion, typeMigrationVersion, + managed, } = savedObj; const source = { [type]: attributes, type, references, + ...(managed != null ? { managed } : {}), ...(namespace && this.registry.isSingleNamespace(type) && { namespace }), ...(namespaces && this.registry.isMultiNamespace(type) && { namespaces }), ...(originId && { originId }), @@ -160,7 +164,6 @@ export class SavedObjectsSerializer implements ISavedObjectsSerializer { ...(updated_at && { updated_at }), ...(createdAt && { created_at: createdAt }), }; - return { _id: this.generateRawId(namespace, type, id), _source: source, diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.ts index 21c4c04d25afe..009bf2d89cf46 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.ts @@ -47,4 +47,5 @@ export const createSavedObjectSanitizedDocSchema = (attributesSchema: SavedObjec created_at: schema.maybe(schema.string()), version: schema.maybe(schema.string()), originId: schema.maybe(schema.string()), + managed: schema.maybe(schema.boolean()), }); diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts index d500bbfcd7716..0493652b1192f 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts @@ -207,6 +207,7 @@ export class SavedObjectsClient implements SavedObjectsClientContract { attributes, migrationVersion: options.migrationVersion, typeMigrationVersion: options.typeMigrationVersion, + managed: options.managed, references: options.references, }), }); @@ -217,7 +218,7 @@ export class SavedObjectsClient implements SavedObjectsClientContract { /** * Creates multiple documents at once * - * @param {array} objects - [{ type, id, attributes, references, migrationVersion, typeMigrationVersion }] + * @param {array} objects - [{ type, id, attributes, references, migrationVersion, typeMigrationVersion, managed }] * @param {object} [options={}] * @property {boolean} [options.overwrite=false] * @returns The result of the create operation containing created saved objects. diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts index 751d33f69e7e0..0d57526c065e3 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts @@ -29,6 +29,7 @@ export class SimpleSavedObjectImpl implements SimpleSavedObject public migrationVersion: SavedObjectType['migrationVersion']; public coreMigrationVersion: SavedObjectType['coreMigrationVersion']; public typeMigrationVersion: SavedObjectType['typeMigrationVersion']; + public managed: SavedObjectType['managed']; public error: SavedObjectType['error']; public references: SavedObjectType['references']; public updatedAt: SavedObjectType['updated_at']; @@ -47,6 +48,7 @@ export class SimpleSavedObjectImpl implements SimpleSavedObject migrationVersion, coreMigrationVersion, typeMigrationVersion, + managed, namespaces, updated_at: updatedAt, created_at: createdAt, @@ -60,6 +62,7 @@ export class SimpleSavedObjectImpl implements SimpleSavedObject this.migrationVersion = migrationVersion; this.coreMigrationVersion = coreMigrationVersion; this.typeMigrationVersion = typeMigrationVersion; + this.managed = managed; this.namespaces = namespaces; this.updatedAt = updatedAt; this.createdAt = createdAt; @@ -96,6 +99,7 @@ export class SimpleSavedObjectImpl implements SimpleSavedObject migrationVersion: this.migrationVersion, coreMigrationVersion: this.coreMigrationVersion, typeMigrationVersion: this.typeMigrationVersion, + managed: this.managed, references: this.references, }) .then((sso) => { diff --git a/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts b/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts index 6268c796a5b82..396b7cfa3a965 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts @@ -26,6 +26,7 @@ const simpleSavedObjectMockDefaults: Partial> = { updatedAt: '', createdAt: '', namespaces: undefined, + managed: false, }; const createSimpleSavedObjectMock = ( @@ -40,6 +41,7 @@ const createSimpleSavedObjectMock = ( migrationVersion: savedObject.migrationVersion, coreMigrationVersion: savedObject.coreMigrationVersion, typeMigrationVersion: savedObject.typeMigrationVersion, + managed: savedObject.managed, error: savedObject.error, references: savedObject.references, updatedAt: savedObject.updated_at, diff --git a/packages/core/saved-objects/core-saved-objects-common/src/server_types.ts b/packages/core/saved-objects/core-saved-objects-common/src/server_types.ts index fbdacb73309fa..9ea8f1c9f0668 100644 --- a/packages/core/saved-objects/core-saved-objects-common/src/server_types.ts +++ b/packages/core/saved-objects/core-saved-objects-common/src/server_types.ts @@ -101,4 +101,12 @@ export interface SavedObject { * space. */ originId?: string; + /** + * Flag indicating if a saved object is managed by Kibana (default=false) + * + * This can be leveraged by applications to e.g. prevent edits to a managed + * saved object. Instead, users can be guided to create a copy first and + * make their edits to the copy. + */ + managed?: boolean; } diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/kibana_migrator.test.ts.snap b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/kibana_migrator.test.ts.snap index efbdf0da12f26..b035c64617ca2 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/kibana_migrator.test.ts.snap +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/kibana_migrator.test.ts.snap @@ -8,6 +8,7 @@ Object { "bmap": "510f1f0adb69830cf8a1c5ce2923ed82", "coreMigrationVersion": "2f4316de49999235636386fe51dc06c1", "created_at": "00da57df13e94e9d98437d13ace4bfe0", + "managed": "88cf246b441a6362458cb6a56ca3f7d7", "namespace": "2f4316de49999235636386fe51dc06c1", "namespaces": "2f4316de49999235636386fe51dc06c1", "originId": "2f4316de49999235636386fe51dc06c1", @@ -39,6 +40,9 @@ Object { "created_at": Object { "type": "date", }, + "managed": Object { + "type": "boolean", + }, "namespace": Object { "type": "keyword", }, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/__snapshots__/build_active_mappings.test.ts.snap b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/__snapshots__/build_active_mappings.test.ts.snap index 42aaff1b7f0df..9941f2a70e696 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/__snapshots__/build_active_mappings.test.ts.snap +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/__snapshots__/build_active_mappings.test.ts.snap @@ -8,6 +8,7 @@ Object { "bbb": "18c78c995965207ed3f6e7fc5c6e55fe", "coreMigrationVersion": "2f4316de49999235636386fe51dc06c1", "created_at": "00da57df13e94e9d98437d13ace4bfe0", + "managed": "88cf246b441a6362458cb6a56ca3f7d7", "namespace": "2f4316de49999235636386fe51dc06c1", "namespaces": "2f4316de49999235636386fe51dc06c1", "originId": "2f4316de49999235636386fe51dc06c1", @@ -31,6 +32,9 @@ Object { "created_at": Object { "type": "date", }, + "managed": Object { + "type": "boolean", + }, "namespace": Object { "type": "keyword", }, @@ -74,6 +78,7 @@ Object { "coreMigrationVersion": "2f4316de49999235636386fe51dc06c1", "created_at": "00da57df13e94e9d98437d13ace4bfe0", "firstType": "635418ab953d81d93f1190b70a8d3f57", + "managed": "88cf246b441a6362458cb6a56ca3f7d7", "namespace": "2f4316de49999235636386fe51dc06c1", "namespaces": "2f4316de49999235636386fe51dc06c1", "originId": "2f4316de49999235636386fe51dc06c1", @@ -101,6 +106,9 @@ Object { }, }, }, + "managed": Object { + "type": "boolean", + }, "namespace": Object { "type": "keyword", }, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.ts index 12ed0931ce0c3..7dd13acbe8c7f 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.ts @@ -156,6 +156,9 @@ export function getBaseMappings(): IndexMapping { typeMigrationVersion: { type: 'version', }, + managed: { + type: 'boolean', + }, }, }; } diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts index de44f163ec94e..df710068da324 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts @@ -817,6 +817,7 @@ describe('DocumentMigrator', () => { references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], coreMigrationVersion: '8.8.0', typeMigrationVersion: '1.0.0', + managed: false, }, ]); }); @@ -832,6 +833,7 @@ describe('DocumentMigrator', () => { references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], coreMigrationVersion: '8.8.0', typeMigrationVersion: '1.0.0', + managed: false, namespace: 'foo-namespace', }, ]); @@ -865,6 +867,7 @@ describe('DocumentMigrator', () => { attributes: { name: 'Sweet Peach' }, references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], // no change coreMigrationVersion: '8.8.0', + managed: false, }, ]); }); @@ -881,6 +884,7 @@ describe('DocumentMigrator', () => { references: [{ id: 'uuidv5', type: 'toy', name: 'BALL!' }], // changed coreMigrationVersion: '8.8.0', namespace: 'foo-namespace', + managed: false, }, ]); }); @@ -978,6 +982,7 @@ describe('DocumentMigrator', () => { references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], // no change coreMigrationVersion: '8.8.0', typeMigrationVersion: '1.0.0', + managed: false, namespaces: ['default'], }, ]); @@ -1008,6 +1013,7 @@ describe('DocumentMigrator', () => { typeMigrationVersion: '1.0.0', namespaces: ['foo-namespace'], originId: 'cute', + managed: false, }, { id: 'foo-namespace:dog:cute', @@ -1063,6 +1069,7 @@ describe('DocumentMigrator', () => { references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], // no change coreMigrationVersion: '8.8.0', typeMigrationVersion: '2.0.0', + managed: false, }, ]); }); @@ -1080,6 +1087,7 @@ describe('DocumentMigrator', () => { coreMigrationVersion: '8.8.0', typeMigrationVersion: '2.0.0', namespace: 'foo-namespace', + managed: false, }, ]); }); @@ -1190,6 +1198,7 @@ describe('DocumentMigrator', () => { coreMigrationVersion: '8.8.0', typeMigrationVersion: '2.0.0', namespaces: ['default'], + managed: false, }, ]); }); @@ -1219,6 +1228,7 @@ describe('DocumentMigrator', () => { typeMigrationVersion: '2.0.0', namespaces: ['foo-namespace'], originId: 'pretty', + managed: false, }, { id: 'foo-namespace:dog:pretty', diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/migrations/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/migrations/index.ts index f5a8b313a96e7..04b33a5cc2ac2 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/migrations/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/migrations/index.ts @@ -6,9 +6,17 @@ * Side Public License, v 1. */ +import { flow } from 'lodash'; +import get from 'lodash/fp/get'; import { TransformFn } from '../types'; import { transformMigrationVersion } from './transform_migration_version'; +import { transformSetManagedDefault } from './transform_set_managed_default'; export const migrations = { - '8.8.0': transformMigrationVersion, + '8.8.0': flow( + transformMigrationVersion, + // extract transformedDoc from TransformResult as input to next transform + get('transformedDoc'), + transformSetManagedDefault + ), } as Record; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/migrations/transform_set_managed_default.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/migrations/transform_set_managed_default.test.ts new file mode 100644 index 0000000000000..1b355a176ab6f --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/migrations/transform_set_managed_default.test.ts @@ -0,0 +1,40 @@ +/* + * 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 { transformSetManagedDefault } from './transform_set_managed_default'; + +describe('transformAddManaged', () => { + it('should add managed if not defined', () => { + expect( + transformSetManagedDefault({ + id: 'a', + attributes: {}, + type: 'something', + }) + ).toHaveProperty('transformedDoc.managed'); + }); + it('should not change managed if already defined', () => { + const docWithManagedFalse = transformSetManagedDefault({ + id: 'a', + attributes: {}, + type: 'something', + managed: false, + }); + const docWithManagedTrue = transformSetManagedDefault({ + id: 'a', + attributes: {}, + type: 'something', + managed: true, + }); + [docWithManagedFalse, docWithManagedTrue].forEach((doc) => { + expect(doc.transformedDoc.managed).toBeDefined(); + }); + expect(docWithManagedFalse.transformedDoc.managed).not.toBeTruthy(); + expect(docWithManagedTrue.transformedDoc.managed).toBeTruthy(); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/migrations/transform_set_managed_default.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/migrations/transform_set_managed_default.ts new file mode 100644 index 0000000000000..8e3e1b82bc8cd --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/migrations/transform_set_managed_default.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 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 { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; + +export const transformSetManagedDefault = (doc: SavedObjectUnsanitizedDoc) => ({ + transformedDoc: { ...doc, managed: doc.managed ?? false }, + additionalDocs: [], +}); diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_create.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_create.ts index 68d1a52e63eda..c9341a0af49a4 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_create.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_create.ts @@ -76,6 +76,7 @@ export const registerBulkCreateRoute = ( if (!allowHttpApiAccess) { throwIfAnyTypeNotVisibleByAPI(typesToCheck, savedObjects.typeRegistry); } + const result = await savedObjects.client.bulkCreate(req.body, { overwrite, migrationVersionCompatibility: 'compatible', diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts index 0496db94fa1d6..d6382efd673fa 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts @@ -26,7 +26,12 @@ describe('importDashboards(req)', () => { references: [], version: 'foo', }, - { id: 'panel-01', type: 'visualization', attributes: { visState: '{}' }, references: [] }, + { + id: 'panel-01', + type: 'visualization', + attributes: { visState: '{}' }, + references: [], + }, ]; }); diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts index 0c32f158abd67..808ed9aa9d592 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts @@ -21,8 +21,8 @@ export async function importDashboards( // docs are not seen as automatically up-to-date. const docs = objects .filter((item) => !exclude.includes(item.type)) - // filter out any document version, if present - .map(({ version, ...doc }) => ({ + // filter out any document version and managed, if present + .map(({ version, managed, ...doc }) => ({ ...doc, ...(!doc.migrationVersion && !doc.typeMigrationVersion ? { typeMigrationVersion: '' } : {}), })); diff --git a/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts b/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts index 873dff4ac1b33..8b3b6b4924d79 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts @@ -84,6 +84,7 @@ export interface SavedObjectsRawDocSource { created_at?: string; references?: SavedObjectReference[]; originId?: string; + managed?: boolean; [typeMapping: string]: any; } @@ -106,6 +107,7 @@ interface SavedObjectDoc { updated_at?: string; created_at?: string; originId?: string; + managed?: boolean; } /** diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/batch_size_bytes.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/batch_size_bytes.test.ts index cba827a6e4d5a..7f468e11bae4a 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/batch_size_bytes.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/batch_size_bytes.test.ts @@ -118,7 +118,7 @@ describe('migration v2', () => { await root.preboot(); await root.setup(); await expect(root.start()).rejects.toMatchInlineSnapshot( - `[Error: Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715248 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.]` + `[Error: Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715264 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.]` ); await retryAsync( @@ -131,7 +131,7 @@ describe('migration v2', () => { expect( records.find((rec) => rec.message.startsWith( - `Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715248 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.` + `Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715264 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.` ) ) ).toBeDefined(); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/rewriting_id.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/rewriting_id.test.ts index b5cad9ec53449..6079469cf4982 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/rewriting_id.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/rewriting_id.test.ts @@ -189,6 +189,7 @@ describe('migration v2', () => { namespaces: ['default'], coreMigrationVersion: expect.any(String), typeMigrationVersion: '8.0.0', + managed: false, }, { id: `foo:${newFooId}`, @@ -199,6 +200,7 @@ describe('migration v2', () => { originId: '1', coreMigrationVersion: expect.any(String), typeMigrationVersion: '8.0.0', + managed: false, }, { // new object for spacex:foo:1 @@ -223,6 +225,7 @@ describe('migration v2', () => { namespaces: ['default'], coreMigrationVersion: expect.any(String), typeMigrationVersion: '8.0.0', + managed: false, }, { id: `bar:${newBarId}`, @@ -233,6 +236,7 @@ describe('migration v2', () => { originId: '1', coreMigrationVersion: expect.any(String), typeMigrationVersion: '8.0.0', + managed: false, }, { // new object for spacex:bar:1 diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts index ba8f3c31fc742..687372ed0a559 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts @@ -28,6 +28,7 @@ export const getSavedObjects = (): SavedObject[] => [ id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', coreMigrationVersion: '8.8.0', typeMigrationVersion: '7.11.0', + managed: false, references: [], type: 'index-pattern', updated_at: '2021-08-05T12:23:57.577Z', @@ -50,6 +51,7 @@ export const getSavedObjects = (): SavedObject[] => [ id: 'b80e6540-b891-11e8-a6d9-e546fe2bba5f', coreMigrationVersion: '8.8.0', typeMigrationVersion: '7.14.0', + managed: false, references: [ { id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts index afc9033efa493..7aa32a5c5584c 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts @@ -19,6 +19,7 @@ export const getSavedObjects = (): SavedObject[] => [ version: '1', coreMigrationVersion: '8.8.0', typeMigrationVersion: '7.9.3', + managed: false, attributes: { title: i18n.translate('home.sampleData.flightsSpec.flightLogTitle', { defaultMessage: '[Flights] Flight Log', @@ -78,6 +79,7 @@ export const getSavedObjects = (): SavedObject[] => [ version: '1', coreMigrationVersion: '8.8.0', typeMigrationVersion: '7.14.0', + managed: false, attributes: { title: i18n.translate('home.sampleData.flightsSpec.departuresCountMapTitle', { defaultMessage: '[Flights] Departures Count Map', @@ -290,5 +292,6 @@ export const getSavedObjects = (): SavedObject[] => [ ], coreMigrationVersion: '8.8.0', typeMigrationVersion: '8.7.0', + managed: false, }, ]; diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts index 6d8a2b46f4e72..c0140bd89283d 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts @@ -18,6 +18,7 @@ export const getSavedObjects = (): SavedObject[] => [ version: '1', coreMigrationVersion: '8.8.0', typeMigrationVersion: '8.0.0', + managed: false, attributes: { title: i18n.translate('home.sampleData.logsSpec.visitorsMapTitle', { defaultMessage: '[Logs] Visitors Map', @@ -47,6 +48,7 @@ export const getSavedObjects = (): SavedObject[] => [ version: '1', coreMigrationVersion: '8.8.0', typeMigrationVersion: '7.14.0', + managed: false, attributes: { title: i18n.translate('home.sampleData.logsSpec.heatmapTitle', { defaultMessage: '[Logs] Unique Destination Heatmap', @@ -89,6 +91,7 @@ export const getSavedObjects = (): SavedObject[] => [ version: '1', coreMigrationVersion: '8.8.0', typeMigrationVersion: '7.14.0', + managed: false, attributes: { title: i18n.translate('home.sampleData.logsSpec.bytesDistributionTitle', { defaultMessage: '[Logs] Bytes distribution', @@ -399,6 +402,7 @@ export const getSavedObjects = (): SavedObject[] => [ ], coreMigrationVersion: '8.8.0', typeMigrationVersion: '8.7.0', + managed: false, }, { id: '2f360f30-ea74-11eb-b4c6-3d2afc1cb389', @@ -407,6 +411,7 @@ export const getSavedObjects = (): SavedObject[] => [ version: '1', coreMigrationVersion: '8.8.0', typeMigrationVersion: '7.9.3', + managed: false, attributes: { title: i18n.translate('home.sampleData.logsSpec.discoverTitle', { defaultMessage: '[Logs] Visits', diff --git a/test/api_integration/apis/saved_objects/bulk_create.ts b/test/api_integration/apis/saved_objects/bulk_create.ts index 975eb73a49532..b365df10f7d74 100644 --- a/test/api_integration/apis/saved_objects/bulk_create.ts +++ b/test/api_integration/apis/saved_objects/bulk_create.ts @@ -75,6 +75,7 @@ export default function ({ getService }: FtrProviderContext) { }, coreMigrationVersion: '8.8.0', typeMigrationVersion: resp.body.saved_objects[1].typeMigrationVersion, + managed: resp.body.saved_objects[1].managed, references: [], namespaces: [SPACE_ID], }, diff --git a/test/api_integration/apis/saved_objects/bulk_get.ts b/test/api_integration/apis/saved_objects/bulk_get.ts index d1873b22b0d62..6c97efd94d70a 100644 --- a/test/api_integration/apis/saved_objects/bulk_get.ts +++ b/test/api_integration/apis/saved_objects/bulk_get.ts @@ -74,6 +74,7 @@ export default function ({ getService }: FtrProviderContext) { migrationVersion: resp.body.saved_objects[0].migrationVersion, coreMigrationVersion: '8.8.0', typeMigrationVersion: resp.body.saved_objects[0].typeMigrationVersion, + managed: resp.body.saved_objects[0].managed, namespaces: ['default'], references: [ { @@ -106,6 +107,7 @@ export default function ({ getService }: FtrProviderContext) { migrationVersion: resp.body.saved_objects[2].migrationVersion, coreMigrationVersion: '8.8.0', typeMigrationVersion: resp.body.saved_objects[2].typeMigrationVersion, + managed: resp.body.saved_objects[2].managed, references: [], }, ], diff --git a/test/api_integration/apis/saved_objects/create.ts b/test/api_integration/apis/saved_objects/create.ts index c1977589255a7..865eb44596ec1 100644 --- a/test/api_integration/apis/saved_objects/create.ts +++ b/test/api_integration/apis/saved_objects/create.ts @@ -52,6 +52,7 @@ export default function ({ getService }: FtrProviderContext) { migrationVersion: resp.body.migrationVersion, coreMigrationVersion: '8.8.0', typeMigrationVersion: resp.body.typeMigrationVersion, + managed: resp.body.managed, updated_at: resp.body.updated_at, created_at: resp.body.created_at, version: resp.body.version, @@ -63,6 +64,7 @@ export default function ({ getService }: FtrProviderContext) { }); expect(resp.body.migrationVersion).to.be.ok(); expect(resp.body.typeMigrationVersion).to.be.ok(); + expect(resp.body.managed).to.not.be.ok(); }); }); diff --git a/test/api_integration/apis/saved_objects/export.ts b/test/api_integration/apis/saved_objects/export.ts index bec4305ab7d4c..ec7b82453ffce 100644 --- a/test/api_integration/apis/saved_objects/export.ts +++ b/test/api_integration/apis/saved_objects/export.ts @@ -350,6 +350,7 @@ export default function ({ getService }: FtrProviderContext) { id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', coreMigrationVersion: '8.8.0', typeMigrationVersion: objects[0].typeMigrationVersion, + managed: objects[0].managed, references: [ { id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', @@ -363,6 +364,7 @@ export default function ({ getService }: FtrProviderContext) { version: objects[0].version, }); expect(objects[0].typeMigrationVersion).to.be.ok(); + expect(objects[0].managed).to.not.be.ok(); expect(() => JSON.parse(objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON) ).not.to.throwError(); @@ -411,6 +413,7 @@ export default function ({ getService }: FtrProviderContext) { id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', coreMigrationVersion: '8.8.0', typeMigrationVersion: objects[0].typeMigrationVersion, + managed: objects[0].managed, references: [ { id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', @@ -424,6 +427,7 @@ export default function ({ getService }: FtrProviderContext) { version: objects[0].version, }); expect(objects[0].typeMigrationVersion).to.be.ok(); + expect(objects[0].managed).to.not.be.ok(); expect(() => JSON.parse(objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON) ).not.to.throwError(); @@ -477,6 +481,7 @@ export default function ({ getService }: FtrProviderContext) { id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', coreMigrationVersion: '8.8.0', typeMigrationVersion: objects[0].typeMigrationVersion, + managed: objects[0].managed, references: [ { id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', @@ -490,6 +495,7 @@ export default function ({ getService }: FtrProviderContext) { version: objects[0].version, }); expect(objects[0].typeMigrationVersion).to.be.ok(); + expect(objects[0].managed).to.not.be.ok(); expect(() => JSON.parse(objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON) ).not.to.throwError(); diff --git a/test/api_integration/apis/saved_objects/get.ts b/test/api_integration/apis/saved_objects/get.ts index db127924b5ec6..a7b1f58c531f4 100644 --- a/test/api_integration/apis/saved_objects/get.ts +++ b/test/api_integration/apis/saved_objects/get.ts @@ -39,6 +39,7 @@ export default function ({ getService }: FtrProviderContext) { migrationVersion: resp.body.migrationVersion, coreMigrationVersion: '8.8.0', typeMigrationVersion: resp.body.typeMigrationVersion, + managed: resp.body.managed, attributes: { title: 'Count of requests', description: '', @@ -59,6 +60,7 @@ export default function ({ getService }: FtrProviderContext) { }); expect(resp.body.migrationVersion).to.be.ok(); expect(resp.body.typeMigrationVersion).to.be.ok(); + expect(resp.body.managed).to.not.be.ok(); })); describe('doc does not exist', () => { diff --git a/test/api_integration/apis/saved_objects/resolve.ts b/test/api_integration/apis/saved_objects/resolve.ts index eaae0cb1be4c2..73f7ad03582cf 100644 --- a/test/api_integration/apis/saved_objects/resolve.ts +++ b/test/api_integration/apis/saved_objects/resolve.ts @@ -44,6 +44,7 @@ export default function ({ getService }: FtrProviderContext) { migrationVersion: resp.body.saved_object.migrationVersion, coreMigrationVersion: '8.8.0', typeMigrationVersion: resp.body.saved_object.typeMigrationVersion, + managed: resp.body.saved_object.managed, attributes: { title: 'Count of requests', description: '', @@ -66,6 +67,7 @@ export default function ({ getService }: FtrProviderContext) { }); expect(resp.body.saved_object.migrationVersion).to.be.ok(); expect(resp.body.saved_object.typeMigrationVersion).to.be.ok(); + expect(resp.body.saved_object.managed).to.not.be.ok(); })); describe('doc does not exist', () => { From d44eaaa3cbefa385dabdcb8e3e38d7d6b88b0d9f Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Thu, 20 Apr 2023 13:28:02 -0600 Subject: [PATCH 28/67] [RAM] Fix maintenance window status not archived when there are no events, but past expiration date (#155433) ## Summary Fix a small bug when the user archives a maintenance window with no events, the maintenance window is considered finished, but it should be archived. ### 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 --- .../get_maintenance_window_date_and_status.test.ts | 10 ++++++++++ .../get_maintenance_window_date_and_status.ts | 7 +++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/alerting/server/maintenance_window_client/get_maintenance_window_date_and_status.test.ts b/x-pack/plugins/alerting/server/maintenance_window_client/get_maintenance_window_date_and_status.test.ts index 944c78b7fe774..c0a61f7097f80 100644 --- a/x-pack/plugins/alerting/server/maintenance_window_client/get_maintenance_window_date_and_status.test.ts +++ b/x-pack/plugins/alerting/server/maintenance_window_client/get_maintenance_window_date_and_status.test.ts @@ -69,6 +69,16 @@ describe('getMaintenanceWindowDateAndStatus', () => { expect(result.eventEndTime).toEqual('2023-03-25T01:00:00.000Z'); expect(result.status).toEqual('archived'); + result = getMaintenanceWindowDateAndStatus({ + events: [], + dateToCompare: new Date(), + expirationDate: moment().subtract(1, 'minute').toDate(), + }); + + expect(result.eventStartTime).toEqual(null); + expect(result.eventEndTime).toEqual(null); + expect(result.status).toEqual('archived'); + jest.useFakeTimers().setSystemTime(new Date('2023-03-28T00:30:00.000Z')); result = getMaintenanceWindowDateAndStatus({ events, diff --git a/x-pack/plugins/alerting/server/maintenance_window_client/get_maintenance_window_date_and_status.ts b/x-pack/plugins/alerting/server/maintenance_window_client/get_maintenance_window_date_and_status.ts index 73a5cea9ff671..4cd1e3322134b 100644 --- a/x-pack/plugins/alerting/server/maintenance_window_client/get_maintenance_window_date_and_status.ts +++ b/x-pack/plugins/alerting/server/maintenance_window_client/get_maintenance_window_date_and_status.ts @@ -30,12 +30,15 @@ export const getMaintenanceWindowDateAndStatus = ({ dateToCompare: Date; expirationDate: Date; }): MaintenanceWindowDateAndStatus => { - // No events, status is finished + // No events, status is finished or archived if (!events.length) { + const status = moment.utc(expirationDate).isBefore(dateToCompare) + ? MaintenanceWindowStatus.Archived + : MaintenanceWindowStatus.Finished; return { eventStartTime: null, eventEndTime: null, - status: MaintenanceWindowStatus.Finished, + status, }; } From ef218cd70423916763e5622d04029e722d209be2 Mon Sep 17 00:00:00 2001 From: Carlos Crespo Date: Thu, 20 Apr 2023 16:31:27 -0300 Subject: [PATCH 29/67] [Infrastructure UI] New Infra api (#155084) closes: [#152103](https://github.com/elastic/kibana/issues/152103) fixes: https://github.com/elastic/kibana/issues/151768 ## Summary This PR adds a new API to return host metrics for the Hosts View page. The difference between this API and Snapshot API is that this one runs a terms aggregation and the API can return a range of 1 to 500 hosts, preventing it from returning all data. It uses Inventory Model aggregations, so the performance should be similar to that of the Snapshot API. The `limit` parameter is what will allow the client to try to get a better response time from the API. #### Snapshot API Returns all 500 hosts 15 minutes image 1 day image 1 week image #### Hosts API 100 hosts limit 15 minutes image 1 day image 1 week image ### How to test ```bash curl --location -u elastic:changeme 'http://0.0.0.0:5601/ftw/api/metrics/infra' \ --header 'kbn-xsrf: xxxx' \ --header 'Content-Type: application/json' \ --data '{ "type": "host", "limit": 100, "metrics": [ { "type": "rx" }, { "type": "tx" }, { "type": "memory" }, { "type": "cpu" }, { "type": "diskLatency" }, { "type": "memoryTotal" } ], "query": { "bool": { "must": [], "filter": [], "should": [], "must_not": [] } }, "range": { "from": "2023-04-18T11:15:31.407Z", "to": "2023-04-18T11:30:31.407Z" }, "sourceId": "default" }' ``` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-io-ts-utils/index.ts | 8 + .../kbn-io-ts-utils/src/date_rt/index.test.ts | 24 ++ packages/kbn-io-ts-utils/src/date_rt/index.ts | 22 ++ .../src/in_range_rt/index.test.ts | 24 ++ .../kbn-io-ts-utils/src/in_range_rt/index.ts | 23 ++ .../src/is_greater_or_equal/index.test.ts | 24 ++ .../src/is_greater_or_equal/index.ts | 23 ++ .../src/route_validation/index.ts | 53 ++++ packages/kbn-io-ts-utils/tsconfig.json | 3 +- x-pack/plugins/infra/common/http_api/index.ts | 1 + .../http_api/infra/get_infra_metrics.ts | 80 ++++++ .../infra/common/http_api/infra/index.ts | 8 + x-pack/plugins/infra/server/infra_server.ts | 2 + .../infra/server/routes/infra/README.md | 115 ++++++++ .../infra/server/routes/infra/index.ts | 62 ++++ .../server/routes/infra/lib/constants.ts | 34 +++ .../server/routes/infra/lib/helpers/query.ts | 100 +++++++ .../routes/infra/lib/host/get_all_hosts.ts | 77 +++++ .../infra/lib/host/get_filtered_hosts.ts | 65 +++++ .../server/routes/infra/lib/host/get_hosts.ts | 35 +++ .../server/routes/infra/lib/mapper.test.ts | 113 ++++++++ .../infra/server/routes/infra/lib/mapper.ts | 94 +++++++ .../infra/server/routes/infra/lib/types.ts | 92 ++++++ .../server/routes/infra/lib/utils.test.ts | 61 ++++ .../infra/server/routes/infra/lib/utils.ts | 49 ++++ .../api_integration/apis/metrics_ui/index.js | 1 + .../api_integration/apis/metrics_ui/infra.ts | 265 ++++++++++++++++++ 27 files changed, 1457 insertions(+), 1 deletion(-) create mode 100644 packages/kbn-io-ts-utils/src/date_rt/index.test.ts create mode 100644 packages/kbn-io-ts-utils/src/date_rt/index.ts create mode 100644 packages/kbn-io-ts-utils/src/in_range_rt/index.test.ts create mode 100644 packages/kbn-io-ts-utils/src/in_range_rt/index.ts create mode 100644 packages/kbn-io-ts-utils/src/is_greater_or_equal/index.test.ts create mode 100644 packages/kbn-io-ts-utils/src/is_greater_or_equal/index.ts create mode 100644 packages/kbn-io-ts-utils/src/route_validation/index.ts create mode 100644 x-pack/plugins/infra/common/http_api/infra/get_infra_metrics.ts create mode 100644 x-pack/plugins/infra/common/http_api/infra/index.ts create mode 100644 x-pack/plugins/infra/server/routes/infra/README.md create mode 100644 x-pack/plugins/infra/server/routes/infra/index.ts create mode 100644 x-pack/plugins/infra/server/routes/infra/lib/constants.ts create mode 100644 x-pack/plugins/infra/server/routes/infra/lib/helpers/query.ts create mode 100644 x-pack/plugins/infra/server/routes/infra/lib/host/get_all_hosts.ts create mode 100644 x-pack/plugins/infra/server/routes/infra/lib/host/get_filtered_hosts.ts create mode 100644 x-pack/plugins/infra/server/routes/infra/lib/host/get_hosts.ts create mode 100644 x-pack/plugins/infra/server/routes/infra/lib/mapper.test.ts create mode 100644 x-pack/plugins/infra/server/routes/infra/lib/mapper.ts create mode 100644 x-pack/plugins/infra/server/routes/infra/lib/types.ts create mode 100644 x-pack/plugins/infra/server/routes/infra/lib/utils.test.ts create mode 100644 x-pack/plugins/infra/server/routes/infra/lib/utils.ts create mode 100644 x-pack/test/api_integration/apis/metrics_ui/infra.ts diff --git a/packages/kbn-io-ts-utils/index.ts b/packages/kbn-io-ts-utils/index.ts index 4e3eaeb9af92b..e52e4d429829e 100644 --- a/packages/kbn-io-ts-utils/index.ts +++ b/packages/kbn-io-ts-utils/index.ts @@ -20,3 +20,11 @@ export { toBooleanRt } from './src/to_boolean_rt'; export { toJsonSchema } from './src/to_json_schema'; export { nonEmptyStringRt } from './src/non_empty_string_rt'; export { createLiteralValueFromUndefinedRT } from './src/literal_value_from_undefined_rt'; +export { createRouteValidationFunction } from './src/route_validation'; +export { inRangeRt, type InRangeBrand, type InRange } from './src/in_range_rt'; +export { dateRt } from './src/date_rt'; +export { + isGreaterOrEqualRt, + type IsGreaterOrEqualBrand, + type IsGreaterOrEqual, +} from './src/is_greater_or_equal'; diff --git a/packages/kbn-io-ts-utils/src/date_rt/index.test.ts b/packages/kbn-io-ts-utils/src/date_rt/index.test.ts new file mode 100644 index 0000000000000..088caf133e62c --- /dev/null +++ b/packages/kbn-io-ts-utils/src/date_rt/index.test.ts @@ -0,0 +1,24 @@ +/* + * 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 { dateRt } from '.'; +import { isRight } from 'fp-ts/lib/Either'; + +describe('dateRt', () => { + it('passes if it is a valide date/time', () => { + expect(isRight(dateRt.decode('2019-08-20T11:18:31.407Z'))).toBe(true); + }); + + it('passes if it is a valide date', () => { + expect(isRight(dateRt.decode('2019-08-20'))).toBe(true); + }); + + it('fails if it is an invalide date/time', () => { + expect(isRight(dateRt.decode('2019-02-30T11:18:31.407Z'))).toBe(false); + }); +}); diff --git a/packages/kbn-io-ts-utils/src/date_rt/index.ts b/packages/kbn-io-ts-utils/src/date_rt/index.ts new file mode 100644 index 0000000000000..c3a8d40a1db57 --- /dev/null +++ b/packages/kbn-io-ts-utils/src/date_rt/index.ts @@ -0,0 +1,22 @@ +/* + * 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 * as rt from 'io-ts'; +import moment from 'moment'; + +export interface DateBrand { + readonly Date: unique symbol; +} + +export type Date = rt.Branded; + +export const dateRt = rt.brand( + rt.string, + (date): date is Date => moment(date, true).isValid(), + 'Date' +); diff --git a/packages/kbn-io-ts-utils/src/in_range_rt/index.test.ts b/packages/kbn-io-ts-utils/src/in_range_rt/index.test.ts new file mode 100644 index 0000000000000..488e8d2bbd4fa --- /dev/null +++ b/packages/kbn-io-ts-utils/src/in_range_rt/index.test.ts @@ -0,0 +1,24 @@ +/* + * 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 { inRangeRt } from '.'; +import { isRight } from 'fp-ts/lib/Either'; + +describe('inRangeRT', () => { + test('passes if value is within range', () => { + expect(isRight(inRangeRt(1, 100).decode(50))).toBe(true); + }); + + test('fails if value above the range', () => { + expect(isRight(inRangeRt(1, 100).decode(101))).toBe(false); + }); + + test('fails if value below the range', () => { + expect(isRight(inRangeRt(1, 100).decode(0))).toBe(false); + }); +}); diff --git a/packages/kbn-io-ts-utils/src/in_range_rt/index.ts b/packages/kbn-io-ts-utils/src/in_range_rt/index.ts new file mode 100644 index 0000000000000..ae3da3d7d2d69 --- /dev/null +++ b/packages/kbn-io-ts-utils/src/in_range_rt/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 * as rt from 'io-ts'; + +export interface InRangeBrand { + readonly InRange: unique symbol; +} + +export type InRange = rt.Branded; + +export const inRangeRt = (start: number, end: number) => + rt.brand( + rt.number, // codec + (n): n is InRange => n >= start && n <= end, + // refinement of the number type + 'InRange' // name of this codec + ); diff --git a/packages/kbn-io-ts-utils/src/is_greater_or_equal/index.test.ts b/packages/kbn-io-ts-utils/src/is_greater_or_equal/index.test.ts new file mode 100644 index 0000000000000..0698fe0485436 --- /dev/null +++ b/packages/kbn-io-ts-utils/src/is_greater_or_equal/index.test.ts @@ -0,0 +1,24 @@ +/* + * 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 { isGreaterOrEqualRt } from '.'; +import { isRight } from 'fp-ts/lib/Either'; + +describe('inRangeRT', () => { + test('passes if value is a positive number', () => { + expect(isRight(isGreaterOrEqualRt(0).decode(1))).toBe(true); + }); + + test('passes if value is 0', () => { + expect(isRight(isGreaterOrEqualRt(0).decode(0))).toBe(true); + }); + + test('fails if value is a negative number', () => { + expect(isRight(isGreaterOrEqualRt(0).decode(-1))).toBe(false); + }); +}); diff --git a/packages/kbn-io-ts-utils/src/is_greater_or_equal/index.ts b/packages/kbn-io-ts-utils/src/is_greater_or_equal/index.ts new file mode 100644 index 0000000000000..0eb59269485b2 --- /dev/null +++ b/packages/kbn-io-ts-utils/src/is_greater_or_equal/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 * as rt from 'io-ts'; + +export interface IsGreaterOrEqualBrand { + readonly IsGreaterOrEqual: unique symbol; +} + +export type IsGreaterOrEqual = rt.Branded; + +export const isGreaterOrEqualRt = (value: number) => + rt.brand( + rt.number, // codec + (n): n is IsGreaterOrEqual => n >= value, + // refinement of the number type + 'IsGreaterOrEqual' // name of this codec + ); diff --git a/packages/kbn-io-ts-utils/src/route_validation/index.ts b/packages/kbn-io-ts-utils/src/route_validation/index.ts new file mode 100644 index 0000000000000..85532c2d5f9eb --- /dev/null +++ b/packages/kbn-io-ts-utils/src/route_validation/index.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 type { RouteValidationFunction } from '@kbn/core/server'; +import { fold } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { Context, Errors, IntersectionType, Type, UnionType, ValidationError } from 'io-ts'; + +type ValdidationResult = ReturnType>; + +export const createRouteValidationFunction = + ( + runtimeType: Type + ): RouteValidationFunction => + (inputValue, { badRequest, ok }) => + pipe( + runtimeType.decode(inputValue), + fold>( + (errors: Errors) => badRequest(formatErrors(errors)), + (result: DecodedValue) => ok(result) + ) + ); + +const getErrorPath = ([first, ...rest]: Context): string[] => { + if (typeof first === 'undefined') { + return []; + } else if (first.type instanceof IntersectionType) { + const [, ...next] = rest; + return getErrorPath(next); + } else if (first.type instanceof UnionType) { + const [, ...next] = rest; + return [first.key, ...getErrorPath(next)]; + } + + return [first.key, ...getErrorPath(rest)]; +}; + +const getErrorType = ({ context }: ValidationError) => + context[context.length - 1]?.type?.name ?? 'unknown'; + +const formatError = (error: ValidationError) => + error.message ?? + `in ${getErrorPath(error.context).join('/')}: ${JSON.stringify( + error.value + )} does not match expected type ${getErrorType(error)}`; + +export const formatErrors = (errors: ValidationError[]) => + `Failed to validate: \n${errors.map((error) => ` ${formatError(error)}`).join('\n')}`; diff --git a/packages/kbn-io-ts-utils/tsconfig.json b/packages/kbn-io-ts-utils/tsconfig.json index d5fd475db97a1..e44c93a0c24b0 100644 --- a/packages/kbn-io-ts-utils/tsconfig.json +++ b/packages/kbn-io-ts-utils/tsconfig.json @@ -11,7 +11,8 @@ "**/*.ts" ], "kbn_references": [ - "@kbn/config-schema" + "@kbn/config-schema", + "@kbn/core" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/infra/common/http_api/index.ts b/x-pack/plugins/infra/common/http_api/index.ts index 52906bcabb65a..934e4200cb31c 100644 --- a/x-pack/plugins/infra/common/http_api/index.ts +++ b/x-pack/plugins/infra/common/http_api/index.ts @@ -13,3 +13,4 @@ export * from './metrics_api'; export * from './log_alerts'; export * from './snapshot_api'; export * from './host_details'; +export * from './infra'; diff --git a/x-pack/plugins/infra/common/http_api/infra/get_infra_metrics.ts b/x-pack/plugins/infra/common/http_api/infra/get_infra_metrics.ts new file mode 100644 index 0000000000000..bcfeeafcee06f --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/infra/get_infra_metrics.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createLiteralValueFromUndefinedRT, inRangeRt, dateRt } from '@kbn/io-ts-utils'; +import * as rt from 'io-ts'; + +export const InfraMetricTypeRT = rt.keyof({ + cpu: null, + diskLatency: null, + memory: null, + memoryTotal: null, + rx: null, + tx: null, +}); + +export const RangeRT = rt.type({ + from: dateRt, + to: dateRt, +}); + +export const InfraAssetMetadataTypeRT = rt.keyof({ + 'host.os.name': null, + 'cloud.provider': null, +}); + +export const InfraAssetMetricsRT = rt.type({ + name: InfraMetricTypeRT, + value: rt.union([rt.number, rt.null]), +}); + +export const InfraAssetMetadataRT = rt.type({ + // keep the actual field name from the index mappings + name: InfraAssetMetadataTypeRT, + value: rt.union([rt.string, rt.number, rt.null]), +}); + +export const GetInfraMetricsRequestBodyPayloadRT = rt.intersection([ + rt.partial({ + query: rt.UnknownRecord, + }), + rt.type({ + type: rt.literal('host'), + limit: rt.union([inRangeRt(1, 500), createLiteralValueFromUndefinedRT(20)]), + metrics: rt.array(rt.type({ type: InfraMetricTypeRT })), + sourceId: rt.string, + range: RangeRT, + }), +]); + +export const InfraAssetMetricsItemRT = rt.type({ + name: rt.string, + metrics: rt.array(InfraAssetMetricsRT), + metadata: rt.array(InfraAssetMetadataRT), +}); + +export const GetInfraMetricsResponsePayloadRT = rt.type({ + type: rt.literal('host'), + nodes: rt.array(InfraAssetMetricsItemRT), +}); + +export type InfraAssetMetrics = rt.TypeOf; +export type InfraAssetMetadata = rt.TypeOf; +export type InfraAssetMetricType = rt.TypeOf; +export type InfraAssetMetricsItem = rt.TypeOf; + +export type GetInfraMetricsRequestBodyPayload = Omit< + rt.TypeOf, + 'limit' | 'range' +> & { + limit?: number; + range: { + from: string; + to: string; + }; +}; +export type GetInfraMetricsResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/http_api/infra/index.ts b/x-pack/plugins/infra/common/http_api/infra/index.ts new file mode 100644 index 0000000000000..75b58fbcf2442 --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/infra/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 './get_infra_metrics'; diff --git a/x-pack/plugins/infra/server/infra_server.ts b/x-pack/plugins/infra/server/infra_server.ts index 18f6c943f234b..bdd4f7d841217 100644 --- a/x-pack/plugins/infra/server/infra_server.ts +++ b/x-pack/plugins/infra/server/infra_server.ts @@ -35,6 +35,7 @@ import { initNodeDetailsRoute } from './routes/node_details'; import { initOverviewRoute } from './routes/overview'; import { initProcessListRoute } from './routes/process_list'; import { initSnapshotRoute } from './routes/snapshot'; +import { initInfraMetricsRoute } from './routes/infra'; export const initInfraServer = (libs: InfraBackendLibs) => { initIpToHostName(libs); @@ -63,4 +64,5 @@ export const initInfraServer = (libs: InfraBackendLibs) => { initGetLogAlertsChartPreviewDataRoute(libs); initProcessListRoute(libs); initOverviewRoute(libs); + initInfraMetricsRoute(libs); }; diff --git a/x-pack/plugins/infra/server/routes/infra/README.md b/x-pack/plugins/infra/server/routes/infra/README.md new file mode 100644 index 0000000000000..50a65ce9c89ba --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra/README.md @@ -0,0 +1,115 @@ +# Infra Hosts API + +This API returns a list of hosts and their metrics. + +**POST /api/metrics/infra** +parameters: + +- type: asset type. 'host' is the only one supported now +- metrics: list of metrics to be calculated and returned for each host +- sourceId: sourceId to retrieve configuration such as index-pattern used to query the results +- limit: max number of hosts - max 500 +- timeRange: time range object containing start and end attributes - passed in timestamp +- (optional) query: filter + +The response includes: + +- hosts: array of metrics and metadata +- metrics: object containing name of the metric and value +- metadata: object containing name of the metadata and value + +## Examples + +Request + +```bash +curl --location -u elastic:changeme 'http://0.0.0.0:5601/ftw/api/metrics/infra' \ +--header 'kbn-xsrf: xxxx' \ +--header 'Content-Type: application/json' \ +--data '{ + "type": 'host', + "limit": 100, + "metrics": [ + { + "type": "rx" + }, + { + "type": "tx" + }, + { + "type": "memory" + }, + { + "type": "cpu" + }, + { + "type": "diskLatency" + }, + { + "type": "memoryTotal" + } + ], + "query": { + "bool": { + "must": [], + "filter": [], + "should": [], + "must_not": [] + } + }, + "range": { + "from": "2023-04-18T11:15:31.407Z", + "to": "2023-04-18T11:30:31.407Z" + }, + "sourceId": "default" +}' +``` + +Response + +```json +{ + "type": "host", + "nodes":[ + { + "metadata":[ + { + "name":"host.os.name", + "value":null + }, + { + "name":"cloud.provider", + "value":null + } + ], + "metrics":[ + { + "name":"cpu", + "value":0.13271302652800487 + }, + { + "name":"diskLatency", + "value":0 + }, + { + "name":"memory", + "value":0.542838307852529 + }, + { + "name":"memoryTotal", + "value":66640704.099216014 + }, + { + "name":"rx", + "value":3959.4930095127706 + }, + { + "name":"tx", + "value":100.26926542816672 + } + ], + "name":"host-0" + } + ] +} +``` diff --git a/x-pack/plugins/infra/server/routes/infra/index.ts b/x-pack/plugins/infra/server/routes/infra/index.ts new file mode 100644 index 0000000000000..d98e8034e6207 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra/index.ts @@ -0,0 +1,62 @@ +/* + * 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 Boom from '@hapi/boom'; +import { createRouteValidationFunction } from '@kbn/io-ts-utils'; + +import { + GetInfraMetricsRequestBodyPayloadRT, + GetInfraMetricsRequestBodyPayload, + GetInfraMetricsResponsePayloadRT, +} from '../../../common/http_api/infra'; +import { InfraBackendLibs } from '../../lib/infra_types'; +import { getHosts } from './lib/host/get_hosts'; + +export const initInfraMetricsRoute = (libs: InfraBackendLibs) => { + const validateBody = createRouteValidationFunction(GetInfraMetricsRequestBodyPayloadRT); + + const { framework } = libs; + + framework.registerRoute( + { + method: 'post', + path: '/api/metrics/infra', + validate: { + body: validateBody, + }, + }, + async (_, request, response) => { + const [{ savedObjects }, { data }] = await libs.getStartServices(); + const params: GetInfraMetricsRequestBodyPayload = request.body; + + try { + const searchClient = data.search.asScoped(request); + const soClient = savedObjects.getScopedClient(request); + const source = await libs.sources.getSourceConfiguration(soClient, params.sourceId); + + const hosts = await getHosts({ searchClient, sourceConfig: source.configuration, params }); + return response.ok({ + body: GetInfraMetricsResponsePayloadRT.encode(hosts), + }); + } catch (err) { + if (Boom.isBoom(err)) { + return response.customError({ + statusCode: err.output.statusCode, + body: { message: err.output.payload.message }, + }); + } + + return response.customError({ + statusCode: err.statusCode ?? 500, + body: { + message: err.message ?? 'An unexpected error occurred', + }, + }); + } + } + ); +}; diff --git a/x-pack/plugins/infra/server/routes/infra/lib/constants.ts b/x-pack/plugins/infra/server/routes/infra/lib/constants.ts new file mode 100644 index 0000000000000..bc9131d1d52fc --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra/lib/constants.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { estypes } from '@elastic/elasticsearch'; + +export const BUCKET_KEY = 'host.name'; +export const METADATA_AGGREGATION_NAME = 'metadata'; +export const FILTER_AGGREGATION_SUB_AGG_NAME = 'result'; +export const INVENTORY_MODEL_NODE_TYPE = 'host'; + +export const MAX_SIZE = 500; + +export const METADATA_AGGREGATION: Record = { + [METADATA_AGGREGATION_NAME]: { + top_metrics: { + metrics: [ + { + field: 'host.os.name', + }, + { + field: 'cloud.provider', + }, + ], + size: 1, + sort: { + '@timestamp': 'desc', + }, + }, + }, +}; diff --git a/x-pack/plugins/infra/server/routes/infra/lib/helpers/query.ts b/x-pack/plugins/infra/server/routes/infra/lib/helpers/query.ts new file mode 100644 index 0000000000000..30a65333987fb --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra/lib/helpers/query.ts @@ -0,0 +1,100 @@ +/* + * 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 { estypes } from '@elastic/elasticsearch'; +import { ISearchClient } from '@kbn/data-plugin/common'; +import { ESSearchRequest } from '@kbn/es-types'; +import { catchError, map, Observable } from 'rxjs'; +import { findInventoryModel } from '../../../../../common/inventory_models'; +import { + GetInfraMetricsRequestBodyPayload, + InfraAssetMetricType, +} from '../../../../../common/http_api/infra'; +import { INVENTORY_MODEL_NODE_TYPE } from '../constants'; + +export const createFilters = ({ + params, + extraFilter, + hostNamesShortList = [], +}: { + params: GetInfraMetricsRequestBodyPayload; + hostNamesShortList?: string[]; + extraFilter?: estypes.QueryDslQueryContainer; +}) => { + const extrafilterClause = extraFilter?.bool?.filter; + const extraFilterList = !!extrafilterClause + ? Array.isArray(extrafilterClause) + ? extrafilterClause + : [extrafilterClause] + : []; + + const hostNamesFilter = + hostNamesShortList.length > 0 + ? [ + { + terms: { + 'host.name': hostNamesShortList, + }, + }, + ] + : []; + + return [ + ...hostNamesFilter, + ...extraFilterList, + { + range: { + '@timestamp': { + gte: new Date(params.range.from).getTime(), + lte: new Date(params.range.to).getTime(), + format: 'epoch_millis', + }, + }, + }, + { + exists: { + field: 'host.name', + }, + }, + ]; +}; + +export const runQuery = ( + serchClient: ISearchClient, + queryRequest: ESSearchRequest, + decoder: (aggregation: Record | undefined) => T | undefined +): Observable => { + return serchClient + .search({ + params: queryRequest, + }) + .pipe( + map((res) => decoder(res.rawResponse.aggregations)), + catchError((err) => { + const error = { + message: err.message, + statusCode: err.statusCode, + attributes: err.errBody?.error, + }; + + throw error; + }) + ); +}; + +export const getInventoryModelAggregations = ( + metrics: InfraAssetMetricType[] +): Record => { + const inventoryModel = findInventoryModel(INVENTORY_MODEL_NODE_TYPE); + return metrics.reduce( + (acc, metric) => ({ + ...acc, + ...inventoryModel.metrics.snapshot?.[metric], + }), + {} + ); +}; diff --git a/x-pack/plugins/infra/server/routes/infra/lib/host/get_all_hosts.ts b/x-pack/plugins/infra/server/routes/infra/lib/host/get_all_hosts.ts new file mode 100644 index 0000000000000..e63f0d78b367d --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra/lib/host/get_all_hosts.ts @@ -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 { estypes } from '@elastic/elasticsearch'; +import { lastValueFrom } from 'rxjs'; +import { ESSearchRequest } from '@kbn/es-types'; +import { InfraStaticSourceConfiguration } from '../../../../lib/sources'; +import { decodeOrThrow } from '../../../../../common/runtime_types'; +import { GetInfraMetricsRequestBodyPayload } from '../../../../../common/http_api/infra'; +import { BUCKET_KEY, MAX_SIZE, METADATA_AGGREGATION } from '../constants'; +import { + GetHostsArgs, + HostsMetricsSearchAggregationResponse, + HostsMetricsSearchAggregationResponseRT, +} from '../types'; +import { createFilters, getInventoryModelAggregations, runQuery } from '../helpers/query'; + +export const getAllHosts = async ( + { searchClient, sourceConfig, params }: GetHostsArgs, + hostNamesShortList: string[] = [] +): Promise => { + const query = createQuery(params, sourceConfig, hostNamesShortList); + return lastValueFrom( + runQuery(searchClient, query, decodeOrThrow(HostsMetricsSearchAggregationResponseRT)) + ); +}; + +const createQuery = ( + params: GetInfraMetricsRequestBodyPayload, + sourceConfig: InfraStaticSourceConfiguration, + hostNamesShortList: string[] +): ESSearchRequest => { + const metricAggregations = getInventoryModelAggregations(params.metrics.map((p) => p.type)); + + return { + allow_no_indices: true, + ignore_unavailable: true, + index: sourceConfig.metricAlias, + body: { + size: 0, + query: { + bool: { + filter: createFilters({ + params, + hostNamesShortList, + }), + }, + }, + aggs: createAggregations(params, metricAggregations), + }, + }; +}; + +const createAggregations = ( + { limit }: GetInfraMetricsRequestBodyPayload, + metricAggregations: Record +): Record => { + return { + nodes: { + terms: { + field: BUCKET_KEY, + size: limit ?? MAX_SIZE, + order: { + _key: 'asc', + }, + }, + aggs: { + ...metricAggregations, + ...METADATA_AGGREGATION, + }, + }, + }; +}; diff --git a/x-pack/plugins/infra/server/routes/infra/lib/host/get_filtered_hosts.ts b/x-pack/plugins/infra/server/routes/infra/lib/host/get_filtered_hosts.ts new file mode 100644 index 0000000000000..95b8753487cf7 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra/lib/host/get_filtered_hosts.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 { ESSearchRequest } from '@kbn/es-types'; +import { lastValueFrom } from 'rxjs'; + +import { InfraStaticSourceConfiguration } from '../../../../lib/sources'; +import { decodeOrThrow } from '../../../../../common/runtime_types'; +import { GetInfraMetricsRequestBodyPayload } from '../../../../../common/http_api/infra'; +import { + FilteredHostsSearchAggregationResponseRT, + FilteredHostsSearchAggregationResponse, + GetHostsArgs, +} from '../types'; +import { BUCKET_KEY, MAX_SIZE } from '../constants'; +import { assertQueryStructure } from '../utils'; +import { createFilters, runQuery } from '../helpers/query'; + +export const getFilteredHosts = async ({ + searchClient, + sourceConfig, + params, +}: GetHostsArgs): Promise => { + const query = createQuery(params, sourceConfig); + return lastValueFrom( + runQuery(searchClient, query, decodeOrThrow(FilteredHostsSearchAggregationResponseRT)) + ); +}; + +const createQuery = ( + params: GetInfraMetricsRequestBodyPayload, + sourceConfig: InfraStaticSourceConfiguration +): ESSearchRequest => { + assertQueryStructure(params.query); + + return { + allow_no_indices: true, + ignore_unavailable: true, + index: sourceConfig.metricAlias, + body: { + size: 0, + query: { + bool: { + ...params.query.bool, + filter: createFilters({ params, extraFilter: params.query }), + }, + }, + aggs: { + nodes: { + terms: { + size: params.limit ?? MAX_SIZE, + field: BUCKET_KEY, + order: { + _key: 'asc', + }, + }, + }, + }, + }, + }; +}; diff --git a/x-pack/plugins/infra/server/routes/infra/lib/host/get_hosts.ts b/x-pack/plugins/infra/server/routes/infra/lib/host/get_hosts.ts new file mode 100644 index 0000000000000..6d44224661750 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra/lib/host/get_hosts.ts @@ -0,0 +1,35 @@ +/* + * 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 { GetInfraMetricsResponsePayload } from '../../../../../common/http_api/infra'; +import { getFilteredHosts } from './get_filtered_hosts'; +import { mapToApiResponse } from '../mapper'; +import { hasFilters } from '../utils'; +import { GetHostsArgs } from '../types'; +import { getAllHosts } from './get_all_hosts'; + +export const getHosts = async (args: GetHostsArgs): Promise => { + const runFilterQuery = hasFilters(args.params.query); + // filter first to prevent filter clauses from impacting the metrics aggregations. + const hostNamesShortList = runFilterQuery ? await getFilteredHostNames(args) : []; + if (runFilterQuery && hostNamesShortList.length === 0) { + return { + type: 'host', + nodes: [], + }; + } + + const result = await getAllHosts(args, hostNamesShortList); + return mapToApiResponse(args.params, result?.nodes.buckets); +}; + +const getFilteredHostNames = async (args: GetHostsArgs) => { + const filteredHosts = await getFilteredHosts(args); + + const { nodes } = filteredHosts ?? {}; + return nodes?.buckets.map((p) => p.key) ?? []; +}; diff --git a/x-pack/plugins/infra/server/routes/infra/lib/mapper.test.ts b/x-pack/plugins/infra/server/routes/infra/lib/mapper.test.ts new file mode 100644 index 0000000000000..67cd31dc090de --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra/lib/mapper.test.ts @@ -0,0 +1,113 @@ +/* + * 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 { mapToApiResponse } from './mapper'; +import { GetInfraMetricsRequestBodyPayload } from '../../../../common/http_api/infra'; + +const metricsApiRequest: GetInfraMetricsRequestBodyPayload = { + type: 'host', + limit: 20, + metrics: [ + { + type: 'cpu', + }, + { + type: 'diskLatency', + }, + { + type: 'memory', + }, + { + type: 'memoryTotal', + }, + { + type: 'rx', + }, + { + type: 'tx', + }, + ], + range: { + from: '2023-04-18T11:15:31.407Z', + to: '2023-04-18T11:15:31.407Z', + }, + query: { bool: [{ must_not: [], filter: [], should: [], must: [] }] }, + sourceId: 'id', +}; + +describe('mapper', () => { + test('should map the aggregation object to the expected response object', () => { + const hosts = mapToApiResponse(metricsApiRequest, [ + { + key: 'host-0', + doc_count: 155, + diskLatency: { + doc_count: 0, + result: { + value: null, + }, + }, + memory: { + value: 0.542838307852529, + }, + tx: { + doc_count: 155, + result: { + value: 100.26926542816672, + }, + }, + rx: { + doc_count: 155, + result: { + value: 3959.4930095127706, + }, + }, + memoryTotal: { + value: 66640704.099216014, + }, + cpu: { + doc_count: 155, + result: { + value: 0.13271302652800487, + }, + }, + metadata: { + top: [ + { + sort: ['2023-04-04T06:35:13.793Z'], + metrics: { + 'host.os.name': null, + 'cloud.provider': '', + }, + }, + ], + }, + }, + ]); + + expect(hosts).toEqual({ + type: 'host', + nodes: [ + { + metadata: [ + { name: 'host.os.name', value: null }, + { name: 'cloud.provider', value: null }, + ], + metrics: [ + { name: 'cpu', value: 0.13271302652800487 }, + { name: 'diskLatency', value: 0 }, + { name: 'memory', value: 0.542838307852529 }, + { name: 'memoryTotal', value: 66640704.099216014 }, + { name: 'rx', value: 3959.4930095127706 }, + { name: 'tx', value: 100.26926542816672 }, + ], + name: 'host-0', + }, + ], + }); + }); +}); diff --git a/x-pack/plugins/infra/server/routes/infra/lib/mapper.ts b/x-pack/plugins/infra/server/routes/infra/lib/mapper.ts new file mode 100644 index 0000000000000..89d2e71b364f9 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra/lib/mapper.ts @@ -0,0 +1,94 @@ +/* + * 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 { BasicMetricValueRT, TopMetricsTypeRT } from '../../../lib/metrics/types'; +import { + GetInfraMetricsRequestBodyPayload, + GetInfraMetricsResponsePayload, + InfraAssetMetadata, + InfraAssetMetrics, +} from '../../../../common/http_api/infra'; + +import { + FilteredMetricsTypeRT, + HostsMetricsSearchBucket, + HostsMetricsSearchValue, + HostsMetricsSearchValueRT, +} from './types'; +import { METADATA_AGGREGATION_NAME } from './constants'; + +export const mapToApiResponse = ( + params: GetInfraMetricsRequestBodyPayload, + buckets?: HostsMetricsSearchBucket[] | undefined +): GetInfraMetricsResponsePayload => { + if (!buckets) { + return { + type: params.type, + nodes: [], + }; + } + + const hosts = buckets.map((bucket) => { + const metrics = convertMetricBucket(params, bucket); + const metadata = convertMetadataBucket(bucket); + + return { name: bucket.key as string, metrics, metadata }; + }); + + return { + type: params.type, + nodes: hosts, + }; +}; + +const normalizeValue = (value: string | number | null) => { + if (typeof value === 'string') { + return value?.trim().length === 0 ? null : value; + } + + return value; +}; + +const convertMetadataBucket = (bucket: HostsMetricsSearchBucket): InfraAssetMetadata[] => { + const metadataAggregation = bucket[METADATA_AGGREGATION_NAME]; + return TopMetricsTypeRT.is(metadataAggregation) + ? metadataAggregation.top + .flatMap((top) => Object.entries(top.metrics)) + .map( + ([key, value]) => + ({ + name: key, + value: normalizeValue(value), + } as InfraAssetMetadata) + ) + : []; +}; + +const convertMetricBucket = ( + params: GetInfraMetricsRequestBodyPayload, + bucket: HostsMetricsSearchBucket +): InfraAssetMetrics[] => { + return params.metrics.map((returnedMetric) => { + const metricBucket = bucket[returnedMetric.type]; + return { + name: returnedMetric.type, + value: HostsMetricsSearchValueRT.is(metricBucket) ? getMetricValue(metricBucket) ?? 0 : null, + } as InfraAssetMetrics; + }); +}; + +export const getMetricValue = (valueObject: HostsMetricsSearchValue) => { + if (FilteredMetricsTypeRT.is(valueObject)) { + return valueObject.result.value; + } + + if (BasicMetricValueRT.is(valueObject)) { + return valueObject.value; + } + + return valueObject; +}; diff --git a/x-pack/plugins/infra/server/routes/infra/lib/types.ts b/x-pack/plugins/infra/server/routes/infra/lib/types.ts new file mode 100644 index 0000000000000..d9800112e7dfe --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra/lib/types.ts @@ -0,0 +1,92 @@ +/* + * 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 { estypes } from '@elastic/elasticsearch'; +import { ISearchClient } from '@kbn/data-plugin/common'; +import * as rt from 'io-ts'; +import { InfraStaticSourceConfiguration } from '../../../../common/source_configuration/source_configuration'; + +import { GetInfraMetricsRequestBodyPayload } from '../../../../common/http_api/infra'; +import { BasicMetricValueRT, TopMetricsTypeRT } from '../../../lib/metrics/types'; + +export const FilteredMetricsTypeRT = rt.type({ + doc_count: rt.number, + result: BasicMetricValueRT, +}); + +export const HostsMetricsSearchValueRT = rt.union([ + BasicMetricValueRT, + FilteredMetricsTypeRT, + TopMetricsTypeRT, +]); + +export const HostsMetricsSearchBucketRT = rt.record( + rt.union([rt.string, rt.undefined]), + rt.union([ + rt.string, + rt.number, + HostsMetricsSearchValueRT, + rt.record(rt.string, rt.string), + rt.type({ doc_count: rt.number }), + ]) +); + +export const HostsNameBucketRT = rt.type({ + key: rt.string, + doc_count: rt.number, +}); + +export const HostsMetricsSearchAggregationResponseRT = rt.union([ + rt.type({ + nodes: rt.intersection([ + rt.partial({ + sum_other_doc_count: rt.number, + doc_count_error_upper_bound: rt.number, + }), + rt.type({ buckets: rt.array(HostsMetricsSearchBucketRT) }), + ]), + }), + rt.undefined, +]); + +export const FilteredHostsSearchAggregationResponseRT = rt.union([ + rt.type({ + nodes: rt.intersection([ + rt.partial({ + sum_other_doc_count: rt.number, + doc_count_error_upper_bound: rt.number, + }), + rt.type({ + buckets: rt.array(HostsNameBucketRT), + }), + ]), + }), + rt.undefined, +]); + +export interface HostsMetricsAggregationQueryConfig { + fieldName: string; + aggregation: estypes.AggregationsAggregationContainer; + runtimeField?: estypes.MappingRuntimeFields; +} + +export interface GetHostsArgs { + searchClient: ISearchClient; + sourceConfig: InfraStaticSourceConfiguration; + params: GetInfraMetricsRequestBodyPayload; +} + +export type HostsMetricsSearchValue = rt.TypeOf; +export type HostsMetricsSearchBucket = rt.TypeOf; + +export type FilteredHostsSearchAggregationResponse = rt.TypeOf< + typeof FilteredHostsSearchAggregationResponseRT +>; + +export type HostsMetricsSearchAggregationResponse = rt.TypeOf< + typeof HostsMetricsSearchAggregationResponseRT +>; diff --git a/x-pack/plugins/infra/server/routes/infra/lib/utils.test.ts b/x-pack/plugins/infra/server/routes/infra/lib/utils.test.ts new file mode 100644 index 0000000000000..504585db478cd --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra/lib/utils.test.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 { assertQueryStructure, hasFilters } from './utils'; + +const query = { bool: { must_not: [], filter: [], should: [], must: [] } }; + +describe('utils', () => { + describe('assertQueryStructure', () => { + test('should successfully parse a partial query object', () => { + const partialQuery = { + ...query, + bool: { + filter: [], + }, + }; + + expect(() => assertQueryStructure(partialQuery)).not.toThrow(); + }); + + test('should successfully parse query object', () => { + expect(() => assertQueryStructure(query)).not.toThrow(); + }); + + test('should fail to parse query object', () => { + const anyObject = { test: [{ a: 1 }] }; + expect(() => assertQueryStructure(anyObject)).toThrow(); + }); + + test('should fail to parse query object without any filter clause', () => { + const anyObject = { bool: {} }; + expect(() => assertQueryStructure(anyObject)).toThrow(); + }); + }); + describe('hasFilters', () => { + test('should return true if there is any filter', () => { + const result = hasFilters({ + ...query, + bool: { + filter: [ + { + term: { + 'host.name': 'host', + }, + }, + ], + }, + }); + expect(result).toEqual(true); + }); + + test('should return false when there is not filter', () => { + const result = hasFilters(query); + expect(result).toEqual(false); + }); + }); +}); diff --git a/x-pack/plugins/infra/server/routes/infra/lib/utils.ts b/x-pack/plugins/infra/server/routes/infra/lib/utils.ts new file mode 100644 index 0000000000000..7ea5a3d0b4fee --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra/lib/utils.ts @@ -0,0 +1,49 @@ +/* + * 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 { estypes } from '@elastic/elasticsearch'; +import Boom from '@hapi/boom'; + +type FilterClauses = keyof estypes.QueryDslBoolQuery; +const validClauses: FilterClauses[] = ['must', 'filter', 'must_not', 'should']; + +interface BoolQuery { + bool: estypes.QueryDslBoolQuery; +} + +const isValidFilter = (query: any): query is BoolQuery => { + const boolClause = (query as estypes.QueryDslQueryContainer).bool; + + if (!boolClause || Object.keys(boolClause).length === 0) { + return false; + } + + return [boolClause.filter, boolClause.must, boolClause.must_not, boolClause.should] + .filter(Boolean) + .every((clause) => Array.isArray(clause) || clause === undefined); +}; + +export const assertQueryStructure: (query: any) => asserts query is BoolQuery = (query) => { + if (!isValidFilter(query)) { + throw Boom.badRequest('Invalid query'); + } +}; + +export const hasFilters = (query?: any) => { + if (!query) { + return false; + } + + assertQueryStructure(query); + + // ignores minimum_should_match + return Object.entries(query.bool) + .filter(([key, _]) => validClauses.includes(key as FilterClauses)) + .some(([_, filter]) => { + return Array.isArray(filter) ? filter.length > 0 : !!filter; + }); +}; diff --git a/x-pack/test/api_integration/apis/metrics_ui/index.js b/x-pack/test/api_integration/apis/metrics_ui/index.js index 150a123121051..8a4b7d3398d1c 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/index.js +++ b/x-pack/test/api_integration/apis/metrics_ui/index.js @@ -22,6 +22,7 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./metrics_process_list')); loadTestFile(require.resolve('./metrics_process_list_chart')); loadTestFile(require.resolve('./infra_log_analysis_validation_log_entry_datasets')); + loadTestFile(require.resolve('./infra')); loadTestFile(require.resolve('./inventory_threshold_alert')); }); } diff --git a/x-pack/test/api_integration/apis/metrics_ui/infra.ts b/x-pack/test/api_integration/apis/metrics_ui/infra.ts new file mode 100644 index 0000000000000..4749492982b9d --- /dev/null +++ b/x-pack/test/api_integration/apis/metrics_ui/infra.ts @@ -0,0 +1,265 @@ +/* + * 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 { + GetInfraMetricsRequestBodyPayload, + GetInfraMetricsResponsePayload, +} from '@kbn/infra-plugin/common/http_api/infra'; +import { DATES } from './constants'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +const ENDPOINT = '/api/metrics/infra'; + +const normalizeNewLine = (text: string) => { + return text.replaceAll(/(\s{2,}|\\n\\s)/g, ' '); +}; +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + const basePayload: GetInfraMetricsRequestBodyPayload = { + type: 'host', + limit: 10, + metrics: [ + { + type: 'cpu', + }, + { + type: 'diskLatency', + }, + { + type: 'memory', + }, + { + type: 'memoryTotal', + }, + { + type: 'rx', + }, + { + type: 'tx', + }, + ], + range: { + from: new Date(DATES['8.0.0'].logs_and_metrics.min).toISOString(), + to: new Date(DATES['8.0.0'].logs_and_metrics.max).toISOString(), + }, + query: { bool: { must_not: [], filter: [], should: [], must: [] } }, + sourceId: 'default', + }; + + const makeRequest = async ({ + body, + invalidBody, + expectedHTTPCode, + }: { + body?: GetInfraMetricsRequestBodyPayload; + invalidBody?: any; + expectedHTTPCode: number; + }) => { + return supertest + .post(ENDPOINT) + .set('kbn-xsrf', 'xxx') + .send(body ?? invalidBody) + .expect(expectedHTTPCode); + }; + + describe('Hosts', () => { + before(() => + esArchiver.load('x-pack/test/functional/es_archives/infra/8.0.0/logs_and_metrics') + ); + after(() => + esArchiver.unload('x-pack/test/functional/es_archives/infra/8.0.0/logs_and_metrics') + ); + + describe('fetch hosts', () => { + it('should return metrics for a host', async () => { + const body: GetInfraMetricsRequestBodyPayload = { ...basePayload, limit: 1 }; + const response = await makeRequest({ body, expectedHTTPCode: 200 }); + + expect(response.body.nodes).length(1); + expect(response.body.nodes).eql([ + { + metadata: [ + { name: 'host.os.name', value: 'CentOS Linux' }, + { name: 'cloud.provider', value: 'gcp' }, + ], + metrics: [ + { name: 'cpu', value: 0.44708333333333333 }, + { name: 'diskLatency', value: null }, + { name: 'memory', value: 0.4563333333333333 }, + { name: 'memoryTotal', value: 15768948736 }, + { name: 'rx', value: null }, + { name: 'tx', value: null }, + ], + name: 'gke-observability-8--observability-8--bc1afd95-f0zc', + }, + ]); + }); + + it('should return all hosts if query params is not sent', async () => { + const body: GetInfraMetricsRequestBodyPayload = { + ...basePayload, + metrics: [ + { + type: 'memory', + }, + ], + query: undefined, + }; + + const response = await makeRequest({ body, expectedHTTPCode: 200 }); + expect(response.body.nodes).eql([ + { + metadata: [ + { name: 'host.os.name', value: 'CentOS Linux' }, + { name: 'cloud.provider', value: 'gcp' }, + ], + metrics: [{ name: 'memory', value: 0.4563333333333333 }], + name: 'gke-observability-8--observability-8--bc1afd95-f0zc', + }, + { + metadata: [ + { name: 'host.os.name', value: 'CentOS Linux' }, + { name: 'cloud.provider', value: 'gcp' }, + ], + metrics: [{ name: 'memory', value: 0.32066666666666666 }], + name: 'gke-observability-8--observability-8--bc1afd95-ngmh', + }, + { + metadata: [ + { name: 'host.os.name', value: 'CentOS Linux' }, + { name: 'cloud.provider', value: 'gcp' }, + ], + metrics: [{ name: 'memory', value: 0.2346666666666667 }], + name: 'gke-observability-8--observability-8--bc1afd95-nhhw', + }, + ]); + }); + + it('should return 3 hosts when filtered by "host.os.name=CentOS Linux"', async () => { + const body: GetInfraMetricsRequestBodyPayload = { + ...basePayload, + metrics: [ + { + type: 'cpu', + }, + ], + query: { bool: { filter: [{ term: { 'host.os.name': 'CentOS Linux' } }] } }, + }; + const response = await makeRequest({ body, expectedHTTPCode: 200 }); + + const names = (response.body as GetInfraMetricsResponsePayload).nodes.map((p) => p.name); + expect(names).eql([ + 'gke-observability-8--observability-8--bc1afd95-f0zc', + 'gke-observability-8--observability-8--bc1afd95-ngmh', + 'gke-observability-8--observability-8--bc1afd95-nhhw', + ]); + }); + + it('should return 0 hosts when filtered by "host.os.name=Ubuntu"', async () => { + const body: GetInfraMetricsRequestBodyPayload = { + ...basePayload, + metrics: [ + { + type: 'cpu', + }, + ], + query: { bool: { filter: [{ term: { 'host.os.name': 'Ubuntu' } }] } }, + }; + const response = await makeRequest({ body, expectedHTTPCode: 200 }); + + const names = (response.body as GetInfraMetricsResponsePayload).nodes.map((p) => p.name); + expect(names).eql([]); + }); + }); + + it('should return 0 hosts when filtered by not "host.name=gke-observability-8--observability-8--bc1afd95-nhhw"', async () => { + const body: GetInfraMetricsRequestBodyPayload = { + ...basePayload, + metrics: [ + { + type: 'cpu', + }, + ], + query: { + bool: { + must_not: [ + { term: { 'host.name': 'gke-observability-8--observability-8--bc1afd95-nhhw' } }, + ], + }, + }, + }; + const response = await makeRequest({ body, expectedHTTPCode: 200 }); + + const names = (response.body as GetInfraMetricsResponsePayload).nodes.map((p) => p.name); + expect(names).eql([ + 'gke-observability-8--observability-8--bc1afd95-f0zc', + 'gke-observability-8--observability-8--bc1afd95-ngmh', + ]); + }); + + describe('endpoint validations', () => { + it('should fail when limit is 0', async () => { + const body: GetInfraMetricsRequestBodyPayload = { ...basePayload, limit: 0 }; + const response = await makeRequest({ body, expectedHTTPCode: 400 }); + + expect(normalizeNewLine(response.body.message)).to.be( + '[request body]: Failed to validate: in limit: 0 does not match expected type InRange in limit: 0 does not match expected type pipe(undefined, BooleanFromString)' + ); + }); + + it('should fail when limit is negative', async () => { + const body: GetInfraMetricsRequestBodyPayload = { ...basePayload, limit: -2 }; + const response = await makeRequest({ body, expectedHTTPCode: 400 }); + + expect(normalizeNewLine(response.body.message)).to.be( + '[request body]: Failed to validate: in limit: -2 does not match expected type InRange in limit: -2 does not match expected type pipe(undefined, BooleanFromString)' + ); + }); + + it('should fail when limit above 500', async () => { + const body: GetInfraMetricsRequestBodyPayload = { ...basePayload, limit: 501 }; + const response = await makeRequest({ body, expectedHTTPCode: 400 }); + + expect(normalizeNewLine(response.body.message)).to.be( + '[request body]: Failed to validate: in limit: 501 does not match expected type InRange in limit: 501 does not match expected type pipe(undefined, BooleanFromString)' + ); + }); + + it('should fail when metric is invalid', async () => { + const invalidBody = { ...basePayload, metrics: [{ type: 'any' }] }; + const response = await makeRequest({ invalidBody, expectedHTTPCode: 400 }); + + expect(normalizeNewLine(response.body.message)).to.be( + '[request body]: Failed to validate: in metrics/0/type: "any" does not match expected type "cpu" | "diskLatency" | "memory" | "memoryTotal" | "rx" | "tx"' + ); + }); + + it('should pass when limit is 1', async () => { + const body: GetInfraMetricsRequestBodyPayload = { ...basePayload, limit: 1 }; + await makeRequest({ body, expectedHTTPCode: 200 }); + }); + + it('should pass when limit is 500', async () => { + const body: GetInfraMetricsRequestBodyPayload = { ...basePayload, limit: 500 }; + await makeRequest({ body, expectedHTTPCode: 200 }); + }); + + it('should fail when range is not informed', async () => { + const invalidBody = { ...basePayload, range: undefined }; + const response = await makeRequest({ invalidBody, expectedHTTPCode: 400 }); + + expect(normalizeNewLine(response.body.message)).to.be( + '[request body]: Failed to validate: in range: undefined does not match expected type { from: Date, to: Date }' + ); + }); + }); + }); +} From 2773faa05e9324aefdbbae031ba42fc6ca56ef23 Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Thu, 20 Apr 2023 16:13:26 -0400 Subject: [PATCH 30/67] [Security Solution] Mapping cleanup for Signals Migration Saved Object (#154949) ## Summary These fields are captured in schema elsewhere (and validated there as well). This effort is part of elastic/security-team#6268. --- .../group2/check_registered_types.test.ts | 2 +- .../migrations/saved_objects.ts | 34 ++----------------- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index 0adc6e5f084fd..6c7e00b1822b7 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -130,7 +130,7 @@ describe('checking migration metadata changes on all registered SO types', () => "search-session": "fae0dfc63274d6a3b90ca583802c48cab8760637", "search-telemetry": "1bbaf2db531b97fa04399440fa52d46e86d54dd8", "security-rule": "151108f4906744a137ddc89f5988310c5b9ba8b6", - "security-solution-signals-migration": "c2db409c1857d330beb3d6fd188fa186f920302c", + "security-solution-signals-migration": "0be3bed0f2ff4fe460493751b8be610a785c5c98", "siem-detection-engine-rule-actions": "123c130dc38120a470d8db9fed9a4cebd2046445", "siem-ui-timeline": "e9d6b3a9fd7af6dc502293c21cbdb309409f3996", "siem-ui-timeline-note": "13c9d4c142f96624a93a623c6d7cba7e1ae9b5a6", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/saved_objects.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/saved_objects.ts index 8a168d3c43ba0..fcb6aede4973f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/saved_objects.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/saved_objects.ts @@ -10,44 +10,16 @@ import type { SavedObjectsType } from '@kbn/core/server'; export const signalsMigrationType = 'security-solution-signals-migration'; export const signalsMigrationMappings: SavedObjectsType['mappings'] = { + dynamic: false, properties: { sourceIndex: { type: 'keyword', }, - destinationIndex: { - type: 'keyword', - index: false, - }, - version: { - type: 'long', - }, - error: { - type: 'text', - index: false, - }, - taskId: { - type: 'keyword', - index: false, - }, - status: { - type: 'keyword', - index: false, - }, - created: { - type: 'date', - index: false, - }, - createdBy: { - type: 'text', - index: false, - }, updated: { type: 'date', - index: false, }, - updatedBy: { - type: 'text', - index: false, + version: { + type: 'long', }, }, }; From f359b6ab4133b625da708cbf9ccad9fe090c15e8 Mon Sep 17 00:00:00 2001 From: Ido Cohen <90558359+CohenIdo@users.noreply.github.com> Date: Thu, 20 Apr 2023 23:13:47 +0300 Subject: [PATCH 31/67] [Cloud Security] first telemetry for vulnerability (#155031) --- .../collectors/indices_stats_collector.ts | 8 ++++- .../server/lib/telemetry/collectors/schema.ts | 28 ++++++++++++++++ .../server/lib/telemetry/collectors/types.ts | 2 ++ .../schema/xpack_plugins.json | 32 +++++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/indices_stats_collector.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/indices_stats_collector.ts index fabd8cd6581bc..7797bdd521cc3 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/indices_stats_collector.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/indices_stats_collector.ts @@ -14,6 +14,8 @@ import { BENCHMARK_SCORE_INDEX_DEFAULT_NS, FINDINGS_INDEX_DEFAULT_NS, LATEST_FINDINGS_INDEX_DEFAULT_NS, + LATEST_VULNERABILITIES_INDEX_DEFAULT_NS, + VULNERABILITIES_INDEX_DEFAULT_NS, } from '../../../../common/constants'; const getIndexDocCount = (esClient: ElasticsearchClient, index: string) => @@ -76,9 +78,11 @@ export const getIndicesStats = async ( coreServices: Promise<[CoreStart, CspServerPluginStartDeps, CspServerPluginStart]>, logger: Logger ): Promise => { - const [findings, latestFindings, score] = await Promise.all([ + const [findings, latestFindings, vulMng, vulMngLatest, score] = await Promise.all([ getIndexStats(esClient, FINDINGS_INDEX_DEFAULT_NS, logger), getIndexStats(esClient, LATEST_FINDINGS_INDEX_DEFAULT_NS, logger), + getIndexStats(esClient, VULNERABILITIES_INDEX_DEFAULT_NS, logger), + getIndexStats(esClient, LATEST_VULNERABILITIES_INDEX_DEFAULT_NS, logger), getIndexStats(esClient, BENCHMARK_SCORE_INDEX_DEFAULT_NS, logger), ]); @@ -100,6 +104,8 @@ export const getIndicesStats = async ( return { findings, latest_findings: latestFindings, + vulnerabilities: vulMng, + latest_vulnerabilities: vulMngLatest, score, latestPackageVersion: status.latestPackageVersion, diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/schema.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/schema.ts index 77f24d98f24a6..4fb7bfee22692 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/schema.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/schema.ts @@ -38,6 +38,34 @@ export const cspmUsageSchema: MakeSchemaFrom = { type: 'date', }, }, + vulnerabilities: { + doc_count: { + type: 'long', + }, + deleted: { + type: 'long', + }, + size_in_bytes: { + type: 'long', + }, + last_doc_timestamp: { + type: 'date', + }, + }, + latest_vulnerabilities: { + doc_count: { + type: 'long', + }, + deleted: { + type: 'long', + }, + size_in_bytes: { + type: 'long', + }, + last_doc_timestamp: { + type: 'date', + }, + }, score: { doc_count: { type: 'long', diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts index ebfc02b7c3e3a..8651a3f577c1d 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts @@ -17,6 +17,8 @@ export interface CspmUsage { export interface CspmIndicesStats { findings: IndexStats | {}; latest_findings: IndexStats | {}; + vulnerabilities: IndexStats | {}; + latest_vulnerabilities: IndexStats | {}; score: IndexStats | {}; latestPackageVersion: string; cspm: BaseCspSetupBothPolicy; 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 594373a71e188..88270d7102caa 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -5454,6 +5454,38 @@ } } }, + "vulnerabilities": { + "properties": { + "doc_count": { + "type": "long" + }, + "deleted": { + "type": "long" + }, + "size_in_bytes": { + "type": "long" + }, + "last_doc_timestamp": { + "type": "date" + } + } + }, + "latest_vulnerabilities": { + "properties": { + "doc_count": { + "type": "long" + }, + "deleted": { + "type": "long" + }, + "size_in_bytes": { + "type": "long" + }, + "last_doc_timestamp": { + "type": "date" + } + } + }, "score": { "properties": { "doc_count": { From 04f151d27ce8fc51f0ae4d9b40f4763899bd0644 Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Thu, 20 Apr 2023 13:14:05 -0700 Subject: [PATCH 32/67] [Security Solution][Exceptions] - Fix issue with tags in shared exception list manage rules component (#155219) ## Summary Addresses https://github.com/elastic/kibana/issues/153077, https://github.com/elastic/kibana/issues/153077 This PR addresses two bugs in the manage rules component for shared exception lists: - Fixes issue where numeric tags were getting overwritten after selecting a tag after it - Fixes issue where tag selection was not "exact" match, to be the same behavior as in the manage rules table --- .../add_to_rules_table/index.tsx | 10 ++--- .../use_add_to_rules_table.test.tsx | 1 + .../use_add_to_rules_table.tsx | 7 ++-- .../flyout_components/translations.ts | 22 +++++++++++ .../components/flyout_components/utils.tsx | 37 +++++++++++++++++-- 5 files changed, 64 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.tsx index 4760371ce5e81..dbd11e0c486f3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.tsx @@ -7,7 +7,8 @@ import React from 'react'; -import { EuiSpacer, EuiPanel, EuiText, EuiInMemoryTable, EuiLoadingContent } from '@elastic/eui'; +import type { Search } from '@elastic/eui'; +import { EuiSkeletonText, EuiSpacer, EuiPanel, EuiText, EuiInMemoryTable } from '@elastic/eui'; import type { Rule } from '../../../../rule_management/logic/types'; import { useAddToRulesTable } from './use_add_to_rules_table'; @@ -40,7 +41,7 @@ const ExceptionsAddToRulesTableComponent: React.FC tableLayout="auto" - search={searchOptions} + search={searchOptions as Search} data-test-subj="addExceptionToRulesTable" tableCaption="Rules table" items={sortedRulesByLinkedRulesOnTop} @@ -48,10 +49,7 @@ const ExceptionsAddToRulesTableComponent: React.FC + ) : undefined } pagination={pagination} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.test.tsx index 42fbc6dce160e..5ec0af8ae9fd0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.test.tsx @@ -146,6 +146,7 @@ describe('useAddToRulesTable', () => { const { options } = filters[0]; expect(options).toEqual([ { + field: 'tags', name: 'some fake tag 1', value: 'some fake tag 1', }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.tsx index eead15990f521..74b7905a37b17 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.tsx @@ -70,18 +70,19 @@ export const useAddToRulesTable = ({ tags.forEach((tag) => acc.add(tag)); return acc; }, new Set()); - return [...uniqueTags].map((tag) => ({ value: tag, name: tag })); + return Array.from(uniqueTags).map((tag) => ({ value: tag, name: tag, field: 'tags' })); }, [sortedRulesByLinkedRulesOnTop]); const searchOptions = useMemo( () => ({ box: { incremental: true, + schema: true, }, filters: [ { type: 'field_value_selection' as const, - field: 'tags', + operator: 'exact', name: i18n.translate( 'xpack.securitySolution.exceptions.addToRulesTable.tagsFilterLabel', { @@ -103,7 +104,7 @@ export const useAddToRulesTable = ({ name: commonI18n.LINK_COLUMN, align: 'left' as HorizontalAlignment, 'data-test-subj': 'ruleActionLinkRuleSwitch', - render: (_, rule: Rule) => ( + render: (_: unknown, rule: Rule) => ( ), }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/translations.ts index 752e15bd86a43..7c352e5f6ce19 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/translations.ts @@ -20,9 +20,31 @@ export const VIEW_RULE_DETAIL_ACTION = i18n.translate( defaultMessage: 'View rule detail', } ); + export const LINK_COLUMN = i18n.translate( 'xpack.securitySolution.rule_exceptions.flyoutComponents.addToRulesTableSelection.link_column', { defaultMessage: 'Link', } ); + +export const NAME_COLUMN = i18n.translate( + 'xpack.securitySolution.rule_exceptions.flyoutComponents.addToRulesTableSelection.name_column', + { + defaultMessage: 'Name', + } +); + +export const ACTION_COLUMN = i18n.translate( + 'xpack.securitySolution.rule_exceptions.flyoutComponents.addToRulesTableSelection.action_column', + { + defaultMessage: 'Action', + } +); + +export const TAGS_COLUMN = i18n.translate( + 'xpack.securitySolution.rule_exceptions.flyoutComponents.addToRulesTableSelection.tags_column', + { + defaultMessage: 'Tags', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx index 4d1570ba3b491..98e59d12681cc 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx @@ -13,11 +13,13 @@ import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import type { ExceptionsBuilderReturnExceptionItem } from '@kbn/securitysolution-list-utils'; import type { HorizontalAlignment } from '@elastic/eui'; +import { EuiBadge } from '@elastic/eui'; import type { Moment } from 'moment'; import { HeaderMenu, generateLinkedRulesMenuItems, } from '@kbn/securitysolution-exception-list-components'; +import { PopoverItems } from '../../../../common/components/popover_items'; import { SecurityPageName } from '../../../../../common/constants'; import { ListDetailsLinkAnchor } from '../../../../exceptions/components'; import { @@ -204,7 +206,7 @@ export const enrichExceptionItemsForUpdate = ({ export const getSharedListsTableColumns = () => [ { field: 'name', - name: 'Name', + name: i18n.NAME_COLUMN, sortable: true, 'data-test-subj': 'exceptionListNameCell', }, @@ -230,7 +232,7 @@ export const getSharedListsTableColumns = () => [ ), }, { - name: 'Action', + name: i18n.ACTION_COLUMN, 'data-test-subj': 'exceptionListRulesActionCell', render: (list: ExceptionListRuleReferencesSchema) => { @@ -255,13 +257,40 @@ export const getRulesTableColumn = () => [ { field: 'name', align: 'left' as HorizontalAlignment, - name: 'Name', + name: i18n.NAME_COLUMN, sortable: true, 'data-test-subj': 'ruleNameCell', truncateText: false, }, { - name: 'Action', + field: 'tags', + align: 'left' as HorizontalAlignment, + name: i18n.TAGS_COLUMN, + 'data-test-subj': 'ruleNameCell', + render: (tags: Rule['tags']) => { + if (tags.length === 0) { + return null; + } + + const renderItem = (tag: string, i: number) => ( + + {tag} + + ); + return ( + + ); + }, + }, + { + name: i18n.ACTION_COLUMN, 'data-test-subj': 'ruleAction-view', render: (rule: Rule) => { return ( From e4ae398e6d1bb95aaaacbde3328a72daad348bc5 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Thu, 20 Apr 2023 13:20:52 -0700 Subject: [PATCH 33/67] [ResponseOps] Edit snooze recurring label (#155338) --- x-pack/plugins/translations/translations/fr-FR.json | 1 - x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - .../sections/rules_list/components/rule_snooze/scheduler.tsx | 4 ++-- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 969438e555138..7a86a2d49d1ba 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -36320,7 +36320,6 @@ "xpack.triggersActionsUI.ruleSnoozeScheduler.recurYearly": "Annuel", "xpack.triggersActionsUI.ruleSnoozeScheduler.repeatIntervalLabel": "Chaque", "xpack.triggersActionsUI.ruleSnoozeScheduler.repeatLabel": "Répéter", - "xpack.triggersActionsUI.ruleSnoozeScheduler.reucrringSwitch": "Rendre récurrent", "xpack.triggersActionsUI.ruleSnoozeScheduler.saveSchedule": "Enregistrer le calendrier", "xpack.triggersActionsUI.ruleSnoozeScheduler.timezoneLabel": "Fuseau horaire", "xpack.triggersActionsUI.rulesSettings.flapping.alertFlappingDetection": "Détection de bagotement d'alerte", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9bad4964ca71c..a8c59ef19152c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -36288,7 +36288,6 @@ "xpack.triggersActionsUI.ruleSnoozeScheduler.recurYearly": "年ごと", "xpack.triggersActionsUI.ruleSnoozeScheduler.repeatIntervalLabel": "毎", "xpack.triggersActionsUI.ruleSnoozeScheduler.repeatLabel": "繰り返し", - "xpack.triggersActionsUI.ruleSnoozeScheduler.reucrringSwitch": "繰り返しにする", "xpack.triggersActionsUI.ruleSnoozeScheduler.saveSchedule": "スケジュールを保存", "xpack.triggersActionsUI.ruleSnoozeScheduler.timezoneLabel": "タイムゾーン", "xpack.triggersActionsUI.rulesSettings.flapping.alertFlappingDetection": "アラートフラップ検出", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d800cf3c9c612..9ad3258100c35 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -36315,7 +36315,6 @@ "xpack.triggersActionsUI.ruleSnoozeScheduler.recurYearly": "每年", "xpack.triggersActionsUI.ruleSnoozeScheduler.repeatIntervalLabel": "每", "xpack.triggersActionsUI.ruleSnoozeScheduler.repeatLabel": "重复", - "xpack.triggersActionsUI.ruleSnoozeScheduler.reucrringSwitch": "设为定期", "xpack.triggersActionsUI.ruleSnoozeScheduler.saveSchedule": "保存计划", "xpack.triggersActionsUI.ruleSnoozeScheduler.timezoneLabel": "时区", "xpack.triggersActionsUI.rulesSettings.flapping.alertFlappingDetection": "告警摆动检测", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/scheduler.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/scheduler.tsx index 3c65a2afba551..1520255f21a80 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/scheduler.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/scheduler.tsx @@ -330,8 +330,8 @@ const RuleSnoozeSchedulerPanel: React.FunctionComponent = ({ setIsRecurring(!isRecurring)} checked={isRecurring} From 45f102f0febae8ba1f921b59252784bcc58467c2 Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Thu, 20 Apr 2023 16:28:06 -0400 Subject: [PATCH 34/67] Fixes security plugin capabilities switcher to handle opt-out and default behaviors (#154098) Closes https://github.com/elastic/kibana/issues/153817 ## Summary This PR implements logical checks within the security plugin's capabilities switcher to account for features that opt out of the Kibana security model (e.g. Enterprise Search features). It also more explicitly handles default cases (when a feature is neither a Kibana or ES feature), exclusions (features handled exclusively by other plugins), and the catalogue feature (we now qualify each catalogue feature capability). In these cases (opt-out, default, exclusion, etc.), the capabilities switcher will ignore the capability and neither enable nor disable it (see detailed list below). We are now effectively ignoring only these: - `spaces` feature ID (handled by spaces plugin capabilities switcher) - `fileUpload` feature ID (handled by file_upload plugin capabilities switcher) - `catalogue` capabilities that are not 'spaces' and are not referenced by at least one Kibana or ES feature - `navLinks` that are not referenced by at least one Kibana feature - Anything that is not a global settings, management, catalogue, nav link, Kibana, or ES feature On the flip side we always affect everything under the `management` feature. This PR _should_ unblock the ability to implement parallel execution of capabilities switchers, https://github.com/elastic/kibana/pull/152982. ### Related Tests - x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts - x-pack/test/ui_capabilities/security_and_spaces/config.ts - x-pack/test/functional/apps/home/config.ts --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../disable_ui_capabilities.test.ts | 559 +++++++++--------- .../authorization/disable_ui_capabilities.ts | 56 +- 2 files changed, 329 insertions(+), 286 deletions(-) diff --git a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts index dd07324835303..7d0039c9106d2 100644 --- a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts +++ b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts @@ -66,8 +66,145 @@ const createMockUser = (user: Partial = {}) => ...user, } as AuthenticatedUser); +const kibanaFeature1 = new KibanaFeature({ + id: 'kibanaFeature1', + name: 'KibanaFeature1', + app: ['app1'], + category: { id: 'foo', label: 'foo' }, + privileges: { + all: { + app: ['foo'], + catalogue: ['foo'], + savedObject: { + all: ['foo'], + read: [], + }, + ui: ['save', 'show'], + }, + read: { + app: ['foo'], + catalogue: ['foo'], + savedObject: { + all: [], + read: ['foo'], + }, + ui: ['show'], + }, + }, +}); + +const kibanaFeature2 = new KibanaFeature({ + id: 'kibanaFeature2', + name: 'KibanaFeature2', + app: ['app1', 'app2'], + category: { id: 'foo', label: 'foo' }, + privileges: { + all: { + app: ['foo'], + catalogue: ['foo'], + savedObject: { + all: ['foo'], + read: [], + }, + ui: ['save', 'show'], + }, + read: { + app: ['foo'], + catalogue: ['foo'], + savedObject: { + all: [], + read: ['foo'], + }, + ui: ['show'], + }, + }, +}); + +const optOutKibanaFeature = new KibanaFeature({ + id: 'optOutFeature', + name: 'Feature that opts out of Kibana sec model', + app: [], + category: { id: 'optOut', label: 'optOut' }, + privileges: null, +}); + +const esManagementFeature = new ElasticsearchFeature({ + id: 'esManagementFeature', + management: { + kibana: ['esManagement'], + }, + privileges: [ + { + requiredClusterPrivileges: ['manage_security'], + ui: [], + }, + ], +}); + describe('usingPrivileges', () => { describe('checkPrivileges errors', () => { + const inputCapabilities = Object.freeze({ + navLinks: { + app1: true, + app2: true, + app3: true, + }, + management: { + kibana: { + indices: true, + }, + }, + catalogue: {}, + kibanaFeature2: { + foo: true, + bar: true, + }, + optOutFeature: { + foo: true, + bar: true, + }, + esManagementFeature: { + foo: true, + bar: true, + }, + unregisteredFeature: { + foo: true, + bar: true, + }, + }); + + const expectedDisabled = Object.freeze({ + navLinks: { + app1: false, + app2: false, + app3: true, // will not diable unregistered app link + }, + management: { + kibana: { + indices: false, + }, + }, + catalogue: {}, + kibanaFeature2: { + foo: false, + bar: false, + }, + optOutFeature: { + // will not disbale features that opt out of Kibana security + foo: true, + bar: true, + }, + esManagementFeature: { + foo: false, + bar: false, + }, + unregisteredFeature: { + // will not disble unregistered features + foo: true, + bar: true, + }, + }); + test(`disables uiCapabilities when a 401 is thrown`, async () => { const mockAuthz = createMockAuthz({ rejectCheckPrivileges: { statusCode: 401, message: 'super informative message' }, @@ -76,76 +213,16 @@ describe('usingPrivileges', () => { const { usingPrivileges } = disableUICapabilitiesFactory( mockRequest, - [ - new KibanaFeature({ - id: 'fooFeature', - name: 'Foo KibanaFeature', - app: ['fooApp', 'foo'], - category: { id: 'foo', label: 'foo' }, - privileges: null, - }), - ], - [ - new ElasticsearchFeature({ - id: 'esFeature', - privileges: [ - { - requiredClusterPrivileges: [], - ui: [], - }, - ], - }), - ], + [kibanaFeature2, optOutKibanaFeature], + [esManagementFeature], mockLoggers.get(), mockAuthz, createMockUser() ); - const result = await usingPrivileges( - Object.freeze({ - navLinks: { - foo: true, - fooApp: true, - bar: true, - }, - management: { - kibana: { - indices: true, - }, - }, - catalogue: {}, - fooFeature: { - foo: true, - bar: true, - }, - barFeature: { - foo: true, - bar: true, - }, - }) - ); + const result = await usingPrivileges(inputCapabilities); - expect(result).toEqual({ - navLinks: { - foo: false, - fooApp: false, - bar: true, - }, - management: { - kibana: { - indices: false, - }, - }, - catalogue: {}, - fooFeature: { - foo: false, - bar: false, - }, - barFeature: { - foo: false, - bar: false, - }, - }); + expect(result).toEqual(expectedDisabled); expect(loggingSystemMock.collect(mockLoggers).debug).toMatchInlineSnapshot(` Array [ @@ -164,74 +241,17 @@ describe('usingPrivileges', () => { const { usingPrivileges } = disableUICapabilitiesFactory( mockRequest, - [ - new KibanaFeature({ - id: 'fooFeature', - name: 'Foo KibanaFeature', - app: ['foo'], - category: { id: 'foo', label: 'foo' }, - privileges: null, - }), - ], - [ - new ElasticsearchFeature({ - id: 'esFeature', - privileges: [ - { - requiredClusterPrivileges: [], - ui: [], - }, - ], - }), - ], + [kibanaFeature2, optOutKibanaFeature], + [esManagementFeature], mockLoggers.get(), mockAuthz, createMockUser() ); - const result = await usingPrivileges( - Object.freeze({ - navLinks: { - foo: true, - bar: true, - }, - management: { - kibana: { - indices: true, - }, - }, - catalogue: {}, - fooFeature: { - foo: true, - bar: true, - }, - barFeature: { - foo: true, - bar: true, - }, - }) - ); + const result = await usingPrivileges(inputCapabilities); + + expect(result).toEqual(expectedDisabled); - expect(result).toEqual({ - navLinks: { - foo: false, - bar: true, - }, - management: { - kibana: { - indices: false, - }, - }, - catalogue: {}, - fooFeature: { - foo: false, - bar: false, - }, - barFeature: { - foo: false, - bar: false, - }, - }); expect(loggingSystemMock.collect(mockLoggers).debug).toMatchInlineSnapshot(` Array [ Array [ @@ -284,24 +304,64 @@ describe('usingPrivileges', () => { }); }); + const esFeatures = [ + new ElasticsearchFeature({ + id: 'esFeature', + privileges: [ + { + requiredClusterPrivileges: ['manage'], + ui: ['es_manage'], + }, + { + requiredClusterPrivileges: ['monitor'], + ui: ['es_monitor'], + }, + ], + }), + new ElasticsearchFeature({ + id: 'esSecurityFeature', + privileges: [ + { + requiredClusterPrivileges: ['manage_security'], + ui: ['es_manage_sec'], + }, + ], + }), + new ElasticsearchFeature({ + id: 'esManagementFeature', + management: { + kibana: ['esManagement'], + }, + privileges: [ + { + requiredClusterPrivileges: ['manage_security'], + ui: [], + }, + ], + }), + ]; + test(`disables ui capabilities when they don't have privileges`, async () => { + // grant some privileges const mockAuthz = createMockAuthz({ resolveCheckPrivileges: { privileges: { kibana: [ - { privilege: actions.ui.get('navLinks', 'foo'), authorized: true }, - { privilege: actions.ui.get('navLinks', 'bar'), authorized: false }, - { privilege: actions.ui.get('navLinks', 'quz'), authorized: false }, + { privilege: actions.ui.get('navLinks', 'app1'), authorized: true }, + { privilege: actions.ui.get('navLinks', 'app2'), authorized: false }, + { privilege: actions.ui.get('navLinks', 'app3'), authorized: false }, { privilege: actions.ui.get('management', 'kibana', 'indices'), authorized: true }, { privilege: actions.ui.get('management', 'kibana', 'settings'), authorized: false }, { privilege: actions.ui.get('management', 'kibana', 'esManagement'), authorized: false, }, - { privilege: actions.ui.get('fooFeature', 'foo'), authorized: true }, - { privilege: actions.ui.get('fooFeature', 'bar'), authorized: false }, - { privilege: actions.ui.get('barFeature', 'foo'), authorized: true }, - { privilege: actions.ui.get('barFeature', 'bar'), authorized: false }, + { privilege: actions.ui.get('kibanaFeature1', 'foo'), authorized: true }, + { privilege: actions.ui.get('kibanaFeature1', 'bar'), authorized: false }, + { privilege: actions.ui.get('kibanaFeature2', 'foo'), authorized: true }, + { privilege: actions.ui.get('kibanaFeature2', 'bar'), authorized: false }, + { privilege: actions.ui.get('optOutFeature', 'foo'), authorized: false }, + { privilege: actions.ui.get('optOutFeature', 'bar'), authorized: false }, ], elasticsearch: { cluster: [ @@ -317,58 +377,8 @@ describe('usingPrivileges', () => { const { usingPrivileges } = disableUICapabilitiesFactory( mockRequest, - [ - new KibanaFeature({ - id: 'fooFeature', - name: 'Foo KibanaFeature', - app: [], - category: { id: 'foo', label: 'foo' }, - privileges: null, - }), - new KibanaFeature({ - id: 'barFeature', - name: 'Bar KibanaFeature', - app: ['bar'], - category: { id: 'foo', label: 'foo' }, - privileges: null, - }), - ], - [ - new ElasticsearchFeature({ - id: 'esFeature', - privileges: [ - { - requiredClusterPrivileges: ['manage'], - ui: ['es_manage'], - }, - { - requiredClusterPrivileges: ['monitor'], - ui: ['es_monitor'], - }, - ], - }), - new ElasticsearchFeature({ - id: 'esSecurityFeature', - privileges: [ - { - requiredClusterPrivileges: ['manage_security'], - ui: ['es_manage_sec'], - }, - ], - }), - new ElasticsearchFeature({ - id: 'esManagementFeature', - management: { - kibana: ['esManagement'], - }, - privileges: [ - { - requiredClusterPrivileges: ['manage_security'], - ui: [], - }, - ], - }), - ], + [kibanaFeature1, kibanaFeature2, optOutKibanaFeature], + esFeatures, loggingSystemMock.create().get(), mockAuthz, createMockUser() @@ -377,9 +387,9 @@ describe('usingPrivileges', () => { const result = await usingPrivileges( Object.freeze({ navLinks: { - foo: true, - bar: true, - quz: true, + app1: true, + app2: true, + app3: true, }, management: { kibana: { @@ -389,11 +399,15 @@ describe('usingPrivileges', () => { }, }, catalogue: {}, - fooFeature: { + kibanaFeature1: { foo: true, bar: true, }, - barFeature: { + kibanaFeature2: { + foo: true, + bar: true, + }, + optOutFeature: { foo: true, bar: true, }, @@ -410,9 +424,9 @@ describe('usingPrivileges', () => { expect(result).toEqual({ navLinks: { - foo: true, - bar: false, - quz: true, + app1: true, + app2: false, + app3: true, }, management: { kibana: { @@ -422,14 +436,19 @@ describe('usingPrivileges', () => { }, }, catalogue: {}, - fooFeature: { + kibanaFeature1: { foo: true, bar: false, }, - barFeature: { + kibanaFeature2: { foo: true, bar: false, }, + optOutFeature: { + // these stay enabled because they opt out of Kibana security + foo: true, + bar: true, + }, esFeature: { es_manage: false, es_monitor: true, @@ -442,6 +461,7 @@ describe('usingPrivileges', () => { }); test(`doesn't re-enable disabled uiCapabilities`, async () => { + // grant all privileges const mockAuthz = createMockAuthz({ resolveCheckPrivileges: { privileges: { @@ -449,13 +469,19 @@ describe('usingPrivileges', () => { { privilege: actions.ui.get('navLinks', 'foo'), authorized: true }, { privilege: actions.ui.get('navLinks', 'bar'), authorized: true }, { privilege: actions.ui.get('management', 'kibana', 'indices'), authorized: true }, - { privilege: actions.ui.get('fooFeature', 'foo'), authorized: true }, - { privilege: actions.ui.get('fooFeature', 'bar'), authorized: true }, - { privilege: actions.ui.get('barFeature', 'foo'), authorized: true }, - { privilege: actions.ui.get('barFeature', 'bar'), authorized: true }, + { privilege: actions.ui.get('kibanaFeature1', 'foo'), authorized: true }, + { privilege: actions.ui.get('kibanaFeature1', 'bar'), authorized: true }, + { privilege: actions.ui.get('kibanaFeature2', 'foo'), authorized: true }, + { privilege: actions.ui.get('kibanaFeature2', 'bar'), authorized: true }, + { privilege: actions.ui.get('optOutFeature', 'foo'), authorized: true }, + { privilege: actions.ui.get('optOutFeature', 'bar'), authorized: true }, ], elasticsearch: { - cluster: [], + cluster: [ + { privilege: 'manage', authorized: true }, + { privilege: 'monitor', authorized: true }, + { privilege: 'manage_security', authorized: true }, + ], index: {}, }, }, @@ -464,62 +490,14 @@ describe('usingPrivileges', () => { const { usingPrivileges } = disableUICapabilitiesFactory( mockRequest, - [ - new KibanaFeature({ - id: 'fooFeature', - name: 'Foo KibanaFeature', - app: [], - category: { id: 'foo', label: 'foo' }, - privileges: null, - }), - new KibanaFeature({ - id: 'barFeature', - name: 'Bar KibanaFeature', - app: [], - category: { id: 'foo', label: 'foo' }, - privileges: null, - }), - ], - [ - new ElasticsearchFeature({ - id: 'esFeature', - privileges: [ - { - requiredClusterPrivileges: [], - ui: [], - }, - ], - }), - ], + [kibanaFeature1, kibanaFeature2, optOutKibanaFeature], + esFeatures, loggingSystemMock.create().get(), mockAuthz, createMockUser() ); - const result = await usingPrivileges( - Object.freeze({ - navLinks: { - foo: false, - bar: false, - }, - management: { - kibana: { - indices: false, - }, - }, - catalogue: {}, - fooFeature: { - foo: false, - bar: false, - }, - barFeature: { - foo: false, - bar: false, - }, - }) - ); - - expect(result).toEqual({ + const allFalseCapabilities = Object.freeze({ navLinks: { foo: false, bar: false, @@ -530,36 +508,43 @@ describe('usingPrivileges', () => { }, }, catalogue: {}, - fooFeature: { + kibanaFeature1: { + foo: false, + bar: false, + }, + kibanaFeature2: { foo: false, bar: false, }, - barFeature: { + optOutFeature: { foo: false, bar: false, }, + esFeature: { + es_manage: false, + es_monitor: false, + }, + esSecurityFeature: { + es_manage_sec: false, + }, + esManagementFeature: {}, }); + const result = await usingPrivileges(allFalseCapabilities); + + expect(result).toEqual(allFalseCapabilities); }); }); describe('all', () => { - test(`disables uiCapabilities`, () => { + test(`disables only registered uiCapabilities that do not opt out of kibana security`, () => { const mockAuthz = createMockAuthz({ rejectCheckPrivileges: new Error(`Don't use me`) }); const { all } = disableUICapabilitiesFactory( mockRequest, - [ - new KibanaFeature({ - id: 'fooFeature', - name: 'Foo KibanaFeature', - app: ['foo'], - category: { id: 'foo', label: 'foo' }, - privileges: null, - }), - ], + [kibanaFeature1, optOutKibanaFeature], [ new ElasticsearchFeature({ - id: 'esFeature', + id: 'esFeature1', privileges: [ { requiredClusterPrivileges: [], @@ -576,8 +561,8 @@ describe('all', () => { const result = all( Object.freeze({ navLinks: { - foo: true, - bar: true, + app1: true, + app2: true, // there is no app2 registered }, management: { kibana: { @@ -585,41 +570,61 @@ describe('all', () => { }, }, catalogue: {}, - fooFeature: { + kibanaFeature1: { foo: true, bar: true, }, - barFeature: { + kibanaFeature2: { + // there is no kibanaFeature2 registered foo: true, bar: true, }, - esFeature: { + optOutFeature: { + foo: true, + bar: true, + }, + esFeature1: { + bar: true, + }, + esFeature2: { bar: true, }, }) ); expect(result).toEqual({ navLinks: { - foo: false, - bar: true, + app1: false, + app2: true, // does NOT disable because it is not a registered navlink }, management: { kibana: { - indices: false, + indices: false, // nested values are always disabled }, }, catalogue: {}, - fooFeature: { + kibanaFeature1: { + // registered kibana features with privileges get diabled foo: false, bar: false, }, - barFeature: { - foo: false, - bar: false, + kibanaFeature2: { + // does NOT disable because it is not a registered Kibana feature + foo: true, + bar: true, }, - esFeature: { + optOutFeature: { + // does NOT disable because it opts out (does not define privileges) + foo: true, + bar: true, + }, + esFeature1: { + // registered es features get diabled bar: false, }, + esFeature2: { + // does NOT disable because it is not a registered ES feature + bar: true, + }, }); }); }); diff --git a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts index 161366cb7309c..6023ea402ae56 100644 --- a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts +++ b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts @@ -67,20 +67,58 @@ export function disableUICapabilitiesFactory( }; }, {}); - const shouldDisableFeatureUICapability = ( - featureId: keyof UICapabilities, - uiCapability: string + const isCatalogueItemReferencedByFeatureSet = ( + catalogueEntry: string, + featureSet: Array | undefined }>> ) => { - // if the navLink isn't for a feature that we have registered, we don't wish to - // disable it based on privileges - return featureId !== 'navLinks' || featureNavLinkIds.includes(uiCapability); + return featureSet.some((feature) => (feature.catalogue ?? []).includes(catalogueEntry)); + }; + + const shouldAffectCapability = (featureId: keyof UICapabilities, uiCapability: string) => { + // This method answers: 'Should we affect a capability based on privileges?' + + // 'spaces' and 'fileUpload' feature ID's are handled independently + // The spaces and file_upload plugins have their own capabilites switchers + + // Always affect global settings + if (featureId === 'globalSettings') { + return true; + } + + // If the feature is 'catalogue', return true if it is the 'spaces' capability + // (we always want to affect that) or if we have a feature that references it + // (i.e. found in the 'catalogue' property of a registered Kibana or ES feature) + if (featureId === 'catalogue') { + return ( + uiCapability === 'spaces' || + isCatalogueItemReferencedByFeatureSet(uiCapability, features) || + isCatalogueItemReferencedByFeatureSet(uiCapability, elasticsearchFeatures) + ); + } + + // if the feature is 'navLinks', return true if the nav link was registered + // (i.e. found in the 'app' property of a registered Kibana feature) + if (featureId === 'navLinks') { + return featureNavLinkIds.includes(uiCapability); + } + + // if the feature is a Kibana feature, return true if it defines privileges + // (i.e. it adheres to the Kibana security model) + // Kibana features with no privileges opt out of the Kibana security model and + // are not subject to our control(e.g.Enterprise Search features) + const kibanaFeature = features.find((f) => f.id === featureId); + if (!!kibanaFeature) return !!kibanaFeature.privileges; + + // Lastly return true if the feature is a registered es feature (we always want to affect these), + // otherwise false(we don't know what this feature is so we don't touch it) + return !!elasticsearchFeatureMap[featureId]; }; const disableAll = (uiCapabilities: UICapabilities) => { return mapValues(uiCapabilities, (featureUICapabilities, featureId) => mapValues(featureUICapabilities, (value, uiCapability) => { if (typeof value === 'boolean') { - if (shouldDisableFeatureUICapability(featureId!, uiCapability!)) { + if (shouldAffectCapability(featureId!, uiCapability!)) { return false; } return value; @@ -175,7 +213,7 @@ export function disableUICapabilitiesFactory( ); // Catalogue and management capbility buckets can also be influenced by ES privileges, - // so the early return is not possible for these. + // so the early return is not possible for these *unless we have the required Kibana privileges. if ((!isCatalogueFeature && !isManagementFeature) || hasRequiredKibanaPrivileges) { return hasRequiredKibanaPrivileges; } @@ -230,7 +268,7 @@ export function disableUICapabilitiesFactory( featureUICapabilities, (value: boolean | Record, uiCapability) => { if (typeof value === 'boolean') { - if (!shouldDisableFeatureUICapability(featureId!, uiCapability!)) { + if (!shouldAffectCapability(featureId!, uiCapability!)) { return value; } return checkPrivilegesForCapability(value, featureId!, uiCapability!); From 59f8635f7598f4086a82b258309e9cf1d3f2a2e4 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Thu, 20 Apr 2023 17:01:56 -0400 Subject: [PATCH 35/67] [RAM] kibana.alert.url (#155309) Add alert url as data to allow our user to go back to the details url in kibana --- .../src/field_maps/alert_field_map.ts | 8 ++++++++ .../kbn-rule-data-utils/src/default_alerts_as_data.ts | 5 +++++ .../field_maps/mapping_from_field_map.test.ts | 5 +++++ .../assets/field_maps/technical_rule_field_map.test.ts | 7 +++++++ 4 files changed, 25 insertions(+) diff --git a/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts b/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts index 91f51ea3acffc..87c40e617f90c 100644 --- a/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts +++ b/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts @@ -30,6 +30,7 @@ import { ALERT_START, ALERT_STATUS, ALERT_TIME_RANGE, + ALERT_URL, ALERT_UUID, ALERT_WORKFLOW_STATUS, SPACE_IDS, @@ -155,6 +156,13 @@ export const alertFieldMap = { array: false, required: false, }, + [ALERT_URL]: { + type: 'keyword', + array: false, + index: false, + required: false, + ignore_above: 2048, + }, [ALERT_UUID]: { type: 'keyword', array: false, diff --git a/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts b/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts index 98ea2d76730c2..d803d6ca503b8 100644 --- a/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts +++ b/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts @@ -94,6 +94,9 @@ const ALERT_RULE_TAGS = `${ALERT_RULE_NAMESPACE}.tags` as const; // kibana.alert.rule_type_id - rule type id for rule that generated this alert const ALERT_RULE_TYPE_ID = `${ALERT_RULE_NAMESPACE}.rule_type_id` as const; +// kibana.alert.url - allow our user to go back to the details url in kibana +const ALERT_URL = `${ALERT_NAMESPACE}.url` as const; + // kibana.alert.rule.uuid - rule ID for rule that generated this alert const ALERT_RULE_UUID = `${ALERT_RULE_NAMESPACE}.uuid` as const; @@ -127,6 +130,7 @@ const fields = { ALERT_START, ALERT_STATUS, ALERT_TIME_RANGE, + ALERT_URL, ALERT_UUID, ALERT_WORKFLOW_STATUS, SPACE_IDS, @@ -164,6 +168,7 @@ export { ALERT_START, ALERT_STATUS, ALERT_TIME_RANGE, + ALERT_URL, ALERT_UUID, ALERT_WORKFLOW_STATUS, SPACE_IDS, diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts index 7a86a45dcd045..5aff2411e07f9 100644 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts +++ b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts @@ -281,6 +281,11 @@ describe('mappingFromFieldMap', () => { type: 'date_range', format: 'epoch_millis||strict_date_optional_time', }, + url: { + ignore_above: 2048, + index: false, + type: 'keyword', + }, uuid: { type: 'keyword', }, diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts index b98c94b163445..4efe4d876a3bc 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts @@ -274,6 +274,13 @@ it('matches snapshot', () => { "required": false, "type": "date_range", }, + "kibana.alert.url": Object { + "array": false, + "ignore_above": 2048, + "index": false, + "required": false, + "type": "keyword", + }, "kibana.alert.uuid": Object { "array": false, "required": true, From 954d73d30a94ff7058f98fc907764bba1ab79ce7 Mon Sep 17 00:00:00 2001 From: Alexi Doak <109488926+doakalexi@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:24:07 -0400 Subject: [PATCH 36/67] [ResponseOps][Window Maintenance] Add timezone field to the create form (#155324) Resolves https://github.com/elastic/kibana/issues/153977 ## Summary Adds the timezone combo box to the create form only if the Kibana Setting is set to `'Browser'`. When you edit a maintenance window the timezone will always be visible. Screen Shot 2023-04-19 at 6 00 18 PM ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [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 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../create_maintenance_windows_form.test.tsx | 22 +++ .../create_maintenance_windows_form.tsx | 136 +++++++++++------- .../recurring_schedule.tsx | 63 ++++++-- .../maintenance_windows/components/schema.ts | 2 + ...rt_from_maintenance_window_to_form.test.ts | 11 ++ ...convert_from_maintenance_window_to_form.ts | 1 + .../pages/maintenance_windows/translations.ts | 7 + x-pack/plugins/alerting/tsconfig.json | 1 + 8 files changed, 180 insertions(+), 63 deletions(-) diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.test.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.test.tsx index df382e57c69d0..b2e97dac3389e 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.test.tsx +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.test.tsx @@ -12,6 +12,11 @@ import { CreateMaintenanceWindowFormProps, CreateMaintenanceWindowForm, } from './create_maintenance_windows_form'; +import { useUiSetting } from '@kbn/kibana-react-plugin/public'; + +jest.mock('@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting', () => ({ + useUiSetting: jest.fn(), +})); const formProps: CreateMaintenanceWindowFormProps = { onCancel: jest.fn(), @@ -24,6 +29,7 @@ describe('CreateMaintenanceWindowForm', () => { beforeEach(() => { jest.clearAllMocks(); appMockRenderer = createAppMockRenderer(); + (useUiSetting as jest.Mock).mockReturnValue('America/New_York'); }); it('renders all form fields except the recurring form fields', async () => { @@ -33,6 +39,19 @@ describe('CreateMaintenanceWindowForm', () => { expect(result.getByTestId('date-field')).toBeInTheDocument(); expect(result.getByTestId('recurring-field')).toBeInTheDocument(); expect(result.queryByTestId('recurring-form')).not.toBeInTheDocument(); + expect(result.queryByTestId('timezone-field')).not.toBeInTheDocument(); + }); + + it('renders timezone field when the kibana setting is set to browser', async () => { + (useUiSetting as jest.Mock).mockReturnValue('Browser'); + + const result = appMockRenderer.render(); + + expect(result.getByTestId('title-field')).toBeInTheDocument(); + expect(result.getByTestId('date-field')).toBeInTheDocument(); + expect(result.getByTestId('recurring-field')).toBeInTheDocument(); + expect(result.queryByTestId('recurring-form')).not.toBeInTheDocument(); + expect(result.getByTestId('timezone-field')).toBeInTheDocument(); }); it('should initialize the form when no initialValue provided', () => { @@ -60,6 +79,7 @@ describe('CreateMaintenanceWindowForm', () => { title: 'test', startDate: '2023-03-24', endDate: '2023-03-26', + timezone: ['America/Los_Angeles'], recurring: true, }} /> @@ -71,10 +91,12 @@ describe('CreateMaintenanceWindowForm', () => { 'Press the down key to open a popover containing a calendar.' ); const recurringInput = within(result.getByTestId('recurring-field')).getByTestId('input'); + const timezoneInput = within(result.getByTestId('timezone-field')).getByTestId('input'); expect(titleInput).toHaveValue('test'); expect(dateInputs[0]).toHaveValue('03/24/2023 12:00 AM'); expect(dateInputs[1]).toHaveValue('03/26/2023 12:00 AM'); expect(recurringInput).toBeChecked(); + expect(timezoneInput).toHaveTextContent('America/Los_Angeles'); }); }); diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx index bef99e1188051..b3fe479c21a88 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx @@ -7,6 +7,7 @@ import React, { useCallback, useState } from 'react'; import moment from 'moment'; import { + FIELD_TYPES, Form, getUseField, useForm, @@ -14,7 +15,14 @@ import { UseMultiFields, } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components'; -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFormLabel, + EuiHorizontalRule, +} from '@elastic/eui'; +import { TIMEZONE_OPTIONS as UI_TIMEZONE_OPTIONS } from '@kbn/core-ui-settings-common'; import { FormProps, schema } from './schema'; import * as i18n from '../translations'; @@ -35,16 +43,20 @@ export interface CreateMaintenanceWindowFormProps { maintenanceWindowId?: string; } -export const useTimeZone = (): string => { - const timeZone = useUiSetting('dateFormat:tz'); - return timeZone === 'Browser' ? moment.tz.guess() : timeZone; +const useDefaultTimezone = () => { + const kibanaTz: string = useUiSetting('dateFormat:tz'); + if (!kibanaTz || kibanaTz === 'Browser') { + return { defaultTimezone: moment.tz?.guess() ?? 'UTC', isBrowser: true }; + } + return { defaultTimezone: kibanaTz, isBrowser: false }; }; +const TIMEZONE_OPTIONS = UI_TIMEZONE_OPTIONS.map((n) => ({ label: n })) ?? [{ label: 'UTC' }]; export const CreateMaintenanceWindowForm = React.memo( ({ onCancel, onSuccess, initialValue, maintenanceWindowId }) => { const [defaultStartDateValue] = useState(moment().toISOString()); const [defaultEndDateValue] = useState(moment().add(30, 'minutes').toISOString()); - const timezone = useTimeZone(); + const { defaultTimezone, isBrowser } = useDefaultTimezone(); const isEditMode = initialValue !== undefined && maintenanceWindowId !== undefined; const { mutate: createMaintenanceWindow, isLoading: isCreateLoading } = @@ -60,7 +72,11 @@ export const CreateMaintenanceWindowForm = React.memo - + - - - - - - {(fields) => ( - - )} - - - - - - + + - - - {isRecurring ? : null} + > + {(fields) => } + + {showTimezone ? ( + + + {i18n.CREATE_FORM_TIMEZONE} + + ), + }, + }} + /> + + ) : null} + + + + + {isRecurring ? : null} + { - const [{ startDate, endDate, recurringSchedule }] = useFormData({ + const [{ startDate, endDate, timezone, recurringSchedule }] = useFormData({ watch: [ 'startDate', 'endDate', + 'timezone', 'recurringSchedule.frequency', 'recurringSchedule.interval', 'recurringSchedule.ends', @@ -104,19 +113,43 @@ export const RecurringSchedule: React.FC = React.memo(() => { }} /> {recurringSchedule?.ends === EndsOptions.ON_DATE ? ( - + <> + + + + + + {timezone ? ( + + + {i18n.CREATE_FORM_TIMEZONE} + + } + /> + + ) : null} + + ) : null} {recurringSchedule?.ends === EndsOptions.AFTER_X ? ( = { }, startDate: {}, endDate: {}, + timezone: {}, recurring: { type: FIELD_TYPES.TOGGLE, label: i18n.CREATE_FORM_REPEAT, diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts index 9dc72ea30b1c1..fd2627d2478f1 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts @@ -34,6 +34,7 @@ describe('convertFromMaintenanceWindowToForm', () => { title, startDate: startDate.toISOString(), endDate: endDate.toISOString(), + timezone: ['UTC'], recurring: false, }); }); @@ -55,6 +56,7 @@ describe('convertFromMaintenanceWindowToForm', () => { title, startDate: startDate.toISOString(), endDate: endDate.toISOString(), + timezone: ['UTC'], recurring: true, recurringSchedule: { byweekday: { 1: false, 2: false, 3: true, 4: false, 5: false, 6: false, 7: false }, @@ -85,6 +87,7 @@ describe('convertFromMaintenanceWindowToForm', () => { title, startDate: startDate.toISOString(), endDate: endDate.toISOString(), + timezone: ['UTC'], recurring: true, recurringSchedule: { byweekday: { 1: false, 2: false, 3: true, 4: false, 5: false, 6: false, 7: false }, @@ -114,6 +117,7 @@ describe('convertFromMaintenanceWindowToForm', () => { title, startDate: startDate.toISOString(), endDate: endDate.toISOString(), + timezone: ['UTC'], recurring: true, recurringSchedule: { byweekday: { 1: false, 2: false, 3: true, 4: false, 5: false, 6: false, 7: false }, @@ -142,6 +146,7 @@ describe('convertFromMaintenanceWindowToForm', () => { title, startDate: startDate.toISOString(), endDate: endDate.toISOString(), + timezone: ['UTC'], recurring: true, recurringSchedule: { ends: 'never', @@ -169,6 +174,7 @@ describe('convertFromMaintenanceWindowToForm', () => { title, startDate: startDate.toISOString(), endDate: endDate.toISOString(), + timezone: ['UTC'], recurring: true, recurringSchedule: { ends: 'never', @@ -197,6 +203,7 @@ describe('convertFromMaintenanceWindowToForm', () => { title, startDate: startDate.toISOString(), endDate: endDate.toISOString(), + timezone: ['UTC'], recurring: true, recurringSchedule: { ends: 'never', @@ -222,6 +229,7 @@ describe('convertFromMaintenanceWindowToForm', () => { title, startDate: startDate.toISOString(), endDate: endDate.toISOString(), + timezone: ['UTC'], recurring: true, recurringSchedule: { customFrequency: Frequency.DAILY, @@ -249,6 +257,7 @@ describe('convertFromMaintenanceWindowToForm', () => { title, startDate: startDate.toISOString(), endDate: endDate.toISOString(), + timezone: ['UTC'], recurring: true, recurringSchedule: { byweekday: { 1: false, 2: false, 3: true, 4: true, 5: false, 6: false, 7: false }, @@ -277,6 +286,7 @@ describe('convertFromMaintenanceWindowToForm', () => { title, startDate: startDate.toISOString(), endDate: endDate.toISOString(), + timezone: ['UTC'], recurring: true, recurringSchedule: { bymonth: 'day', @@ -306,6 +316,7 @@ describe('convertFromMaintenanceWindowToForm', () => { title, startDate: startDate.toISOString(), endDate: endDate.toISOString(), + timezone: ['UTC'], recurring: true, recurringSchedule: { customFrequency: Frequency.YEARLY, diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.ts index c4b2c551de0cd..954e6b8bd2658 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.ts @@ -24,6 +24,7 @@ export const convertFromMaintenanceWindowToForm = ( title: maintenanceWindow.title, startDate, endDate: endDate.toISOString(), + timezone: [maintenanceWindow.rRule.tzid], recurring, }; if (!recurring) return form; diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts index 7dcb388f84967..1b3317d9182b2 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts @@ -102,6 +102,13 @@ export const CREATE_FORM_SCHEDULE_INVALID = i18n.translate( } ); +export const CREATE_FORM_TIMEZONE = i18n.translate( + 'xpack.alerting.maintenanceWindows.createForm.timezone', + { + defaultMessage: 'Time zone', + } +); + export const CREATE_FORM_REPEAT = i18n.translate( 'xpack.alerting.maintenanceWindows.createForm.repeat', { diff --git a/x-pack/plugins/alerting/tsconfig.json b/x-pack/plugins/alerting/tsconfig.json index 3cf3904d3942b..200b8e3c2a79e 100644 --- a/x-pack/plugins/alerting/tsconfig.json +++ b/x-pack/plugins/alerting/tsconfig.json @@ -51,6 +51,7 @@ "@kbn/core-doc-links-server-mocks", "@kbn/doc-links", "@kbn/core-saved-objects-utils-server", + "@kbn/core-ui-settings-common", ], "exclude": [ "target/**/*", From 1676432b6197358988f59585a7c225c35cb4fb93 Mon Sep 17 00:00:00 2001 From: Alexi Doak <109488926+doakalexi@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:24:34 -0400 Subject: [PATCH 37/67] [ResponseOps][Window Maintenance] Add the upcoming events popover to the maintenance window table (#154978) Resolves https://github.com/elastic/kibana/issues/154815 ## Summary Adding upcoming popover Screen Shot 2023-04-14 at 2 27 58 PM ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [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 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Lisa Cawley --- .../components/maintenance_windows_list.tsx | 16 ++- .../upcoming_events_popover.test.tsx | 67 +++++++++ .../components/upcoming_events_popover.tsx | 127 ++++++++++++++++++ ...rt_from_maintenance_window_to_form.test.ts | 4 +- .../helpers/convert_to_rrule.test.ts | 4 +- .../helpers/convert_to_rrule.ts | 3 +- .../pages/maintenance_windows/translations.ts | 4 + 7 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/alerting/public/pages/maintenance_windows/components/upcoming_events_popover.test.tsx create mode 100644 x-pack/plugins/alerting/public/pages/maintenance_windows/components/upcoming_events_popover.tsx diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/maintenance_windows_list.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/maintenance_windows_list.tsx index 33a36a2bc0dea..9d4fc521c3f66 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/maintenance_windows_list.tsx +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/maintenance_windows_list.tsx @@ -12,12 +12,15 @@ import { EuiBasicTableColumn, EuiButton, useEuiBackgroundColor, + EuiFlexGroup, + EuiFlexItem, SearchFilterConfig, } from '@elastic/eui'; import { css } from '@emotion/react'; import { MaintenanceWindowFindResponse, SortDirection } from '../types'; import * as i18n from '../translations'; import { useEditMaintenanceWindowsNavigation } from '../../../hooks/use_navigation'; +import { UpcomingEventsPopover } from './upcoming_events_popover'; import { StatusColor, STATUS_DISPLAY, STATUS_SORT } from '../constants'; import { MaintenanceWindowStatus } from '../../../../common'; import { StatusFilter } from './status_filter'; @@ -61,7 +64,18 @@ const columns: Array> = [ field: 'eventStartTime', name: i18n.TABLE_START_TIME, dataType: 'date', - render: (startDate: string) => formatDate(startDate, 'MM/DD/YY HH:mm A'), + render: (startDate: string, item: MaintenanceWindowFindResponse) => { + return ( + + {formatDate(startDate, 'MM/DD/YY HH:mm A')} + {item.events.length > 1 ? ( + + + + ) : null} + + ); + }, sortable: true, }, { diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/upcoming_events_popover.test.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/upcoming_events_popover.test.tsx new file mode 100644 index 0000000000000..574bb7d1f7549 --- /dev/null +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/upcoming_events_popover.test.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { fireEvent } from '@testing-library/react'; +import * as React from 'react'; +import { AppMockRenderer, createAppMockRenderer } from '../../../lib/test_utils'; +import { UpcomingEventsPopover } from './upcoming_events_popover'; +import { MaintenanceWindowStatus } from '../../../../common'; + +describe('rule_actions_popover', () => { + let appMockRenderer: AppMockRenderer; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRenderer = createAppMockRenderer(); + }); + + it('renders the top 3 events', () => { + const result = appMockRenderer.render( + + ); + + const popoverButton = result.getByTestId('upcoming-events-icon-button'); + expect(popoverButton).toBeInTheDocument(); + fireEvent.click(popoverButton); + + expect(result.getByTestId('upcoming-events-popover-title')).toBeInTheDocument(); + expect(result.getByTestId('upcoming-events-popover-title')).toHaveTextContent( + 'Repeats every Friday' + ); + expect(result.getAllByTestId('upcoming-events-popover-item').length).toBe(3); + }); +}); diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/upcoming_events_popover.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/upcoming_events_popover.tsx new file mode 100644 index 0000000000000..a20907a2e1236 --- /dev/null +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/upcoming_events_popover.tsx @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useMemo, useState } from 'react'; +import moment from 'moment'; +import { findIndex } from 'lodash'; +import { + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiPopover, + EuiPopoverTitle, + EuiSpacer, + EuiText, + formatDate, +} from '@elastic/eui'; +import * as i18n from '../translations'; +import { recurringSummary } from '../helpers/recurring_summary'; +import { getPresets } from '../helpers/get_presets'; +import { MaintenanceWindowFindResponse } from '../types'; +import { convertFromMaintenanceWindowToForm } from '../helpers/convert_from_maintenance_window_to_form'; + +interface UpcomingEventsPopoverProps { + maintenanceWindowFindResponse: MaintenanceWindowFindResponse; +} + +export const UpcomingEventsPopover: React.FC = React.memo( + ({ maintenanceWindowFindResponse }) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const onButtonClick = useCallback(() => { + setIsPopoverOpen((open) => !open); + }, []); + const closePopover = useCallback(() => { + setIsPopoverOpen(false); + }, []); + + const { startDate, recurringSchedule, topEvents, presets } = useMemo(() => { + const maintenanceWindow = convertFromMaintenanceWindowToForm(maintenanceWindowFindResponse); + const date = moment(maintenanceWindow.startDate); + const currentEventIndex = findIndex( + maintenanceWindowFindResponse.events, + (event) => + event.gte === maintenanceWindowFindResponse.eventStartTime && + event.lte === maintenanceWindowFindResponse.eventEndTime + ); + return { + startDate: date, + recurringSchedule: maintenanceWindow.recurringSchedule, + topEvents: maintenanceWindowFindResponse.events.slice( + currentEventIndex + 1, + currentEventIndex + 4 + ), + presets: getPresets(date), + }; + }, [maintenanceWindowFindResponse]); + + return ( + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + anchorPosition="downCenter" + > + + {i18n.CREATE_FORM_RECURRING_SUMMARY_PREFIX( + recurringSummary(startDate, recurringSchedule, presets) + )} + + + + + {i18n.UPCOMING} + + + + {topEvents.map((event, index) => ( + + + + + + + + {formatDate(event.gte, 'MM/DD/YY HH:mm A')} + + + + {index < topEvents.length - 1 ? ( + + ) : null} + + ))} + + + ); + } +); +UpcomingEventsPopover.displayName = 'UpcomingEventsPopover'; diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts index fd2627d2478f1..c892f7db66729 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_from_maintenance_window_to_form.test.ts @@ -194,7 +194,7 @@ describe('convertFromMaintenanceWindowToForm', () => { tzid: 'UTC', freq: RRuleFrequency.YEARLY, interval: 1, - bymonth: [2], + bymonth: [3], bymonthday: [22], }, }); @@ -307,7 +307,7 @@ describe('convertFromMaintenanceWindowToForm', () => { tzid: 'UTC', freq: RRuleFrequency.YEARLY, interval: 3, - bymonth: [2], + bymonth: [3], bymonthday: [22], }, }); diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.test.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.test.ts index 737bb47f4aa33..7eaecba5169b8 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.test.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.test.ts @@ -121,7 +121,7 @@ describe('convertToRRule', () => { tzid: 'UTC', freq: RRuleFrequency.YEARLY, interval: 1, - bymonth: [2], + bymonth: [3], bymonthday: [22], }); }); @@ -209,7 +209,7 @@ describe('convertToRRule', () => { tzid: 'UTC', freq: RRuleFrequency.YEARLY, interval: 3, - bymonth: [2], + bymonth: [3], bymonthday: [22], }); }); diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.ts index b284e50579deb..90706165c717b 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/helpers/convert_to_rrule.ts @@ -68,7 +68,8 @@ export const convertToRRule = ( } if (frequency === Frequency.YEARLY) { - rRule.bymonth = [startDate.month()]; + // rRule expects 1 based indexing for months + rRule.bymonth = [startDate.month() + 1]; rRule.bymonthday = [startDate.date()]; } diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts index 1b3317d9182b2..30b83963cc5b7 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts @@ -468,3 +468,7 @@ export const EXPERIMENTAL_DESCRIPTION = i18n.translate( 'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will take a best effort approach to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.', } ); + +export const UPCOMING = i18n.translate('xpack.alerting.maintenanceWindows.upcoming', { + defaultMessage: 'Upcoming', +}); From 9ac6f58ab342a2d11856e77d5d5da1cf4b2bda32 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Thu, 20 Apr 2023 18:32:42 -0400 Subject: [PATCH 38/67] [RAM] allow user to predefined their id when they create connector (#155392) ## Summary allow user to predefined their id when they create connector ### 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 --- .../s@{spaceid}@api@actions@connector.yaml | 11 +++- .../actions/server/actions_client.test.ts | 64 +++++++++++++++++++ .../plugins/actions/server/actions_client.ts | 15 ++++- .../actions/server/routes/create.test.ts | 3 +- .../plugins/actions/server/routes/create.ts | 9 ++- .../group2/tests/actions/create.ts | 60 +++++++++++++++++ 6 files changed, 157 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml index fbf14725db7ff..b3d42c3f47484 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml @@ -8,6 +8,15 @@ post: parameters: - $ref: '../components/headers/kbn_xsrf.yaml' - $ref: '../components/parameters/space_id.yaml' + - in: path + name: id + description: > + An UUID v1 or v4 identifier for the connector. If you omit this parameter, + an identifier is randomly generated. + required: true + schema: + type: string + example: ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 requestBody: required: true content: @@ -18,7 +27,7 @@ post: oneOf: - $ref: '../components/schemas/create_connector_request_cases_webhook.yaml' - $ref: '../components/schemas/create_connector_request_email.yaml' - - $ref: '../components/schemas/create_connector_request_index.yaml' + - $ref: '../components/schemas/create_connector_request_index.yaml' - $ref: '../components/schemas/create_connector_request_jira.yaml' - $ref: '../components/schemas/create_connector_request_opsgenie.yaml' - $ref: '../components/schemas/create_connector_request_pagerduty.yaml' diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts index 3c4b1a876c522..2b0565c8df9d0 100644 --- a/x-pack/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -691,6 +691,70 @@ describe('create()', () => { }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`); }); + + test('throws error when predefined id match a pre-configure action id', async () => { + const preDefinedId = 'mySuperRadTestPreconfiguredId'; + actionTypeRegistry.register({ + id: 'my-action-type', + name: 'My action type', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + validate: { + config: { schema: schema.object({}) }, + secrets: { schema: schema.object({}) }, + params: { schema: schema.object({}) }, + }, + executor, + }); + + actionsClient = new ActionsClient({ + logger, + actionTypeRegistry, + unsecuredSavedObjectsClient, + scopedClusterClient, + defaultKibanaIndex, + preconfiguredActions: [ + { + id: preDefinedId, + actionTypeId: 'my-action-type', + secrets: { + test: 'test1', + }, + isPreconfigured: true, + isDeprecated: false, + name: 'test', + config: { + foo: 'bar', + }, + }, + ], + + actionExecutor, + executionEnqueuer, + ephemeralExecutionEnqueuer, + bulkExecutionEnqueuer, + request, + authorization: authorization as unknown as ActionsAuthorization, + connectorTokenClient: connectorTokenClientMock.create(), + getEventLogClient, + }); + + await expect( + actionsClient.create({ + action: { + name: 'my name', + actionTypeId: 'my-action-type', + config: {}, + secrets: {}, + }, + options: { + id: preDefinedId, + }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"This mySuperRadTestPreconfiguredId already exist in preconfigured action."` + ); + }); }); describe('get()', () => { diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index 867fc4f9c7e47..201237ce33033 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -106,6 +106,7 @@ interface Action extends ActionUpdate { export interface CreateOptions { action: Action; + options?: { id?: string }; } interface ConstructorOptions { @@ -191,8 +192,20 @@ export class ActionsClient { */ public async create({ action: { actionTypeId, name, config, secrets }, + options, }: CreateOptions): Promise { - const id = SavedObjectsUtils.generateId(); + const id = options?.id || SavedObjectsUtils.generateId(); + + if (this.preconfiguredActions.some((preconfiguredAction) => preconfiguredAction.id === id)) { + throw Boom.badRequest( + i18n.translate('xpack.actions.serverSideErrors.predefinedIdConnectorAlreadyExists', { + defaultMessage: 'This {id} already exist in preconfigured action.', + values: { + id, + }, + }) + ); + } try { await this.authorization.ensureAuthorized('create', actionTypeId); diff --git a/x-pack/plugins/actions/server/routes/create.test.ts b/x-pack/plugins/actions/server/routes/create.test.ts index 184dcaa00599c..dc06a119ca2fa 100644 --- a/x-pack/plugins/actions/server/routes/create.test.ts +++ b/x-pack/plugins/actions/server/routes/create.test.ts @@ -31,7 +31,7 @@ describe('createActionRoute', () => { const [config, handler] = router.post.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/actions/connector"`); + expect(config.path).toMatchInlineSnapshot(`"/api/actions/connector/{id?}"`); const createResult = { id: '1', @@ -86,6 +86,7 @@ describe('createActionRoute', () => { "name": "My name", "secrets": Object {}, }, + "options": undefined, }, ] `); diff --git a/x-pack/plugins/actions/server/routes/create.ts b/x-pack/plugins/actions/server/routes/create.ts index 369b421de41a3..c55661ce7b921 100644 --- a/x-pack/plugins/actions/server/routes/create.ts +++ b/x-pack/plugins/actions/server/routes/create.ts @@ -50,8 +50,13 @@ export const createActionRoute = ( ) => { router.post( { - path: `${BASE_ACTION_API_PATH}/connector`, + path: `${BASE_ACTION_API_PATH}/connector/{id?}`, validate: { + params: schema.maybe( + schema.object({ + id: schema.maybe(schema.string()), + }) + ), body: bodySchema, }, }, @@ -60,7 +65,7 @@ export const createActionRoute = ( const actionsClient = (await context.actions).getActionsClient(); const action = rewriteBodyReq(req.body); return res.ok({ - body: rewriteBodyRes(await actionsClient.create({ action })), + body: rewriteBodyRes(await actionsClient.create({ action, options: req.params })), }); }) ) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/create.ts index d32fcb86224f2..dce40323e9e1c 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/create.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { v4 as uuidv4 } from 'uuid'; import expect from '@kbn/expect'; import { UserAtSpaceScenarios } from '../../../scenarios'; import { checkAAD, getUrlPrefix, ObjectRemover } from '../../../../common/lib'; @@ -257,6 +258,65 @@ export default function createActionTests({ getService }: FtrProviderContext) { throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); } }); + + it('should handle create action request appropriately with a predefined id', async () => { + const predefinedId = uuidv4(); + const response = await supertestWithoutAuth + .post(`${getUrlPrefix(space.id)}/api/actions/connector/${predefinedId}`) + .auth(user.username, user.password) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: 'test.index-record', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + secrets: { + encrypted: 'This value should be encrypted', + }, + }); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'global_read at space1': + case 'space_1_all_alerts_none_actions at space1': + case 'space_1_all at space2': + expect(response.statusCode).to.eql(403); + expect(response.body).to.eql({ + statusCode: 403, + error: 'Forbidden', + message: 'Unauthorized to create a "test.index-record" action', + }); + break; + case 'superuser at space1': + case 'space_1_all at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(response.statusCode).to.eql(200); + objectRemover.add(space.id, response.body.id, 'action', 'actions'); + expect(response.body).to.eql({ + id: predefinedId, + is_preconfigured: false, + is_deprecated: false, + is_missing_secrets: false, + name: 'My action', + connector_type_id: 'test.index-record', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + }); + expect(typeof response.body.id).to.be('string'); + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: space.id, + type: 'action', + id: response.body.id, + }); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); }); } }); From 012d019044da4fdf9b48b8b997a3c93a88872e62 Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Thu, 20 Apr 2023 18:50:33 -0400 Subject: [PATCH 39/67] [Security Solution] Update Register as AV restriction message (#154687) ## Summary Based on user feedback, we're updating the Register as AV restriction message to better communicate why Windows Server does not support it. In addition, we change the icon to be more noticeable by users to view the restrictions. ![image](https://user-images.githubusercontent.com/56395104/230986046-54b88920-e2c1-422a-b192-11fdd97c35c5.png) ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../view/components/antivirus_registration_form/index.tsx | 5 ++++- .../pages/policy/view/components/config_form/index.tsx | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/antivirus_registration_form/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/antivirus_registration_form/index.tsx index 24062bf00ef46..4921353317a64 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/antivirus_registration_form/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/antivirus_registration_form/index.tsx @@ -60,7 +60,10 @@ export const AntivirusRegistrationForm = memo(() => { supportedOss={[OperatingSystem.WINDOWS]} osRestriction={i18n.translate( 'xpack.securitySolution.endpoint.policy.details.av.windowsServerNotSupported', - { defaultMessage: 'Windows Server operating systems unsupported' } + { + defaultMessage: + 'Windows Server operating systems unsupported because Antivirus registration requires Windows Security Center, which is not included in Windows Server operating systems.', + } )} > {TRANSLATIONS.description} diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.tsx index 23db0cd14bf07..b5f46c91dade4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.tsx @@ -90,7 +90,7 @@ export const ConfigForm: FC = memo(
- + From 90242065d986e40c9c9f8c7ad1d75c1693e12e4f Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 20 Apr 2023 18:55:47 -0400 Subject: [PATCH 40/67] skip failing test suite (#155429) --- x-pack/test/functional/apps/infra/hosts_view.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts index 3e229fcdc1142..3cf0091c93bd4 100644 --- a/x-pack/test/functional/apps/infra/hosts_view.ts +++ b/x-pack/test/functional/apps/infra/hosts_view.ts @@ -150,7 +150,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // Tests - describe('Hosts View', function () { + // Failing: See https://github.com/elastic/kibana/issues/155429 + describe.skip('Hosts View', function () { this.tags('includeFirefox'); before(async () => { From 3bfdefc21a0bb564e97ffbd579bb3ffd19a3af5a Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 21 Apr 2023 01:08:24 -0400 Subject: [PATCH 41/67] [api-docs] 2023-04-21 Daily api_docs build (#155475) Generated by https://buildkite.com/elastic/kibana-api-docs-daily/builds/314 --- api_docs/actions.devdocs.json | 2 +- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- api_docs/aiops.devdocs.json | 40 +++ api_docs/aiops.mdx | 4 +- api_docs/alerting.devdocs.json | 16 +- api_docs/alerting.mdx | 4 +- api_docs/apm.devdocs.json | 70 ++++- api_docs/apm.mdx | 4 +- api_docs/asset_manager.mdx | 2 +- api_docs/banners.devdocs.json | 4 +- api_docs/banners.mdx | 2 +- api_docs/bfetch.mdx | 2 +- api_docs/canvas.mdx | 2 +- api_docs/cases.devdocs.json | 2 +- api_docs/cases.mdx | 2 +- api_docs/charts.mdx | 2 +- api_docs/cloud.mdx | 2 +- api_docs/cloud_chat.mdx | 2 +- api_docs/cloud_data_migration.mdx | 2 +- api_docs/cloud_defend.mdx | 2 +- api_docs/cloud_experiments.mdx | 2 +- api_docs/cloud_security_posture.mdx | 2 +- api_docs/console.mdx | 2 +- api_docs/content_management.mdx | 2 +- api_docs/controls.mdx | 2 +- api_docs/custom_integrations.mdx | 2 +- api_docs/dashboard.mdx | 2 +- api_docs/dashboard_enhanced.mdx | 2 +- api_docs/data.devdocs.json | 22 +- api_docs/data.mdx | 2 +- api_docs/data_query.mdx | 2 +- api_docs/data_search.mdx | 2 +- api_docs/data_view_editor.mdx | 2 +- api_docs/data_view_field_editor.mdx | 2 +- api_docs/data_view_management.mdx | 2 +- api_docs/data_views.devdocs.json | 18 +- api_docs/data_views.mdx | 2 +- api_docs/data_visualizer.mdx | 2 +- api_docs/deprecations_by_api.mdx | 2 +- api_docs/deprecations_by_plugin.mdx | 12 +- api_docs/deprecations_by_team.mdx | 2 +- api_docs/dev_tools.mdx | 2 +- api_docs/discover.mdx | 2 +- api_docs/discover_enhanced.mdx | 2 +- api_docs/ecs_data_quality_dashboard.mdx | 2 +- api_docs/embeddable.mdx | 2 +- api_docs/embeddable_enhanced.mdx | 2 +- api_docs/encrypted_saved_objects.mdx | 2 +- api_docs/enterprise_search.mdx | 2 +- api_docs/es_ui_shared.mdx | 2 +- api_docs/event_annotation.mdx | 2 +- api_docs/event_log.mdx | 2 +- api_docs/exploratory_view.devdocs.json | 2 +- api_docs/exploratory_view.mdx | 2 +- api_docs/expression_error.mdx | 2 +- api_docs/expression_gauge.mdx | 2 +- api_docs/expression_heatmap.devdocs.json | 2 +- api_docs/expression_heatmap.mdx | 2 +- api_docs/expression_image.devdocs.json | 2 +- api_docs/expression_image.mdx | 2 +- api_docs/expression_legacy_metric_vis.mdx | 2 +- api_docs/expression_metric.mdx | 2 +- api_docs/expression_metric_vis.mdx | 2 +- api_docs/expression_partition_vis.mdx | 2 +- api_docs/expression_repeat_image.devdocs.json | 2 +- api_docs/expression_repeat_image.mdx | 2 +- api_docs/expression_reveal_image.mdx | 2 +- api_docs/expression_shape.devdocs.json | 4 +- api_docs/expression_shape.mdx | 2 +- api_docs/expression_tagcloud.mdx | 2 +- api_docs/expression_x_y.devdocs.json | 22 +- api_docs/expression_x_y.mdx | 2 +- api_docs/expressions.mdx | 2 +- api_docs/features.mdx | 2 +- api_docs/field_formats.mdx | 2 +- api_docs/file_upload.mdx | 2 +- api_docs/files.devdocs.json | 6 +- api_docs/files.mdx | 2 +- api_docs/files_management.mdx | 2 +- api_docs/fleet.devdocs.json | 132 ++++++-- api_docs/fleet.mdx | 4 +- api_docs/global_search.mdx | 2 +- api_docs/guided_onboarding.mdx | 2 +- api_docs/home.mdx | 2 +- api_docs/image_embeddable.mdx | 2 +- api_docs/index_lifecycle_management.mdx | 2 +- api_docs/index_management.mdx | 2 +- api_docs/infra.mdx | 2 +- api_docs/inspector.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/kbn_ace.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_utils.mdx | 2 +- api_docs/kbn_alerting_state_types.mdx | 2 +- api_docs/kbn_alerts.mdx | 2 +- .../kbn_alerts_as_data_utils.devdocs.json | 8 +- api_docs/kbn_alerts_as_data_utils.mdx | 2 +- api_docs/kbn_alerts_ui_shared.mdx | 2 +- api_docs/kbn_analytics.mdx | 2 +- api_docs/kbn_analytics_client.devdocs.json | 4 + api_docs/kbn_analytics_client.mdx | 2 +- ..._analytics_shippers_elastic_v3_browser.mdx | 2 +- ...n_analytics_shippers_elastic_v3_common.mdx | 2 +- ...n_analytics_shippers_elastic_v3_server.mdx | 2 +- api_docs/kbn_analytics_shippers_fullstory.mdx | 2 +- api_docs/kbn_analytics_shippers_gainsight.mdx | 2 +- api_docs/kbn_apm_config_loader.mdx | 2 +- api_docs/kbn_apm_synthtrace.mdx | 2 +- api_docs/kbn_apm_synthtrace_client.mdx | 2 +- api_docs/kbn_apm_utils.mdx | 2 +- api_docs/kbn_axe_config.mdx | 2 +- api_docs/kbn_cases_components.mdx | 2 +- api_docs/kbn_cell_actions.mdx | 2 +- api_docs/kbn_chart_expressions_common.mdx | 2 +- api_docs/kbn_chart_icons.mdx | 2 +- api_docs/kbn_ci_stats_core.mdx | 2 +- api_docs/kbn_ci_stats_performance_metrics.mdx | 2 +- api_docs/kbn_ci_stats_reporter.mdx | 2 +- api_docs/kbn_cli_dev_mode.mdx | 2 +- api_docs/kbn_code_editor.mdx | 2 +- api_docs/kbn_code_editor_mocks.devdocs.json | 2 +- api_docs/kbn_code_editor_mocks.mdx | 2 +- api_docs/kbn_coloring.mdx | 2 +- api_docs/kbn_config.mdx | 2 +- api_docs/kbn_config_mocks.mdx | 2 +- api_docs/kbn_config_schema.mdx | 2 +- .../kbn_content_management_content_editor.mdx | 2 +- .../kbn_content_management_table_list.mdx | 2 +- api_docs/kbn_core_analytics_browser.mdx | 2 +- .../kbn_core_analytics_browser_internal.mdx | 2 +- api_docs/kbn_core_analytics_browser_mocks.mdx | 2 +- api_docs/kbn_core_analytics_server.mdx | 2 +- .../kbn_core_analytics_server_internal.mdx | 2 +- api_docs/kbn_core_analytics_server_mocks.mdx | 2 +- api_docs/kbn_core_application_browser.mdx | 2 +- .../kbn_core_application_browser_internal.mdx | 2 +- .../kbn_core_application_browser_mocks.mdx | 2 +- api_docs/kbn_core_application_common.mdx | 2 +- api_docs/kbn_core_apps_browser_internal.mdx | 2 +- api_docs/kbn_core_apps_browser_mocks.mdx | 2 +- api_docs/kbn_core_apps_server_internal.mdx | 2 +- api_docs/kbn_core_base_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_common.mdx | 2 +- api_docs/kbn_core_base_server_internal.mdx | 2 +- api_docs/kbn_core_base_server_mocks.mdx | 2 +- .../kbn_core_capabilities_browser_mocks.mdx | 2 +- api_docs/kbn_core_capabilities_common.mdx | 2 +- api_docs/kbn_core_capabilities_server.mdx | 2 +- .../kbn_core_capabilities_server_mocks.mdx | 2 +- api_docs/kbn_core_chrome_browser.mdx | 2 +- api_docs/kbn_core_chrome_browser_mocks.mdx | 2 +- api_docs/kbn_core_config_server_internal.mdx | 2 +- api_docs/kbn_core_custom_branding_browser.mdx | 2 +- ..._core_custom_branding_browser_internal.mdx | 2 +- ...kbn_core_custom_branding_browser_mocks.mdx | 2 +- api_docs/kbn_core_custom_branding_common.mdx | 2 +- api_docs/kbn_core_custom_branding_server.mdx | 2 +- ...n_core_custom_branding_server_internal.mdx | 2 +- .../kbn_core_custom_branding_server_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_browser.mdx | 2 +- ...kbn_core_deprecations_browser_internal.mdx | 2 +- .../kbn_core_deprecations_browser_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_common.mdx | 2 +- api_docs/kbn_core_deprecations_server.mdx | 2 +- .../kbn_core_deprecations_server_internal.mdx | 2 +- .../kbn_core_deprecations_server_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_browser.mdx | 2 +- api_docs/kbn_core_doc_links_browser_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_server.mdx | 2 +- api_docs/kbn_core_doc_links_server_mocks.mdx | 2 +- ...e_elasticsearch_client_server_internal.mdx | 2 +- ...ticsearch_client_server_mocks.devdocs.json | 88 +++--- ...core_elasticsearch_client_server_mocks.mdx | 2 +- ...kbn_core_elasticsearch_server.devdocs.json | 24 +- api_docs/kbn_core_elasticsearch_server.mdx | 2 +- ...elasticsearch_server_internal.devdocs.json | 12 +- ...kbn_core_elasticsearch_server_internal.mdx | 2 +- .../kbn_core_elasticsearch_server_mocks.mdx | 2 +- .../kbn_core_environment_server_internal.mdx | 2 +- .../kbn_core_environment_server_mocks.mdx | 2 +- .../kbn_core_execution_context_browser.mdx | 2 +- ...ore_execution_context_browser_internal.mdx | 2 +- ...n_core_execution_context_browser_mocks.mdx | 2 +- .../kbn_core_execution_context_common.mdx | 2 +- .../kbn_core_execution_context_server.mdx | 2 +- ...core_execution_context_server_internal.mdx | 2 +- ...bn_core_execution_context_server_mocks.mdx | 2 +- api_docs/kbn_core_fatal_errors_browser.mdx | 2 +- .../kbn_core_fatal_errors_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_browser.mdx | 2 +- api_docs/kbn_core_http_browser_internal.mdx | 2 +- api_docs/kbn_core_http_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_common.mdx | 2 +- .../kbn_core_http_context_server_mocks.mdx | 2 +- ...re_http_request_handler_context_server.mdx | 2 +- api_docs/kbn_core_http_resources_server.mdx | 2 +- ...bn_core_http_resources_server_internal.mdx | 2 +- .../kbn_core_http_resources_server_mocks.mdx | 2 +- .../kbn_core_http_router_server_internal.mdx | 2 +- .../kbn_core_http_router_server_mocks.mdx | 2 +- api_docs/kbn_core_http_server.mdx | 2 +- api_docs/kbn_core_http_server_internal.mdx | 2 +- api_docs/kbn_core_http_server_mocks.mdx | 2 +- api_docs/kbn_core_i18n_browser.mdx | 2 +- api_docs/kbn_core_i18n_browser_mocks.mdx | 2 +- api_docs/kbn_core_i18n_server.mdx | 2 +- api_docs/kbn_core_i18n_server_internal.mdx | 2 +- api_docs/kbn_core_i18n_server_mocks.mdx | 2 +- ...n_core_injected_metadata_browser_mocks.mdx | 2 +- ...kbn_core_integrations_browser_internal.mdx | 2 +- .../kbn_core_integrations_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_browser.mdx | 2 +- api_docs/kbn_core_lifecycle_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_server.mdx | 2 +- api_docs/kbn_core_lifecycle_server_mocks.mdx | 2 +- api_docs/kbn_core_logging_browser_mocks.mdx | 2 +- api_docs/kbn_core_logging_common_internal.mdx | 2 +- api_docs/kbn_core_logging_server.mdx | 2 +- api_docs/kbn_core_logging_server_internal.mdx | 2 +- api_docs/kbn_core_logging_server_mocks.mdx | 2 +- ...ore_metrics_collectors_server_internal.mdx | 2 +- ...n_core_metrics_collectors_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_server.mdx | 2 +- api_docs/kbn_core_metrics_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_server_mocks.mdx | 2 +- api_docs/kbn_core_mount_utils_browser.mdx | 2 +- api_docs/kbn_core_node_server.mdx | 2 +- api_docs/kbn_core_node_server_internal.mdx | 2 +- api_docs/kbn_core_node_server_mocks.mdx | 2 +- ...bn_core_notifications_browser.devdocs.json | 4 +- api_docs/kbn_core_notifications_browser.mdx | 2 +- ...bn_core_notifications_browser_internal.mdx | 2 +- .../kbn_core_notifications_browser_mocks.mdx | 2 +- api_docs/kbn_core_overlays_browser.mdx | 2 +- .../kbn_core_overlays_browser_internal.mdx | 2 +- api_docs/kbn_core_overlays_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_browser.mdx | 2 +- api_docs/kbn_core_plugins_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_server.mdx | 2 +- api_docs/kbn_core_plugins_server_mocks.mdx | 2 +- api_docs/kbn_core_preboot_server.mdx | 2 +- api_docs/kbn_core_preboot_server_mocks.mdx | 2 +- api_docs/kbn_core_rendering_browser_mocks.mdx | 2 +- .../kbn_core_rendering_server_internal.mdx | 2 +- api_docs/kbn_core_rendering_server_mocks.mdx | 2 +- api_docs/kbn_core_root_server_internal.mdx | 2 +- ...ore_saved_objects_api_browser.devdocs.json | 32 ++ .../kbn_core_saved_objects_api_browser.mdx | 4 +- ...core_saved_objects_api_server.devdocs.json | 72 +++++ .../kbn_core_saved_objects_api_server.mdx | 4 +- ...core_saved_objects_api_server_internal.mdx | 2 +- ...bn_core_saved_objects_api_server_mocks.mdx | 2 +- ...ore_saved_objects_base_server_internal.mdx | 2 +- ...n_core_saved_objects_base_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_browser.mdx | 2 +- ...bn_core_saved_objects_browser_internal.mdx | 2 +- .../kbn_core_saved_objects_browser_mocks.mdx | 2 +- ...kbn_core_saved_objects_common.devdocs.json | 8 + api_docs/kbn_core_saved_objects_common.mdx | 2 +- ..._objects_import_export_server_internal.mdx | 2 +- ...ved_objects_import_export_server_mocks.mdx | 2 +- ...cts_migration_server_internal.devdocs.json | 6 +- ...aved_objects_migration_server_internal.mdx | 2 +- ...e_saved_objects_migration_server_mocks.mdx | 2 +- ...kbn_core_saved_objects_server.devdocs.json | 30 ++ api_docs/kbn_core_saved_objects_server.mdx | 4 +- ...kbn_core_saved_objects_server_internal.mdx | 2 +- .../kbn_core_saved_objects_server_mocks.mdx | 2 +- .../kbn_core_saved_objects_utils_server.mdx | 2 +- api_docs/kbn_core_status_common.mdx | 2 +- api_docs/kbn_core_status_common_internal.mdx | 2 +- api_docs/kbn_core_status_server.mdx | 2 +- api_docs/kbn_core_status_server_internal.mdx | 2 +- api_docs/kbn_core_status_server_mocks.mdx | 2 +- ...core_test_helpers_deprecations_getters.mdx | 2 +- ...n_core_test_helpers_http_setup_browser.mdx | 2 +- api_docs/kbn_core_test_helpers_kbn_server.mdx | 2 +- ...n_core_test_helpers_so_type_serializer.mdx | 2 +- api_docs/kbn_core_test_helpers_test_utils.mdx | 2 +- api_docs/kbn_core_theme_browser.mdx | 2 +- api_docs/kbn_core_theme_browser_internal.mdx | 2 +- api_docs/kbn_core_theme_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_browser.mdx | 2 +- .../kbn_core_ui_settings_browser_internal.mdx | 2 +- .../kbn_core_ui_settings_browser_mocks.mdx | 2 +- .../kbn_core_ui_settings_common.devdocs.json | 2 +- api_docs/kbn_core_ui_settings_common.mdx | 2 +- api_docs/kbn_core_ui_settings_server.mdx | 2 +- .../kbn_core_ui_settings_server_internal.mdx | 2 +- .../kbn_core_ui_settings_server_mocks.mdx | 2 +- api_docs/kbn_core_usage_data_server.mdx | 2 +- .../kbn_core_usage_data_server_internal.mdx | 2 +- api_docs/kbn_core_usage_data_server_mocks.mdx | 2 +- api_docs/kbn_crypto.mdx | 2 +- api_docs/kbn_crypto_browser.mdx | 2 +- api_docs/kbn_cypress_config.mdx | 2 +- api_docs/kbn_datemath.mdx | 2 +- api_docs/kbn_dev_cli_errors.mdx | 2 +- api_docs/kbn_dev_cli_runner.mdx | 2 +- api_docs/kbn_dev_proc_runner.mdx | 2 +- api_docs/kbn_dev_utils.mdx | 2 +- api_docs/kbn_doc_links.devdocs.json | 2 +- api_docs/kbn_doc_links.mdx | 2 +- api_docs/kbn_docs_utils.mdx | 2 +- api_docs/kbn_dom_drag_drop.mdx | 2 +- api_docs/kbn_ebt_tools.mdx | 2 +- api_docs/kbn_ecs.mdx | 2 +- api_docs/kbn_ecs_data_quality_dashboard.mdx | 2 +- api_docs/kbn_es.mdx | 2 +- api_docs/kbn_es_archiver.mdx | 2 +- api_docs/kbn_es_errors.mdx | 2 +- api_docs/kbn_es_query.mdx | 2 +- api_docs/kbn_es_types.mdx | 2 +- api_docs/kbn_eslint_plugin_imports.mdx | 2 +- api_docs/kbn_expandable_flyout.mdx | 2 +- api_docs/kbn_field_types.mdx | 2 +- api_docs/kbn_find_used_node_modules.mdx | 2 +- .../kbn_ftr_common_functional_services.mdx | 2 +- api_docs/kbn_generate.mdx | 2 +- api_docs/kbn_generate_csv.mdx | 2 +- api_docs/kbn_generate_csv_types.mdx | 2 +- api_docs/kbn_guided_onboarding.mdx | 2 +- api_docs/kbn_handlebars.mdx | 2 +- api_docs/kbn_hapi_mocks.mdx | 2 +- api_docs/kbn_health_gateway_server.mdx | 2 +- api_docs/kbn_home_sample_data_card.mdx | 2 +- api_docs/kbn_home_sample_data_tab.mdx | 2 +- api_docs/kbn_i18n.mdx | 2 +- api_docs/kbn_i18n_react.mdx | 2 +- api_docs/kbn_import_resolver.mdx | 2 +- api_docs/kbn_interpreter.mdx | 2 +- api_docs/kbn_io_ts_utils.devdocs.json | 291 ++++++++++++++++++ api_docs/kbn_io_ts_utils.mdx | 4 +- api_docs/kbn_jest_serializers.mdx | 2 +- api_docs/kbn_journeys.devdocs.json | 191 +++++++++++- api_docs/kbn_journeys.mdx | 7 +- api_docs/kbn_json_ast.mdx | 2 +- api_docs/kbn_kibana_manifest_schema.mdx | 2 +- .../kbn_language_documentation_popover.mdx | 2 +- api_docs/kbn_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_vscode_config.mdx | 2 +- api_docs/kbn_mapbox_gl.mdx | 2 +- api_docs/kbn_ml_agg_utils.mdx | 2 +- api_docs/kbn_ml_date_picker.mdx | 2 +- api_docs/kbn_ml_is_defined.mdx | 2 +- api_docs/kbn_ml_is_populated_object.mdx | 2 +- api_docs/kbn_ml_local_storage.mdx | 2 +- api_docs/kbn_ml_nested_property.mdx | 2 +- api_docs/kbn_ml_number_utils.mdx | 2 +- api_docs/kbn_ml_query_utils.mdx | 2 +- api_docs/kbn_ml_random_sampler_utils.mdx | 2 +- api_docs/kbn_ml_route_utils.mdx | 2 +- api_docs/kbn_ml_string_hash.mdx | 2 +- api_docs/kbn_ml_trained_models_utils.mdx | 2 +- api_docs/kbn_ml_url_state.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_object_versioning.mdx | 2 +- api_docs/kbn_observability_alert_details.mdx | 2 +- api_docs/kbn_optimizer.mdx | 2 +- api_docs/kbn_optimizer_webpack_helpers.mdx | 2 +- api_docs/kbn_osquery_io_ts_types.mdx | 2 +- ..._performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- api_docs/kbn_react_field.mdx | 2 +- api_docs/kbn_repo_file_maps.mdx | 2 +- api_docs/kbn_repo_linter.mdx | 2 +- api_docs/kbn_repo_path.mdx | 2 +- api_docs/kbn_repo_source_classifier.mdx | 2 +- api_docs/kbn_reporting_common.mdx | 2 +- api_docs/kbn_rison.mdx | 2 +- api_docs/kbn_rule_data_utils.devdocs.json | 34 +- api_docs/kbn_rule_data_utils.mdx | 4 +- api_docs/kbn_saved_objects_settings.mdx | 2 +- api_docs/kbn_security_solution_side_nav.mdx | 2 +- ...kbn_security_solution_storybook_config.mdx | 2 +- .../kbn_securitysolution_autocomplete.mdx | 2 +- ...n_securitysolution_data_table.devdocs.json | 4 +- api_docs/kbn_securitysolution_data_table.mdx | 2 +- api_docs/kbn_securitysolution_ecs.mdx | 2 +- ...kbn_securitysolution_es_utils.devdocs.json | 12 +- api_docs/kbn_securitysolution_es_utils.mdx | 2 +- ...ion_exception_list_components.devdocs.json | 8 +- ...ritysolution_exception_list_components.mdx | 2 +- api_docs/kbn_securitysolution_grouping.mdx | 2 +- api_docs/kbn_securitysolution_hook_utils.mdx | 2 +- ..._securitysolution_io_ts_alerting_types.mdx | 2 +- .../kbn_securitysolution_io_ts_list_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_utils.mdx | 2 +- api_docs/kbn_securitysolution_list_api.mdx | 2 +- .../kbn_securitysolution_list_constants.mdx | 2 +- api_docs/kbn_securitysolution_list_hooks.mdx | 2 +- api_docs/kbn_securitysolution_list_utils.mdx | 2 +- api_docs/kbn_securitysolution_rules.mdx | 2 +- api_docs/kbn_securitysolution_t_grid.mdx | 2 +- api_docs/kbn_securitysolution_utils.mdx | 2 +- api_docs/kbn_server_http_tools.mdx | 2 +- api_docs/kbn_server_route_repository.mdx | 2 +- api_docs/kbn_shared_svg.mdx | 2 +- api_docs/kbn_shared_ux_avatar_solution.mdx | 2 +- ...vatar_user_profile_components.devdocs.json | 2 +- ...ared_ux_avatar_user_profile_components.mdx | 2 +- .../kbn_shared_ux_button_exit_full_screen.mdx | 2 +- ...hared_ux_button_exit_full_screen_mocks.mdx | 2 +- .../kbn_shared_ux_button_toolbar.devdocs.json | 4 +- api_docs/kbn_shared_ux_button_toolbar.mdx | 2 +- .../kbn_shared_ux_card_no_data.devdocs.json | 8 +- api_docs/kbn_shared_ux_card_no_data.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_file_context.mdx | 2 +- api_docs/kbn_shared_ux_file_image.mdx | 2 +- api_docs/kbn_shared_ux_file_image_mocks.mdx | 2 +- api_docs/kbn_shared_ux_file_mocks.mdx | 2 +- api_docs/kbn_shared_ux_file_picker.mdx | 2 +- api_docs/kbn_shared_ux_file_types.mdx | 2 +- api_docs/kbn_shared_ux_file_upload.mdx | 2 +- api_docs/kbn_shared_ux_file_util.mdx | 2 +- api_docs/kbn_shared_ux_link_redirect_app.mdx | 2 +- .../kbn_shared_ux_link_redirect_app_mocks.mdx | 2 +- api_docs/kbn_shared_ux_markdown.mdx | 2 +- .../kbn_shared_ux_markdown_mocks.devdocs.json | 8 +- api_docs/kbn_shared_ux_markdown_mocks.mdx | 2 +- .../kbn_shared_ux_page_analytics_no_data.mdx | 2 +- ...shared_ux_page_analytics_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_no_data.mdx | 2 +- ...bn_shared_ux_page_kibana_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_template.mdx | 2 +- ...n_shared_ux_page_kibana_template_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data.mdx | 2 +- .../kbn_shared_ux_page_no_data_config.mdx | 2 +- ...bn_shared_ux_page_no_data_config_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_solution_nav.mdx | 2 +- .../kbn_shared_ux_prompt_no_data_views.mdx | 2 +- ...n_shared_ux_prompt_no_data_views_mocks.mdx | 2 +- api_docs/kbn_shared_ux_prompt_not_found.mdx | 2 +- api_docs/kbn_shared_ux_router.mdx | 2 +- api_docs/kbn_shared_ux_router_mocks.mdx | 2 +- api_docs/kbn_shared_ux_storybook_config.mdx | 2 +- api_docs/kbn_shared_ux_storybook_mock.mdx | 2 +- api_docs/kbn_shared_ux_utility.mdx | 2 +- api_docs/kbn_slo_schema.mdx | 2 +- api_docs/kbn_some_dev_log.mdx | 2 +- api_docs/kbn_std.mdx | 2 +- api_docs/kbn_stdio_dev_helpers.mdx | 2 +- api_docs/kbn_storybook.mdx | 2 +- api_docs/kbn_telemetry_tools.mdx | 2 +- api_docs/kbn_test.mdx | 2 +- api_docs/kbn_test_jest_helpers.mdx | 2 +- api_docs/kbn_test_subj_selector.mdx | 2 +- api_docs/kbn_tooling_log.mdx | 2 +- api_docs/kbn_ts_projects.mdx | 2 +- api_docs/kbn_typed_react_router_config.mdx | 2 +- api_docs/kbn_ui_actions_browser.mdx | 2 +- api_docs/kbn_ui_shared_deps_src.mdx | 2 +- api_docs/kbn_ui_theme.mdx | 2 +- .../kbn_user_profile_components.devdocs.json | 2 +- api_docs/kbn_user_profile_components.mdx | 2 +- api_docs/kbn_utility_types.mdx | 2 +- api_docs/kbn_utility_types_jest.mdx | 2 +- api_docs/kbn_utils.mdx | 2 +- api_docs/kbn_yarn_lock_validator.mdx | 2 +- api_docs/kibana_overview.mdx | 2 +- api_docs/kibana_react.devdocs.json | 4 +- api_docs/kibana_react.mdx | 2 +- api_docs/kibana_utils.mdx | 2 +- api_docs/kubernetes_security.mdx | 2 +- api_docs/lens.devdocs.json | 28 +- api_docs/lens.mdx | 2 +- api_docs/license_api_guard.mdx | 2 +- api_docs/license_management.mdx | 2 +- api_docs/licensing.mdx | 2 +- api_docs/lists.devdocs.json | 6 +- api_docs/lists.mdx | 2 +- api_docs/management.mdx | 2 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.mdx | 2 +- api_docs/ml.mdx | 2 +- api_docs/monitoring.mdx | 2 +- api_docs/monitoring_collection.mdx | 2 +- api_docs/navigation.devdocs.json | 2 +- api_docs/navigation.mdx | 2 +- api_docs/newsfeed.mdx | 2 +- api_docs/notifications.mdx | 2 +- api_docs/observability.devdocs.json | 28 +- api_docs/observability.mdx | 2 +- api_docs/observability_shared.devdocs.json | 4 +- api_docs/observability_shared.mdx | 2 +- api_docs/osquery.devdocs.json | 4 +- api_docs/osquery.mdx | 2 +- api_docs/plugin_directory.mdx | 24 +- api_docs/presentation_util.devdocs.json | 2 +- api_docs/presentation_util.mdx | 2 +- api_docs/profiling.mdx | 2 +- api_docs/remote_clusters.mdx | 2 +- api_docs/reporting.mdx | 2 +- api_docs/rollup.mdx | 2 +- api_docs/rule_registry.devdocs.json | 16 +- api_docs/rule_registry.mdx | 2 +- api_docs/runtime_fields.mdx | 2 +- api_docs/saved_objects.mdx | 2 +- api_docs/saved_objects_finder.mdx | 2 +- .../saved_objects_management.devdocs.json | 12 +- api_docs/saved_objects_management.mdx | 2 +- api_docs/saved_objects_tagging.mdx | 2 +- .../saved_objects_tagging_oss.devdocs.json | 2 +- api_docs/saved_objects_tagging_oss.mdx | 2 +- api_docs/saved_search.mdx | 2 +- api_docs/screenshot_mode.mdx | 2 +- api_docs/screenshotting.mdx | 2 +- api_docs/security.mdx | 2 +- api_docs/security_solution.mdx | 2 +- api_docs/session_view.mdx | 2 +- api_docs/share.mdx | 2 +- api_docs/snapshot_restore.mdx | 2 +- api_docs/spaces.mdx | 2 +- api_docs/stack_alerts.mdx | 2 +- api_docs/stack_connectors.mdx | 2 +- api_docs/task_manager.mdx | 2 +- api_docs/telemetry.mdx | 2 +- .../telemetry_collection_manager.devdocs.json | 6 +- api_docs/telemetry_collection_manager.mdx | 2 +- api_docs/telemetry_collection_xpack.mdx | 2 +- api_docs/telemetry_management_section.mdx | 2 +- api_docs/threat_intelligence.mdx | 2 +- api_docs/timelines.mdx | 2 +- api_docs/transform.mdx | 2 +- api_docs/triggers_actions_ui.devdocs.json | 2 +- api_docs/triggers_actions_ui.mdx | 2 +- api_docs/ui_actions.mdx | 2 +- api_docs/ui_actions_enhanced.mdx | 2 +- api_docs/unified_field_list.mdx | 2 +- api_docs/unified_histogram.mdx | 2 +- api_docs/unified_search.devdocs.json | 12 +- api_docs/unified_search.mdx | 2 +- api_docs/unified_search_autocomplete.mdx | 2 +- api_docs/url_forwarding.mdx | 2 +- api_docs/usage_collection.devdocs.json | 6 +- api_docs/usage_collection.mdx | 2 +- api_docs/ux.mdx | 2 +- api_docs/vis_default_editor.mdx | 2 +- api_docs/vis_type_gauge.mdx | 2 +- api_docs/vis_type_heatmap.mdx | 2 +- api_docs/vis_type_pie.mdx | 2 +- api_docs/vis_type_table.mdx | 2 +- api_docs/vis_type_timelion.mdx | 2 +- api_docs/vis_type_timeseries.mdx | 2 +- api_docs/vis_type_vega.mdx | 2 +- api_docs/vis_type_vislib.mdx | 2 +- api_docs/vis_type_xy.devdocs.json | 2 +- api_docs/vis_type_xy.mdx | 2 +- api_docs/visualizations.devdocs.json | 12 +- api_docs/visualizations.mdx | 2 +- 556 files changed, 1649 insertions(+), 754 deletions(-) diff --git a/api_docs/actions.devdocs.json b/api_docs/actions.devdocs.json index 01b2670ee2eac..398265dab8a48 100644 --- a/api_docs/actions.devdocs.json +++ b/api_docs/actions.devdocs.json @@ -1941,7 +1941,7 @@ "label": "ActionsClient", "description": [], "signature": [ - "{ create: ({ action: { actionTypeId, name, config, secrets }, }: ", + "{ create: ({ action: { actionTypeId, name, config, secrets }, options, }: ", "CreateOptions", ") => Promise<", { diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 0bda3b189fa1d..6bfdc25b53149 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 348dec0d0e80a..4cf73a72168eb 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.devdocs.json b/api_docs/aiops.devdocs.json index 409240044a405..b122c05b656d6 100644 --- a/api_docs/aiops.devdocs.json +++ b/api_docs/aiops.devdocs.json @@ -468,6 +468,46 @@ "path": "x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "aiops", + "id": "def-public.AiopsAppDependencies.fieldStats", + "type": "Object", + "tags": [], + "label": "fieldStats", + "description": [], + "signature": [ + "{ useFieldStatsTrigger: () => { renderOption: ((option: ", + "EuiComboBoxOptionOption", + ", searchValue: string, OPTION_CONTENT_CLASSNAME: string) => React.ReactNode) | undefined; closeFlyout: () => void; }; FieldStatsFlyoutProvider: React.FC<{ dataView: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + "; fieldStatsServices: ", + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.FieldStatsServices", + "text": "FieldStatsServices" + }, + "; timeRangeMs?: ", + { + "pluginId": "@kbn/ml-date-picker", + "scope": "common", + "docId": "kibKbnMlDatePickerPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + }, + " | undefined; dslQuery?: object | undefined; }>; } | undefined" + ], + "path": "x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 10881ef4a3846..c0fa0ef3e4c02 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 38 | 0 | 23 | 0 | +| 39 | 0 | 24 | 0 | ## Client diff --git a/api_docs/alerting.devdocs.json b/api_docs/alerting.devdocs.json index 007a6f4481be6..cfec38128269c 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -3049,9 +3049,9 @@ "label": "validate", "description": [], "signature": [ - "{ params?: ", + "{ params: ", "RuleTypeParamsValidator", - " | undefined; } | undefined" + "; }" ], "path": "x-pack/plugins/alerting/server/types.ts", "deprecated": false, @@ -9246,6 +9246,18 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "alerting", + "id": "def-common.INTERNAL_ALERTING_API_FIND_RULES_PATH", + "type": "string", + "tags": [], + "label": "INTERNAL_ALERTING_API_FIND_RULES_PATH", + "description": [], + "path": "x-pack/plugins/alerting/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "alerting", "id": "def-common.INTERNAL_BASE_ALERTING_API_PATH", diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index f55155afb84e6..ad9cd7c82bac4 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 602 | 1 | 581 | 42 | +| 603 | 1 | 582 | 42 | ## Client diff --git a/api_docs/apm.devdocs.json b/api_docs/apm.devdocs.json index 68e7c2735ea9f..93eeed1f13395 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -821,7 +821,7 @@ "label": "APIEndpoint", "description": [], "signature": [ - "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/title\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/samples\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/nodes\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/summary\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/dependency\" | \"GET /internal/apm/services\" | \"POST /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search\" | \"POST /api/apm/services/{serviceName}/annotation\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/services/{serviceName}/alerts_count\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"GET /internal/apm/service-group/counts\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/traces/find\" | \"POST /internal/apm/traces/aggregated_critical_path\" | \"GET /internal/apm/traces/{traceId}/transactions/{transactionId}\" | \"GET /internal/apm/traces/{traceId}/spans/{spanId}\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"GET /internal/apm/rule_types/transaction_error_rate/chart_preview\" | \"GET /internal/apm/rule_types/transaction_duration/chart_preview\" | \"GET /internal/apm/rule_types/error_count/chart_preview\" | \"GET /api/apm/settings/agent-configuration\" | \"GET /api/apm/settings/agent-configuration/view\" | \"DELETE /api/apm/settings/agent-configuration\" | \"PUT /api/apm/settings/agent-configuration\" | \"POST /api/apm/settings/agent-configuration/search\" | \"GET /api/apm/settings/agent-configuration/environments\" | \"GET /api/apm/settings/agent-configuration/agent_name\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps\" | \"POST /api/apm/sourcemaps\" | \"DELETE /api/apm/sourcemaps/{id}\" | \"POST /internal/apm/sourcemaps/migrate_fleet_artifacts\" | \"GET /internal/apm/fleet/has_apm_policies\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/fleet/java_agent_versions\" | \"GET /internal/apm/dependencies/top_dependencies\" | \"GET /internal/apm/dependencies/upstream_services\" | \"GET /internal/apm/dependencies/metadata\" | \"GET /internal/apm/dependencies/charts/latency\" | \"GET /internal/apm/dependencies/charts/throughput\" | \"GET /internal/apm/dependencies/charts/error_rate\" | \"GET /internal/apm/dependencies/operations\" | \"GET /internal/apm/dependencies/charts/distribution\" | \"GET /internal/apm/dependencies/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys\" | \"GET /internal/apm/storage_explorer\" | \"GET /internal/apm/services/{serviceName}/storage_details\" | \"GET /internal/apm/storage_chart\" | \"GET /internal/apm/storage_explorer/privileges\" | \"GET /internal/apm/storage_explorer_summary_stats\" | \"GET /internal/apm/storage_explorer/is_cross_cluster_search\" | \"GET /internal/apm/storage_explorer/get_services\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\" | \"GET /internal/apm/settings/labs\" | \"GET /internal/apm/get_agents_per_service\" | \"GET /internal/apm/get_latest_agent_versions\" | \"GET /internal/apm/services/{serviceName}/agent_instances\" | \"GET /internal/apm/services/{serviceName}/mobile/filters\" | \"GET /internal/apm/mobile-services/{serviceName}/transactions/charts/sessions\" | \"GET /internal/apm/mobile-services/{serviceName}/transactions/charts/http_requests\" | \"GET /internal/apm/mobile-services/{serviceName}/stats\" | \"GET /internal/apm/mobile-services/{serviceName}/location/stats\" | \"GET /internal/apm/mobile-services/{serviceName}/terms\"" + "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/title\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/samples\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/nodes\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/summary\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/dependency\" | \"GET /internal/apm/services\" | \"POST /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search\" | \"POST /api/apm/services/{serviceName}/annotation\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/services/{serviceName}/alerts_count\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"GET /internal/apm/service-group/counts\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/traces/find\" | \"POST /internal/apm/traces/aggregated_critical_path\" | \"GET /internal/apm/traces/{traceId}/transactions/{transactionId}\" | \"GET /internal/apm/traces/{traceId}/spans/{spanId}\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"GET /internal/apm/rule_types/transaction_error_rate/chart_preview\" | \"GET /internal/apm/rule_types/transaction_duration/chart_preview\" | \"GET /internal/apm/rule_types/error_count/chart_preview\" | \"GET /api/apm/settings/agent-configuration\" | \"GET /api/apm/settings/agent-configuration/view\" | \"DELETE /api/apm/settings/agent-configuration\" | \"PUT /api/apm/settings/agent-configuration\" | \"POST /api/apm/settings/agent-configuration/search\" | \"GET /api/apm/settings/agent-configuration/environments\" | \"GET /api/apm/settings/agent-configuration/agent_name\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps\" | \"POST /api/apm/sourcemaps\" | \"DELETE /api/apm/sourcemaps/{id}\" | \"POST /internal/apm/sourcemaps/migrate_fleet_artifacts\" | \"GET /internal/apm/fleet/has_apm_policies\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/fleet/java_agent_versions\" | \"GET /internal/apm/dependencies/top_dependencies\" | \"GET /internal/apm/dependencies/upstream_services\" | \"GET /internal/apm/dependencies/metadata\" | \"GET /internal/apm/dependencies/charts/latency\" | \"GET /internal/apm/dependencies/charts/throughput\" | \"GET /internal/apm/dependencies/charts/error_rate\" | \"GET /internal/apm/dependencies/operations\" | \"GET /internal/apm/dependencies/charts/distribution\" | \"GET /internal/apm/dependencies/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys\" | \"GET /internal/apm/storage_explorer\" | \"GET /internal/apm/services/{serviceName}/storage_details\" | \"GET /internal/apm/storage_chart\" | \"GET /internal/apm/storage_explorer/privileges\" | \"GET /internal/apm/storage_explorer_summary_stats\" | \"GET /internal/apm/storage_explorer/is_cross_cluster_search\" | \"GET /internal/apm/storage_explorer/get_services\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\" | \"GET /internal/apm/settings/labs\" | \"GET /internal/apm/get_agents_per_service\" | \"GET /internal/apm/get_latest_agent_versions\" | \"GET /internal/apm/services/{serviceName}/agent_instances\" | \"GET /internal/apm/services/{serviceName}/mobile/filters\" | \"GET /internal/apm/mobile-services/{serviceName}/most_used_charts\" | \"GET /internal/apm/mobile-services/{serviceName}/transactions/charts/sessions\" | \"GET /internal/apm/mobile-services/{serviceName}/transactions/charts/http_requests\" | \"GET /internal/apm/mobile-services/{serviceName}/stats\" | \"GET /internal/apm/mobile-services/{serviceName}/location/stats\" | \"GET /internal/apm/mobile-services/{serviceName}/terms\"" ], "path": "x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts", "deprecated": false, @@ -1215,6 +1215,68 @@ "SessionsTimeseries", ", ", "APMRouteCreateOptions", + ">; \"GET /internal/apm/mobile-services/{serviceName}/most_used_charts\": ", + { + "pluginId": "@kbn/server-route-repository", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"GET /internal/apm/mobile-services/{serviceName}/most_used_charts\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ serviceName: ", + "StringC", + "; }>; query: ", + "IntersectionC", + "<[", + "TypeC", + "<{ kuery: ", + "StringC", + "; }>, ", + "TypeC", + "<{ start: ", + "Type", + "; end: ", + "Type", + "; }>, ", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"ENVIRONMENT_NOT_DEFINED\">, ", + "LiteralC", + "<\"ENVIRONMENT_ALL\">, ", + "BrandC", + "<", + "StringC", + ", ", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.NonEmptyStringBrand", + "text": "NonEmptyStringBrand" + }, + ">]>; }>, ", + "PartialC", + "<{ transactionType: ", + "StringC", + "; }>]>; }>, ", + { + "pluginId": "apm", + "scope": "server", + "docId": "kibApmPluginApi", + "section": "def-server.APMRouteHandlerResources", + "text": "APMRouteHandlerResources" + }, + ", { mostUsedCharts: { key: ", + "MobilePropertyType", + "; options: { key: string | number; docCount: number; }[] & { key: string | number; docCount: number; }[]; }[]; }, ", + "APMRouteCreateOptions", ">; \"GET /internal/apm/services/{serviceName}/mobile/filters\": ", { "pluginId": "@kbn/server-route-repository", @@ -4133,6 +4195,8 @@ "StringC", "; transactionType: ", "StringC", + "; transactionName: ", + "StringC", "; }>, ", "TypeC", "<{ environment: ", @@ -4207,6 +4271,8 @@ "StringC", "; transactionType: ", "StringC", + "; transactionName: ", + "StringC", "; }>, ", "TypeC", "<{ environment: ", @@ -4279,6 +4345,8 @@ "StringC", "; transactionType: ", "StringC", + "; transactionName: ", + "StringC", "; }>, ", "TypeC", "<{ environment: ", diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index c998a7a7144e3..a5226b9ec1a9f 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) for ques | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 43 | 0 | 43 | 109 | +| 43 | 0 | 43 | 110 | ## Client diff --git a/api_docs/asset_manager.mdx b/api_docs/asset_manager.mdx index e848436d777e3..5704f9f7b9f81 100644 --- a/api_docs/asset_manager.mdx +++ b/api_docs/asset_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/assetManager title: "assetManager" image: https://source.unsplash.com/400x175/?github description: API docs for the assetManager plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'assetManager'] --- import assetManagerObj from './asset_manager.devdocs.json'; diff --git a/api_docs/banners.devdocs.json b/api_docs/banners.devdocs.json index 6bd8c557af59f..ac8f2e365bb54 100644 --- a/api_docs/banners.devdocs.json +++ b/api_docs/banners.devdocs.json @@ -39,7 +39,7 @@ "label": "placement", "description": [], "signature": [ - "\"disabled\" | \"top\"" + "\"top\" | \"disabled\"" ], "path": "x-pack/plugins/banners/common/types.ts", "deprecated": false, @@ -137,7 +137,7 @@ "label": "BannerPlacement", "description": [], "signature": [ - "\"disabled\" | \"top\"" + "\"top\" | \"disabled\"" ], "path": "x-pack/plugins/banners/common/types.ts", "deprecated": false, diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 2a2128100d52b..5fcf3933483c9 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index a8f930db9e65c..d3a45d4595553 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 5f5bba2336e34..edb1a65ab929d 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.devdocs.json b/api_docs/cases.devdocs.json index 890144e0b538d..f41f5e5efe59a 100644 --- a/api_docs/cases.devdocs.json +++ b/api_docs/cases.devdocs.json @@ -898,7 +898,7 @@ "CaseSeverity", " | undefined; assignees?: string | string[] | undefined; reporters?: string | string[] | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; fields?: string | string[] | undefined; from?: string | undefined; page?: number | undefined; perPage?: number | undefined; search?: string | undefined; searchFields?: string | string[] | undefined; rootSearchFields?: string[] | undefined; sortField?: string | undefined; sortOrder?: \"asc\" | \"desc\" | undefined; to?: string | undefined; owner?: string | string[] | undefined; }, signal?: AbortSignal | undefined) => Promise<", "Cases", - ">; getCasesStatus: (query: { from?: string | undefined; to?: string | undefined; owner?: string | string[] | undefined; }, signal?: AbortSignal | undefined) => Promise<{ countOpenCases: number; countInProgressCases: number; countClosedCases: number; }>; getCasesMetrics: (query: { features: string[]; } & { from?: string | undefined; to?: string | undefined; owner?: string | string[] | undefined; }, signal?: AbortSignal | undefined) => Promise<{ mttr?: number | null | undefined; }>; bulkGet: (params: ", + ">; getCasesStatus: (query: { from?: string | undefined; to?: string | undefined; owner?: string | string[] | undefined; }, signal?: AbortSignal | undefined) => Promise<{ countOpenCases: number; countInProgressCases: number; countClosedCases: number; }>; getCasesMetrics: (query: { features: string[]; } & { from?: string | undefined; to?: string | undefined; owner?: string | string[] | undefined; }, signal?: AbortSignal | undefined) => Promise<{ mttr?: number | null | undefined; }>; bulkGet: (params: ", { "pluginId": "cases", "scope": "common", diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 2d5921d0a7fa9..48d5f0ea25d03 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 3768780876d40..e4c1c729146d8 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 9cd07109fd4c6..0ef73b05d8bb0 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_chat.mdx b/api_docs/cloud_chat.mdx index b95eb71a01306..28bf235d4e803 100644 --- a/api_docs/cloud_chat.mdx +++ b/api_docs/cloud_chat.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudChat title: "cloudChat" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudChat plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudChat'] --- import cloudChatObj from './cloud_chat.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index 884a85b316bf8..ea00c024dabf2 100644 --- a/api_docs/cloud_data_migration.mdx +++ b/api_docs/cloud_data_migration.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDataMigration title: "cloudDataMigration" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDataMigration plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDataMigration'] --- import cloudDataMigrationObj from './cloud_data_migration.devdocs.json'; diff --git a/api_docs/cloud_defend.mdx b/api_docs/cloud_defend.mdx index 67f71e567ed0e..b6680d6d7dbd1 100644 --- a/api_docs/cloud_defend.mdx +++ b/api_docs/cloud_defend.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDefend title: "cloudDefend" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDefend plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDefend'] --- import cloudDefendObj from './cloud_defend.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index adb9e2df05e93..08bce01d73062 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index 3e5562983375b..a88a3ac25809b 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index 945b06637a3ce..aaa30afdee1ef 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx index 603f9a2ee1dff..1f578689d0a00 100644 --- a/api_docs/content_management.mdx +++ b/api_docs/content_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/contentManagement title: "contentManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the contentManagement plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index b7531a38a9fd2..9e673b08f099a 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 2e462d1ada11b..5f75f99f25e24 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 8567dd037fc43..eee68e6d1bd7b 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index 09b0eaa2c4c92..707e03a38207b 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.devdocs.json b/api_docs/data.devdocs.json index 85576fb9250b9..a1ab4045a706c 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -10549,6 +10549,10 @@ "plugin": "@kbn/core-saved-objects-api-browser", "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts" }, + { + "plugin": "@kbn/core-saved-objects-api-browser", + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts" + }, { "plugin": "@kbn/core-saved-objects-api-server", "path": "packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts" @@ -10617,6 +10621,10 @@ "plugin": "@kbn/core-saved-objects-browser-internal", "path": "packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts" }, + { + "plugin": "@kbn/core-saved-objects-browser-internal", + "path": "packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts" + }, { "plugin": "@kbn/core-saved-objects-browser-internal", "path": "packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts" @@ -18233,7 +18241,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -19155,8 +19165,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", @@ -28382,6 +28390,10 @@ "plugin": "@kbn/core-saved-objects-api-browser", "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts" }, + { + "plugin": "@kbn/core-saved-objects-api-browser", + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts" + }, { "plugin": "@kbn/core-saved-objects-api-server", "path": "packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts" @@ -28450,6 +28462,10 @@ "plugin": "@kbn/core-saved-objects-browser-internal", "path": "packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts" }, + { + "plugin": "@kbn/core-saved-objects-browser-internal", + "path": "packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts" + }, { "plugin": "@kbn/core-saved-objects-browser-internal", "path": "packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts" diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 3f5c1bae7a4bb..f22998f41aaf7 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index cf7d8aa206fff..d08cdb5429fa8 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 3f05adde84d4b..810eacf23336f 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index f7e1deab79008..9c8b060ef7919 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 5c09a7b3ca916..c5097f4dc01c1 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index a3d412dc348cd..ddb9680520f03 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.devdocs.json b/api_docs/data_views.devdocs.json index 59294bf0b6169..5dab9a07d047d 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -14475,7 +14475,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -15397,8 +15399,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", @@ -21669,7 +21669,7 @@ "signature": [ "Pick<", "Toast", - ", \"prefix\" | \"defaultValue\" | \"children\" | \"is\" | \"security\" | \"slot\" | \"style\" | \"className\" | \"aria-label\" | \"data-test-subj\" | \"css\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"placeholder\" | \"spellCheck\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"color\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChange\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClick\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"iconType\" | \"onClose\" | \"toastLifeTimeMs\"> & { title?: string | ", + ", \"prefix\" | \"defaultValue\" | \"children\" | \"onChange\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"color\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClick\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"data-test-subj\" | \"css\" | \"iconType\" | \"onClose\" | \"toastLifeTimeMs\"> & { title?: string | ", { "pluginId": "@kbn/core-mount-utils-browser", "scope": "common", @@ -25770,7 +25770,7 @@ "signature": [ "Pick<", "Toast", - ", \"prefix\" | \"defaultValue\" | \"children\" | \"is\" | \"security\" | \"slot\" | \"style\" | \"className\" | \"aria-label\" | \"data-test-subj\" | \"css\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"placeholder\" | \"spellCheck\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"color\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChange\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClick\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"iconType\" | \"onClose\" | \"toastLifeTimeMs\"> & { title?: string | ", + ", \"prefix\" | \"defaultValue\" | \"children\" | \"onChange\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"color\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClick\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"data-test-subj\" | \"css\" | \"iconType\" | \"onClose\" | \"toastLifeTimeMs\"> & { title?: string | ", { "pluginId": "@kbn/core-mount-utils-browser", "scope": "common", @@ -25997,6 +25997,10 @@ "plugin": "@kbn/core-saved-objects-api-browser", "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts" }, + { + "plugin": "@kbn/core-saved-objects-api-browser", + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts" + }, { "plugin": "@kbn/core-saved-objects-api-server", "path": "packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts" @@ -26065,6 +26069,10 @@ "plugin": "@kbn/core-saved-objects-browser-internal", "path": "packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts" }, + { + "plugin": "@kbn/core-saved-objects-browser-internal", + "path": "packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts" + }, { "plugin": "@kbn/core-saved-objects-browser-internal", "path": "packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts" diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 6613d7b5cce3c..9301c42220b3a 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index ed4a651881e8a..aca7cffe95904 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 0d65891b71bad..f730084172123 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index bb2ecce66530d..cacf63a726fb4 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -116,8 +116,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject)+ 3 more | - | -| | [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject)+ 29 more | - | +| | [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject)+ 4 more | - | +| | [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=SavedObject)+ 32 more | - | | | [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=migrationVersion), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts#:~:text=migrationVersion) | - | | | [create.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts#:~:text=SavedObjectReference), [create.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts#:~:text=SavedObjectReference), [bulk_update.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_update.ts#:~:text=SavedObjectReference), [bulk_update.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_update.ts#:~:text=SavedObjectReference), [update.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/update.ts#:~:text=SavedObjectReference), [update.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/update.ts#:~:text=SavedObjectReference) | - | @@ -139,7 +139,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | ---------------|-----------|-----------| | | [repository.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts#:~:text=migrationVersion), [repository.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts#:~:text=migrationVersion) | - | | | [repository.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts#:~:text=migrationVersion), [repository.test.common.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-server-internal/src/test_helpers/repository.test.common.ts#:~:text=migrationVersion) | - | -| | [repository.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts#:~:text=migrationVersion), [repository.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts#:~:text=migrationVersion) | - | +| | [repository.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts#:~:text=migrationVersion), [repository.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts#:~:text=migrationVersion), [repository.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts#:~:text=migrationVersion), [repository.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts#:~:text=migrationVersion) | - | | | [internal_utils.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts#:~:text=migrationVersion), [internal_utils.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts#:~:text=migrationVersion) | - | @@ -156,8 +156,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject)+ 19 more | - | -| | [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject)+ 77 more | - | +| | [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject)+ 20 more | - | +| | [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObject)+ 80 more | - | | | [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObjectsClientContract), [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=SavedObjectsClientContract), [saved_objects_client.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts#:~:text=SavedObjectsClientContract), [saved_objects_client.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts#:~:text=SavedObjectsClientContract), [simple_saved_object.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.test.ts#:~:text=SavedObjectsClientContract), [simple_saved_object.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.test.ts#:~:text=SavedObjectsClientContract) | - | | | [simple_saved_object.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts#:~:text=create), [simple_saved_object.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.test.ts#:~:text=create), [saved_objects_client.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts#:~:text=create), [saved_objects_client.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts#:~:text=create), [saved_objects_client.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts#:~:text=create), [saved_objects_client.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts#:~:text=create), [saved_objects_client.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts#:~:text=create), [saved_objects_client.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts#:~:text=create) | - | | | [saved_objects_client.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts#:~:text=bulkCreate), [saved_objects_client.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts#:~:text=bulkCreate), [saved_objects_client.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts#:~:text=bulkCreate), [saved_objects_client.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts#:~:text=bulkCreate) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 60757f77d1709..c210b2af4a6b3 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index c5476bbaa6ab9..880a19dbd0e91 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 4371518bfaa3b..f6cb112f22cc5 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 20e88ee9a6cc6..b3793bce178b0 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/ecs_data_quality_dashboard.mdx b/api_docs/ecs_data_quality_dashboard.mdx index a461bbadec29e..fb9dfb81dacae 100644 --- a/api_docs/ecs_data_quality_dashboard.mdx +++ b/api_docs/ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ecsDataQualityDashboard title: "ecsDataQualityDashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the ecsDataQualityDashboard plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 8007a28a54c7c..1dca0ef9fb7d2 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 4d9c5c8e7b08b..5826149eca609 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index b31c6d4cc56b0..bc44d093b0ff4 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index ffdd53dbd62fc..9ad8338b79886 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index c775996206174..7b08e077f39e1 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index ae832bf824d2b..7d9413f9555b1 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 53dbd441018bb..bfe5735a5b5aa 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/exploratory_view.devdocs.json b/api_docs/exploratory_view.devdocs.json index 8d7c0c15654a4..955001e768109 100644 --- a/api_docs/exploratory_view.devdocs.json +++ b/api_docs/exploratory_view.devdocs.json @@ -1760,7 +1760,7 @@ "label": "align", "description": [], "signature": [ - "\"right\" | \"left\" | \"center\" | undefined" + "\"left\" | \"right\" | \"center\" | undefined" ], "path": "x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/embeddable/embeddable.tsx", "deprecated": false, diff --git a/api_docs/exploratory_view.mdx b/api_docs/exploratory_view.mdx index 7117de22a6fae..2ebfc71ebc060 100644 --- a/api_docs/exploratory_view.mdx +++ b/api_docs/exploratory_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/exploratoryView title: "exploratoryView" image: https://source.unsplash.com/400x175/?github description: API docs for the exploratoryView plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'exploratoryView'] --- import exploratoryViewObj from './exploratory_view.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 525b0efa9cfad..5f952a34778d8 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index f19fdf39642cf..17b1e1a7dd0c3 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.devdocs.json b/api_docs/expression_heatmap.devdocs.json index 747b9bef44b61..0e2adfddecb47 100644 --- a/api_docs/expression_heatmap.devdocs.json +++ b/api_docs/expression_heatmap.devdocs.json @@ -1707,7 +1707,7 @@ "label": "options", "description": [], "signature": [ - "(\"right\" | \"left\" | \"top\" | \"bottom\")[]" + "(\"top\" | \"left\" | \"right\" | \"bottom\")[]" ], "path": "src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_legend.ts", "deprecated": false, diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 4eecb16c31f84..8e23328ac3938 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.devdocs.json b/api_docs/expression_image.devdocs.json index 9d01073c26d1f..ab0abc18f68ea 100644 --- a/api_docs/expression_image.devdocs.json +++ b/api_docs/expression_image.devdocs.json @@ -431,7 +431,7 @@ "label": "OriginString", "description": [], "signature": [ - "\"right\" | \"left\" | \"top\" | \"bottom\"" + "\"top\" | \"left\" | \"right\" | \"bottom\"" ], "path": "src/plugins/expression_image/common/types/expression_renderers.ts", "deprecated": false, diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index fb6b79947615d..2a5739fbf1ce6 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index b490005a0e4d0..e5641b9c5fd0d 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index 85bded968098f..b8a570a50ce47 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index 4e56daabbec11..7ea3ec6b861eb 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index 3910a33c754e2..92e6b7076b109 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.devdocs.json b/api_docs/expression_repeat_image.devdocs.json index 1d7d9d10e45c7..00b5af2b81d57 100644 --- a/api_docs/expression_repeat_image.devdocs.json +++ b/api_docs/expression_repeat_image.devdocs.json @@ -500,7 +500,7 @@ "label": "OriginString", "description": [], "signature": [ - "\"right\" | \"left\" | \"top\" | \"bottom\"" + "\"top\" | \"left\" | \"right\" | \"bottom\"" ], "path": "src/plugins/expression_repeat_image/common/types/expression_renderers.ts", "deprecated": false, diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index b7ff5f80cbd45..5641f62c19926 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index 57751d587dc89..10142b9bf030a 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.devdocs.json b/api_docs/expression_shape.devdocs.json index 3c6d97d7ce0f4..9e6ff34da8b51 100644 --- a/api_docs/expression_shape.devdocs.json +++ b/api_docs/expression_shape.devdocs.json @@ -1599,7 +1599,7 @@ "label": "OriginString", "description": [], "signature": [ - "\"right\" | \"left\" | \"top\" | \"bottom\"" + "\"top\" | \"left\" | \"right\" | \"bottom\"" ], "path": "src/plugins/expression_shape/common/types/expression_renderers.ts", "deprecated": false, @@ -2633,7 +2633,7 @@ "label": "OriginString", "description": [], "signature": [ - "\"right\" | \"left\" | \"top\" | \"bottom\"" + "\"top\" | \"left\" | \"right\" | \"bottom\"" ], "path": "src/plugins/expression_shape/common/types/expression_renderers.ts", "deprecated": false, diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index 4147ab5952333..26f353bb29f5f 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index 703ea27cf3818..4961a72b260d7 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.devdocs.json b/api_docs/expression_x_y.devdocs.json index ea819b254bf64..17db243bca6b5 100644 --- a/api_docs/expression_x_y.devdocs.json +++ b/api_docs/expression_x_y.devdocs.json @@ -875,7 +875,7 @@ "\nPosition of the legend relative to the chart" ], "signature": [ - "\"right\" | \"left\" | \"top\" | \"bottom\"" + "\"top\" | \"left\" | \"right\" | \"bottom\"" ], "path": "src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts", "deprecated": false, @@ -923,7 +923,7 @@ "\nHorizontal Alignment of the legend when it is set inside chart" ], "signature": [ - "\"right\" | \"left\" | undefined" + "\"left\" | \"right\" | undefined" ], "path": "src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts", "deprecated": false, @@ -1867,7 +1867,7 @@ "label": "overrides", "description": [], "signature": [ - "(Partial> | undefined>; gridLine?: ", + ", \"gridLine\">> | undefined>; title?: string | undefined; gridLine?: ", { "pluginId": "@kbn/chart-expressions-common", "scope": "common", @@ -2106,7 +2106,7 @@ "label": "AllowedXYOverrides", "description": [], "signature": [ - "{ axisX?: { children?: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | undefined; title?: string | undefined; style?: ", + "{ axisX?: { children?: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | undefined; style?: ", { "pluginId": "@kbn/chart-expressions-common", "scope": "common", @@ -2118,7 +2118,7 @@ "RecursivePartial", "> | undefined>; gridLine?: ", + ", \"gridLine\">> | undefined>; title?: string | undefined; gridLine?: ", { "pluginId": "@kbn/chart-expressions-common", "scope": "common", @@ -2140,7 +2140,7 @@ "YDomainRange", " | undefined>; position?: ", "Position", - " | undefined; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; integersOnly?: boolean | undefined; tickFormat?: \"ignore\" | undefined; showGridLines?: boolean | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; } | undefined; axisLeft?: { children?: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | undefined; title?: string | undefined; style?: ", + " | undefined; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; integersOnly?: boolean | undefined; tickFormat?: \"ignore\" | undefined; showGridLines?: boolean | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; } | undefined; axisLeft?: { children?: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | undefined; style?: ", { "pluginId": "@kbn/chart-expressions-common", "scope": "common", @@ -2152,7 +2152,7 @@ "RecursivePartial", "> | undefined>; gridLine?: ", + ", \"gridLine\">> | undefined>; title?: string | undefined; gridLine?: ", { "pluginId": "@kbn/chart-expressions-common", "scope": "common", @@ -2174,7 +2174,7 @@ "YDomainRange", " | undefined>; position?: ", "Position", - " | undefined; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; integersOnly?: boolean | undefined; tickFormat?: \"ignore\" | undefined; showGridLines?: boolean | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; } | undefined; axisRight?: { children?: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | undefined; title?: string | undefined; style?: ", + " | undefined; hide?: boolean | undefined; showOverlappingTicks?: boolean | undefined; showOverlappingLabels?: boolean | undefined; timeAxisLayerCount?: number | undefined; integersOnly?: boolean | undefined; tickFormat?: \"ignore\" | undefined; showGridLines?: boolean | undefined; labelFormat?: \"ignore\" | undefined; showDuplicatedTicks?: boolean | undefined; } | undefined; axisRight?: { children?: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | undefined; style?: ", { "pluginId": "@kbn/chart-expressions-common", "scope": "common", @@ -2186,7 +2186,7 @@ "RecursivePartial", "> | undefined>; gridLine?: ", + ", \"gridLine\">> | undefined>; title?: string | undefined; gridLine?: ", { "pluginId": "@kbn/chart-expressions-common", "scope": "common", @@ -2875,7 +2875,7 @@ "label": "IconPosition", "description": [], "signature": [ - "\"right\" | \"left\" | \"above\" | \"below\" | \"auto\"" + "\"left\" | \"right\" | \"above\" | \"below\" | \"auto\"" ], "path": "src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts", "deprecated": false, diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 605a8a4b3bef5..a70dc43040e76 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index ad1ab82fa4842..823159e766547 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 9151874067f58..711def3633bf5 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index 1cd315e9ca353..0996e6259bf11 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 7657a53cabc3f..307f54008c991 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.devdocs.json b/api_docs/files.devdocs.json index 284fc310bee93..a5d56dd4feea1 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -912,7 +912,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -1834,8 +1836,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", diff --git a/api_docs/files.mdx b/api_docs/files.mdx index ae68c060bd5d0..b3f4dba8b7273 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/files_management.mdx b/api_docs/files_management.mdx index d9d3b812cd8eb..bbf97bb4e042f 100644 --- a/api_docs/files_management.mdx +++ b/api_docs/files_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/filesManagement title: "filesManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the filesManagement plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'filesManagement'] --- import filesManagementObj from './files_management.devdocs.json'; diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index 66e49033cc9df..df8f7a32becfc 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -6315,7 +6315,9 @@ "section": "def-common.AuthenticatedUser", "text": "AuthenticatedUser" }, - " | undefined; bumpRevision?: boolean | undefined; force?: boolean | undefined; skipEnsureInstalled?: boolean | undefined; skipUniqueNameVerification?: boolean | undefined; overwrite?: boolean | undefined; packageInfo?: ", + " | undefined; authorizationHeader?: ", + "HTTPAuthorizationHeader", + " | null | undefined; bumpRevision?: boolean | undefined; force?: boolean | undefined; skipEnsureInstalled?: boolean | undefined; skipUniqueNameVerification?: boolean | undefined; overwrite?: boolean | undefined; packageInfo?: ", { "pluginId": "fleet", "scope": "common", @@ -6476,6 +6478,21 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.create.$4.authorizationHeader", + "type": "CompoundType", + "tags": [], + "label": "authorizationHeader", + "description": [], + "signature": [ + "HTTPAuthorizationHeader", + " | null | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "fleet", "id": "def-server.PackagePolicyClient.create.$4.bumpRevision", @@ -6650,7 +6667,9 @@ "section": "def-common.AuthenticatedUser", "text": "AuthenticatedUser" }, - " | undefined; bumpRevision?: boolean | undefined; force?: true | undefined; } | undefined) => Promise<", + " | undefined; bumpRevision?: boolean | undefined; force?: true | undefined; authorizationHeader?: ", + "HTTPAuthorizationHeader", + " | null | undefined; } | undefined) => Promise<", { "pluginId": "fleet", "scope": "common", @@ -6781,6 +6800,21 @@ "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.bulkCreate.$4.authorizationHeader", + "type": "CompoundType", + "tags": [], + "label": "authorizationHeader", + "description": [], + "signature": [ + "HTTPAuthorizationHeader", + " | null | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false } ] } @@ -9365,7 +9399,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -10287,8 +10323,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", @@ -10700,7 +10734,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -11622,8 +11658,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", @@ -12048,7 +12082,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -12970,8 +13006,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", @@ -13393,7 +13427,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -14315,8 +14351,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", @@ -14741,7 +14775,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -15663,8 +15699,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", @@ -22726,7 +22760,7 @@ "section": "def-common.ElasticsearchAssetType", "text": "ElasticsearchAssetType" }, - "; }" + "; deferred?: boolean | undefined; }" ], "path": "x-pack/plugins/fleet/common/types/models/epm.ts", "deprecated": false, @@ -23657,7 +23691,7 @@ "label": "PackageSpecCategory", "description": [], "signature": [ - "\"connector\" | \"monitoring\" | \"security\" | \"infrastructure\" | \"observability\" | \"cloud\" | \"custom\" | \"enterprise_search\" | \"analytics_engine\" | \"application_observability\" | \"app_search\" | \"auditd\" | \"authentication\" | \"aws\" | \"azure\" | \"big_data\" | \"cdn_security\" | \"config_management\" | \"connector_client\" | \"connector_package\" | \"containers\" | \"content_source\" | \"crawler\" | \"credential_management\" | \"crm\" | \"custom_logs\" | \"database_security\" | \"datastore\" | \"dns_security\" | \"edr_xdr\" | \"elasticsearch_sdk\" | \"elastic_stack\" | \"email_security\" | \"firewall_security\" | \"google_cloud\" | \"iam\" | \"ids_ips\" | \"java_observability\" | \"kubernetes\" | \"language_client\" | \"languages\" | \"load_balancer\" | \"message_queue\" | \"native_search\" | \"network\" | \"network_security\" | \"notification\" | \"os_system\" | \"process_manager\" | \"productivity\" | \"productivity_security\" | \"proxy_security\" | \"sdk_search\" | \"stream_processing\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"virtualization\" | \"vpn_security\" | \"vulnerability_management\" | \"web\" | \"web_application_firewall\" | \"websphere\" | \"workplace_search\"" + "\"connector\" | \"security\" | \"monitoring\" | \"infrastructure\" | \"observability\" | \"cloud\" | \"custom\" | \"enterprise_search\" | \"analytics_engine\" | \"application_observability\" | \"app_search\" | \"auditd\" | \"authentication\" | \"aws\" | \"azure\" | \"big_data\" | \"cdn_security\" | \"config_management\" | \"connector_client\" | \"connector_package\" | \"containers\" | \"content_source\" | \"crawler\" | \"credential_management\" | \"crm\" | \"custom_logs\" | \"database_security\" | \"datastore\" | \"dns_security\" | \"edr_xdr\" | \"elasticsearch_sdk\" | \"elastic_stack\" | \"email_security\" | \"firewall_security\" | \"google_cloud\" | \"iam\" | \"ids_ips\" | \"java_observability\" | \"kubernetes\" | \"language_client\" | \"languages\" | \"load_balancer\" | \"message_queue\" | \"native_search\" | \"network\" | \"network_security\" | \"notification\" | \"os_system\" | \"process_manager\" | \"productivity\" | \"productivity_security\" | \"proxy_security\" | \"sdk_search\" | \"stream_processing\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"virtualization\" | \"vpn_security\" | \"vulnerability_management\" | \"web\" | \"web_application_firewall\" | \"websphere\" | \"workplace_search\"" ], "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", "deprecated": false, @@ -23779,7 +23813,7 @@ "label": "RegistrySearchResult", "description": [], "signature": [ - "{ type?: \"input\" | \"integration\" | undefined; name: string; description: string; version: string; title: string; path: string; download: string; internal?: boolean | undefined; icons?: (", + "{ type?: \"input\" | \"integration\" | undefined; name: string; description: string; title: string; version: string; path: string; download: string; internal?: boolean | undefined; icons?: (", { "pluginId": "fleet", "scope": "common", @@ -25470,6 +25504,17 @@ "path": "x-pack/plugins/fleet/common/constants/routes.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.EPM_API_ROUTES.REAUTHORIZE_TRANSFORMS", + "type": "string", + "tags": [], + "label": "REAUTHORIZE_TRANSFORMS", + "description": [], + "path": "x-pack/plugins/fleet/common/constants/routes.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -25816,6 +25861,53 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "fleet", + "id": "def-common.epmRouteService.getReauthorizeTransformsPath", + "type": "Function", + "tags": [], + "label": "getReauthorizeTransformsPath", + "description": [], + "signature": [ + "(pkgName: string, pkgVersion: string) => string" + ], + "path": "x-pack/plugins/fleet/common/services/routes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-common.epmRouteService.getReauthorizeTransformsPath.$1", + "type": "string", + "tags": [], + "label": "pkgName", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/fleet/common/services/routes.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "fleet", + "id": "def-common.epmRouteService.getReauthorizeTransformsPath.$2", + "type": "string", + "tags": [], + "label": "pkgVersion", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/fleet/common/services/routes.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "initialIsOpen": false diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index c2886bd1024a5..f1a34342d9ed3 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1103 | 3 | 998 | 27 | +| 1109 | 3 | 1004 | 28 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index f940b4fcf495f..e5180ea54c5f9 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index ab829887b23a1..d01239408c028 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 371b6ec28b3db..9a3c60b620a1a 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/image_embeddable.mdx b/api_docs/image_embeddable.mdx index 4027dd7e65936..f46b769442430 100644 --- a/api_docs/image_embeddable.mdx +++ b/api_docs/image_embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/imageEmbeddable title: "imageEmbeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the imageEmbeddable plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'imageEmbeddable'] --- import imageEmbeddableObj from './image_embeddable.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index 0aee4872f24ed..3d5fedb9c28b1 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index afe5428691d92..c56e4e1ac00e1 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 286d3290ad0e4..2ef0550b3c7ec 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 42016b66daf73..be420e800114c 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 501e179a1c099..77f9f7caf3d13 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index 053175863af1a..9d79fbd636e02 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 4bdb843dc3269..d38801a007d3a 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 3d0026198d905..d5b19befd4e62 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index a1340ac35ecfa..9d8f4dce4e4e6 100644 --- a/api_docs/kbn_alerting_state_types.mdx +++ b/api_docs/kbn_alerting_state_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-state-types title: "@kbn/alerting-state-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-state-types plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index 3a40615c08a29..496c97c35fa87 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.devdocs.json b/api_docs/kbn_alerts_as_data_utils.devdocs.json index 8c980bb4a392a..8aeb89061c77d 100644 --- a/api_docs/kbn_alerts_as_data_utils.devdocs.json +++ b/api_docs/kbn_alerts_as_data_utils.devdocs.json @@ -114,7 +114,7 @@ "label": "AlertFieldMap", "description": [], "signature": [ - "{ readonly \"kibana.alert.action_group\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.case_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.duration.us\": { readonly type: \"long\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.end\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping_history\": { readonly type: \"boolean\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.maintenance_window_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.instance.id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.last_detected\": { readonly type: \"date\"; readonly required: false; readonly array: false; }; readonly \"kibana.alert.reason\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.category\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.consumer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.execution.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.name\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.producer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.revision\": { readonly type: \"long\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_type_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: true; }; readonly \"@timestamp\": { readonly type: \"date\"; readonly required: true; readonly array: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }" + "{ readonly \"kibana.alert.action_group\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.case_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.duration.us\": { readonly type: \"long\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.end\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping_history\": { readonly type: \"boolean\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.maintenance_window_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.instance.id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.last_detected\": { readonly type: \"date\"; readonly required: false; readonly array: false; }; readonly \"kibana.alert.reason\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.category\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.consumer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.execution.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.name\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.producer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.revision\": { readonly type: \"long\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_type_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: true; }; readonly \"@timestamp\": { readonly type: \"date\"; readonly required: true; readonly array: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }" ], "path": "packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts", "deprecated": false, @@ -150,7 +150,7 @@ "label": "ExperimentalRuleFieldMap", "description": [], "signature": [ - "{ readonly \"kibana.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; readonly required: false; }; readonly \"kibana.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; readonly required: false; }; }" + "{ readonly \"kibana.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; readonly required: false; }; readonly \"kibana.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; readonly required: false; }; readonly \"kibana.alert.evaluation.values\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; readonly required: false; readonly array: true; }; }" ], "path": "packages/kbn-alerts-as-data-utils/src/field_maps/legacy_experimental_field_map.ts", "deprecated": false, @@ -182,7 +182,7 @@ "label": "alertFieldMap", "description": [], "signature": [ - "{ readonly \"kibana.alert.action_group\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.case_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.duration.us\": { readonly type: \"long\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.end\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping_history\": { readonly type: \"boolean\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.maintenance_window_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.instance.id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.last_detected\": { readonly type: \"date\"; readonly required: false; readonly array: false; }; readonly \"kibana.alert.reason\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.category\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.consumer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.execution.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.name\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.producer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.revision\": { readonly type: \"long\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_type_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: true; }; readonly \"@timestamp\": { readonly type: \"date\"; readonly required: true; readonly array: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }" + "{ readonly \"kibana.alert.action_group\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.case_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.duration.us\": { readonly type: \"long\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.end\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping_history\": { readonly type: \"boolean\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.maintenance_window_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.instance.id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.last_detected\": { readonly type: \"date\"; readonly required: false; readonly array: false; }; readonly \"kibana.alert.reason\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.category\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.consumer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.execution.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.name\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.producer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.revision\": { readonly type: \"long\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_type_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.url\": { readonly type: \"keyword\"; readonly array: false; readonly index: false; readonly required: false; readonly ignore_above: 2048; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: true; }; readonly \"@timestamp\": { readonly type: \"date\"; readonly required: true; readonly array: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }" ], "path": "packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts", "deprecated": false, @@ -233,7 +233,7 @@ "label": "legacyExperimentalFieldMap", "description": [], "signature": [ - "{ readonly \"kibana.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; readonly required: false; }; readonly \"kibana.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; readonly required: false; }; }" + "{ readonly \"kibana.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; readonly required: false; }; readonly \"kibana.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; readonly required: false; }; readonly \"kibana.alert.evaluation.values\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; readonly required: false; readonly array: true; }; }" ], "path": "packages/kbn-alerts-as-data-utils/src/field_maps/legacy_experimental_field_map.ts", "deprecated": false, diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index 07fa1da2c3fdc..af3a71ef4577b 100644 --- a/api_docs/kbn_alerts_as_data_utils.mdx +++ b/api_docs/kbn_alerts_as_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-as-data-utils title: "@kbn/alerts-as-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-as-data-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-as-data-utils'] --- import kbnAlertsAsDataUtilsObj from './kbn_alerts_as_data_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index f5021476e5f49..73cb3b3c1b270 100644 --- a/api_docs/kbn_alerts_ui_shared.mdx +++ b/api_docs/kbn_alerts_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-ui-shared title: "@kbn/alerts-ui-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-ui-shared plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-ui-shared'] --- import kbnAlertsUiSharedObj from './kbn_alerts_ui_shared.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index e94e40097f032..c41d203f9bad1 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.devdocs.json b/api_docs/kbn_analytics_client.devdocs.json index 91e27c34dad59..82163bbf3b11d 100644 --- a/api_docs/kbn_analytics_client.devdocs.json +++ b/api_docs/kbn_analytics_client.devdocs.json @@ -758,6 +758,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, { "plugin": "@kbn/core-analytics-browser-mocks", "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index abc54380532ba..cdc8ea6d81beb 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index baed09c3e5ccf..eaf3487e3149d 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index 7ce6fa5d7469c..9110cc32f4c31 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index 8ecbc7e49d01f..df8a64c48bd63 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 6c27ce5db8cfb..05865a67ce0b7 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_gainsight.mdx b/api_docs/kbn_analytics_shippers_gainsight.mdx index 3a5cf3a361d46..f960174f73859 100644 --- a/api_docs/kbn_analytics_shippers_gainsight.mdx +++ b/api_docs/kbn_analytics_shippers_gainsight.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-gainsight title: "@kbn/analytics-shippers-gainsight" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-gainsight plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-gainsight'] --- import kbnAnalyticsShippersGainsightObj from './kbn_analytics_shippers_gainsight.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index c285567ce864a..8900077fd2ace 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 063b1740f59b5..c8432ebc4205c 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index aa86b1d04d774..b76de3fe835c9 100644 --- a/api_docs/kbn_apm_synthtrace_client.mdx +++ b/api_docs/kbn_apm_synthtrace_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace-client title: "@kbn/apm-synthtrace-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace-client plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 91662b2f085a5..61e2869b4e841 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index e5ae1486ae348..ac22df442633d 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index a19320ccda226..738eaa012d294 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cases-components'] --- import kbnCasesComponentsObj from './kbn_cases_components.devdocs.json'; diff --git a/api_docs/kbn_cell_actions.mdx b/api_docs/kbn_cell_actions.mdx index 071e47698abee..891ae75c684ea 100644 --- a/api_docs/kbn_cell_actions.mdx +++ b/api_docs/kbn_cell_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cell-actions title: "@kbn/cell-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cell-actions plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cell-actions'] --- import kbnCellActionsObj from './kbn_cell_actions.devdocs.json'; diff --git a/api_docs/kbn_chart_expressions_common.mdx b/api_docs/kbn_chart_expressions_common.mdx index 6cbfa7316cd70..ea357626d903d 100644 --- a/api_docs/kbn_chart_expressions_common.mdx +++ b/api_docs/kbn_chart_expressions_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-expressions-common title: "@kbn/chart-expressions-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-expressions-common plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-expressions-common'] --- import kbnChartExpressionsCommonObj from './kbn_chart_expressions_common.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index b0958df1d649f..d47f7c20a984c 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index 3331edc3f96d5..b003b37a45c17 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index 49fee67f64c43..6d8ba1ec6ec6d 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index a28a0769f6d79..c9b004312e1fc 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index cd9b9e5c6d1ee..23afc0f33949d 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_code_editor.mdx b/api_docs/kbn_code_editor.mdx index 0f1fb1fb71d8f..2128bc3924343 100644 --- a/api_docs/kbn_code_editor.mdx +++ b/api_docs/kbn_code_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor title: "@kbn/code-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_code_editor_mocks.devdocs.json b/api_docs/kbn_code_editor_mocks.devdocs.json index 18ea0f97a7fc7..1c39d497c16d1 100644 --- a/api_docs/kbn_code_editor_mocks.devdocs.json +++ b/api_docs/kbn_code_editor_mocks.devdocs.json @@ -534,7 +534,7 @@ "label": "Params", "description": [], "signature": [ - "{ value: any; \"aria-label\": any; placeholder: any; allowFullScreen: any; languageId: any; useDarkTheme: any; transparentBackground: any; }" + "{ value: any; placeholder: any; \"aria-label\": any; allowFullScreen: any; languageId: any; useDarkTheme: any; transparentBackground: any; }" ], "path": "packages/shared-ux/code_editor/mocks/storybook.ts", "deprecated": false, diff --git a/api_docs/kbn_code_editor_mocks.mdx b/api_docs/kbn_code_editor_mocks.mdx index 714ef2b878fcd..2c4aa38ed970a 100644 --- a/api_docs/kbn_code_editor_mocks.mdx +++ b/api_docs/kbn_code_editor_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor-mocks title: "@kbn/code-editor-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor-mocks'] --- import kbnCodeEditorMocksObj from './kbn_code_editor_mocks.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index df1eb43f8c67d..1f2f008178f13 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index f311f1eee5a27..0ce1b008677ff 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index 470968d50d845..b7e89d00b3240 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 1f493df2129f4..81635d8b9bd51 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_content_editor.mdx b/api_docs/kbn_content_management_content_editor.mdx index 9860b8335ea58..1288f3330685d 100644 --- a/api_docs/kbn_content_management_content_editor.mdx +++ b/api_docs/kbn_content_management_content_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-editor title: "@kbn/content-management-content-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-editor plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-editor'] --- import kbnContentManagementContentEditorObj from './kbn_content_management_content_editor.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index b934f826eee2d..3dfb0e5bff18e 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index bbf4f05364882..5562e1bf3070f 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 7f3708168936b..d81b99987512d 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index d66e54be71a08..fb39ec52f5459 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 8408c9e4e4fdf..5a1af3d3f7d7d 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index 61f4f5b6c3051..111fe6d8e5318 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index 83100905f5ef4..d7be4f034346a 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index 90225469bdbf8..b57a29bb0ad0a 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index 57a2dc2c514a7..00a6fa0cb815d 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index 16bd5773b95e7..e1a48c61d3b9b 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index 2f6e98f38515e..4bad226894e0c 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index 97d35ded06253..5c042475509cb 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index 71d8df1a5b108..69f9212722cc9 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_apps_server_internal.mdx b/api_docs/kbn_core_apps_server_internal.mdx index 321850ff7277d..65e3d10226f52 100644 --- a/api_docs/kbn_core_apps_server_internal.mdx +++ b/api_docs/kbn_core_apps_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-server-internal title: "@kbn/core-apps-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-server-internal'] --- import kbnCoreAppsServerInternalObj from './kbn_core_apps_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 83f9a87825afd..86f50bf667604 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 28e8e2c3d4bba..cf75d243d2b43 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index 9ee1cd2fa496d..16fa31f01e292 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index 1356483a8d97b..4ca1597fcfadc 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 1f98113a927d0..a86cc6571c831 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index 968a690b339ec..027e44a2e349d 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index 7cda44c53af75..492519d0fef3a 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index afa9a31cd9487..a8ef5c7e24843 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 0f5a69dee73ed..cf04b0d4146b0 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index 46dcf88a3c197..c3ba2d14d0f84 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index fc131c1460d8c..69c93fed8bdf6 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser.mdx b/api_docs/kbn_core_custom_branding_browser.mdx index 97fcf979018f8..3447e44fdeb14 100644 --- a/api_docs/kbn_core_custom_branding_browser.mdx +++ b/api_docs/kbn_core_custom_branding_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser title: "@kbn/core-custom-branding-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser'] --- import kbnCoreCustomBrandingBrowserObj from './kbn_core_custom_branding_browser.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_internal.mdx b/api_docs/kbn_core_custom_branding_browser_internal.mdx index 7f9f083c57ea6..512e0a4658e11 100644 --- a/api_docs/kbn_core_custom_branding_browser_internal.mdx +++ b/api_docs/kbn_core_custom_branding_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-internal title: "@kbn/core-custom-branding-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-internal'] --- import kbnCoreCustomBrandingBrowserInternalObj from './kbn_core_custom_branding_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_mocks.mdx b/api_docs/kbn_core_custom_branding_browser_mocks.mdx index e613abbf44659..57674f1a86f54 100644 --- a/api_docs/kbn_core_custom_branding_browser_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-mocks title: "@kbn/core-custom-branding-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-mocks'] --- import kbnCoreCustomBrandingBrowserMocksObj from './kbn_core_custom_branding_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_common.mdx b/api_docs/kbn_core_custom_branding_common.mdx index c1563f5a7eeca..3271ecbc40835 100644 --- a/api_docs/kbn_core_custom_branding_common.mdx +++ b/api_docs/kbn_core_custom_branding_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-common title: "@kbn/core-custom-branding-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-common plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-common'] --- import kbnCoreCustomBrandingCommonObj from './kbn_core_custom_branding_common.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server.mdx b/api_docs/kbn_core_custom_branding_server.mdx index 8237aab764b3f..947830e94c31a 100644 --- a/api_docs/kbn_core_custom_branding_server.mdx +++ b/api_docs/kbn_core_custom_branding_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server title: "@kbn/core-custom-branding-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server'] --- import kbnCoreCustomBrandingServerObj from './kbn_core_custom_branding_server.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_internal.mdx b/api_docs/kbn_core_custom_branding_server_internal.mdx index 98d95c6d37b9f..fed153ea6f806 100644 --- a/api_docs/kbn_core_custom_branding_server_internal.mdx +++ b/api_docs/kbn_core_custom_branding_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-internal title: "@kbn/core-custom-branding-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-internal'] --- import kbnCoreCustomBrandingServerInternalObj from './kbn_core_custom_branding_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_mocks.mdx b/api_docs/kbn_core_custom_branding_server_mocks.mdx index d75725b7899a2..14c84336e55d8 100644 --- a/api_docs/kbn_core_custom_branding_server_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-mocks title: "@kbn/core-custom-branding-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-mocks'] --- import kbnCoreCustomBrandingServerMocksObj from './kbn_core_custom_branding_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index dd774a0f77817..f5dac83c7c859 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index d2fe1cbc14966..bd675bc3f02fb 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index d3884371a3db5..41ca6aa3b4e82 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index 6cdd897d99633..5d235b665bccf 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index b00ea4077cf20..e11a7361be6fe 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index 6353e802e27db..86023b4693bda 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index f6acd36cc38c2..1b1996813ec5e 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index cea09f8427c74..014a6cff4fb8f 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index fcfa359e065ff..8a827ee08a3cb 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index c47f6b0ab3f79..cf25c21fc3e7b 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index f1df3fa970252..d81566d93b87b 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index 30f75c6f0d0d8..52c845ba3e159 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.devdocs.json b/api_docs/kbn_core_elasticsearch_client_server_mocks.devdocs.json index a605c8268586c..b36b07569b7da 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.devdocs.json +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.devdocs.json @@ -361,7 +361,17 @@ "SearchRequest", " | undefined, options?: ", "TransportRequestOptions", - " | undefined]>; name: string | symbol; monitoring: ", + " | undefined]>; name: string | symbol; security: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; monitoring: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", "scope": "common", @@ -1137,16 +1147,6 @@ }, "<", "default", - ">; security: ", - { - "pluginId": "@kbn/core-elasticsearch-client-server-mocks", - "scope": "common", - "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", - "section": "def-common.DeeplyMockedApi", - "text": "DeeplyMockedApi" - }, - "<", - "default", ">; shutdown: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", @@ -1468,7 +1468,17 @@ "SearchRequest", " | undefined, options?: ", "TransportRequestOptions", - " | undefined]>; name: string | symbol; monitoring: ", + " | undefined]>; name: string | symbol; security: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; monitoring: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", "scope": "common", @@ -2244,16 +2254,6 @@ }, "<", "default", - ">; security: ", - { - "pluginId": "@kbn/core-elasticsearch-client-server-mocks", - "scope": "common", - "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", - "section": "def-common.DeeplyMockedApi", - "text": "DeeplyMockedApi" - }, - "<", - "default", ">; shutdown: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", @@ -2529,7 +2529,17 @@ "SearchRequest", " | undefined, options?: ", "TransportRequestOptions", - " | undefined]>; name: string | symbol; monitoring: ", + " | undefined]>; name: string | symbol; security: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; monitoring: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", "scope": "common", @@ -3305,16 +3315,6 @@ }, "<", "default", - ">; security: ", - { - "pluginId": "@kbn/core-elasticsearch-client-server-mocks", - "scope": "common", - "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", - "section": "def-common.DeeplyMockedApi", - "text": "DeeplyMockedApi" - }, - "<", - "default", ">; shutdown: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", @@ -3681,7 +3681,17 @@ "SearchRequest", " | undefined, options?: ", "TransportRequestOptions", - " | undefined]>; name: string | symbol; monitoring: ", + " | undefined]>; name: string | symbol; security: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; monitoring: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", "scope": "common", @@ -4457,16 +4467,6 @@ }, "<", "default", - ">; security: ", - { - "pluginId": "@kbn/core-elasticsearch-client-server-mocks", - "scope": "common", - "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", - "section": "def-common.DeeplyMockedApi", - "text": "DeeplyMockedApi" - }, - "<", - "default", ">; shutdown: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index e8a6a5bd9d974..25bdcf097fc8f 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.devdocs.json b/api_docs/kbn_core_elasticsearch_server.devdocs.json index 2fa0afea256a4..ee492a19564d3 100644 --- a/api_docs/kbn_core_elasticsearch_server.devdocs.json +++ b/api_docs/kbn_core_elasticsearch_server.devdocs.json @@ -1144,7 +1144,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -2066,8 +2068,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", @@ -2810,7 +2810,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -3732,8 +3734,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", @@ -4012,7 +4012,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -4934,8 +4936,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", @@ -5467,7 +5467,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -6389,8 +6391,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 452434e7f7028..d3e52e4498e97 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.devdocs.json b/api_docs/kbn_core_elasticsearch_server_internal.devdocs.json index e61d0f6b28377..96c31bba82765 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.devdocs.json +++ b/api_docs/kbn_core_elasticsearch_server_internal.devdocs.json @@ -198,7 +198,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -1120,8 +1122,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", @@ -1828,7 +1828,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -2750,8 +2752,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index 55602e77c39ac..d7116ffefd9e3 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index bbc421518eacc..8763333e3c1ca 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index 8a1e25f226672..c72123128deb8 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index 5950a6f4e28a4..2bca835abcc60 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index 7530cdb8b9f88..e0133ab2eceac 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index 44c4f6b1d8356..f4285539090c7 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 9d0d0c727bb07..e88005161eee4 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index 4213ef4dd473f..9bb759d225d18 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index 582d77767ebac..391aab7e7c207 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index 3fdd7e0a3958a..b0a32a23bc8fd 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index d637f492ceff4..d044bcd313597 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index 2d47d49cf0f54..3185c3568f7cd 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index d750c317ac6a7..6d4e823e86bc7 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index 8ddc42521054a..2379a6b30140a 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index 33f9e5d9bccaa..3a8785ce36e04 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index c771d7b51b6b2..cd9f5e1f32fae 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index 61a0e48cfdeb8..030701d5c5535 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index b662fe75b1955..3041948b6c08e 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_request_handler_context_server.mdx b/api_docs/kbn_core_http_request_handler_context_server.mdx index f189aa1a55351..6c55bb1acc35f 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-request-handler-context-server'] --- import kbnCoreHttpRequestHandlerContextServerObj from './kbn_core_http_request_handler_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server.mdx b/api_docs/kbn_core_http_resources_server.mdx index 47a782b2d28d8..1fc1939592f1d 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server'] --- import kbnCoreHttpResourcesServerObj from './kbn_core_http_resources_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_internal.mdx b/api_docs/kbn_core_http_resources_server_internal.mdx index 9cc4ba8ad2475..1ce4c26ce2a2e 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-internal'] --- import kbnCoreHttpResourcesServerInternalObj from './kbn_core_http_resources_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_mocks.mdx b/api_docs/kbn_core_http_resources_server_mocks.mdx index 26de4dde620bb..c4dd257493cdb 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-mocks'] --- import kbnCoreHttpResourcesServerMocksObj from './kbn_core_http_resources_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 96c131b566386..7baa129a75c92 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 04cdaf7a0c296..9115db4edf6cc 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index ae3de1a53b6bf..152820cbb488b 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 32a24eb6c8a59..1dc544d47e781 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index cc73fbc025f7d..12f4523bca3e3 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index 5b4c019df0b30..290dc43e0f6a4 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index 49579be05fecd..1892f31f90652 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index f89c8bd6d88c8..8cd6df89cee80 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index 1d072e4d63ed9..71388b8df33ef 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index 1ca1b4a931e7f..b46f5cadc7bf9 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index bc405aa9920a6..188df6cc9052d 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index 75255e3e34bfd..75b8dc9819d5a 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index 086837c33ee81..a9e58cbc2c4fb 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 735b927f7d9ab..d29ee50a427bf 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index bf4a1ead3d536..a3f5c62d35e87 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx index 3a4b5ed5dddea..336ba1167e471 100644 --- a/api_docs/kbn_core_lifecycle_server.mdx +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server title: "@kbn/core-lifecycle-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server'] --- import kbnCoreLifecycleServerObj from './kbn_core_lifecycle_server.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server_mocks.mdx b/api_docs/kbn_core_lifecycle_server_mocks.mdx index 448715c87a21f..ead03154da97c 100644 --- a/api_docs/kbn_core_lifecycle_server_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server-mocks title: "@kbn/core-lifecycle-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server-mocks'] --- import kbnCoreLifecycleServerMocksObj from './kbn_core_lifecycle_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_browser_mocks.mdx b/api_docs/kbn_core_logging_browser_mocks.mdx index ac96262b9c76b..70dcc85174431 100644 --- a/api_docs/kbn_core_logging_browser_mocks.mdx +++ b/api_docs/kbn_core_logging_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-browser-mocks title: "@kbn/core-logging-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-browser-mocks'] --- import kbnCoreLoggingBrowserMocksObj from './kbn_core_logging_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_common_internal.mdx b/api_docs/kbn_core_logging_common_internal.mdx index 630a94dfbb8f3..eaaab07d66216 100644 --- a/api_docs/kbn_core_logging_common_internal.mdx +++ b/api_docs/kbn_core_logging_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-common-internal title: "@kbn/core-logging-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-common-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-common-internal'] --- import kbnCoreLoggingCommonInternalObj from './kbn_core_logging_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index ab67e69ef94eb..764140b0a5e6c 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index f69a9bfa85c75..96a65db189368 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index dc3d5d6f6a377..7edad6a24183a 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index fb713ae5250ef..1bed7b5faea0a 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index e310f4f18e0bf..f8d4fffbfe021 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index 7c1524c5080fb..daf89498a1cfc 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index 751f7e9aa7a09..2faf2f55b3dae 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index 38f7647c57437..9a208c0343c78 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index 2bacd959896e9..22a3bada69676 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 79767c900f656..a85a0e176e836 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index 3d60a557b515f..0418f4ca39d60 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index c11cd5a44f85e..fef44b08e7f61 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.devdocs.json b/api_docs/kbn_core_notifications_browser.devdocs.json index 5d44379591cb3..3f14a12b02887 100644 --- a/api_docs/kbn_core_notifications_browser.devdocs.json +++ b/api_docs/kbn_core_notifications_browser.devdocs.json @@ -736,7 +736,7 @@ "signature": [ "Pick<", "Toast", - ", \"prefix\" | \"defaultValue\" | \"children\" | \"is\" | \"security\" | \"slot\" | \"style\" | \"className\" | \"aria-label\" | \"data-test-subj\" | \"css\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"placeholder\" | \"spellCheck\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"color\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChange\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClick\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"iconType\" | \"onClose\" | \"toastLifeTimeMs\"> & { title?: string | ", + ", \"prefix\" | \"defaultValue\" | \"children\" | \"onChange\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"color\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClick\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"data-test-subj\" | \"css\" | \"iconType\" | \"onClose\" | \"toastLifeTimeMs\"> & { title?: string | ", { "pluginId": "@kbn/core-mount-utils-browser", "scope": "common", @@ -795,7 +795,7 @@ "signature": [ "Pick<", "Toast", - ", \"prefix\" | \"defaultValue\" | \"children\" | \"is\" | \"security\" | \"slot\" | \"style\" | \"className\" | \"aria-label\" | \"data-test-subj\" | \"css\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"placeholder\" | \"spellCheck\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"color\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChange\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClick\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"iconType\" | \"onClose\" | \"toastLifeTimeMs\"> & { title?: string | ", + ", \"prefix\" | \"defaultValue\" | \"children\" | \"onChange\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"color\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDown\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClick\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"data-test-subj\" | \"css\" | \"iconType\" | \"onClose\" | \"toastLifeTimeMs\"> & { title?: string | ", { "pluginId": "@kbn/core-mount-utils-browser", "scope": "common", diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index 98f962cda1366..5b4c9e57d0c5e 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index e6dbdc9b96f76..1c53d4f1151a2 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index 19758a5e79d5e..ac5a768548470 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index 4a45c849bcc96..6538de0675581 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index b92e689e4ef1e..2be250444e13d 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index 7cb6c9dda7a39..36a17d070c6e5 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index 5bdceb6b6984c..7a0bd26bc2490 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index 799ca27985b43..94f1a573c6e08 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index eed2f8d932755..20d7ec12378e1 100644 --- a/api_docs/kbn_core_plugins_server.mdx +++ b/api_docs/kbn_core_plugins_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server title: "@kbn/core-plugins-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server'] --- import kbnCorePluginsServerObj from './kbn_core_plugins_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server_mocks.mdx b/api_docs/kbn_core_plugins_server_mocks.mdx index f0153c6d49f71..1186e3108b3e3 100644 --- a/api_docs/kbn_core_plugins_server_mocks.mdx +++ b/api_docs/kbn_core_plugins_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server-mocks title: "@kbn/core-plugins-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server-mocks'] --- import kbnCorePluginsServerMocksObj from './kbn_core_plugins_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index ff9fe03bb5113..9e81829a0ef67 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index bf57e6bd4ee7a..920f32db8a28e 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index 40b41d802ac0b..ff75e7da8fd40 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_internal.mdx b/api_docs/kbn_core_rendering_server_internal.mdx index f4db0194802b1..5d8f7df7ac176 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-internal'] --- import kbnCoreRenderingServerInternalObj from './kbn_core_rendering_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_mocks.mdx b/api_docs/kbn_core_rendering_server_mocks.mdx index b4d73ea88a6ac..fc7ce48733faa 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_root_server_internal.mdx b/api_docs/kbn_core_root_server_internal.mdx index 2646c889d696b..66f6d3135417b 100644 --- a/api_docs/kbn_core_root_server_internal.mdx +++ b/api_docs/kbn_core_root_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-root-server-internal title: "@kbn/core-root-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-root-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json index c6ab4101d6c7a..f104ac919f51e 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json @@ -3390,6 +3390,22 @@ "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-browser", + "id": "def-common.SavedObjectsCreateOptions.managed", + "type": "CompoundType", + "tags": [], + "label": "managed", + "description": [ + "\nFlag indicating if a saved object is managed by Kibana (default=false)\n\nThis can be leveraged by applications to e.g. prevent edits to a managed\nsaved object. Instead, users can be guided to create a copy first and\nmake their edits to the copy." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -4308,6 +4324,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/core-saved-objects-api-browser", + "id": "def-common.SimpleSavedObject.managed", + "type": "CompoundType", + "tags": [], + "label": "managed", + "description": [ + "\nFlag indicating if a saved object is managed by Kibana (default=false)\n\nThis can be leveraged by applications to e.g. prevent edits to a managed\nsaved object. Instead, users can be guided to create a copy first and\nmake their edits to the copy." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/core-saved-objects-api-browser", "id": "def-common.SimpleSavedObject.get", diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index cc97c9bc44726..814eebd1bcbfa 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 109 | 1 | 0 | 0 | +| 111 | 1 | 0 | 0 | ## Common diff --git a/api_docs/kbn_core_saved_objects_api_server.devdocs.json b/api_docs/kbn_core_saved_objects_api_server.devdocs.json index ccbc23a7ac04a..b9f92ca1d1807 100644 --- a/api_docs/kbn_core_saved_objects_api_server.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_server.devdocs.json @@ -2571,6 +2571,22 @@ "path": "packages/core/saved-objects/core-saved-objects-common/src/server_types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-common.SavedObject.managed", + "type": "CompoundType", + "tags": [], + "label": "managed", + "description": [ + "\nFlag indicating if a saved object is managed by Kibana (default=false)\n\nThis can be leveraged by applications to e.g. prevent edits to a managed\nsaved object. Instead, users can be guided to create a copy first and\nmake their edits to the copy." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-common/src/server_types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -3328,6 +3344,22 @@ "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-common.SavedObjectsBulkCreateObject.managed", + "type": "CompoundType", + "tags": [], + "label": "managed", + "description": [ + "\nFlag indicating if a saved object is managed by Kibana (default=false)\n\nThis can be leveraged by applications to e.g. prevent edits to a managed\nsaved object. Instead, users can be guided to create a copy first and\nmake their edits to the copy." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -6036,6 +6068,14 @@ "plugin": "canvas", "path": "x-pack/plugins/canvas/server/workpad_route_context.ts" }, + { + "plugin": "@kbn/core-saved-objects-api-server-internal", + "path": "packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts" + }, + { + "plugin": "@kbn/core-saved-objects-api-server-internal", + "path": "packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts" + }, { "plugin": "@kbn/core-saved-objects-api-server-internal", "path": "packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts" @@ -6152,6 +6192,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-common.SavedObjectsCreateOptions.managed", + "type": "CompoundType", + "tags": [], + "label": "managed", + "description": [ + "\nFlag indicating if a saved object is managed by Kibana (default=false)\n\nThis can be leveraged by applications to e.g. prevent edits to a managed\nsaved object. Instead, users can be guided to create a copy first and\nmake their edits to the copy." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/core-saved-objects-api-server", "id": "def-common.SavedObjectsCreateOptions.migrationVersionCompatibility", @@ -7244,6 +7300,22 @@ "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-api-server", + "id": "def-common.SavedObjectsIncrementCounterOptions.managed", + "type": "CompoundType", + "tags": [], + "label": "managed", + "description": [ + "\nFlag indicating if a saved object is managed by Kibana (default=false).\nOnly used when upserting a saved object. If the saved object already\nexist this option has no effect.\n\nThis can be leveraged by applications to e.g. prevent edits to a managed\nsaved object. Instead, users can be guided to create a copy first and\nmake their edits to the copy." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 74d79efb220eb..4c5f84aee2327 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 347 | 1 | 5 | 1 | +| 351 | 1 | 5 | 1 | ## Common diff --git a/api_docs/kbn_core_saved_objects_api_server_internal.mdx b/api_docs/kbn_core_saved_objects_api_server_internal.mdx index ddf6fb2bed3cf..3f8241f9ab849 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index 46159abcb32a5..72ad46898861d 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index a8b49b8d681e1..7e7ccf32b70ab 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index 335aa6b5510a5..76f857265d07b 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index bba9a0fe3f2b3..395382f11c5a6 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index c78ce88a69f12..e30fc42495448 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index 5a6fb2c44b372..7797116f838b6 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.devdocs.json b/api_docs/kbn_core_saved_objects_common.devdocs.json index f6d51fc124d6e..9b9113df93f0a 100644 --- a/api_docs/kbn_core_saved_objects_common.devdocs.json +++ b/api_docs/kbn_core_saved_objects_common.devdocs.json @@ -1063,6 +1063,10 @@ "plugin": "@kbn/core-saved-objects-api-browser", "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts" }, + { + "plugin": "@kbn/core-saved-objects-api-browser", + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts" + }, { "plugin": "@kbn/core-saved-objects-api-server", "path": "packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts" @@ -1131,6 +1135,10 @@ "plugin": "@kbn/core-saved-objects-browser-internal", "path": "packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts" }, + { + "plugin": "@kbn/core-saved-objects-browser-internal", + "path": "packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts" + }, { "plugin": "@kbn/core-saved-objects-browser-internal", "path": "packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts" diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 7e86e3ad14112..c7820ecbca374 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index ceb95fa4e2395..34d40c1a0e34b 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index 46f276fa9a5fe..95f19589a3e7b 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json b/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json index fe4d27af786d9..02115886635d7 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json @@ -2409,7 +2409,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -3331,8 +3333,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index 5e8bd70323aa0..1237af41a106a 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index df63883550aa3..2b1be8ab9ccf3 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.devdocs.json b/api_docs/kbn_core_saved_objects_server.devdocs.json index 64c296dff415d..f6be8896da932 100644 --- a/api_docs/kbn_core_saved_objects_server.devdocs.json +++ b/api_docs/kbn_core_saved_objects_server.devdocs.json @@ -6036,6 +6036,22 @@ "path": "packages/core/saved-objects/core-saved-objects-common/src/server_types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-server", + "id": "def-common.SavedObject.managed", + "type": "CompoundType", + "tags": [], + "label": "managed", + "description": [ + "\nFlag indicating if a saved object is managed by Kibana (default=false)\n\nThis can be leveraged by applications to e.g. prevent edits to a managed\nsaved object. Instead, users can be guided to create a copy first and\nmake their edits to the copy." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-common/src/server_types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -8221,6 +8237,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/core-saved-objects-server", + "id": "def-common.SavedObjectsRawDocSource.managed", + "type": "CompoundType", + "tags": [], + "label": "managed", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-server/src/serialization.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/core-saved-objects-server", "id": "def-common.SavedObjectsRawDocSource.Unnamed", diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 7308525d17c4a..01110761151d4 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 494 | 1 | 99 | 4 | +| 496 | 1 | 100 | 4 | ## Common diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 9bbcba68753f9..c1570d44d6a4e 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index 39bcf94ad1517..b4d9023205b5b 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index 2176df6e1f627..68054fd030ba7 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index ee5f2f06c798c..0099c87fe71c1 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index cfef8384a8358..942ceea0dddb0 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index 0077e0166e34f..b9013fd6ca323 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 702c012e25f88..e9c7d05fc7a4f 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index 1346f773d4326..0c6549c2c40a8 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index bc0cff6212ae7..0148b222dc307 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 2e84b6ef2d794..5c3b407e8d7e1 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_kbn_server.mdx b/api_docs/kbn_core_test_helpers_kbn_server.mdx index acb33a14cf28d..9f82264d3416a 100644 --- a/api_docs/kbn_core_test_helpers_kbn_server.mdx +++ b/api_docs/kbn_core_test_helpers_kbn_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-kbn-server title: "@kbn/core-test-helpers-kbn-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-kbn-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-kbn-server'] --- import kbnCoreTestHelpersKbnServerObj from './kbn_core_test_helpers_kbn_server.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index bc4c85f76f85c..361d7ee4abaef 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-so-type-serializer'] --- import kbnCoreTestHelpersSoTypeSerializerObj from './kbn_core_test_helpers_so_type_serializer.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_test_utils.mdx b/api_docs/kbn_core_test_helpers_test_utils.mdx index f5d8e8e4b8a8e..729bf46fe8b67 100644 --- a/api_docs/kbn_core_test_helpers_test_utils.mdx +++ b/api_docs/kbn_core_test_helpers_test_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-test-utils title: "@kbn/core-test-helpers-test-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-test-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-test-utils'] --- import kbnCoreTestHelpersTestUtilsObj from './kbn_core_test_helpers_test_utils.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 1900793efb403..59ca3b555e77c 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index 1f118ad62feaa..d8e9d7d490f07 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-internal'] --- import kbnCoreThemeBrowserInternalObj from './kbn_core_theme_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 334b5611b5be3..fcbeaaf620f5b 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index ff8d60d9cd031..db1ea4bacbeff 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index bb8c17a6dd7ec..4bd232bbc6d3d 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index 2ab598732c667..50362b87de095 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.devdocs.json b/api_docs/kbn_core_ui_settings_common.devdocs.json index 6f31370eab373..a218916953f18 100644 --- a/api_docs/kbn_core_ui_settings_common.devdocs.json +++ b/api_docs/kbn_core_ui_settings_common.devdocs.json @@ -473,7 +473,7 @@ "\nUI element type to represent the settings." ], "signature": [ - "\"string\" | \"number\" | \"boolean\" | \"undefined\" | \"select\" | \"image\" | \"color\" | \"json\" | \"markdown\" | \"array\"" + "\"string\" | \"number\" | \"boolean\" | \"undefined\" | \"color\" | \"select\" | \"image\" | \"json\" | \"markdown\" | \"array\"" ], "path": "packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts", "deprecated": false, diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index 0203ee7cbd037..955203aeab79b 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index c0e47a9ce8832..5e5c93b62137f 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index 6bc188e6d6442..586bb305927ea 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index 63929de47b247..8bde395eb141e 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index 47ec2ccf93dab..ac875acccae8d 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index 5540930ed5fec..e9e8099f4692e 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index 40a8e634fbf38..d98b4e73ab7f0 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index 2855b17a3bbde..a367c613df999 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index f5aa87a327f2a..0b4c15845ebc7 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index a5eade6a0eeb8..e951de4adfb35 100644 --- a/api_docs/kbn_cypress_config.mdx +++ b/api_docs/kbn_cypress_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cypress-config title: "@kbn/cypress-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cypress-config plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 760812b3d9de3..3d864f429b0c3 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index 3ff9e4d7e3e71..af952748b1e5a 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 9aac93e14f30c..b66e4a59edd0e 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index 228133ab7d29d..74b88642009bd 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index 91524933ec5d0..df18c4a7ae5e6 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.devdocs.json b/api_docs/kbn_doc_links.devdocs.json index 4e53df24182e0..b01beac37dc53 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -300,7 +300,7 @@ "label": "enterpriseSearch", "description": [], "signature": [ - "{ readonly apiKeys: string; readonly behavioralAnalytics: string; readonly behavioralAnalyticsEvents: string; readonly bulkApi: string; readonly configuration: string; readonly connectors: string; readonly connectorsMongoDB: string; readonly connectorsMySQL: string; readonly connectorsWorkplaceSearch: string; readonly crawlerExtractionRules: string; readonly crawlerManaging: string; readonly crawlerOverview: string; readonly deployTrainedModels: string; readonly documentLevelSecurity: string; readonly elser: string; readonly engines: string; readonly ingestPipelines: string; readonly languageAnalyzers: string; readonly languageClients: string; readonly licenseManagement: string; readonly machineLearningStart: string; readonly mailService: string; readonly start: string; readonly syncRules: string; readonly troubleshootSetup: string; readonly usersAccess: string; }" + "{ readonly apiKeys: string; readonly behavioralAnalytics: string; readonly behavioralAnalyticsEvents: string; readonly bulkApi: string; readonly configuration: string; readonly connectors: string; readonly connectorsAzureBlobStorage: string; readonly connectorsGoogleCloudStorage: string; readonly connectorsMicrosoftSQL: string; readonly connectorsMongoDB: string; readonly connectorsMySQL: string; readonly connectorsNetworkDrive: string; readonly connectorsOracle: string; readonly connectorsPostgreSQL: string; readonly connectorsS3: string; readonly connectorsWorkplaceSearch: string; readonly crawlerExtractionRules: string; readonly crawlerManaging: string; readonly crawlerOverview: string; readonly deployTrainedModels: string; readonly documentLevelSecurity: string; readonly elser: string; readonly engines: string; readonly ingestionApis: string; readonly ingestPipelines: string; readonly languageAnalyzers: string; readonly languageClients: string; readonly licenseManagement: string; readonly machineLearningStart: string; readonly mailService: string; readonly start: string; readonly syncRules: string; readonly troubleshootSetup: string; readonly usersAccess: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 5323199ab5690..237b608f7aa52 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index 078dcb629fdbe..d12f4055fc03a 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_dom_drag_drop.mdx b/api_docs/kbn_dom_drag_drop.mdx index b4b41943700a4..a9c6b2c6c0a28 100644 --- a/api_docs/kbn_dom_drag_drop.mdx +++ b/api_docs/kbn_dom_drag_drop.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dom-drag-drop title: "@kbn/dom-drag-drop" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dom-drag-drop plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dom-drag-drop'] --- import kbnDomDragDropObj from './kbn_dom_drag_drop.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index ce5fef552eaaf..f7d904bd7bce7 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs.mdx b/api_docs/kbn_ecs.mdx index e8ea18c35009b..a070106c4a4f4 100644 --- a/api_docs/kbn_ecs.mdx +++ b/api_docs/kbn_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs title: "@kbn/ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs'] --- import kbnEcsObj from './kbn_ecs.devdocs.json'; diff --git a/api_docs/kbn_ecs_data_quality_dashboard.mdx b/api_docs/kbn_ecs_data_quality_dashboard.mdx index 3584bd6954340..d6933f82a8a56 100644 --- a/api_docs/kbn_ecs_data_quality_dashboard.mdx +++ b/api_docs/kbn_ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs-data-quality-dashboard title: "@kbn/ecs-data-quality-dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs-data-quality-dashboard plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs-data-quality-dashboard'] --- import kbnEcsDataQualityDashboardObj from './kbn_ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index dcda809c7b317..b90987c9e70ef 100644 --- a/api_docs/kbn_es.mdx +++ b/api_docs/kbn_es.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es title: "@kbn/es" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 981f921d0f381..9664b87b6910b 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index 11d880247da77..8a8c25e5981a8 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index afc16a4d74ee2..56f69b7a09446 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 59fb0c2e57049..4de4378862f0f 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 88719e3c85f7e..99046ee7a9a8d 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_expandable_flyout.mdx b/api_docs/kbn_expandable_flyout.mdx index b5947a8295232..86a0743c821f5 100644 --- a/api_docs/kbn_expandable_flyout.mdx +++ b/api_docs/kbn_expandable_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-expandable-flyout title: "@kbn/expandable-flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/expandable-flyout plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/expandable-flyout'] --- import kbnExpandableFlyoutObj from './kbn_expandable_flyout.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index b487899d2b554..d0b208fb97a8e 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index 4fc7ad7580aa5..46afc78d5608e 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 3037e205cc6c1..7ae2b45126f93 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index ac9cf890c741c..7d52e4ad2721e 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_generate_csv.mdx b/api_docs/kbn_generate_csv.mdx index 034ee24b31adc..bf0035738a250 100644 --- a/api_docs/kbn_generate_csv.mdx +++ b/api_docs/kbn_generate_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv title: "@kbn/generate-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_generate_csv_types.mdx b/api_docs/kbn_generate_csv_types.mdx index 2e87800016af5..62ba0711eb511 100644 --- a/api_docs/kbn_generate_csv_types.mdx +++ b/api_docs/kbn_generate_csv_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv-types title: "@kbn/generate-csv-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv-types plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv-types'] --- import kbnGenerateCsvTypesObj from './kbn_generate_csv_types.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index 454b6ec5c6172..dd29963efcb66 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 87aaec89cc979..7f02b89475b3e 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index bffe3c1cf4e3d..4955b1030ea07 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_health_gateway_server.mdx b/api_docs/kbn_health_gateway_server.mdx index 42af824e4c2cc..26528fbcb1d05 100644 --- a/api_docs/kbn_health_gateway_server.mdx +++ b/api_docs/kbn_health_gateway_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-health-gateway-server title: "@kbn/health-gateway-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/health-gateway-server plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/health-gateway-server'] --- import kbnHealthGatewayServerObj from './kbn_health_gateway_server.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 732c8f5562d4b..ca6f2a8e2ad02 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index eb4fe7a1d62c8..ade2366ab0e33 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 4ace6960d3795..0a6a407c727d5 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_i18n_react.mdx b/api_docs/kbn_i18n_react.mdx index 4d88cd19ac9b5..7bf9b5d9dd0c8 100644 --- a/api_docs/kbn_i18n_react.mdx +++ b/api_docs/kbn_i18n_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n-react title: "@kbn/i18n-react" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n-react plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n-react'] --- import kbnI18nReactObj from './kbn_i18n_react.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 2c6154c92b9fb..fd1da1af10952 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 1baddb05cda74..3bfb04e709422 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.devdocs.json b/api_docs/kbn_io_ts_utils.devdocs.json index 1ffbfca120dda..9b5b0e6802254 100644 --- a/api_docs/kbn_io_ts_utils.devdocs.json +++ b/api_docs/kbn_io_ts_utils.devdocs.json @@ -54,6 +54,50 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/io-ts-utils", + "id": "def-common.createRouteValidationFunction", + "type": "Function", + "tags": [], + "label": "createRouteValidationFunction", + "description": [], + "signature": [ + "(runtimeType: ", + "Type", + ") => ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteValidationFunction", + "text": "RouteValidationFunction" + }, + "" + ], + "path": "packages/kbn-io-ts-utils/src/route_validation/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/io-ts-utils", + "id": "def-common.createRouteValidationFunction.$1", + "type": "Object", + "tags": [], + "label": "runtimeType", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "packages/kbn-io-ts-utils/src/route_validation/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/io-ts-utils", "id": "def-common.deepExactRt", @@ -156,6 +200,111 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/io-ts-utils", + "id": "def-common.inRangeRt", + "type": "Function", + "tags": [], + "label": "inRangeRt", + "description": [], + "signature": [ + "(start: number, end: number) => ", + "BrandC", + "<", + "NumberC", + ", ", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.InRangeBrand", + "text": "InRangeBrand" + }, + ">" + ], + "path": "packages/kbn-io-ts-utils/src/in_range_rt/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/io-ts-utils", + "id": "def-common.inRangeRt.$1", + "type": "number", + "tags": [], + "label": "start", + "description": [], + "signature": [ + "number" + ], + "path": "packages/kbn-io-ts-utils/src/in_range_rt/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/io-ts-utils", + "id": "def-common.inRangeRt.$2", + "type": "number", + "tags": [], + "label": "end", + "description": [], + "signature": [ + "number" + ], + "path": "packages/kbn-io-ts-utils/src/in_range_rt/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/io-ts-utils", + "id": "def-common.isGreaterOrEqualRt", + "type": "Function", + "tags": [], + "label": "isGreaterOrEqualRt", + "description": [], + "signature": [ + "(value: number) => ", + "BrandC", + "<", + "NumberC", + ", ", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.IsGreaterOrEqualBrand", + "text": "IsGreaterOrEqualBrand" + }, + ">" + ], + "path": "packages/kbn-io-ts-utils/src/is_greater_or_equal/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/io-ts-utils", + "id": "def-common.isGreaterOrEqualRt.$1", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "number" + ], + "path": "packages/kbn-io-ts-utils/src/is_greater_or_equal/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/io-ts-utils", "id": "def-common.mergeRt", @@ -322,6 +471,78 @@ } ], "interfaces": [ + { + "parentPluginId": "@kbn/io-ts-utils", + "id": "def-common.InRangeBrand", + "type": "Interface", + "tags": [], + "label": "InRangeBrand", + "description": [], + "path": "packages/kbn-io-ts-utils/src/in_range_rt/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/io-ts-utils", + "id": "def-common.InRangeBrand.InRange", + "type": "Uncategorized", + "tags": [], + "label": "InRange", + "description": [], + "signature": [ + "typeof ", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.InRangeBrand", + "text": "InRangeBrand" + }, + "[\"InRange\"]" + ], + "path": "packages/kbn-io-ts-utils/src/in_range_rt/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/io-ts-utils", + "id": "def-common.IsGreaterOrEqualBrand", + "type": "Interface", + "tags": [], + "label": "IsGreaterOrEqualBrand", + "description": [], + "path": "packages/kbn-io-ts-utils/src/is_greater_or_equal/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/io-ts-utils", + "id": "def-common.IsGreaterOrEqualBrand.IsGreaterOrEqual", + "type": "Uncategorized", + "tags": [], + "label": "IsGreaterOrEqual", + "description": [], + "signature": [ + "typeof ", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.IsGreaterOrEqualBrand", + "text": "IsGreaterOrEqualBrand" + }, + "[\"IsGreaterOrEqual\"]" + ], + "path": "packages/kbn-io-ts-utils/src/is_greater_or_equal/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/io-ts-utils", "id": "def-common.NonEmptyStringBrand", @@ -379,9 +600,79 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/io-ts-utils", + "id": "def-common.InRange", + "type": "Type", + "tags": [], + "label": "InRange", + "description": [], + "signature": [ + "number & ", + "Brand", + "<", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.InRangeBrand", + "text": "InRangeBrand" + }, + ">" + ], + "path": "packages/kbn-io-ts-utils/src/in_range_rt/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/io-ts-utils", + "id": "def-common.IsGreaterOrEqual", + "type": "Type", + "tags": [], + "label": "IsGreaterOrEqual", + "description": [], + "signature": [ + "number & ", + "Brand", + "<", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.IsGreaterOrEqualBrand", + "text": "IsGreaterOrEqualBrand" + }, + ">" + ], + "path": "packages/kbn-io-ts-utils/src/is_greater_or_equal/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "objects": [ + { + "parentPluginId": "@kbn/io-ts-utils", + "id": "def-common.dateRt", + "type": "Object", + "tags": [], + "label": "dateRt", + "description": [], + "signature": [ + "BrandC", + "<", + "StringC", + ", ", + "DateBrand", + ">" + ], + "path": "packages/kbn-io-ts-utils/src/date_rt/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/io-ts-utils", "id": "def-common.indexPatternRt", diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index e2d533cf83e6a..503dbab8e3754 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) for ques | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 24 | 0 | 24 | 3 | +| 38 | 0 | 38 | 4 | ## Common diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 369df25ee21ab..dea3262170428 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.devdocs.json b/api_docs/kbn_journeys.devdocs.json index bc2b5dfe5fffa..b0a4c65e8be13 100644 --- a/api_docs/kbn_journeys.devdocs.json +++ b/api_docs/kbn_journeys.devdocs.json @@ -1230,6 +1230,195 @@ "initialIsOpen": false } ], - "objects": [] + "objects": [ + { + "parentPluginId": "@kbn/journeys", + "id": "def-common.JOURNEY_APM_CONFIG", + "type": "Object", + "tags": [], + "label": "JOURNEY_APM_CONFIG", + "description": [], + "path": "packages/kbn-journeys/journey/journey_apm_config.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/journeys", + "id": "def-common.JOURNEY_APM_CONFIG.serverUrl", + "type": "string", + "tags": [], + "label": "serverUrl", + "description": [], + "path": "packages/kbn-journeys/journey/journey_apm_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/journeys", + "id": "def-common.JOURNEY_APM_CONFIG.secretToken", + "type": "string", + "tags": [], + "label": "secretToken", + "description": [], + "path": "packages/kbn-journeys/journey/journey_apm_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/journeys", + "id": "def-common.JOURNEY_APM_CONFIG.active", + "type": "string", + "tags": [], + "label": "active", + "description": [], + "path": "packages/kbn-journeys/journey/journey_apm_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/journeys", + "id": "def-common.JOURNEY_APM_CONFIG.contextPropagationOnly", + "type": "string", + "tags": [], + "label": "contextPropagationOnly", + "description": [], + "path": "packages/kbn-journeys/journey/journey_apm_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/journeys", + "id": "def-common.JOURNEY_APM_CONFIG.environment", + "type": "string", + "tags": [], + "label": "environment", + "description": [], + "path": "packages/kbn-journeys/journey/journey_apm_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/journeys", + "id": "def-common.JOURNEY_APM_CONFIG.transactionSampleRate", + "type": "string", + "tags": [], + "label": "transactionSampleRate", + "description": [], + "path": "packages/kbn-journeys/journey/journey_apm_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/journeys", + "id": "def-common.JOURNEY_APM_CONFIG.captureBody", + "type": "string", + "tags": [], + "label": "captureBody", + "description": [ + "// capture request body for both errors and request transactions\n// https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#capture-body" + ], + "path": "packages/kbn-journeys/journey/journey_apm_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/journeys", + "id": "def-common.JOURNEY_APM_CONFIG.captureRequestHeaders", + "type": "boolean", + "tags": [], + "label": "captureRequestHeaders", + "description": [ + "// capture request headers\n// https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#capture-headers" + ], + "path": "packages/kbn-journeys/journey/journey_apm_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/journeys", + "id": "def-common.JOURNEY_APM_CONFIG.longFieldMaxLength", + "type": "number", + "tags": [], + "label": "longFieldMaxLength", + "description": [ + "// request body with bigger size will be trimmed.\n// 300_000 is the default of the APM server.\n// for a body with larger size, we might need to reconfigure the APM server to increase the limit.\n// https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#long-field-max-length" + ], + "path": "packages/kbn-journeys/journey/journey_apm_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/journeys", + "id": "def-common.JOURNEY_APM_CONFIG.globalLabels", + "type": "Object", + "tags": [], + "label": "globalLabels", + "description": [], + "path": "packages/kbn-journeys/journey/journey_apm_config.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/journeys", + "id": "def-common.JOURNEY_APM_CONFIG.globalLabels.performancePhase", + "type": "string", + "tags": [], + "label": "performancePhase", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-journeys/journey/journey_apm_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/journeys", + "id": "def-common.JOURNEY_APM_CONFIG.globalLabels.branch", + "type": "string", + "tags": [], + "label": "branch", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-journeys/journey/journey_apm_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/journeys", + "id": "def-common.JOURNEY_APM_CONFIG.globalLabels.gitRev", + "type": "string", + "tags": [], + "label": "gitRev", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-journeys/journey/journey_apm_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/journeys", + "id": "def-common.JOURNEY_APM_CONFIG.globalLabels.ciBuildName", + "type": "string", + "tags": [], + "label": "ciBuildName", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-journeys/journey/journey_apm_config.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "initialIsOpen": false + } + ] } } \ No newline at end of file diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index 4743bf78090c5..aa82431ea0e3f 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; @@ -21,10 +21,13 @@ Contact [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kiban | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 70 | 0 | 65 | 5 | +| 85 | 0 | 77 | 5 | ## Common +### Objects + + ### Classes diff --git a/api_docs/kbn_json_ast.mdx b/api_docs/kbn_json_ast.mdx index 1465c519c26d7..cb2330485201b 100644 --- a/api_docs/kbn_json_ast.mdx +++ b/api_docs/kbn_json_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-ast title: "@kbn/json-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-ast plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-ast'] --- import kbnJsonAstObj from './kbn_json_ast.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 8a4d83213f446..7954d741bfcb3 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_language_documentation_popover.mdx b/api_docs/kbn_language_documentation_popover.mdx index 844fb261a31e7..7ceb5757539a4 100644 --- a/api_docs/kbn_language_documentation_popover.mdx +++ b/api_docs/kbn_language_documentation_popover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation-popover title: "@kbn/language-documentation-popover" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation-popover plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/language-documentation-popover'] --- import kbnLanguageDocumentationPopoverObj from './kbn_language_documentation_popover.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index d0e4eb4f68007..81a30f0763d92 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index cd50f940078d3..8cd4373bdcf17 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index dd624c01ccd27..f660720b2133c 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index feb285c25a65d..05c475dfc93c2 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 0aec9863bfd9c..d82c025dbab1e 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index 61d5a9dffeedf..f92ec1a980b78 100644 --- a/api_docs/kbn_ml_date_picker.mdx +++ b/api_docs/kbn_ml_date_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-picker title: "@kbn/ml-date-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-picker plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-picker'] --- import kbnMlDatePickerObj from './kbn_ml_date_picker.devdocs.json'; diff --git a/api_docs/kbn_ml_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index bc004392a5d41..9d000095378bd 100644 --- a/api_docs/kbn_ml_is_defined.mdx +++ b/api_docs/kbn_ml_is_defined.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-defined title: "@kbn/ml-is-defined" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-defined plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-defined'] --- import kbnMlIsDefinedObj from './kbn_ml_is_defined.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 6f6ea69767650..40ce5731de31e 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_local_storage.mdx b/api_docs/kbn_ml_local_storage.mdx index 90bbe3247bcea..f14deff209d77 100644 --- a/api_docs/kbn_ml_local_storage.mdx +++ b/api_docs/kbn_ml_local_storage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-local-storage title: "@kbn/ml-local-storage" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-local-storage plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-local-storage'] --- import kbnMlLocalStorageObj from './kbn_ml_local_storage.devdocs.json'; diff --git a/api_docs/kbn_ml_nested_property.mdx b/api_docs/kbn_ml_nested_property.mdx index 8ff972997e11f..ef15635229b53 100644 --- a/api_docs/kbn_ml_nested_property.mdx +++ b/api_docs/kbn_ml_nested_property.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-nested-property title: "@kbn/ml-nested-property" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-nested-property plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-nested-property'] --- import kbnMlNestedPropertyObj from './kbn_ml_nested_property.devdocs.json'; diff --git a/api_docs/kbn_ml_number_utils.mdx b/api_docs/kbn_ml_number_utils.mdx index cf4903244b7a5..05c5432c4f57f 100644 --- a/api_docs/kbn_ml_number_utils.mdx +++ b/api_docs/kbn_ml_number_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-number-utils title: "@kbn/ml-number-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-number-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-number-utils'] --- import kbnMlNumberUtilsObj from './kbn_ml_number_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_query_utils.mdx b/api_docs/kbn_ml_query_utils.mdx index ef93af3bf8f2b..8327a7ed0b4d5 100644 --- a/api_docs/kbn_ml_query_utils.mdx +++ b/api_docs/kbn_ml_query_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-query-utils title: "@kbn/ml-query-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-query-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-query-utils'] --- import kbnMlQueryUtilsObj from './kbn_ml_query_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_random_sampler_utils.mdx b/api_docs/kbn_ml_random_sampler_utils.mdx index 87e7104b919c0..4998fe175ef71 100644 --- a/api_docs/kbn_ml_random_sampler_utils.mdx +++ b/api_docs/kbn_ml_random_sampler_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-random-sampler-utils title: "@kbn/ml-random-sampler-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-random-sampler-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-random-sampler-utils'] --- import kbnMlRandomSamplerUtilsObj from './kbn_ml_random_sampler_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_route_utils.mdx b/api_docs/kbn_ml_route_utils.mdx index 0746d44eb914b..8e1d50e0f9364 100644 --- a/api_docs/kbn_ml_route_utils.mdx +++ b/api_docs/kbn_ml_route_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-route-utils title: "@kbn/ml-route-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-route-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-route-utils'] --- import kbnMlRouteUtilsObj from './kbn_ml_route_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 486008f7f2eef..f18f1327cb256 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_ml_trained_models_utils.mdx b/api_docs/kbn_ml_trained_models_utils.mdx index 12066d5a19cbe..43aeec4bf32ca 100644 --- a/api_docs/kbn_ml_trained_models_utils.mdx +++ b/api_docs/kbn_ml_trained_models_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-trained-models-utils title: "@kbn/ml-trained-models-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-trained-models-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index 0c35cbec039fe..390009d4685e5 100644 --- a/api_docs/kbn_ml_url_state.mdx +++ b/api_docs/kbn_ml_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-url-state title: "@kbn/ml-url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-url-state plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 021a996c1af06..605d07152c5b0 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_object_versioning.mdx b/api_docs/kbn_object_versioning.mdx index b216580e3fe0a..8d3135bdd7f76 100644 --- a/api_docs/kbn_object_versioning.mdx +++ b/api_docs/kbn_object_versioning.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-object-versioning title: "@kbn/object-versioning" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/object-versioning plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/object-versioning'] --- import kbnObjectVersioningObj from './kbn_object_versioning.devdocs.json'; diff --git a/api_docs/kbn_observability_alert_details.mdx b/api_docs/kbn_observability_alert_details.mdx index cfd788aa1c5e1..48886f59e6160 100644 --- a/api_docs/kbn_observability_alert_details.mdx +++ b/api_docs/kbn_observability_alert_details.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alert-details title: "@kbn/observability-alert-details" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alert-details plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alert-details'] --- import kbnObservabilityAlertDetailsObj from './kbn_observability_alert_details.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index e1e6b75d349eb..58204fde5d980 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index cc2783a01db33..ac5cab95f43ec 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index 095f3df1b23cd..d351b9e974a46 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 5680dde71dbf6..820b381b56e3b 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index ce8b983886c6b..4f63a60e98d21 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index 80e9be96710ea..481b254612404 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index fa435bb113264..5d88f2bb7cd87 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index d9b61d068bb73..fb0c69e268327 100644 --- a/api_docs/kbn_repo_file_maps.mdx +++ b/api_docs/kbn_repo_file_maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-file-maps title: "@kbn/repo-file-maps" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-file-maps plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-file-maps'] --- import kbnRepoFileMapsObj from './kbn_repo_file_maps.devdocs.json'; diff --git a/api_docs/kbn_repo_linter.mdx b/api_docs/kbn_repo_linter.mdx index fe9e0b7464406..fa756525922ae 100644 --- a/api_docs/kbn_repo_linter.mdx +++ b/api_docs/kbn_repo_linter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-linter title: "@kbn/repo-linter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-linter plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-linter'] --- import kbnRepoLinterObj from './kbn_repo_linter.devdocs.json'; diff --git a/api_docs/kbn_repo_path.mdx b/api_docs/kbn_repo_path.mdx index cbaada4cbfd67..dc04d8563bc52 100644 --- a/api_docs/kbn_repo_path.mdx +++ b/api_docs/kbn_repo_path.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-path title: "@kbn/repo-path" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-path plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-path'] --- import kbnRepoPathObj from './kbn_repo_path.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 7180c39d48d22..af0847941070f 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_reporting_common.mdx b/api_docs/kbn_reporting_common.mdx index 18f4023702c94..52ff2a0ac41b5 100644 --- a/api_docs/kbn_reporting_common.mdx +++ b/api_docs/kbn_reporting_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-common title: "@kbn/reporting-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-common plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index 0e3cc9b286422..5f654ffa57ef8 100644 --- a/api_docs/kbn_rison.mdx +++ b/api_docs/kbn_rison.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rison title: "@kbn/rison" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rison plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.devdocs.json b/api_docs/kbn_rule_data_utils.devdocs.json index efe81b0b3e925..7163f86fb0fd1 100644 --- a/api_docs/kbn_rule_data_utils.devdocs.json +++ b/api_docs/kbn_rule_data_utils.devdocs.json @@ -283,6 +283,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.ALERT_EVALUATION_VALUES", + "type": "string", + "tags": [], + "label": "ALERT_EVALUATION_VALUES", + "description": [], + "signature": [ + "\"kibana.alert.evaluation.values\"" + ], + "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/rule-data-utils", "id": "def-common.ALERT_FLAPPING", @@ -1228,6 +1243,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.ALERT_URL", + "type": "string", + "tags": [], + "label": "ALERT_URL", + "description": [], + "signature": [ + "\"kibana.alert.url\"" + ], + "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/rule-data-utils", "id": "def-common.ALERT_UUID", @@ -1341,7 +1371,7 @@ "label": "DefaultAlertFieldName", "description": [], "signature": [ - "\"@timestamp\" | \"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert\" | \"kibana.alert.action_group\" | \"kibana.alert.case_ids\" | \"kibana.alert.duration.us\" | \"kibana.alert.end\" | \"kibana.alert.flapping\" | \"kibana.alert.flapping_history\" | \"kibana.alert.maintenance_window_ids\" | \"kibana.alert.instance.id\" | \"kibana.alert.last_detected\" | \"kibana.alert.reason\" | \"kibana.alert.rule\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.alert.rule.revision\" | \"kibana.alert.rule.tags\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.start\" | \"kibana.alert.status\" | \"kibana.alert.time_range\" | \"kibana.alert.uuid\" | \"kibana.alert.workflow_status\" | \"kibana.space_ids\" | \"kibana.version\"" + "\"@timestamp\" | \"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert\" | \"kibana.alert.action_group\" | \"kibana.alert.case_ids\" | \"kibana.alert.duration.us\" | \"kibana.alert.end\" | \"kibana.alert.flapping\" | \"kibana.alert.flapping_history\" | \"kibana.alert.maintenance_window_ids\" | \"kibana.alert.instance.id\" | \"kibana.alert.last_detected\" | \"kibana.alert.reason\" | \"kibana.alert.rule\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.alert.rule.revision\" | \"kibana.alert.rule.tags\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.start\" | \"kibana.alert.status\" | \"kibana.alert.time_range\" | \"kibana.alert.url\" | \"kibana.alert.uuid\" | \"kibana.alert.workflow_status\" | \"kibana.space_ids\" | \"kibana.version\"" ], "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", "deprecated": false, @@ -1506,7 +1536,7 @@ "label": "TechnicalRuleDataFieldName", "description": [], "signature": [ - "\"@timestamp\" | \"event.action\" | \"tags\" | \"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert\" | \"kibana.alert.action_group\" | \"kibana.alert.case_ids\" | \"kibana.alert.duration.us\" | \"kibana.alert.end\" | \"kibana.alert.flapping\" | \"kibana.alert.maintenance_window_ids\" | \"kibana.alert.instance.id\" | \"kibana.alert.reason\" | \"kibana.alert.rule\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.alert.rule.tags\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.start\" | \"kibana.alert.status\" | \"kibana.alert.time_range\" | \"kibana.alert.uuid\" | \"kibana.alert.workflow_status\" | \"kibana.space_ids\" | \"kibana.version\" | \"kibana.alert.risk_score\" | \"kibana.alert.rule.author\" | \"kibana.alert.rule.created_at\" | \"kibana.alert.rule.created_by\" | \"kibana.alert.rule.description\" | \"kibana.alert.rule.enabled\" | \"kibana.alert.rule.from\" | \"kibana.alert.rule.interval\" | \"kibana.alert.rule.license\" | \"kibana.alert.rule.note\" | \"kibana.alert.rule.references\" | \"kibana.alert.rule.rule_id\" | \"kibana.alert.rule.rule_name_override\" | \"kibana.alert.rule.to\" | \"kibana.alert.rule.type\" | \"kibana.alert.rule.updated_at\" | \"kibana.alert.rule.updated_by\" | \"kibana.alert.rule.version\" | \"kibana.alert.severity\" | \"kibana.alert.suppression.docs_count\" | \"kibana.alert.suppression.end\" | \"kibana.alert.suppression.terms\" | \"kibana.alert.suppression.terms.field\" | \"kibana.alert.suppression.start\" | \"kibana.alert.suppression.terms.value\" | \"kibana.alert.system_status\" | \"kibana.alert.workflow_reason\" | \"kibana.alert.workflow_user\" | \"ecs.version\" | \"event.kind\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"event.module\" | \"kibana.alert.building_block_type\" | \"kibana.alert.rule.exceptions_list\" | \"kibana.alert.rule.namespace\" | \"kibana.alert.rule.threat.framework\" | \"kibana.alert.rule.threat.tactic.id\" | \"kibana.alert.rule.threat.tactic.name\" | \"kibana.alert.rule.threat.tactic.reference\" | \"kibana.alert.rule.threat.technique.id\" | \"kibana.alert.rule.threat.technique.name\" | \"kibana.alert.rule.threat.technique.reference\" | \"kibana.alert.rule.threat.technique.subtechnique.id\" | \"kibana.alert.rule.threat.technique.subtechnique.name\" | \"kibana.alert.rule.threat.technique.subtechnique.reference\"" + "\"@timestamp\" | \"event.action\" | \"tags\" | \"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert\" | \"kibana.alert.action_group\" | \"kibana.alert.case_ids\" | \"kibana.alert.duration.us\" | \"kibana.alert.end\" | \"kibana.alert.flapping\" | \"kibana.alert.maintenance_window_ids\" | \"kibana.alert.instance.id\" | \"kibana.alert.reason\" | \"kibana.alert.rule\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.alert.rule.tags\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.start\" | \"kibana.alert.status\" | \"kibana.alert.time_range\" | \"kibana.alert.uuid\" | \"kibana.alert.workflow_status\" | \"kibana.space_ids\" | \"kibana.version\" | \"kibana.alert.risk_score\" | \"kibana.alert.rule.author\" | \"kibana.alert.rule.created_at\" | \"kibana.alert.rule.created_by\" | \"kibana.alert.rule.description\" | \"kibana.alert.rule.enabled\" | \"kibana.alert.rule.from\" | \"kibana.alert.rule.interval\" | \"kibana.alert.rule.license\" | \"kibana.alert.rule.note\" | \"kibana.alert.rule.references\" | \"kibana.alert.rule.rule_id\" | \"kibana.alert.rule.rule_name_override\" | \"kibana.alert.rule.to\" | \"kibana.alert.rule.type\" | \"kibana.alert.rule.updated_at\" | \"kibana.alert.rule.updated_by\" | \"kibana.alert.rule.version\" | \"kibana.alert.severity\" | \"kibana.alert.suppression.docs_count\" | \"kibana.alert.suppression.end\" | \"kibana.alert.suppression.terms\" | \"kibana.alert.suppression.terms.field\" | \"kibana.alert.suppression.start\" | \"kibana.alert.suppression.terms.value\" | \"kibana.alert.system_status\" | \"kibana.alert.workflow_reason\" | \"kibana.alert.workflow_user\" | \"ecs.version\" | \"event.kind\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.evaluation.values\" | \"event.module\" | \"kibana.alert.building_block_type\" | \"kibana.alert.rule.exceptions_list\" | \"kibana.alert.rule.namespace\" | \"kibana.alert.rule.threat.framework\" | \"kibana.alert.rule.threat.tactic.id\" | \"kibana.alert.rule.threat.tactic.name\" | \"kibana.alert.rule.threat.tactic.reference\" | \"kibana.alert.rule.threat.technique.id\" | \"kibana.alert.rule.threat.technique.name\" | \"kibana.alert.rule.threat.technique.reference\" | \"kibana.alert.rule.threat.technique.subtechnique.id\" | \"kibana.alert.rule.threat.technique.subtechnique.name\" | \"kibana.alert.rule.threat.technique.subtechnique.reference\"" ], "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", "deprecated": false, diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 3345d55d51c5a..16eb8b268e12a 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-detections-response](https://github.com/orgs/elastic/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 103 | 0 | 100 | 0 | +| 105 | 0 | 102 | 0 | ## Common diff --git a/api_docs/kbn_saved_objects_settings.mdx b/api_docs/kbn_saved_objects_settings.mdx index 5c3e19f8e60f7..2d5a7a078d24c 100644 --- a/api_docs/kbn_saved_objects_settings.mdx +++ b/api_docs/kbn_saved_objects_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-saved-objects-settings title: "@kbn/saved-objects-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/saved-objects-settings plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/saved-objects-settings'] --- import kbnSavedObjectsSettingsObj from './kbn_saved_objects_settings.devdocs.json'; diff --git a/api_docs/kbn_security_solution_side_nav.mdx b/api_docs/kbn_security_solution_side_nav.mdx index edded7c0db0d7..476ecb69cdbcc 100644 --- a/api_docs/kbn_security_solution_side_nav.mdx +++ b/api_docs/kbn_security_solution_side_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-side-nav title: "@kbn/security-solution-side-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-side-nav plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-side-nav'] --- import kbnSecuritySolutionSideNavObj from './kbn_security_solution_side_nav.devdocs.json'; diff --git a/api_docs/kbn_security_solution_storybook_config.mdx b/api_docs/kbn_security_solution_storybook_config.mdx index 25f6bc6bb8464..5bec888bb4e5a 100644 --- a/api_docs/kbn_security_solution_storybook_config.mdx +++ b/api_docs/kbn_security_solution_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-storybook-config title: "@kbn/security-solution-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-storybook-config plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-storybook-config'] --- import kbnSecuritySolutionStorybookConfigObj from './kbn_security_solution_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 5e67b3e73ac18..7d0c70dbb7b39 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_data_table.devdocs.json b/api_docs/kbn_securitysolution_data_table.devdocs.json index 2aa15fe5580f4..39a3a4cfa98fb 100644 --- a/api_docs/kbn_securitysolution_data_table.devdocs.json +++ b/api_docs/kbn_securitysolution_data_table.devdocs.json @@ -1229,7 +1229,7 @@ "section": "def-common.SortColumnTable", "text": "SortColumnTable" }, - "[]; readonly title: string; readonly filters?: ", + "[]; readonly title: string; readonly isLoading: boolean; readonly filters?: ", { "pluginId": "@kbn/es-query", "scope": "common", @@ -1251,7 +1251,7 @@ "section": "def-common.IFieldSubType", "text": "IFieldSubType" }, - " | undefined; type?: string | undefined; })[]; readonly isLoading: boolean; readonly viewMode: ", + " | undefined; type?: string | undefined; })[]; readonly viewMode: ", { "pluginId": "@kbn/securitysolution-data-table", "scope": "common", diff --git a/api_docs/kbn_securitysolution_data_table.mdx b/api_docs/kbn_securitysolution_data_table.mdx index b898eba5046ed..1c5b3548f92c4 100644 --- a/api_docs/kbn_securitysolution_data_table.mdx +++ b/api_docs/kbn_securitysolution_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-data-table title: "@kbn/securitysolution-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-data-table plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-data-table'] --- import kbnSecuritysolutionDataTableObj from './kbn_securitysolution_data_table.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_ecs.mdx b/api_docs/kbn_securitysolution_ecs.mdx index 7174d233543b9..085a9a78a69cf 100644 --- a/api_docs/kbn_securitysolution_ecs.mdx +++ b/api_docs/kbn_securitysolution_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-ecs title: "@kbn/securitysolution-ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-ecs plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-ecs'] --- import kbnSecuritysolutionEcsObj from './kbn_securitysolution_ecs.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.devdocs.json b/api_docs/kbn_securitysolution_es_utils.devdocs.json index 19baa142ff863..360332ea75d2d 100644 --- a/api_docs/kbn_securitysolution_es_utils.devdocs.json +++ b/api_docs/kbn_securitysolution_es_utils.devdocs.json @@ -622,7 +622,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -1540,8 +1542,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", @@ -1866,7 +1866,9 @@ "TransportRequestOptions", " | undefined): Promise<", "SearchResponse", - ">; }; name: string | symbol; monitoring: ", + ">; }; name: string | symbol; security: ", + "default", + "; monitoring: ", "default", "; count: { (this: That, params?: ", "CountRequest", @@ -2784,8 +2786,6 @@ "SearchTemplateResponse", ">; }; searchableSnapshots: ", "default", - "; security: ", - "default", "; shutdown: ", "default", "; slm: ", diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 35409a3131925..1df63d4877934 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.devdocs.json b/api_docs/kbn_securitysolution_exception_list_components.devdocs.json index 1ba351068e142..2eb73d6829b89 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.devdocs.json +++ b/api_docs/kbn_securitysolution_exception_list_components.devdocs.json @@ -834,7 +834,7 @@ "label": "formattedDateComponent", "description": [], "signature": [ - "\"symbol\" | \"object\" | \"source\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | React.ComponentType | \"meta\" | \"pattern\" | \"title\" | \"template\" | \"main\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"article\" | \"aside\" | \"audio\" | \"b\" | \"base\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"body\" | \"br\" | \"button\" | \"canvas\" | \"caption\" | \"cite\" | \"code\" | \"col\" | \"colgroup\" | \"data\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"form\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"header\" | \"hgroup\" | \"hr\" | \"html\" | \"i\" | \"iframe\" | \"img\" | \"input\" | \"ins\" | \"kbd\" | \"keygen\" | \"label\" | \"legend\" | \"li\" | \"mark\" | \"menu\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"output\" | \"p\" | \"param\" | \"picture\" | \"pre\" | \"progress\" | \"q\" | \"rp\" | \"rt\" | \"ruby\" | \"s\" | \"samp\" | \"slot\" | \"script\" | \"section\" | \"select\" | \"span\" | \"strong\" | \"style\" | \"summary\" | \"table\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"time\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"svg\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"image\" | \"line\" | \"linearGradient\" | \"marker\" | \"mask\" | \"metadata\" | \"mpath\" | \"path\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"rect\" | \"stop\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" + "\"symbol\" | \"object\" | \"source\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | React.ComponentType | \"slot\" | \"style\" | \"title\" | \"meta\" | \"pattern\" | \"template\" | \"main\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"article\" | \"aside\" | \"audio\" | \"b\" | \"base\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"body\" | \"br\" | \"button\" | \"canvas\" | \"caption\" | \"cite\" | \"code\" | \"col\" | \"colgroup\" | \"data\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"form\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"header\" | \"hgroup\" | \"hr\" | \"html\" | \"i\" | \"iframe\" | \"img\" | \"input\" | \"ins\" | \"kbd\" | \"keygen\" | \"label\" | \"legend\" | \"li\" | \"mark\" | \"menu\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"output\" | \"p\" | \"param\" | \"picture\" | \"pre\" | \"progress\" | \"q\" | \"rp\" | \"rt\" | \"ruby\" | \"s\" | \"samp\" | \"script\" | \"section\" | \"select\" | \"span\" | \"strong\" | \"summary\" | \"table\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"time\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"svg\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"image\" | \"line\" | \"linearGradient\" | \"marker\" | \"mask\" | \"metadata\" | \"mpath\" | \"path\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"rect\" | \"stop\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" ], "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/index.tsx", "deprecated": false, @@ -848,7 +848,7 @@ "label": "securityLinkAnchorComponent", "description": [], "signature": [ - "\"symbol\" | \"object\" | \"source\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | React.ComponentType | \"meta\" | \"pattern\" | \"title\" | \"template\" | \"main\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"article\" | \"aside\" | \"audio\" | \"b\" | \"base\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"body\" | \"br\" | \"button\" | \"canvas\" | \"caption\" | \"cite\" | \"code\" | \"col\" | \"colgroup\" | \"data\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"form\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"header\" | \"hgroup\" | \"hr\" | \"html\" | \"i\" | \"iframe\" | \"img\" | \"input\" | \"ins\" | \"kbd\" | \"keygen\" | \"label\" | \"legend\" | \"li\" | \"mark\" | \"menu\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"output\" | \"p\" | \"param\" | \"picture\" | \"pre\" | \"progress\" | \"q\" | \"rp\" | \"rt\" | \"ruby\" | \"s\" | \"samp\" | \"slot\" | \"script\" | \"section\" | \"select\" | \"span\" | \"strong\" | \"style\" | \"summary\" | \"table\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"time\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"svg\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"image\" | \"line\" | \"linearGradient\" | \"marker\" | \"mask\" | \"metadata\" | \"mpath\" | \"path\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"rect\" | \"stop\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" + "\"symbol\" | \"object\" | \"source\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | React.ComponentType | \"slot\" | \"style\" | \"title\" | \"meta\" | \"pattern\" | \"template\" | \"main\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"article\" | \"aside\" | \"audio\" | \"b\" | \"base\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"body\" | \"br\" | \"button\" | \"canvas\" | \"caption\" | \"cite\" | \"code\" | \"col\" | \"colgroup\" | \"data\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"form\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"header\" | \"hgroup\" | \"hr\" | \"html\" | \"i\" | \"iframe\" | \"img\" | \"input\" | \"ins\" | \"kbd\" | \"keygen\" | \"label\" | \"legend\" | \"li\" | \"mark\" | \"menu\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"output\" | \"p\" | \"param\" | \"picture\" | \"pre\" | \"progress\" | \"q\" | \"rp\" | \"rt\" | \"ruby\" | \"s\" | \"samp\" | \"script\" | \"section\" | \"select\" | \"span\" | \"strong\" | \"summary\" | \"table\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"time\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"svg\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"image\" | \"line\" | \"linearGradient\" | \"marker\" | \"mask\" | \"metadata\" | \"mpath\" | \"path\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"rect\" | \"stop\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" ], "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/index.tsx", "deprecated": false, @@ -987,7 +987,7 @@ "label": "securityLinkAnchorComponent", "description": [], "signature": [ - "\"symbol\" | \"object\" | \"source\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | React.ComponentType | \"meta\" | \"pattern\" | \"title\" | \"template\" | \"main\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"article\" | \"aside\" | \"audio\" | \"b\" | \"base\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"body\" | \"br\" | \"button\" | \"canvas\" | \"caption\" | \"cite\" | \"code\" | \"col\" | \"colgroup\" | \"data\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"form\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"header\" | \"hgroup\" | \"hr\" | \"html\" | \"i\" | \"iframe\" | \"img\" | \"input\" | \"ins\" | \"kbd\" | \"keygen\" | \"label\" | \"legend\" | \"li\" | \"mark\" | \"menu\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"output\" | \"p\" | \"param\" | \"picture\" | \"pre\" | \"progress\" | \"q\" | \"rp\" | \"rt\" | \"ruby\" | \"s\" | \"samp\" | \"slot\" | \"script\" | \"section\" | \"select\" | \"span\" | \"strong\" | \"style\" | \"summary\" | \"table\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"time\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"svg\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"image\" | \"line\" | \"linearGradient\" | \"marker\" | \"mask\" | \"metadata\" | \"mpath\" | \"path\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"rect\" | \"stop\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" + "\"symbol\" | \"object\" | \"source\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | React.ComponentType | \"slot\" | \"style\" | \"title\" | \"meta\" | \"pattern\" | \"template\" | \"main\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"article\" | \"aside\" | \"audio\" | \"b\" | \"base\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"body\" | \"br\" | \"button\" | \"canvas\" | \"caption\" | \"cite\" | \"code\" | \"col\" | \"colgroup\" | \"data\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"form\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"header\" | \"hgroup\" | \"hr\" | \"html\" | \"i\" | \"iframe\" | \"img\" | \"input\" | \"ins\" | \"kbd\" | \"keygen\" | \"label\" | \"legend\" | \"li\" | \"mark\" | \"menu\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"output\" | \"p\" | \"param\" | \"picture\" | \"pre\" | \"progress\" | \"q\" | \"rp\" | \"rt\" | \"ruby\" | \"s\" | \"samp\" | \"script\" | \"section\" | \"select\" | \"span\" | \"strong\" | \"summary\" | \"table\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"time\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"svg\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"image\" | \"line\" | \"linearGradient\" | \"marker\" | \"mask\" | \"metadata\" | \"mpath\" | \"path\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"rect\" | \"stop\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" ], "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", "deprecated": false, @@ -1001,7 +1001,7 @@ "label": "formattedDateComponent", "description": [], "signature": [ - "\"symbol\" | \"object\" | \"source\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | React.ComponentType | \"meta\" | \"pattern\" | \"title\" | \"template\" | \"main\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"article\" | \"aside\" | \"audio\" | \"b\" | \"base\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"body\" | \"br\" | \"button\" | \"canvas\" | \"caption\" | \"cite\" | \"code\" | \"col\" | \"colgroup\" | \"data\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"form\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"header\" | \"hgroup\" | \"hr\" | \"html\" | \"i\" | \"iframe\" | \"img\" | \"input\" | \"ins\" | \"kbd\" | \"keygen\" | \"label\" | \"legend\" | \"li\" | \"mark\" | \"menu\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"output\" | \"p\" | \"param\" | \"picture\" | \"pre\" | \"progress\" | \"q\" | \"rp\" | \"rt\" | \"ruby\" | \"s\" | \"samp\" | \"slot\" | \"script\" | \"section\" | \"select\" | \"span\" | \"strong\" | \"style\" | \"summary\" | \"table\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"time\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"svg\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"image\" | \"line\" | \"linearGradient\" | \"marker\" | \"mask\" | \"metadata\" | \"mpath\" | \"path\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"rect\" | \"stop\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" + "\"symbol\" | \"object\" | \"source\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | React.ComponentType | \"slot\" | \"style\" | \"title\" | \"meta\" | \"pattern\" | \"template\" | \"main\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"article\" | \"aside\" | \"audio\" | \"b\" | \"base\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"body\" | \"br\" | \"button\" | \"canvas\" | \"caption\" | \"cite\" | \"code\" | \"col\" | \"colgroup\" | \"data\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"form\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"header\" | \"hgroup\" | \"hr\" | \"html\" | \"i\" | \"iframe\" | \"img\" | \"input\" | \"ins\" | \"kbd\" | \"keygen\" | \"label\" | \"legend\" | \"li\" | \"mark\" | \"menu\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"output\" | \"p\" | \"param\" | \"picture\" | \"pre\" | \"progress\" | \"q\" | \"rp\" | \"rt\" | \"ruby\" | \"s\" | \"samp\" | \"script\" | \"section\" | \"select\" | \"span\" | \"strong\" | \"summary\" | \"table\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"time\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"svg\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"image\" | \"line\" | \"linearGradient\" | \"marker\" | \"mask\" | \"metadata\" | \"mpath\" | \"path\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"rect\" | \"stop\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" ], "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", "deprecated": false, diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index edbcef383a9e4..35453fb0b1a31 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_grouping.mdx b/api_docs/kbn_securitysolution_grouping.mdx index dd6a09d571f3c..7276dcef35bd3 100644 --- a/api_docs/kbn_securitysolution_grouping.mdx +++ b/api_docs/kbn_securitysolution_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-grouping title: "@kbn/securitysolution-grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-grouping plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-grouping'] --- import kbnSecuritysolutionGroupingObj from './kbn_securitysolution_grouping.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 1d835702cbe21..a7cb93e0ca41c 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 4a4889a2246b2..1a5fe7a8eff63 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 8a32466a3abbc..fae1f7cdd2313 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index ae143cab8a343..360fd19200755 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index 6fce7b36a3dfa..2b6f02cc7215b 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index 40bf897b06c2c..0255ba2088e5e 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 3b1ecaf1f6e66..02b65ca6fde37 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index fbed8ffd2680c..786b822ce39de 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index a7cfff43d2cd5..7f1e021d2997e 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index e6ac27bba005c..7b8ea7f8fa153 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index 75c35043958c2..bbaae18e81926 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index d813903659c73..b22479ca8cb62 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index 5789cd2a5ce90..e959b8f8857f1 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index 70c5530f94e71..4dce3d0bcc354 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 2f8fe6b3aa803..b3241f80861cf 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_solution.mdx b/api_docs/kbn_shared_ux_avatar_solution.mdx index beaa98374a383..7fdc62d57e0e5 100644 --- a/api_docs/kbn_shared_ux_avatar_solution.mdx +++ b/api_docs/kbn_shared_ux_avatar_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-solution title: "@kbn/shared-ux-avatar-solution" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-solution plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-solution'] --- import kbnSharedUxAvatarSolutionObj from './kbn_shared_ux_avatar_solution.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.devdocs.json b/api_docs/kbn_shared_ux_avatar_user_profile_components.devdocs.json index 3bcad06a1a554..8db820277866f 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.devdocs.json +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.devdocs.json @@ -490,7 +490,7 @@ }, " extends Pick<", "EuiSelectableProps", - "<{}>, \"height\" | \"errorMessage\" | \"singleSelection\" | \"loadingMessage\" | \"noMatchesMessage\" | \"emptyMessage\">" + "<{}>, \"singleSelection\" | \"height\" | \"errorMessage\" | \"loadingMessage\" | \"noMatchesMessage\" | \"emptyMessage\">" ], "path": "packages/shared-ux/avatar/user_profile/impl/user_profiles_selectable.tsx", "deprecated": false, diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index df39cf2bfac2d..74b168d876714 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx index ef64bc2379a7e..c67019c024447 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen title: "@kbn/shared-ux-button-exit-full-screen" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen'] --- import kbnSharedUxButtonExitFullScreenObj from './kbn_shared_ux_button_exit_full_screen.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index 7c23015601b7a..523f937ac0308 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.devdocs.json b/api_docs/kbn_shared_ux_button_toolbar.devdocs.json index 57ab85b6c5e8e..40debaa698553 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.devdocs.json +++ b/api_docs/kbn_shared_ux_button_toolbar.devdocs.json @@ -434,7 +434,7 @@ }, " extends Pick<", "EuiButtonPropsForButton", - ", \"data-test-subj\" | \"onClick\" | \"iconType\" | \"iconSide\">" + ", \"onClick\" | \"data-test-subj\" | \"iconType\" | \"iconSide\">" ], "path": "packages/shared-ux/button_toolbar/src/buttons/toolbar_button/toolbar_button.tsx", "deprecated": false, @@ -509,7 +509,7 @@ "label": "Props", "description": [], "signature": [ - "{ 'data-test-subj'?: string | undefined; onClick?: React.MouseEventHandler | undefined; iconSide?: ", + "{ onClick?: React.MouseEventHandler | undefined; 'data-test-subj'?: string | undefined; iconSide?: ", "ButtonContentIconSide", "; }" ], diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 108257adf5838..7e026fc462e17 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.devdocs.json b/api_docs/kbn_shared_ux_card_no_data.devdocs.json index 00a6c59e3d871..dbafd6a4c803c 100644 --- a/api_docs/kbn_shared_ux_card_no_data.devdocs.json +++ b/api_docs/kbn_shared_ux_card_no_data.devdocs.json @@ -148,7 +148,7 @@ "signature": [ "Partial> & { button?: React.ReactNode; onClick?: React.MouseEventHandler | undefined; description?: React.ReactNode; category?: string | undefined; canAccessFleet?: boolean | undefined; }" + ", \"description\" | \"onClick\" | \"isDisabled\" | \"button\" | \"layout\">> & { button?: React.ReactNode; onClick?: React.MouseEventHandler | undefined; description?: React.ReactNode; category?: string | undefined; canAccessFleet?: boolean | undefined; }" ], "path": "packages/shared-ux/card/no_data/types/index.d.ts", "deprecated": false, @@ -184,13 +184,13 @@ "\nProps for the `NoDataCard` sevice-connected component." ], "signature": [ - "{ prefix?: string | undefined; id?: string | undefined; defaultValue?: string | number | readonly string[] | undefined; children?: React.ReactNode; description?: React.ReactNode; category?: string | undefined; is?: string | undefined; title?: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | undefined; security?: string | undefined; button?: React.ReactNode; footer?: React.ReactNode; slot?: string | undefined; style?: React.CSSProperties | undefined; image?: string | React.ReactElement> | undefined; className?: string | undefined; 'aria-label'?: string | undefined; 'data-test-subj'?: string | undefined; css?: ", + "{ prefix?: string | undefined; id?: string | undefined; defaultValue?: string | number | readonly string[] | undefined; children?: React.ReactNode; description?: React.ReactNode; category?: string | undefined; onChange?: React.FormEventHandler | undefined; defaultChecked?: boolean | undefined; suppressContentEditableWarning?: boolean | undefined; suppressHydrationWarning?: boolean | undefined; accessKey?: string | undefined; className?: string | undefined; contentEditable?: Booleanish | \"inherit\" | undefined; contextMenu?: string | undefined; dir?: string | undefined; draggable?: Booleanish | undefined; hidden?: boolean | undefined; lang?: string | undefined; placeholder?: string | undefined; slot?: string | undefined; spellCheck?: Booleanish | undefined; style?: React.CSSProperties | undefined; tabIndex?: number | undefined; title?: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | undefined; translate?: \"yes\" | \"no\" | undefined; radioGroup?: string | undefined; role?: React.AriaRole | undefined; about?: string | undefined; datatype?: string | undefined; inlist?: any; property?: string | undefined; resource?: string | undefined; typeof?: string | undefined; vocab?: string | undefined; autoCapitalize?: string | undefined; autoCorrect?: string | undefined; autoSave?: string | undefined; itemProp?: string | undefined; itemScope?: boolean | undefined; itemType?: string | undefined; itemID?: string | undefined; itemRef?: string | undefined; results?: number | undefined; security?: string | undefined; unselectable?: \"on\" | \"off\" | undefined; inputMode?: \"search\" | \"none\" | \"text\" | \"tel\" | \"url\" | \"email\" | \"numeric\" | \"decimal\" | undefined; is?: string | undefined; 'aria-activedescendant'?: string | undefined; 'aria-atomic'?: Booleanish | undefined; 'aria-autocomplete'?: \"none\" | \"list\" | \"inline\" | \"both\" | undefined; 'aria-busy'?: Booleanish | undefined; 'aria-checked'?: boolean | \"true\" | \"false\" | \"mixed\" | undefined; 'aria-colcount'?: number | undefined; 'aria-colindex'?: number | undefined; 'aria-colspan'?: number | undefined; 'aria-controls'?: string | undefined; 'aria-current'?: boolean | \"page\" | \"date\" | \"time\" | \"true\" | \"false\" | \"step\" | \"location\" | undefined; 'aria-describedby'?: string | undefined; 'aria-details'?: string | undefined; 'aria-disabled'?: Booleanish | undefined; 'aria-dropeffect'?: \"execute\" | \"link\" | \"none\" | \"copy\" | \"move\" | \"popup\" | undefined; 'aria-errormessage'?: string | undefined; 'aria-expanded'?: Booleanish | undefined; 'aria-flowto'?: string | undefined; 'aria-grabbed'?: Booleanish | undefined; 'aria-haspopup'?: boolean | \"dialog\" | \"menu\" | \"true\" | \"false\" | \"grid\" | \"listbox\" | \"tree\" | undefined; 'aria-hidden'?: Booleanish | undefined; 'aria-invalid'?: boolean | \"true\" | \"false\" | \"grammar\" | \"spelling\" | undefined; 'aria-keyshortcuts'?: string | undefined; 'aria-label'?: string | undefined; 'aria-labelledby'?: string | undefined; 'aria-level'?: number | undefined; 'aria-live'?: \"off\" | \"assertive\" | \"polite\" | undefined; 'aria-modal'?: Booleanish | undefined; 'aria-multiline'?: Booleanish | undefined; 'aria-multiselectable'?: Booleanish | undefined; 'aria-orientation'?: \"horizontal\" | \"vertical\" | undefined; 'aria-owns'?: string | undefined; 'aria-placeholder'?: string | undefined; 'aria-posinset'?: number | undefined; 'aria-pressed'?: boolean | \"true\" | \"false\" | \"mixed\" | undefined; 'aria-readonly'?: Booleanish | undefined; 'aria-relevant'?: \"text\" | \"all\" | \"additions\" | \"additions removals\" | \"additions text\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\" | undefined; 'aria-required'?: Booleanish | undefined; 'aria-roledescription'?: string | undefined; 'aria-rowcount'?: number | undefined; 'aria-rowindex'?: number | undefined; 'aria-rowspan'?: number | undefined; 'aria-selected'?: Booleanish | undefined; 'aria-setsize'?: number | undefined; 'aria-sort'?: \"none\" | \"ascending\" | \"descending\" | \"other\" | undefined; 'aria-valuemax'?: number | undefined; 'aria-valuemin'?: number | undefined; 'aria-valuenow'?: number | undefined; 'aria-valuetext'?: string | undefined; dangerouslySetInnerHTML?: { __html: string; } | undefined; onCopy?: React.ClipboardEventHandler | undefined; onCopyCapture?: React.ClipboardEventHandler | undefined; onCut?: React.ClipboardEventHandler | undefined; onCutCapture?: React.ClipboardEventHandler | undefined; onPaste?: React.ClipboardEventHandler | undefined; onPasteCapture?: React.ClipboardEventHandler | undefined; onCompositionEnd?: React.CompositionEventHandler | undefined; onCompositionEndCapture?: React.CompositionEventHandler | undefined; onCompositionStart?: React.CompositionEventHandler | undefined; onCompositionStartCapture?: React.CompositionEventHandler | undefined; onCompositionUpdate?: React.CompositionEventHandler | undefined; onCompositionUpdateCapture?: React.CompositionEventHandler | undefined; onFocus?: React.FocusEventHandler | undefined; onFocusCapture?: React.FocusEventHandler | undefined; onBlur?: React.FocusEventHandler | undefined; onBlurCapture?: React.FocusEventHandler | undefined; onChangeCapture?: React.FormEventHandler | undefined; onBeforeInput?: React.FormEventHandler | undefined; onBeforeInputCapture?: React.FormEventHandler | undefined; onInput?: React.FormEventHandler | undefined; onInputCapture?: React.FormEventHandler | undefined; onReset?: React.FormEventHandler | undefined; onResetCapture?: React.FormEventHandler | undefined; onSubmit?: React.FormEventHandler | undefined; onSubmitCapture?: React.FormEventHandler | undefined; onInvalid?: React.FormEventHandler | undefined; onInvalidCapture?: React.FormEventHandler | undefined; onLoad?: React.ReactEventHandler | undefined; onLoadCapture?: React.ReactEventHandler | undefined; onError?: React.ReactEventHandler | undefined; onErrorCapture?: React.ReactEventHandler | undefined; onKeyDown?: React.KeyboardEventHandler | undefined; onKeyDownCapture?: React.KeyboardEventHandler | undefined; onKeyPress?: React.KeyboardEventHandler | undefined; onKeyPressCapture?: React.KeyboardEventHandler | undefined; onKeyUp?: React.KeyboardEventHandler | undefined; onKeyUpCapture?: React.KeyboardEventHandler | undefined; onAbort?: React.ReactEventHandler | undefined; onAbortCapture?: React.ReactEventHandler | undefined; onCanPlay?: React.ReactEventHandler | undefined; onCanPlayCapture?: React.ReactEventHandler | undefined; onCanPlayThrough?: React.ReactEventHandler | undefined; onCanPlayThroughCapture?: React.ReactEventHandler | undefined; onDurationChange?: React.ReactEventHandler | undefined; onDurationChangeCapture?: React.ReactEventHandler | undefined; onEmptied?: React.ReactEventHandler | undefined; onEmptiedCapture?: React.ReactEventHandler | undefined; onEncrypted?: React.ReactEventHandler | undefined; onEncryptedCapture?: React.ReactEventHandler | undefined; onEnded?: React.ReactEventHandler | undefined; onEndedCapture?: React.ReactEventHandler | undefined; onLoadedData?: React.ReactEventHandler | undefined; onLoadedDataCapture?: React.ReactEventHandler | undefined; onLoadedMetadata?: React.ReactEventHandler | undefined; onLoadedMetadataCapture?: React.ReactEventHandler | undefined; onLoadStart?: React.ReactEventHandler | undefined; onLoadStartCapture?: React.ReactEventHandler | undefined; onPause?: React.ReactEventHandler | undefined; onPauseCapture?: React.ReactEventHandler | undefined; onPlay?: React.ReactEventHandler | undefined; onPlayCapture?: React.ReactEventHandler | undefined; onPlaying?: React.ReactEventHandler | undefined; onPlayingCapture?: React.ReactEventHandler | undefined; onProgress?: React.ReactEventHandler | undefined; onProgressCapture?: React.ReactEventHandler | undefined; onRateChange?: React.ReactEventHandler | undefined; onRateChangeCapture?: React.ReactEventHandler | undefined; onSeeked?: React.ReactEventHandler | undefined; onSeekedCapture?: React.ReactEventHandler | undefined; onSeeking?: React.ReactEventHandler | undefined; onSeekingCapture?: React.ReactEventHandler | undefined; onStalled?: React.ReactEventHandler | undefined; onStalledCapture?: React.ReactEventHandler | undefined; onSuspend?: React.ReactEventHandler | undefined; onSuspendCapture?: React.ReactEventHandler | undefined; onTimeUpdate?: React.ReactEventHandler | undefined; onTimeUpdateCapture?: React.ReactEventHandler | undefined; onVolumeChange?: React.ReactEventHandler | undefined; onVolumeChangeCapture?: React.ReactEventHandler | undefined; onWaiting?: React.ReactEventHandler | undefined; onWaitingCapture?: React.ReactEventHandler | undefined; onAuxClick?: React.MouseEventHandler | undefined; onAuxClickCapture?: React.MouseEventHandler | undefined; onClick?: React.MouseEventHandler | undefined; onClickCapture?: React.MouseEventHandler | undefined; onContextMenu?: React.MouseEventHandler | undefined; onContextMenuCapture?: React.MouseEventHandler | undefined; onDoubleClick?: React.MouseEventHandler | undefined; onDoubleClickCapture?: React.MouseEventHandler | undefined; onDrag?: React.DragEventHandler | undefined; onDragCapture?: React.DragEventHandler | undefined; onDragEnd?: React.DragEventHandler | undefined; onDragEndCapture?: React.DragEventHandler | undefined; onDragEnter?: React.DragEventHandler | undefined; onDragEnterCapture?: React.DragEventHandler | undefined; onDragExit?: React.DragEventHandler | undefined; onDragExitCapture?: React.DragEventHandler | undefined; onDragLeave?: React.DragEventHandler | undefined; onDragLeaveCapture?: React.DragEventHandler | undefined; onDragOver?: React.DragEventHandler | undefined; onDragOverCapture?: React.DragEventHandler | undefined; onDragStart?: React.DragEventHandler | undefined; onDragStartCapture?: React.DragEventHandler | undefined; onDrop?: React.DragEventHandler | undefined; onDropCapture?: React.DragEventHandler | undefined; onMouseDown?: React.MouseEventHandler | undefined; onMouseDownCapture?: React.MouseEventHandler | undefined; onMouseEnter?: React.MouseEventHandler | undefined; onMouseLeave?: React.MouseEventHandler | undefined; onMouseMove?: React.MouseEventHandler | undefined; onMouseMoveCapture?: React.MouseEventHandler | undefined; onMouseOut?: React.MouseEventHandler | undefined; onMouseOutCapture?: React.MouseEventHandler | undefined; onMouseOver?: React.MouseEventHandler | undefined; onMouseOverCapture?: React.MouseEventHandler | undefined; onMouseUp?: React.MouseEventHandler | undefined; onMouseUpCapture?: React.MouseEventHandler | undefined; onSelect?: React.ReactEventHandler | undefined; onSelectCapture?: React.ReactEventHandler | undefined; onTouchCancel?: React.TouchEventHandler | undefined; onTouchCancelCapture?: React.TouchEventHandler | undefined; onTouchEnd?: React.TouchEventHandler | undefined; onTouchEndCapture?: React.TouchEventHandler | undefined; onTouchMove?: React.TouchEventHandler | undefined; onTouchMoveCapture?: React.TouchEventHandler | undefined; onTouchStart?: React.TouchEventHandler | undefined; onTouchStartCapture?: React.TouchEventHandler | undefined; onPointerDown?: React.PointerEventHandler | undefined; onPointerDownCapture?: React.PointerEventHandler | undefined; onPointerMove?: React.PointerEventHandler | undefined; onPointerMoveCapture?: React.PointerEventHandler | undefined; onPointerUp?: React.PointerEventHandler | undefined; onPointerUpCapture?: React.PointerEventHandler | undefined; onPointerCancel?: React.PointerEventHandler | undefined; onPointerCancelCapture?: React.PointerEventHandler | undefined; onPointerEnter?: React.PointerEventHandler | undefined; onPointerEnterCapture?: React.PointerEventHandler | undefined; onPointerLeave?: React.PointerEventHandler | undefined; onPointerLeaveCapture?: React.PointerEventHandler | undefined; onPointerOver?: React.PointerEventHandler | undefined; onPointerOverCapture?: React.PointerEventHandler | undefined; onPointerOut?: React.PointerEventHandler | undefined; onPointerOutCapture?: React.PointerEventHandler | undefined; onGotPointerCapture?: React.PointerEventHandler | undefined; onGotPointerCaptureCapture?: React.PointerEventHandler | undefined; onLostPointerCapture?: React.PointerEventHandler | undefined; onLostPointerCaptureCapture?: React.PointerEventHandler | undefined; onScroll?: React.UIEventHandler | undefined; onScrollCapture?: React.UIEventHandler | undefined; onWheel?: React.WheelEventHandler | undefined; onWheelCapture?: React.WheelEventHandler | undefined; onAnimationStart?: React.AnimationEventHandler | undefined; onAnimationStartCapture?: React.AnimationEventHandler | undefined; onAnimationEnd?: React.AnimationEventHandler | undefined; onAnimationEndCapture?: React.AnimationEventHandler | undefined; onAnimationIteration?: React.AnimationEventHandler | undefined; onAnimationIterationCapture?: React.AnimationEventHandler | undefined; onTransitionEnd?: React.TransitionEventHandler | undefined; onTransitionEndCapture?: React.TransitionEventHandler | undefined; hasBorder?: boolean | undefined; paddingSize?: \"m\" | \"none\" | \"s\" | \"xs\" | \"l\" | \"xl\" | undefined; 'data-test-subj'?: string | undefined; css?: ", "Interpolation", "<", "Theme", - ">; defaultChecked?: boolean | undefined; suppressContentEditableWarning?: boolean | undefined; suppressHydrationWarning?: boolean | undefined; accessKey?: string | undefined; contentEditable?: Booleanish | \"inherit\" | undefined; contextMenu?: string | undefined; dir?: string | undefined; draggable?: Booleanish | undefined; hidden?: boolean | undefined; lang?: string | undefined; placeholder?: string | undefined; spellCheck?: Booleanish | undefined; tabIndex?: number | undefined; translate?: \"yes\" | \"no\" | undefined; radioGroup?: string | undefined; role?: React.AriaRole | undefined; about?: string | undefined; datatype?: string | undefined; inlist?: any; property?: string | undefined; resource?: string | undefined; typeof?: string | undefined; vocab?: string | undefined; autoCapitalize?: string | undefined; autoCorrect?: string | undefined; autoSave?: string | undefined; itemProp?: string | undefined; itemScope?: boolean | undefined; itemType?: string | undefined; itemID?: string | undefined; itemRef?: string | undefined; results?: number | undefined; unselectable?: \"on\" | \"off\" | undefined; inputMode?: \"search\" | \"none\" | \"text\" | \"tel\" | \"url\" | \"email\" | \"numeric\" | \"decimal\" | undefined; 'aria-activedescendant'?: string | undefined; 'aria-atomic'?: Booleanish | undefined; 'aria-autocomplete'?: \"none\" | \"list\" | \"inline\" | \"both\" | undefined; 'aria-busy'?: Booleanish | undefined; 'aria-checked'?: boolean | \"true\" | \"false\" | \"mixed\" | undefined; 'aria-colcount'?: number | undefined; 'aria-colindex'?: number | undefined; 'aria-colspan'?: number | undefined; 'aria-controls'?: string | undefined; 'aria-current'?: boolean | \"page\" | \"date\" | \"time\" | \"true\" | \"false\" | \"step\" | \"location\" | undefined; 'aria-describedby'?: string | undefined; 'aria-details'?: string | undefined; 'aria-disabled'?: Booleanish | undefined; 'aria-dropeffect'?: \"execute\" | \"link\" | \"none\" | \"copy\" | \"move\" | \"popup\" | undefined; 'aria-errormessage'?: string | undefined; 'aria-expanded'?: Booleanish | undefined; 'aria-flowto'?: string | undefined; 'aria-grabbed'?: Booleanish | undefined; 'aria-haspopup'?: boolean | \"dialog\" | \"menu\" | \"true\" | \"false\" | \"grid\" | \"listbox\" | \"tree\" | undefined; 'aria-hidden'?: Booleanish | undefined; 'aria-invalid'?: boolean | \"true\" | \"false\" | \"grammar\" | \"spelling\" | undefined; 'aria-keyshortcuts'?: string | undefined; 'aria-labelledby'?: string | undefined; 'aria-level'?: number | undefined; 'aria-live'?: \"off\" | \"assertive\" | \"polite\" | undefined; 'aria-modal'?: Booleanish | undefined; 'aria-multiline'?: Booleanish | undefined; 'aria-multiselectable'?: Booleanish | undefined; 'aria-orientation'?: \"horizontal\" | \"vertical\" | undefined; 'aria-owns'?: string | undefined; 'aria-placeholder'?: string | undefined; 'aria-posinset'?: number | undefined; 'aria-pressed'?: boolean | \"true\" | \"false\" | \"mixed\" | undefined; 'aria-readonly'?: Booleanish | undefined; 'aria-relevant'?: \"text\" | \"all\" | \"additions\" | \"additions removals\" | \"additions text\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\" | undefined; 'aria-required'?: Booleanish | undefined; 'aria-roledescription'?: string | undefined; 'aria-rowcount'?: number | undefined; 'aria-rowindex'?: number | undefined; 'aria-rowspan'?: number | undefined; 'aria-selected'?: Booleanish | undefined; 'aria-setsize'?: number | undefined; 'aria-sort'?: \"none\" | \"ascending\" | \"descending\" | \"other\" | undefined; 'aria-valuemax'?: number | undefined; 'aria-valuemin'?: number | undefined; 'aria-valuenow'?: number | undefined; 'aria-valuetext'?: string | undefined; dangerouslySetInnerHTML?: { __html: string; } | undefined; onCopy?: React.ClipboardEventHandler | undefined; onCopyCapture?: React.ClipboardEventHandler | undefined; onCut?: React.ClipboardEventHandler | undefined; onCutCapture?: React.ClipboardEventHandler | undefined; onPaste?: React.ClipboardEventHandler | undefined; onPasteCapture?: React.ClipboardEventHandler | undefined; onCompositionEnd?: React.CompositionEventHandler | undefined; onCompositionEndCapture?: React.CompositionEventHandler | undefined; onCompositionStart?: React.CompositionEventHandler | undefined; onCompositionStartCapture?: React.CompositionEventHandler | undefined; onCompositionUpdate?: React.CompositionEventHandler | undefined; onCompositionUpdateCapture?: React.CompositionEventHandler | undefined; onFocus?: React.FocusEventHandler | undefined; onFocusCapture?: React.FocusEventHandler | undefined; onBlur?: React.FocusEventHandler | undefined; onBlurCapture?: React.FocusEventHandler | undefined; onChange?: React.FormEventHandler | undefined; onChangeCapture?: React.FormEventHandler | undefined; onBeforeInput?: React.FormEventHandler | undefined; onBeforeInputCapture?: React.FormEventHandler | undefined; onInput?: React.FormEventHandler | undefined; onInputCapture?: React.FormEventHandler | undefined; onReset?: React.FormEventHandler | undefined; onResetCapture?: React.FormEventHandler | undefined; onSubmit?: React.FormEventHandler | undefined; onSubmitCapture?: React.FormEventHandler | undefined; onInvalid?: React.FormEventHandler | undefined; onInvalidCapture?: React.FormEventHandler | undefined; onLoad?: React.ReactEventHandler | undefined; onLoadCapture?: React.ReactEventHandler | undefined; onError?: React.ReactEventHandler | undefined; onErrorCapture?: React.ReactEventHandler | undefined; onKeyDown?: React.KeyboardEventHandler | undefined; onKeyDownCapture?: React.KeyboardEventHandler | undefined; onKeyPress?: React.KeyboardEventHandler | undefined; onKeyPressCapture?: React.KeyboardEventHandler | undefined; onKeyUp?: React.KeyboardEventHandler | undefined; onKeyUpCapture?: React.KeyboardEventHandler | undefined; onAbort?: React.ReactEventHandler | undefined; onAbortCapture?: React.ReactEventHandler | undefined; onCanPlay?: React.ReactEventHandler | undefined; onCanPlayCapture?: React.ReactEventHandler | undefined; onCanPlayThrough?: React.ReactEventHandler | undefined; onCanPlayThroughCapture?: React.ReactEventHandler | undefined; onDurationChange?: React.ReactEventHandler | undefined; onDurationChangeCapture?: React.ReactEventHandler | undefined; onEmptied?: React.ReactEventHandler | undefined; onEmptiedCapture?: React.ReactEventHandler | undefined; onEncrypted?: React.ReactEventHandler | undefined; onEncryptedCapture?: React.ReactEventHandler | undefined; onEnded?: React.ReactEventHandler | undefined; onEndedCapture?: React.ReactEventHandler | undefined; onLoadedData?: React.ReactEventHandler | undefined; onLoadedDataCapture?: React.ReactEventHandler | undefined; onLoadedMetadata?: React.ReactEventHandler | undefined; onLoadedMetadataCapture?: React.ReactEventHandler | undefined; onLoadStart?: React.ReactEventHandler | undefined; onLoadStartCapture?: React.ReactEventHandler | undefined; onPause?: React.ReactEventHandler | undefined; onPauseCapture?: React.ReactEventHandler | undefined; onPlay?: React.ReactEventHandler | undefined; onPlayCapture?: React.ReactEventHandler | undefined; onPlaying?: React.ReactEventHandler | undefined; onPlayingCapture?: React.ReactEventHandler | undefined; onProgress?: React.ReactEventHandler | undefined; onProgressCapture?: React.ReactEventHandler | undefined; onRateChange?: React.ReactEventHandler | undefined; onRateChangeCapture?: React.ReactEventHandler | undefined; onSeeked?: React.ReactEventHandler | undefined; onSeekedCapture?: React.ReactEventHandler | undefined; onSeeking?: React.ReactEventHandler | undefined; onSeekingCapture?: React.ReactEventHandler | undefined; onStalled?: React.ReactEventHandler | undefined; onStalledCapture?: React.ReactEventHandler | undefined; onSuspend?: React.ReactEventHandler | undefined; onSuspendCapture?: React.ReactEventHandler | undefined; onTimeUpdate?: React.ReactEventHandler | undefined; onTimeUpdateCapture?: React.ReactEventHandler | undefined; onVolumeChange?: React.ReactEventHandler | undefined; onVolumeChangeCapture?: React.ReactEventHandler | undefined; onWaiting?: React.ReactEventHandler | undefined; onWaitingCapture?: React.ReactEventHandler | undefined; onAuxClick?: React.MouseEventHandler | undefined; onAuxClickCapture?: React.MouseEventHandler | undefined; onClick?: React.MouseEventHandler | undefined; onClickCapture?: React.MouseEventHandler | undefined; onContextMenu?: React.MouseEventHandler | undefined; onContextMenuCapture?: React.MouseEventHandler | undefined; onDoubleClick?: React.MouseEventHandler | undefined; onDoubleClickCapture?: React.MouseEventHandler | undefined; onDrag?: React.DragEventHandler | undefined; onDragCapture?: React.DragEventHandler | undefined; onDragEnd?: React.DragEventHandler | undefined; onDragEndCapture?: React.DragEventHandler | undefined; onDragEnter?: React.DragEventHandler | undefined; onDragEnterCapture?: React.DragEventHandler | undefined; onDragExit?: React.DragEventHandler | undefined; onDragExitCapture?: React.DragEventHandler | undefined; onDragLeave?: React.DragEventHandler | undefined; onDragLeaveCapture?: React.DragEventHandler | undefined; onDragOver?: React.DragEventHandler | undefined; onDragOverCapture?: React.DragEventHandler | undefined; onDragStart?: React.DragEventHandler | undefined; onDragStartCapture?: React.DragEventHandler | undefined; onDrop?: React.DragEventHandler | undefined; onDropCapture?: React.DragEventHandler | undefined; onMouseDown?: React.MouseEventHandler | undefined; onMouseDownCapture?: React.MouseEventHandler | undefined; onMouseEnter?: React.MouseEventHandler | undefined; onMouseLeave?: React.MouseEventHandler | undefined; onMouseMove?: React.MouseEventHandler | undefined; onMouseMoveCapture?: React.MouseEventHandler | undefined; onMouseOut?: React.MouseEventHandler | undefined; onMouseOutCapture?: React.MouseEventHandler | undefined; onMouseOver?: React.MouseEventHandler | undefined; onMouseOverCapture?: React.MouseEventHandler | undefined; onMouseUp?: React.MouseEventHandler | undefined; onMouseUpCapture?: React.MouseEventHandler | undefined; onSelect?: React.ReactEventHandler | undefined; onSelectCapture?: React.ReactEventHandler | undefined; onTouchCancel?: React.TouchEventHandler | undefined; onTouchCancelCapture?: React.TouchEventHandler | undefined; onTouchEnd?: React.TouchEventHandler | undefined; onTouchEndCapture?: React.TouchEventHandler | undefined; onTouchMove?: React.TouchEventHandler | undefined; onTouchMoveCapture?: React.TouchEventHandler | undefined; onTouchStart?: React.TouchEventHandler | undefined; onTouchStartCapture?: React.TouchEventHandler | undefined; onPointerDown?: React.PointerEventHandler | undefined; onPointerDownCapture?: React.PointerEventHandler | undefined; onPointerMove?: React.PointerEventHandler | undefined; onPointerMoveCapture?: React.PointerEventHandler | undefined; onPointerUp?: React.PointerEventHandler | undefined; onPointerUpCapture?: React.PointerEventHandler | undefined; onPointerCancel?: React.PointerEventHandler | undefined; onPointerCancelCapture?: React.PointerEventHandler | undefined; onPointerEnter?: React.PointerEventHandler | undefined; onPointerEnterCapture?: React.PointerEventHandler | undefined; onPointerLeave?: React.PointerEventHandler | undefined; onPointerLeaveCapture?: React.PointerEventHandler | undefined; onPointerOver?: React.PointerEventHandler | undefined; onPointerOverCapture?: React.PointerEventHandler | undefined; onPointerOut?: React.PointerEventHandler | undefined; onPointerOutCapture?: React.PointerEventHandler | undefined; onGotPointerCapture?: React.PointerEventHandler | undefined; onGotPointerCaptureCapture?: React.PointerEventHandler | undefined; onLostPointerCapture?: React.PointerEventHandler | undefined; onLostPointerCaptureCapture?: React.PointerEventHandler | undefined; onScroll?: React.UIEventHandler | undefined; onScrollCapture?: React.UIEventHandler | undefined; onWheel?: React.WheelEventHandler | undefined; onWheelCapture?: React.WheelEventHandler | undefined; onAnimationStart?: React.AnimationEventHandler | undefined; onAnimationStartCapture?: React.AnimationEventHandler | undefined; onAnimationEnd?: React.AnimationEventHandler | undefined; onAnimationEndCapture?: React.AnimationEventHandler | undefined; onAnimationIteration?: React.AnimationEventHandler | undefined; onAnimationIterationCapture?: React.AnimationEventHandler | undefined; onTransitionEnd?: React.TransitionEventHandler | undefined; onTransitionEndCapture?: React.TransitionEventHandler | undefined; paddingSize?: \"m\" | \"none\" | \"s\" | \"xs\" | \"l\" | \"xl\" | undefined; href?: string | undefined; rel?: string | undefined; target?: string | undefined; icon?: React.ReactElement<", + ">; button?: React.ReactNode; footer?: React.ReactNode; image?: string | React.ReactElement> | undefined; href?: string | undefined; rel?: string | undefined; target?: string | undefined; icon?: React.ReactElement<", "EuiIconProps", - ", string | React.JSXElementConstructor> | null | undefined; display?: \"warning\" | \"success\" | \"subdued\" | \"primary\" | \"accent\" | \"danger\" | \"transparent\" | \"plain\" | undefined; hasBorder?: boolean | undefined; textAlign?: \"right\" | \"left\" | \"center\" | undefined; titleElement?: \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"span\" | undefined; titleSize?: \"s\" | \"xs\" | undefined; betaBadgeProps?: (", + ", string | React.JSXElementConstructor> | null | undefined; display?: \"warning\" | \"success\" | \"subdued\" | \"primary\" | \"accent\" | \"danger\" | \"transparent\" | \"plain\" | undefined; textAlign?: \"left\" | \"right\" | \"center\" | undefined; titleElement?: \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"span\" | undefined; titleSize?: \"s\" | \"xs\" | undefined; betaBadgeProps?: (", "CommonProps", " & ", "DisambiguateSet", diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index c68ef789fbdbb..6744cc7da9aa8 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index 695f2cff79895..604622664685d 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index 36ca2b6012f1a..2218b967c0672 100644 --- a/api_docs/kbn_shared_ux_file_context.mdx +++ b/api_docs/kbn_shared_ux_file_context.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-context title: "@kbn/shared-ux-file-context" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-context plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-context'] --- import kbnSharedUxFileContextObj from './kbn_shared_ux_file_context.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image.mdx b/api_docs/kbn_shared_ux_file_image.mdx index 2353fe64e7fa5..961468e71949a 100644 --- a/api_docs/kbn_shared_ux_file_image.mdx +++ b/api_docs/kbn_shared_ux_file_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image title: "@kbn/shared-ux-file-image" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image'] --- import kbnSharedUxFileImageObj from './kbn_shared_ux_file_image.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image_mocks.mdx b/api_docs/kbn_shared_ux_file_image_mocks.mdx index 41369b47d7695..f82ac77b98773 100644 --- a/api_docs/kbn_shared_ux_file_image_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_image_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image-mocks title: "@kbn/shared-ux-file-image-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image-mocks'] --- import kbnSharedUxFileImageMocksObj from './kbn_shared_ux_file_image_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_mocks.mdx b/api_docs/kbn_shared_ux_file_mocks.mdx index 7a2a7cd63a069..1ab3bafb4ee5f 100644 --- a/api_docs/kbn_shared_ux_file_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-mocks title: "@kbn/shared-ux-file-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-mocks'] --- import kbnSharedUxFileMocksObj from './kbn_shared_ux_file_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_picker.mdx b/api_docs/kbn_shared_ux_file_picker.mdx index e38116a4c51f8..f48a321a34b54 100644 --- a/api_docs/kbn_shared_ux_file_picker.mdx +++ b/api_docs/kbn_shared_ux_file_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-picker title: "@kbn/shared-ux-file-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-picker plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-picker'] --- import kbnSharedUxFilePickerObj from './kbn_shared_ux_file_picker.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_types.mdx b/api_docs/kbn_shared_ux_file_types.mdx index 42a15ee900a51..cf77561da9e6e 100644 --- a/api_docs/kbn_shared_ux_file_types.mdx +++ b/api_docs/kbn_shared_ux_file_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-types title: "@kbn/shared-ux-file-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-types plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-types'] --- import kbnSharedUxFileTypesObj from './kbn_shared_ux_file_types.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_upload.mdx b/api_docs/kbn_shared_ux_file_upload.mdx index b26284ac14961..84e8d32366c10 100644 --- a/api_docs/kbn_shared_ux_file_upload.mdx +++ b/api_docs/kbn_shared_ux_file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-upload title: "@kbn/shared-ux-file-upload" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-upload plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-upload'] --- import kbnSharedUxFileUploadObj from './kbn_shared_ux_file_upload.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_util.mdx b/api_docs/kbn_shared_ux_file_util.mdx index 14ad06eaae562..d79e2ec066afd 100644 --- a/api_docs/kbn_shared_ux_file_util.mdx +++ b/api_docs/kbn_shared_ux_file_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-util title: "@kbn/shared-ux-file-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-util plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-util'] --- import kbnSharedUxFileUtilObj from './kbn_shared_ux_file_util.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app.mdx b/api_docs/kbn_shared_ux_link_redirect_app.mdx index d92a08bcb9832..35763b229e400 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app title: "@kbn/shared-ux-link-redirect-app" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app'] --- import kbnSharedUxLinkRedirectAppObj from './kbn_shared_ux_link_redirect_app.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 22627650e89bc..714f376ffb7c5 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown.mdx b/api_docs/kbn_shared_ux_markdown.mdx index 321cd32d3a8d1..f4f56efd30485 100644 --- a/api_docs/kbn_shared_ux_markdown.mdx +++ b/api_docs/kbn_shared_ux_markdown.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown title: "@kbn/shared-ux-markdown" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown'] --- import kbnSharedUxMarkdownObj from './kbn_shared_ux_markdown.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown_mocks.devdocs.json b/api_docs/kbn_shared_ux_markdown_mocks.devdocs.json index 8a44ef7777828..3595a96dd2846 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.devdocs.json +++ b/api_docs/kbn_shared_ux_markdown_mocks.devdocs.json @@ -433,13 +433,13 @@ "label": "getServices", "description": [], "signature": [ - "() => { prefix?: string | undefined; value?: string | undefined; id?: string | undefined; defaultValue?: string | number | readonly string[] | undefined; children?: React.ReactNode; errors?: ", - "EuiMarkdownParseError", - "[] | undefined; is?: string | undefined; title?: string | undefined; security?: string | undefined; slot?: string | undefined; style?: React.CSSProperties | undefined; className?: string | undefined; 'aria-label'?: string | undefined; 'data-test-subj'?: string | undefined; css?: ", + "() => { prefix?: string | undefined; value?: string | undefined; id?: string | undefined; defaultValue?: string | number | readonly string[] | undefined; children?: React.ReactNode; onChange?: ((value: string) => void) | undefined; defaultChecked?: boolean | undefined; suppressContentEditableWarning?: boolean | undefined; suppressHydrationWarning?: boolean | undefined; accessKey?: string | undefined; className?: string | undefined; contentEditable?: Booleanish | \"inherit\" | undefined; contextMenu?: string | undefined; dir?: string | undefined; draggable?: Booleanish | undefined; hidden?: boolean | undefined; lang?: string | undefined; placeholder?: string | undefined; slot?: string | undefined; spellCheck?: Booleanish | undefined; style?: React.CSSProperties | undefined; tabIndex?: number | undefined; title?: string | undefined; translate?: \"yes\" | \"no\" | undefined; radioGroup?: string | undefined; role?: React.AriaRole | undefined; about?: string | undefined; datatype?: string | undefined; inlist?: any; property?: string | undefined; resource?: string | undefined; typeof?: string | undefined; vocab?: string | undefined; autoCapitalize?: string | undefined; autoCorrect?: string | undefined; autoSave?: string | undefined; color?: string | undefined; itemProp?: string | undefined; itemScope?: boolean | undefined; itemType?: string | undefined; itemID?: string | undefined; itemRef?: string | undefined; results?: number | undefined; security?: string | undefined; unselectable?: \"on\" | \"off\" | undefined; inputMode?: \"search\" | \"none\" | \"text\" | \"tel\" | \"url\" | \"email\" | \"numeric\" | \"decimal\" | undefined; is?: string | undefined; 'aria-activedescendant'?: string | undefined; 'aria-atomic'?: Booleanish | undefined; 'aria-autocomplete'?: \"none\" | \"list\" | \"inline\" | \"both\" | undefined; 'aria-busy'?: Booleanish | undefined; 'aria-checked'?: boolean | \"true\" | \"false\" | \"mixed\" | undefined; 'aria-colcount'?: number | undefined; 'aria-colindex'?: number | undefined; 'aria-colspan'?: number | undefined; 'aria-controls'?: string | undefined; 'aria-current'?: boolean | \"page\" | \"date\" | \"time\" | \"true\" | \"false\" | \"step\" | \"location\" | undefined; 'aria-describedby'?: string | undefined; 'aria-details'?: string | undefined; 'aria-disabled'?: Booleanish | undefined; 'aria-dropeffect'?: \"execute\" | \"link\" | \"none\" | \"copy\" | \"move\" | \"popup\" | undefined; 'aria-errormessage'?: string | undefined; 'aria-expanded'?: Booleanish | undefined; 'aria-flowto'?: string | undefined; 'aria-grabbed'?: Booleanish | undefined; 'aria-haspopup'?: boolean | \"dialog\" | \"menu\" | \"true\" | \"false\" | \"grid\" | \"listbox\" | \"tree\" | undefined; 'aria-hidden'?: Booleanish | undefined; 'aria-invalid'?: boolean | \"true\" | \"false\" | \"grammar\" | \"spelling\" | undefined; 'aria-keyshortcuts'?: string | undefined; 'aria-label'?: string | undefined; 'aria-labelledby'?: string | undefined; 'aria-level'?: number | undefined; 'aria-live'?: \"off\" | \"assertive\" | \"polite\" | undefined; 'aria-modal'?: Booleanish | undefined; 'aria-multiline'?: Booleanish | undefined; 'aria-multiselectable'?: Booleanish | undefined; 'aria-orientation'?: \"horizontal\" | \"vertical\" | undefined; 'aria-owns'?: string | undefined; 'aria-placeholder'?: string | undefined; 'aria-posinset'?: number | undefined; 'aria-pressed'?: boolean | \"true\" | \"false\" | \"mixed\" | undefined; 'aria-readonly'?: Booleanish | undefined; 'aria-relevant'?: \"text\" | \"all\" | \"additions\" | \"additions removals\" | \"additions text\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\" | undefined; 'aria-required'?: Booleanish | undefined; 'aria-roledescription'?: string | undefined; 'aria-rowcount'?: number | undefined; 'aria-rowindex'?: number | undefined; 'aria-rowspan'?: number | undefined; 'aria-selected'?: Booleanish | undefined; 'aria-setsize'?: number | undefined; 'aria-sort'?: \"none\" | \"ascending\" | \"descending\" | \"other\" | undefined; 'aria-valuemax'?: number | undefined; 'aria-valuemin'?: number | undefined; 'aria-valuenow'?: number | undefined; 'aria-valuetext'?: string | undefined; dangerouslySetInnerHTML?: { __html: string; } | undefined; onCopy?: React.ClipboardEventHandler | undefined; onCopyCapture?: React.ClipboardEventHandler | undefined; onCut?: React.ClipboardEventHandler | undefined; onCutCapture?: React.ClipboardEventHandler | undefined; onPaste?: React.ClipboardEventHandler | undefined; onPasteCapture?: React.ClipboardEventHandler | undefined; onCompositionEnd?: React.CompositionEventHandler | undefined; onCompositionEndCapture?: React.CompositionEventHandler | undefined; onCompositionStart?: React.CompositionEventHandler | undefined; onCompositionStartCapture?: React.CompositionEventHandler | undefined; onCompositionUpdate?: React.CompositionEventHandler | undefined; onCompositionUpdateCapture?: React.CompositionEventHandler | undefined; onFocus?: React.FocusEventHandler | undefined; onFocusCapture?: React.FocusEventHandler | undefined; onBlur?: React.FocusEventHandler | undefined; onBlurCapture?: React.FocusEventHandler | undefined; onChangeCapture?: React.FormEventHandler | undefined; onBeforeInput?: React.FormEventHandler | undefined; onBeforeInputCapture?: React.FormEventHandler | undefined; onInput?: React.FormEventHandler | undefined; onInputCapture?: React.FormEventHandler | undefined; onReset?: React.FormEventHandler | undefined; onResetCapture?: React.FormEventHandler | undefined; onSubmit?: React.FormEventHandler | undefined; onSubmitCapture?: React.FormEventHandler | undefined; onInvalid?: React.FormEventHandler | undefined; onInvalidCapture?: React.FormEventHandler | undefined; onLoad?: React.ReactEventHandler | undefined; onLoadCapture?: React.ReactEventHandler | undefined; onError?: React.ReactEventHandler | undefined; onErrorCapture?: React.ReactEventHandler | undefined; onKeyDown?: React.KeyboardEventHandler | undefined; onKeyDownCapture?: React.KeyboardEventHandler | undefined; onKeyPress?: React.KeyboardEventHandler | undefined; onKeyPressCapture?: React.KeyboardEventHandler | undefined; onKeyUp?: React.KeyboardEventHandler | undefined; onKeyUpCapture?: React.KeyboardEventHandler | undefined; onAbort?: React.ReactEventHandler | undefined; onAbortCapture?: React.ReactEventHandler | undefined; onCanPlay?: React.ReactEventHandler | undefined; onCanPlayCapture?: React.ReactEventHandler | undefined; onCanPlayThrough?: React.ReactEventHandler | undefined; onCanPlayThroughCapture?: React.ReactEventHandler | undefined; onDurationChange?: React.ReactEventHandler | undefined; onDurationChangeCapture?: React.ReactEventHandler | undefined; onEmptied?: React.ReactEventHandler | undefined; onEmptiedCapture?: React.ReactEventHandler | undefined; onEncrypted?: React.ReactEventHandler | undefined; onEncryptedCapture?: React.ReactEventHandler | undefined; onEnded?: React.ReactEventHandler | undefined; onEndedCapture?: React.ReactEventHandler | undefined; onLoadedData?: React.ReactEventHandler | undefined; onLoadedDataCapture?: React.ReactEventHandler | undefined; onLoadedMetadata?: React.ReactEventHandler | undefined; onLoadedMetadataCapture?: React.ReactEventHandler | undefined; onLoadStart?: React.ReactEventHandler | undefined; onLoadStartCapture?: React.ReactEventHandler | undefined; onPause?: React.ReactEventHandler | undefined; onPauseCapture?: React.ReactEventHandler | undefined; onPlay?: React.ReactEventHandler | undefined; onPlayCapture?: React.ReactEventHandler | undefined; onPlaying?: React.ReactEventHandler | undefined; onPlayingCapture?: React.ReactEventHandler | undefined; onProgress?: React.ReactEventHandler | undefined; onProgressCapture?: React.ReactEventHandler | undefined; onRateChange?: React.ReactEventHandler | undefined; onRateChangeCapture?: React.ReactEventHandler | undefined; onSeeked?: React.ReactEventHandler | undefined; onSeekedCapture?: React.ReactEventHandler | undefined; onSeeking?: React.ReactEventHandler | undefined; onSeekingCapture?: React.ReactEventHandler | undefined; onStalled?: React.ReactEventHandler | undefined; onStalledCapture?: React.ReactEventHandler | undefined; onSuspend?: React.ReactEventHandler | undefined; onSuspendCapture?: React.ReactEventHandler | undefined; onTimeUpdate?: React.ReactEventHandler | undefined; onTimeUpdateCapture?: React.ReactEventHandler | undefined; onVolumeChange?: React.ReactEventHandler | undefined; onVolumeChangeCapture?: React.ReactEventHandler | undefined; onWaiting?: React.ReactEventHandler | undefined; onWaitingCapture?: React.ReactEventHandler | undefined; onAuxClick?: React.MouseEventHandler | undefined; onAuxClickCapture?: React.MouseEventHandler | undefined; onClick?: React.MouseEventHandler | undefined; onClickCapture?: React.MouseEventHandler | undefined; onContextMenu?: React.MouseEventHandler | undefined; onContextMenuCapture?: React.MouseEventHandler | undefined; onDoubleClick?: React.MouseEventHandler | undefined; onDoubleClickCapture?: React.MouseEventHandler | undefined; onDrag?: React.DragEventHandler | undefined; onDragCapture?: React.DragEventHandler | undefined; onDragEnd?: React.DragEventHandler | undefined; onDragEndCapture?: React.DragEventHandler | undefined; onDragEnter?: React.DragEventHandler | undefined; onDragEnterCapture?: React.DragEventHandler | undefined; onDragExit?: React.DragEventHandler | undefined; onDragExitCapture?: React.DragEventHandler | undefined; onDragLeave?: React.DragEventHandler | undefined; onDragLeaveCapture?: React.DragEventHandler | undefined; onDragOver?: React.DragEventHandler | undefined; onDragOverCapture?: React.DragEventHandler | undefined; onDragStart?: React.DragEventHandler | undefined; onDragStartCapture?: React.DragEventHandler | undefined; onDrop?: React.DragEventHandler | undefined; onDropCapture?: React.DragEventHandler | undefined; onMouseDown?: React.MouseEventHandler | undefined; onMouseDownCapture?: React.MouseEventHandler | undefined; onMouseEnter?: React.MouseEventHandler | undefined; onMouseLeave?: React.MouseEventHandler | undefined; onMouseMove?: React.MouseEventHandler | undefined; onMouseMoveCapture?: React.MouseEventHandler | undefined; onMouseOut?: React.MouseEventHandler | undefined; onMouseOutCapture?: React.MouseEventHandler | undefined; onMouseOver?: React.MouseEventHandler | undefined; onMouseOverCapture?: React.MouseEventHandler | undefined; onMouseUp?: React.MouseEventHandler | undefined; onMouseUpCapture?: React.MouseEventHandler | undefined; onSelect?: React.ReactEventHandler | undefined; onSelectCapture?: React.ReactEventHandler | undefined; onTouchCancel?: React.TouchEventHandler | undefined; onTouchCancelCapture?: React.TouchEventHandler | undefined; onTouchEnd?: React.TouchEventHandler | undefined; onTouchEndCapture?: React.TouchEventHandler | undefined; onTouchMove?: React.TouchEventHandler | undefined; onTouchMoveCapture?: React.TouchEventHandler | undefined; onTouchStart?: React.TouchEventHandler | undefined; onTouchStartCapture?: React.TouchEventHandler | undefined; onPointerDown?: React.PointerEventHandler | undefined; onPointerDownCapture?: React.PointerEventHandler | undefined; onPointerMove?: React.PointerEventHandler | undefined; onPointerMoveCapture?: React.PointerEventHandler | undefined; onPointerUp?: React.PointerEventHandler | undefined; onPointerUpCapture?: React.PointerEventHandler | undefined; onPointerCancel?: React.PointerEventHandler | undefined; onPointerCancelCapture?: React.PointerEventHandler | undefined; onPointerEnter?: React.PointerEventHandler | undefined; onPointerEnterCapture?: React.PointerEventHandler | undefined; onPointerLeave?: React.PointerEventHandler | undefined; onPointerLeaveCapture?: React.PointerEventHandler | undefined; onPointerOver?: React.PointerEventHandler | undefined; onPointerOverCapture?: React.PointerEventHandler | undefined; onPointerOut?: React.PointerEventHandler | undefined; onPointerOutCapture?: React.PointerEventHandler | undefined; onGotPointerCapture?: React.PointerEventHandler | undefined; onGotPointerCaptureCapture?: React.PointerEventHandler | undefined; onLostPointerCapture?: React.PointerEventHandler | undefined; onLostPointerCaptureCapture?: React.PointerEventHandler | undefined; onScroll?: React.UIEventHandler | undefined; onScrollCapture?: React.UIEventHandler | undefined; onWheel?: React.WheelEventHandler | undefined; onWheelCapture?: React.WheelEventHandler | undefined; onAnimationStart?: React.AnimationEventHandler | undefined; onAnimationStartCapture?: React.AnimationEventHandler | undefined; onAnimationEnd?: React.AnimationEventHandler | undefined; onAnimationEndCapture?: React.AnimationEventHandler | undefined; onAnimationIteration?: React.AnimationEventHandler | undefined; onAnimationIterationCapture?: React.AnimationEventHandler | undefined; onTransitionEnd?: React.TransitionEventHandler | undefined; onTransitionEndCapture?: React.TransitionEventHandler | undefined; 'data-test-subj'?: string | undefined; css?: ", "Interpolation", "<", "Theme", - ">; defaultChecked?: boolean | undefined; suppressContentEditableWarning?: boolean | undefined; suppressHydrationWarning?: boolean | undefined; accessKey?: string | undefined; contentEditable?: Booleanish | \"inherit\" | undefined; contextMenu?: string | undefined; dir?: string | undefined; draggable?: Booleanish | undefined; hidden?: boolean | undefined; lang?: string | undefined; placeholder?: string | undefined; spellCheck?: Booleanish | undefined; tabIndex?: number | undefined; translate?: \"yes\" | \"no\" | undefined; radioGroup?: string | undefined; role?: React.AriaRole | undefined; about?: string | undefined; datatype?: string | undefined; inlist?: any; property?: string | undefined; resource?: string | undefined; typeof?: string | undefined; vocab?: string | undefined; autoCapitalize?: string | undefined; autoCorrect?: string | undefined; autoSave?: string | undefined; color?: string | undefined; itemProp?: string | undefined; itemScope?: boolean | undefined; itemType?: string | undefined; itemID?: string | undefined; itemRef?: string | undefined; results?: number | undefined; unselectable?: \"on\" | \"off\" | undefined; inputMode?: \"search\" | \"none\" | \"text\" | \"tel\" | \"url\" | \"email\" | \"numeric\" | \"decimal\" | undefined; 'aria-activedescendant'?: string | undefined; 'aria-atomic'?: Booleanish | undefined; 'aria-autocomplete'?: \"none\" | \"list\" | \"inline\" | \"both\" | undefined; 'aria-busy'?: Booleanish | undefined; 'aria-checked'?: boolean | \"true\" | \"false\" | \"mixed\" | undefined; 'aria-colcount'?: number | undefined; 'aria-colindex'?: number | undefined; 'aria-colspan'?: number | undefined; 'aria-controls'?: string | undefined; 'aria-current'?: boolean | \"page\" | \"date\" | \"time\" | \"true\" | \"false\" | \"step\" | \"location\" | undefined; 'aria-describedby'?: string | undefined; 'aria-details'?: string | undefined; 'aria-disabled'?: Booleanish | undefined; 'aria-dropeffect'?: \"execute\" | \"link\" | \"none\" | \"copy\" | \"move\" | \"popup\" | undefined; 'aria-errormessage'?: string | undefined; 'aria-expanded'?: Booleanish | undefined; 'aria-flowto'?: string | undefined; 'aria-grabbed'?: Booleanish | undefined; 'aria-haspopup'?: boolean | \"dialog\" | \"menu\" | \"true\" | \"false\" | \"grid\" | \"listbox\" | \"tree\" | undefined; 'aria-hidden'?: Booleanish | undefined; 'aria-invalid'?: boolean | \"true\" | \"false\" | \"grammar\" | \"spelling\" | undefined; 'aria-keyshortcuts'?: string | undefined; 'aria-labelledby'?: string | undefined; 'aria-level'?: number | undefined; 'aria-live'?: \"off\" | \"assertive\" | \"polite\" | undefined; 'aria-modal'?: Booleanish | undefined; 'aria-multiline'?: Booleanish | undefined; 'aria-multiselectable'?: Booleanish | undefined; 'aria-orientation'?: \"horizontal\" | \"vertical\" | undefined; 'aria-owns'?: string | undefined; 'aria-placeholder'?: string | undefined; 'aria-posinset'?: number | undefined; 'aria-pressed'?: boolean | \"true\" | \"false\" | \"mixed\" | undefined; 'aria-readonly'?: Booleanish | undefined; 'aria-relevant'?: \"text\" | \"all\" | \"additions\" | \"additions removals\" | \"additions text\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\" | undefined; 'aria-required'?: Booleanish | undefined; 'aria-roledescription'?: string | undefined; 'aria-rowcount'?: number | undefined; 'aria-rowindex'?: number | undefined; 'aria-rowspan'?: number | undefined; 'aria-selected'?: Booleanish | undefined; 'aria-setsize'?: number | undefined; 'aria-sort'?: \"none\" | \"ascending\" | \"descending\" | \"other\" | undefined; 'aria-valuemax'?: number | undefined; 'aria-valuemin'?: number | undefined; 'aria-valuenow'?: number | undefined; 'aria-valuetext'?: string | undefined; dangerouslySetInnerHTML?: { __html: string; } | undefined; onCopy?: React.ClipboardEventHandler | undefined; onCopyCapture?: React.ClipboardEventHandler | undefined; onCut?: React.ClipboardEventHandler | undefined; onCutCapture?: React.ClipboardEventHandler | undefined; onPaste?: React.ClipboardEventHandler | undefined; onPasteCapture?: React.ClipboardEventHandler | undefined; onCompositionEnd?: React.CompositionEventHandler | undefined; onCompositionEndCapture?: React.CompositionEventHandler | undefined; onCompositionStart?: React.CompositionEventHandler | undefined; onCompositionStartCapture?: React.CompositionEventHandler | undefined; onCompositionUpdate?: React.CompositionEventHandler | undefined; onCompositionUpdateCapture?: React.CompositionEventHandler | undefined; onFocus?: React.FocusEventHandler | undefined; onFocusCapture?: React.FocusEventHandler | undefined; onBlur?: React.FocusEventHandler | undefined; onBlurCapture?: React.FocusEventHandler | undefined; onChange?: ((value: string) => void) | undefined; onChangeCapture?: React.FormEventHandler | undefined; onBeforeInput?: React.FormEventHandler | undefined; onBeforeInputCapture?: React.FormEventHandler | undefined; onInput?: React.FormEventHandler | undefined; onInputCapture?: React.FormEventHandler | undefined; onReset?: React.FormEventHandler | undefined; onResetCapture?: React.FormEventHandler | undefined; onSubmit?: React.FormEventHandler | undefined; onSubmitCapture?: React.FormEventHandler | undefined; onInvalid?: React.FormEventHandler | undefined; onInvalidCapture?: React.FormEventHandler | undefined; onLoad?: React.ReactEventHandler | undefined; onLoadCapture?: React.ReactEventHandler | undefined; onError?: React.ReactEventHandler | undefined; onErrorCapture?: React.ReactEventHandler | undefined; onKeyDown?: React.KeyboardEventHandler | undefined; onKeyDownCapture?: React.KeyboardEventHandler | undefined; onKeyPress?: React.KeyboardEventHandler | undefined; onKeyPressCapture?: React.KeyboardEventHandler | undefined; onKeyUp?: React.KeyboardEventHandler | undefined; onKeyUpCapture?: React.KeyboardEventHandler | undefined; onAbort?: React.ReactEventHandler | undefined; onAbortCapture?: React.ReactEventHandler | undefined; onCanPlay?: React.ReactEventHandler | undefined; onCanPlayCapture?: React.ReactEventHandler | undefined; onCanPlayThrough?: React.ReactEventHandler | undefined; onCanPlayThroughCapture?: React.ReactEventHandler | undefined; onDurationChange?: React.ReactEventHandler | undefined; onDurationChangeCapture?: React.ReactEventHandler | undefined; onEmptied?: React.ReactEventHandler | undefined; onEmptiedCapture?: React.ReactEventHandler | undefined; onEncrypted?: React.ReactEventHandler | undefined; onEncryptedCapture?: React.ReactEventHandler | undefined; onEnded?: React.ReactEventHandler | undefined; onEndedCapture?: React.ReactEventHandler | undefined; onLoadedData?: React.ReactEventHandler | undefined; onLoadedDataCapture?: React.ReactEventHandler | undefined; onLoadedMetadata?: React.ReactEventHandler | undefined; onLoadedMetadataCapture?: React.ReactEventHandler | undefined; onLoadStart?: React.ReactEventHandler | undefined; onLoadStartCapture?: React.ReactEventHandler | undefined; onPause?: React.ReactEventHandler | undefined; onPauseCapture?: React.ReactEventHandler | undefined; onPlay?: React.ReactEventHandler | undefined; onPlayCapture?: React.ReactEventHandler | undefined; onPlaying?: React.ReactEventHandler | undefined; onPlayingCapture?: React.ReactEventHandler | undefined; onProgress?: React.ReactEventHandler | undefined; onProgressCapture?: React.ReactEventHandler | undefined; onRateChange?: React.ReactEventHandler | undefined; onRateChangeCapture?: React.ReactEventHandler | undefined; onSeeked?: React.ReactEventHandler | undefined; onSeekedCapture?: React.ReactEventHandler | undefined; onSeeking?: React.ReactEventHandler | undefined; onSeekingCapture?: React.ReactEventHandler | undefined; onStalled?: React.ReactEventHandler | undefined; onStalledCapture?: React.ReactEventHandler | undefined; onSuspend?: React.ReactEventHandler | undefined; onSuspendCapture?: React.ReactEventHandler | undefined; onTimeUpdate?: React.ReactEventHandler | undefined; onTimeUpdateCapture?: React.ReactEventHandler | undefined; onVolumeChange?: React.ReactEventHandler | undefined; onVolumeChangeCapture?: React.ReactEventHandler | undefined; onWaiting?: React.ReactEventHandler | undefined; onWaitingCapture?: React.ReactEventHandler | undefined; onAuxClick?: React.MouseEventHandler | undefined; onAuxClickCapture?: React.MouseEventHandler | undefined; onClick?: React.MouseEventHandler | undefined; onClickCapture?: React.MouseEventHandler | undefined; onContextMenu?: React.MouseEventHandler | undefined; onContextMenuCapture?: React.MouseEventHandler | undefined; onDoubleClick?: React.MouseEventHandler | undefined; onDoubleClickCapture?: React.MouseEventHandler | undefined; onDrag?: React.DragEventHandler | undefined; onDragCapture?: React.DragEventHandler | undefined; onDragEnd?: React.DragEventHandler | undefined; onDragEndCapture?: React.DragEventHandler | undefined; onDragEnter?: React.DragEventHandler | undefined; onDragEnterCapture?: React.DragEventHandler | undefined; onDragExit?: React.DragEventHandler | undefined; onDragExitCapture?: React.DragEventHandler | undefined; onDragLeave?: React.DragEventHandler | undefined; onDragLeaveCapture?: React.DragEventHandler | undefined; onDragOver?: React.DragEventHandler | undefined; onDragOverCapture?: React.DragEventHandler | undefined; onDragStart?: React.DragEventHandler | undefined; onDragStartCapture?: React.DragEventHandler | undefined; onDrop?: React.DragEventHandler | undefined; onDropCapture?: React.DragEventHandler | undefined; onMouseDown?: React.MouseEventHandler | undefined; onMouseDownCapture?: React.MouseEventHandler | undefined; onMouseEnter?: React.MouseEventHandler | undefined; onMouseLeave?: React.MouseEventHandler | undefined; onMouseMove?: React.MouseEventHandler | undefined; onMouseMoveCapture?: React.MouseEventHandler | undefined; onMouseOut?: React.MouseEventHandler | undefined; onMouseOutCapture?: React.MouseEventHandler | undefined; onMouseOver?: React.MouseEventHandler | undefined; onMouseOverCapture?: React.MouseEventHandler | undefined; onMouseUp?: React.MouseEventHandler | undefined; onMouseUpCapture?: React.MouseEventHandler | undefined; onSelect?: React.ReactEventHandler | undefined; onSelectCapture?: React.ReactEventHandler | undefined; onTouchCancel?: React.TouchEventHandler | undefined; onTouchCancelCapture?: React.TouchEventHandler | undefined; onTouchEnd?: React.TouchEventHandler | undefined; onTouchEndCapture?: React.TouchEventHandler | undefined; onTouchMove?: React.TouchEventHandler | undefined; onTouchMoveCapture?: React.TouchEventHandler | undefined; onTouchStart?: React.TouchEventHandler | undefined; onTouchStartCapture?: React.TouchEventHandler | undefined; onPointerDown?: React.PointerEventHandler | undefined; onPointerDownCapture?: React.PointerEventHandler | undefined; onPointerMove?: React.PointerEventHandler | undefined; onPointerMoveCapture?: React.PointerEventHandler | undefined; onPointerUp?: React.PointerEventHandler | undefined; onPointerUpCapture?: React.PointerEventHandler | undefined; onPointerCancel?: React.PointerEventHandler | undefined; onPointerCancelCapture?: React.PointerEventHandler | undefined; onPointerEnter?: React.PointerEventHandler | undefined; onPointerEnterCapture?: React.PointerEventHandler | undefined; onPointerLeave?: React.PointerEventHandler | undefined; onPointerLeaveCapture?: React.PointerEventHandler | undefined; onPointerOver?: React.PointerEventHandler | undefined; onPointerOverCapture?: React.PointerEventHandler | undefined; onPointerOut?: React.PointerEventHandler | undefined; onPointerOutCapture?: React.PointerEventHandler | undefined; onGotPointerCapture?: React.PointerEventHandler | undefined; onGotPointerCaptureCapture?: React.PointerEventHandler | undefined; onLostPointerCapture?: React.PointerEventHandler | undefined; onLostPointerCaptureCapture?: React.PointerEventHandler | undefined; onScroll?: React.UIEventHandler | undefined; onScrollCapture?: React.UIEventHandler | undefined; onWheel?: React.WheelEventHandler | undefined; onWheelCapture?: React.WheelEventHandler | undefined; onAnimationStart?: React.AnimationEventHandler | undefined; onAnimationStartCapture?: React.AnimationEventHandler | undefined; onAnimationEnd?: React.AnimationEventHandler | undefined; onAnimationEndCapture?: React.AnimationEventHandler | undefined; onAnimationIteration?: React.AnimationEventHandler | undefined; onAnimationIterationCapture?: React.AnimationEventHandler | undefined; onTransitionEnd?: React.TransitionEventHandler | undefined; onTransitionEndCapture?: React.TransitionEventHandler | undefined; height?: number | \"full\" | undefined; readOnly: boolean; maxHeight?: number | undefined; autoExpandPreview?: boolean | undefined; parsingPluginList?: ", + ">; errors?: ", + "EuiMarkdownParseError", + "[] | undefined; height?: number | \"full\" | undefined; readOnly: boolean; maxHeight?: number | undefined; autoExpandPreview?: boolean | undefined; parsingPluginList?: ", "PluggableList", "<", "Settings", diff --git a/api_docs/kbn_shared_ux_markdown_mocks.mdx b/api_docs/kbn_shared_ux_markdown_mocks.mdx index 4d9ba562a688a..5d46068526cc8 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.mdx +++ b/api_docs/kbn_shared_ux_markdown_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown-mocks title: "@kbn/shared-ux-markdown-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown-mocks'] --- import kbnSharedUxMarkdownMocksObj from './kbn_shared_ux_markdown_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index f31c06cdcefe1..a703893f75dec 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index 4be5cf0a56ee1..c6146d86359c0 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index 168413e262671..2fc62d3203dab 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 524c1ff746967..e4a702e9d4de6 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index 55fa2b0ae3b15..0281befb594ed 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index ed670cabe25e8..bc4addd3208ad 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index 96243a193177f..0144374aab4fb 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index 044721d260078..da46bad1d78e5 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index 74808ff365570..4681ccfbf18d8 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index 83e1e1473ce12..ac7fbb18cee1d 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index aaa935152d277..c5f2135a7e18e 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index 26de1039527e6..90f8c67503a8a 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index f3e00b73a9714..0c4bc9ae73323 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_not_found.mdx b/api_docs/kbn_shared_ux_prompt_not_found.mdx index b11967841a90e..da5e9ae1eb9a1 100644 --- a/api_docs/kbn_shared_ux_prompt_not_found.mdx +++ b/api_docs/kbn_shared_ux_prompt_not_found.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-not-found title: "@kbn/shared-ux-prompt-not-found" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-not-found plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-not-found'] --- import kbnSharedUxPromptNotFoundObj from './kbn_shared_ux_prompt_not_found.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 434c920814f46..aa816570472a8 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index a5a61a8c5c9fe..135cdbf742f31 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index 5157cc5fa9273..9ca85a30f64b6 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index 399a594d4755b..ce99698fd9f1b 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index b4fe6a2f4e57d..0e8f337108e82 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_slo_schema.mdx b/api_docs/kbn_slo_schema.mdx index e2f98d7df5a53..97a606deb5c99 100644 --- a/api_docs/kbn_slo_schema.mdx +++ b/api_docs/kbn_slo_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-slo-schema title: "@kbn/slo-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/slo-schema plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index ca0b3b3b89180..d1a8eebc6a352 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 797a5224e6422..0217dc9a965db 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index e3b72f32afd1c..b4b67a76d6345 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 69ca75f1b1a7b..fd6b8221cc8fd 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index c22a72bacd8e7..a2a8c3a870b1a 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index cb320fb09998e..245f8573362f4 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 4d7893ebac23c..7c8dcb3038c28 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index 0438a884bb668..f4a6b85e91f0c 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 601a95786408a..2da057aa80b43 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index 96c4e09406920..efa271163a527 100644 --- a/api_docs/kbn_ts_projects.mdx +++ b/api_docs/kbn_ts_projects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ts-projects title: "@kbn/ts-projects" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ts-projects plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ts-projects'] --- import kbnTsProjectsObj from './kbn_ts_projects.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 414ac3451f9d8..e36f3796a6a55 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_actions_browser.mdx b/api_docs/kbn_ui_actions_browser.mdx index 00176fb3bf00e..3cd1a6aef2f77 100644 --- a/api_docs/kbn_ui_actions_browser.mdx +++ b/api_docs/kbn_ui_actions_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-actions-browser title: "@kbn/ui-actions-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-actions-browser plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-actions-browser'] --- import kbnUiActionsBrowserObj from './kbn_ui_actions_browser.devdocs.json'; diff --git a/api_docs/kbn_ui_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index bf12be46d108e..11107207bbef6 100644 --- a/api_docs/kbn_ui_shared_deps_src.mdx +++ b/api_docs/kbn_ui_shared_deps_src.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-shared-deps-src title: "@kbn/ui-shared-deps-src" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-shared-deps-src plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-shared-deps-src'] --- import kbnUiSharedDepsSrcObj from './kbn_ui_shared_deps_src.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index ad80982ad1262..a6c35e39c2b54 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2023-04-20 +date: 2023-04-21 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.devdocs.json b/api_docs/kbn_user_profile_components.devdocs.json index f75915d1917f0..bbc4c86988e45 100644 --- a/api_docs/kbn_user_profile_components.devdocs.json +++ b/api_docs/kbn_user_profile_components.devdocs.json @@ -719,7 +719,7 @@ }, "
); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities.tsx index 2869d8d7da321..7c73eccff12eb 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities.tsx @@ -13,6 +13,7 @@ import { LATEST_VULNERABILITIES_INDEX_PATTERN } from '../../../../common/constan import { useKibana } from '../../../common/hooks/use_kibana'; import { showErrorToast } from '../../../common/utils/show_error_toast'; import { MAX_FINDINGS_TO_LOAD } from '../../../common/constants'; +import { FindingsBaseEsQuery } from '../../../common/types'; type LatestFindingsRequest = IKibanaSearchRequest; type LatestFindingsResponse = IKibanaSearchResponse>; @@ -20,14 +21,35 @@ interface FindingsAggs { count: estypes.AggregationsMultiBucketAggregateBase; } -export const getFindingsQuery = ({ query, sort }: any) => ({ +interface VulnerabilitiesQuery extends FindingsBaseEsQuery { + sort: estypes.Sort; + enabled: boolean; +} + +export const getFindingsQuery = ({ query, sort }: VulnerabilitiesQuery) => ({ index: LATEST_VULNERABILITIES_INDEX_PATTERN, - query, + query: { + ...query, + bool: { + ...query?.bool, + filter: [ + ...(query?.bool?.filter || []), + { exists: { field: 'vulnerability.score.base' } }, + { exists: { field: 'vulnerability.score.version' } }, + { exists: { field: 'resource.name' } }, + { match_phrase: { 'vulnerability.enumeration': 'CVE' } }, + ], + must_not: [ + ...(query?.bool?.must_not || []), + { match_phrase: { 'vulnerability.severity': 'UNKNOWN' } }, + ], + }, + }, size: MAX_FINDINGS_TO_LOAD, sort, }); -export const useLatestVulnerabilities = (options: any) => { +export const useLatestVulnerabilities = (options: VulnerabilitiesQuery) => { const { data, notifications: { toasts }, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts new file mode 100644 index 0000000000000..b2c0ca0ca8366 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts @@ -0,0 +1,24 @@ +/* + * 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'; + +export const FILTER_IN = i18n.translate('xpack.csp.vulnerabilities.table.filterIn', { + defaultMessage: 'Filter in', +}); +export const FILTER_OUT = i18n.translate('xpack.csp.vulnerabilities.table.filterOut', { + defaultMessage: 'Filter out', +}); +export const SEARCH_BAR_PLACEHOLDER = i18n.translate( + 'xpack.csp.vulnerabilities.searchBar.placeholder', + { + defaultMessage: 'Search vulnerabilities (eg. vulnerability.severity : "CRITICAL" )', + } +); +export const VULNERABILITIES = i18n.translate('xpack.csp.vulnerabilities', { + defaultMessage: 'Vulnerabilities', +}); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts index 8f01271677321..8d5d267f8135c 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts @@ -77,10 +77,9 @@ export interface VulnerabilityRecord { } export interface Vulnerability { - published_at: string; + published_date: string; score: { version: string; - impact: number; base: number; }; cwe: string[]; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/get_filters.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/get_filters.ts new file mode 100644 index 0000000000000..7f7d9ff544c62 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/get_filters.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 { + type Filter, + buildFilter, + FILTERS, + FilterStateStore, + compareFilters, + FilterCompareOptions, +} from '@kbn/es-query'; +import type { Serializable } from '@kbn/utility-types'; +import type { FindingsBaseProps } from '../../../common/types'; + +const compareOptions: FilterCompareOptions = { + negate: false, +}; + +/** + * adds a new filter to a new filters array + * removes existing filter if negated filter is added + * + * @returns {Filter[]} a new array of filters to be added back to filterManager + */ +export const getFilters = ({ + filters: existingFilters, + dataView, + field, + value, + negate, +}: { + filters: Filter[]; + dataView: FindingsBaseProps['dataView']; + field: string; + value: Serializable; + negate: boolean; +}): Filter[] => { + const dataViewField = dataView.fields.find((f) => f.spec.name === field); + if (!dataViewField) return existingFilters; + + const phraseFilter = buildFilter( + dataView, + dataViewField, + FILTERS.PHRASE, + negate, + false, + value, + null, + FilterStateStore.APP_STATE + ); + + const nextFilters = [ + ...existingFilters.filter( + // Exclude existing filters that match the newly added 'phraseFilter' + (filter) => !compareFilters(filter, phraseFilter, compareOptions) + ), + phraseFilter, + ]; + + return nextFilters; +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/index.ts similarity index 91% rename from x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils.ts rename to x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/index.ts index 1e330a00b4fcf..0f7a3b6f1477b 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { VectorScoreBase, Vector } from './types'; +import { VectorScoreBase, Vector } from '../types'; export const getVectorScoreList = (vectorBaseScore: VectorScoreBase) => { const result: Vector[] = []; @@ -24,7 +24,7 @@ export const getVectorScoreList = (vectorBaseScore: VectorScoreBase) => { if (v3Vector) { result.push({ - version: '2.0', + version: '3.0', vector: v3Vector, score: v3Score, }); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx index bc54dd08f5126..22142a5818731 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities.tsx @@ -9,8 +9,10 @@ import { EuiButtonIcon, EuiDataGrid, EuiDataGridCellValueElementProps, + EuiDataGridColumnCellAction, EuiProgress, EuiSpacer, + EuiToolTip, useEuiTheme, } from '@elastic/eui'; import { css } from '@emotion/react'; @@ -37,6 +39,8 @@ import { vulnerabilitiesColumns, } from './vulnerabilities_table_columns'; import { defaultLoadingRenderer, defaultNoDataRenderer } from '../../components/cloud_posture_page'; +import { getFilters } from './utils/get_filters'; +import { FILTER_IN, FILTER_OUT, SEARCH_BAR_PLACEHOLDER, VULNERABILITIES } from './translations'; const getDefaultQuery = ({ query, filters }: any): any => ({ query, @@ -77,6 +81,7 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => { onSort, setUrlQuery, onResetFilters, + urlQuery, } = useCloudPostureTable({ dataView, defaultQuery: getDefaultQuery, @@ -118,8 +123,87 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => { }); const columns = useMemo(() => { - return getVulnerabilitiesColumnsGrid(); - }, []); + const getColumnIdValue = (rowIndex: number, columnId: string) => { + const vulnerabilityRow = data?.page[rowIndex] as VulnerabilityRecord; + if (columnId === vulnerabilitiesColumns.vulnerability) { + return vulnerabilityRow.vulnerability.id; + } + if (columnId === vulnerabilitiesColumns.cvss) { + return vulnerabilityRow.vulnerability.score.base; + } + if (columnId === vulnerabilitiesColumns.resource) { + return vulnerabilityRow.resource?.name; + } + if (columnId === vulnerabilitiesColumns.severity) { + return vulnerabilityRow.vulnerability.severity; + } + if (columnId === vulnerabilitiesColumns.package_version) { + return vulnerabilityRow.vulnerability?.package?.name; + } + if (columnId === vulnerabilitiesColumns.fix_version) { + return vulnerabilityRow.vulnerability.package?.fixed_version; + } + }; + + const cellActions: EuiDataGridColumnCellAction[] = [ + ({ Component, rowIndex, columnId }) => { + const value = getColumnIdValue(rowIndex, columnId); + + if (!value) return null; + return ( + + { + setUrlQuery({ + pageIndex: 0, + filters: getFilters({ + filters: urlQuery.filters, + dataView, + field: columnId, + value, + negate: false, + }), + }); + }} + > + {FILTER_IN} + + + ); + }, + ({ Component, rowIndex, columnId }) => { + const value = getColumnIdValue(rowIndex, columnId); + + if (!value) return null; + return ( + + { + setUrlQuery({ + pageIndex: 0, + filters: getFilters({ + filters: urlQuery.filters, + dataView, + field: columnId, + value: getColumnIdValue(rowIndex, columnId), + negate: true, + }), + }); + }} + > + {FILTER_OUT} + + + ); + }, + ]; + + return getVulnerabilitiesColumnsGrid(cellActions); + }, [data?.page, dataView, setUrlQuery, urlQuery.filters]); const renderCellValue = useMemo(() => { return ({ rowIndex, columnId }: EuiDataGridCellValueElementProps) => { @@ -176,10 +260,13 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => { ); } if (columnId === vulnerabilitiesColumns.fix_version) { + if (!vulnerabilityRow.vulnerability.package?.fixed_version) { + return null; + } return ( <> {vulnerabilityRow.vulnerability.package?.name}{' '} - {vulnerabilityRow.vulnerability.package?.fixed_version} + {vulnerabilityRow.vulnerability.package.fixed_version} ); } @@ -207,6 +294,7 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => { setUrlQuery({ ...newQuery, pageIndex: 0 }); }} loading={isLoading} + placeholder={SEARCH_BAR_PLACEHOLDER} /> {!isLoading && data.page.length === 0 ? ( @@ -236,8 +324,16 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => { & .euiDataGridRowCell { font-size: ${euiTheme.size.m}; } + & + .euiDataGridRowCell__expandActions + > [data-test-subj='euiDataGridCellExpandButton'] { + display: none; + } + & .euiDataGridRowCell__expandFlex { + align-items: center; + } `} - aria-label="Data grid styling demo" + aria-label={VULNERABILITIES} columns={columns} columnVisibility={{ visibleColumns: columns.map(({ id }) => id), diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx index 547bec3c9b173..89a1232540a0d 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx @@ -114,11 +114,11 @@ const VulnerabilityOverviewTiles = ({ vulnerability }: VulnerabilityTabProps) => margin-bottom: 6px; `; - const date = moment(vulnerability?.published_at).format('LL').toString(); + const date = moment(vulnerability?.published_date).format('LL').toString(); return ( - {vulnerability?.score?.version && vulnerability?.score?.impact && ( + {vulnerability?.score?.version && vulnerability?.score?.base && (
- +
)} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_table_columns.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_table_columns.ts index 88a6a8e569c80..bfa23ac440cd6 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_table_columns.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_table_columns.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiDataGridColumn } from '@elastic/eui'; +import { EuiDataGridColumn, EuiDataGridColumnCellAction } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; export const vulnerabilitiesColumns = { @@ -27,7 +27,9 @@ const defaultColumnProps = () => ({ }, }); -export const getVulnerabilitiesColumnsGrid = (): EuiDataGridColumn[] => [ +export const getVulnerabilitiesColumnsGrid = ( + cellActions: EuiDataGridColumnCellAction[] +): EuiDataGridColumn[] => [ { ...defaultColumnProps(), id: vulnerabilitiesColumns.actions, @@ -36,6 +38,7 @@ export const getVulnerabilitiesColumnsGrid = (): EuiDataGridColumn[] => [ actions: false, isSortable: false, isResizable: false, + cellActions: [], }, { ...defaultColumnProps(), @@ -43,15 +46,16 @@ export const getVulnerabilitiesColumnsGrid = (): EuiDataGridColumn[] => [ displayAsText: i18n.translate('xpack.csp.vulnerabilityTable.column.vulnerability', { defaultMessage: 'Vulnerability', }), - initialWidth: 150, - isResizable: false, + initialWidth: 130, + cellActions, }, { ...defaultColumnProps(), id: vulnerabilitiesColumns.cvss, displayAsText: 'CVSS', - initialWidth: 84, + initialWidth: 80, isResizable: false, + cellActions, }, { ...defaultColumnProps(), @@ -59,6 +63,7 @@ export const getVulnerabilitiesColumnsGrid = (): EuiDataGridColumn[] => [ displayAsText: i18n.translate('xpack.csp.vulnerabilityTable.column.resource', { defaultMessage: 'Resource', }), + cellActions, }, { ...defaultColumnProps(), @@ -67,6 +72,7 @@ export const getVulnerabilitiesColumnsGrid = (): EuiDataGridColumn[] => [ defaultMessage: 'Severity', }), initialWidth: 100, + cellActions, }, { ...defaultColumnProps(), @@ -74,6 +80,7 @@ export const getVulnerabilitiesColumnsGrid = (): EuiDataGridColumn[] => [ displayAsText: i18n.translate('xpack.csp.vulnerabilityTable.column.packageAndVersion', { defaultMessage: 'Package and Version', }), + cellActions, }, { ...defaultColumnProps(), @@ -81,5 +88,6 @@ export const getVulnerabilitiesColumnsGrid = (): EuiDataGridColumn[] => [ displayAsText: i18n.translate('xpack.csp.vulnerabilityTable.column.fixVersion', { defaultMessage: 'Fix Version', }), + cellActions, }, ]; From e6f7af346863fc80900c357cb4267603301e7351 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Fri, 21 Apr 2023 11:53:14 +0200 Subject: [PATCH 45/67] [Security Solutions] Add telemetry to Security Solutions Cell Actions + Anomalies count (#154830) EPIC issue: https://github.com/elastic/kibana/issues/145276 ## Summary [Dashboard with both events](https://telemetry-v2-staging.elastic.dev/s/securitysolution/app/dashboards#/view/d47a90f0-a6e4-11ed-a6e6-d32d2209b7b7?_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-90d%2Fd%2Cto%3Anow))) Add 2 telemetry events. 1. Cell Action clicked ![Screenshot 2023-04-17 at 13 29 53](https://user-images.githubusercontent.com/1490444/232471951-6c0ca1a1-a81e-48d7-9a96-b750f0248e55.png) Event: ```json { "timestamp": "2023-03-20T14:23:53.452Z", "event_type": "Cell Action Clicked", "context": { ... }, "properties": { "metadata": { "telemetry": { "component": "RiskScoreTable" } }, "fieldName": "host.name", "displayName": "Show top host.name", "actionId": "security-default-cellActions-showTopN" } } ```
2. Anomalies count click (EA page) ![Screenshot 2023-04-17 at 13 30 19](https://user-images.githubusercontent.com/1490444/232471971-4b16b28f-497b-4de8-a8ff-d98a4fbf15cf.png) Event: ```json { "timestamp": "2023-03-20T14:23:53.452Z", "event_type": "Anomalies Count Clicked", "context": { ... }, "properties": { "jobId": "suspicious_login_activity", "count": 134 } } ``` ### 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 --- .../public/actions/register.ts | 73 +++++++++-------- .../public/actions/telemetry.test.ts | 45 +++++++++++ .../public/actions/telemetry.ts | 33 ++++++++ .../security_solution/public/actions/types.ts | 9 +++ .../lib/telemetry/telemetry_client.mock.ts | 2 + .../common/lib/telemetry/telemetry_client.ts | 10 +++ .../common/lib/telemetry/telemetry_events.ts | 56 +++++++++++++ .../public/common/lib/telemetry/types.ts | 32 +++++++- .../components/risk_score/constants.ts | 4 + .../host_risk_score_table/columns.tsx | 4 + .../user_risk_score_table/columns.tsx | 4 + .../anomalies/anomalies_count_link.test.tsx | 42 ++++++++++ .../anomalies/anomalies_count_link.tsx | 80 +++++++++++++++++++ .../entity_analytics/anomalies/columns.tsx | 71 +--------------- .../entity_analytics/risk_score/columns.tsx | 4 + .../security_solution/public/plugin.tsx | 5 +- 16 files changed, 370 insertions(+), 104 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/actions/telemetry.test.ts create mode 100644 x-pack/plugins/security_solution/public/actions/telemetry.ts create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.test.tsx create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.tsx diff --git a/x-pack/plugins/security_solution/public/actions/register.ts b/x-pack/plugins/security_solution/public/actions/register.ts index 0fafb561d45a1..2df581342f614 100644 --- a/x-pack/plugins/security_solution/public/actions/register.ts +++ b/x-pack/plugins/security_solution/public/actions/register.ts @@ -6,10 +6,9 @@ */ import { CELL_VALUE_TRIGGER } from '@kbn/embeddable-plugin/public'; -import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type * as H from 'history'; import type { SecurityAppStore } from '../common/store/types'; -import type { StartPlugins, StartServices } from '../types'; +import type { StartServices } from '../types'; import { createFilterInCellActionFactory, createFilterOutCellActionFactory } from './filter'; import { createAddToTimelineLensAction, @@ -23,18 +22,20 @@ import { import { createToggleColumnCellActionFactory } from './toggle_column'; import { SecurityCellActionsTrigger } from './constants'; import type { SecurityCellActionName, SecurityCellActions } from './types'; +import { enhanceActionWithTelemetry } from './telemetry'; export const registerUIActions = ( - { uiActions }: StartPlugins, store: SecurityAppStore, history: H.History, services: StartServices ) => { - registerLensActions(uiActions, store); - registerCellActions(uiActions, store, history, services); + registerLensActions(store, services); + registerCellActions(store, history, services); }; -const registerLensActions = (uiActions: UiActionsStart, store: SecurityAppStore) => { +const registerLensActions = (store: SecurityAppStore, services: StartServices) => { + const { uiActions } = services; + const addToTimelineAction = createAddToTimelineLensAction({ store, order: 1 }); uiActions.addTriggerAction(CELL_VALUE_TRIGGER, addToTimelineAction); @@ -43,7 +44,6 @@ const registerLensActions = (uiActions: UiActionsStart, store: SecurityAppStore) }; const registerCellActions = ( - uiActions: UiActionsStart, store: SecurityAppStore, history: H.History, services: StartServices @@ -57,37 +57,46 @@ const registerCellActions = ( toggleColumn: createToggleColumnCellActionFactory({ store }), }; - registerCellActionsTrigger(uiActions, SecurityCellActionsTrigger.DEFAULT, cellActions, [ - 'filterIn', - 'filterOut', - 'addToTimeline', - 'showTopN', - 'copyToClipboard', - ]); + registerCellActionsTrigger({ + triggerId: SecurityCellActionsTrigger.DEFAULT, + cellActions, + actionsOrder: ['filterIn', 'filterOut', 'addToTimeline', 'showTopN', 'copyToClipboard'], + services, + }); - registerCellActionsTrigger(uiActions, SecurityCellActionsTrigger.DETAILS_FLYOUT, cellActions, [ - 'filterIn', - 'filterOut', - 'addToTimeline', - 'toggleColumn', - 'showTopN', - 'copyToClipboard', - ]); + registerCellActionsTrigger({ + triggerId: SecurityCellActionsTrigger.DETAILS_FLYOUT, + cellActions, + actionsOrder: [ + 'filterIn', + 'filterOut', + 'addToTimeline', + 'toggleColumn', + 'showTopN', + 'copyToClipboard', + ], + services, + }); }; -const registerCellActionsTrigger = ( - uiActions: UiActionsStart, - triggerId: SecurityCellActionsTrigger, - cellActions: SecurityCellActions, - actionsOrder: SecurityCellActionName[] -) => { +const registerCellActionsTrigger = ({ + triggerId, + cellActions, + actionsOrder, + services, +}: { + triggerId: SecurityCellActionsTrigger; + cellActions: SecurityCellActions; + actionsOrder: SecurityCellActionName[]; + services: StartServices; +}) => { + const { uiActions } = services; uiActions.registerTrigger({ id: triggerId }); actionsOrder.forEach((actionName, order) => { const actionFactory = cellActions[actionName]; - uiActions.addTriggerAction( - triggerId, - actionFactory({ id: `${triggerId}-${actionName}`, order }) - ); + const action = actionFactory({ id: `${triggerId}-${actionName}`, order }); + + uiActions.addTriggerAction(triggerId, enhanceActionWithTelemetry(action, services)); }); }; diff --git a/x-pack/plugins/security_solution/public/actions/telemetry.test.ts b/x-pack/plugins/security_solution/public/actions/telemetry.test.ts new file mode 100644 index 0000000000000..82a691348a206 --- /dev/null +++ b/x-pack/plugins/security_solution/public/actions/telemetry.test.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 type { StartServices } from '../types'; +import { enhanceActionWithTelemetry } from './telemetry'; +import { createAction } from '@kbn/ui-actions-plugin/public'; +import type { CellActionExecutionContext } from '@kbn/cell-actions'; + +const actionId = 'test_action_id'; +const displayName = 'test-actions'; +const fieldName = 'test.user.name'; +const fieldValue = 'test value'; +const metadata = undefined; + +const action = createAction({ + id: actionId, + execute: async () => {}, + getIconType: () => 'test-icon', + getDisplayName: () => displayName, +}); +const context = { + field: { name: fieldName, value: fieldValue, type: 'text' }, + metadata, +} as CellActionExecutionContext; + +describe('enhanceActionWithTelemetry', () => { + it('calls telemetry report when the action is executed', () => { + const telemetry = { reportCellActionClicked: jest.fn() }; + const services = { telemetry } as unknown as StartServices; + + const enhancedAction = enhanceActionWithTelemetry(action, services); + enhancedAction.execute(context); + + expect(telemetry.reportCellActionClicked).toHaveBeenCalledWith({ + displayName, + actionId, + fieldName, + metadata, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/actions/telemetry.ts b/x-pack/plugins/security_solution/public/actions/telemetry.ts new file mode 100644 index 0000000000000..c051f11a81271 --- /dev/null +++ b/x-pack/plugins/security_solution/public/actions/telemetry.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 type { CellAction, CellActionExecutionContext } from '@kbn/cell-actions'; +import type { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; +import type { StartServices } from '../types'; +import type { SecurityCellActionExecutionContext } from './types'; + +export const enhanceActionWithTelemetry = ( + action: CellAction, + services: StartServices +): CellAction => { + const { telemetry } = services; + const { execute, ...rest } = action; + const enhancedExecute = ( + context: ActionExecutionContext + ): Promise => { + telemetry.reportCellActionClicked({ + actionId: rest.id, + displayName: rest.getDisplayName(context), + fieldName: context.field.name, + metadata: context.metadata, + }); + + return execute(context); + }; + + return { ...rest, execute: enhancedExecute }; +}; diff --git a/x-pack/plugins/security_solution/public/actions/types.ts b/x-pack/plugins/security_solution/public/actions/types.ts index da6b2cc3919a9..1d15896ae6b35 100644 --- a/x-pack/plugins/security_solution/public/actions/types.ts +++ b/x-pack/plugins/security_solution/public/actions/types.ts @@ -26,6 +26,15 @@ export interface SecurityMetadata extends Record { * and we need all the filtering actions to perform the opposite (negate) operation. */ negateFilters?: boolean; + /** + * `metadata.telemetry` is used by the telemetry service to add context to the event. + */ + telemetry?: { + /** + * It defines which UI component renders the CellActions. + */ + component: string; + }; } export interface SecurityCellActionExecutionContext extends CellActionExecutionContext { diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts index 4f2b2bdafe96f..9cd22c469160d 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts @@ -15,4 +15,6 @@ export const createTelemetryClientMock = (): jest.Mocked = reportEntityAlertsClicked: jest.fn(), reportEntityRiskFiltered: jest.fn(), reportMLJobUpdate: jest.fn(), + reportCellActionClicked: jest.fn(), + reportAnomaliesCountClicked: jest.fn(), }); diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts index c95c8153261e1..edd50340e873e 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts @@ -15,6 +15,8 @@ import type { ReportEntityAlertsClickedParams, ReportEntityRiskFilteredParams, ReportMLJobUpdateParams, + ReportCellActionClickedParams, + ReportAnomaliesCountClickedParams, } from './types'; import { TelemetryEventTypes } from './types'; @@ -88,4 +90,12 @@ export class TelemetryClient implements TelemetryClientStart { public reportMLJobUpdate = (params: ReportMLJobUpdateParams) => { this.analytics.reportEvent(TelemetryEventTypes.MLJobUpdate, params); }; + + public reportCellActionClicked = (params: ReportCellActionClickedParams) => { + this.analytics.reportEvent(TelemetryEventTypes.CellActionClicked, params); + }; + + public reportAnomaliesCountClicked = (params: ReportAnomaliesCountClickedParams) => { + this.analytics.reportEvent(TelemetryEventTypes.AnomaliesCountClicked, params); + }; } diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_events.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_events.ts index cb82b16658224..e69f90b57a1f0 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_events.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_events.ts @@ -182,6 +182,60 @@ const mlJobUpdateEvent: TelemetryEvent = { }, }; +const cellActionClickedEvent: TelemetryEvent = { + eventType: TelemetryEventTypes.CellActionClicked, + schema: { + fieldName: { + type: 'keyword', + _meta: { + description: 'Field Name', + optional: false, + }, + }, + actionId: { + type: 'keyword', + _meta: { + description: 'Action id', + optional: false, + }, + }, + displayName: { + type: 'keyword', + _meta: { + description: 'User friendly action name', + optional: false, + }, + }, + metadata: { + type: 'pass_through', + _meta: { + description: 'Action metadata', + optional: true, + }, + }, + }, +}; + +const anomaliesCountClickedEvent: TelemetryEvent = { + eventType: TelemetryEventTypes.AnomaliesCountClicked, + schema: { + jobId: { + type: 'keyword', + _meta: { + description: 'Job id', + optional: false, + }, + }, + count: { + type: 'integer', + _meta: { + description: 'Number of anomalies', + optional: false, + }, + }, + }, +}; + export const telemetryEvents = [ alertsGroupingToggledEvent, alertsGroupingChangedEvent, @@ -190,4 +244,6 @@ export const telemetryEvents = [ entityAlertsClickedEvent, entityRiskFilteredEvent, mlJobUpdateEvent, + cellActionClickedEvent, + anomaliesCountClickedEvent, ]; diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts index ba084c49cc033..f9f7c84c488ed 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts @@ -8,6 +8,7 @@ import type { RootSchema } from '@kbn/analytics-client'; import type { AnalyticsServiceSetup } from '@kbn/core/public'; import type { RiskSeverity } from '../../../../common/search_strategy'; +import type { SecurityMetadata } from '../../../actions/types'; export interface TelemetryServiceSetupParams { analytics: AnalyticsServiceSetup; @@ -21,6 +22,8 @@ export enum TelemetryEventTypes { EntityAlertsClicked = 'Entity Alerts Clicked', EntityRiskFiltered = 'Entity Risk Filtered', MLJobUpdate = 'ML Job Update', + CellActionClicked = 'Cell Action Clicked', + AnomaliesCountClicked = 'Anomalies Count Clicked', } export interface ReportAlertsGroupingChangedParams { @@ -69,6 +72,18 @@ export interface ReportMLJobUpdateParams { errorMessage?: string; } +export interface ReportCellActionClickedParams { + metadata: SecurityMetadata | undefined; + displayName: string; + actionId: string; + fieldName: string; +} + +export interface ReportAnomaliesCountClickedParams { + jobId: string; + count: number; +} + export type TelemetryEventParams = | ReportAlertsGroupingChangedParams | ReportAlertsGroupingToggledParams @@ -76,7 +91,10 @@ export type TelemetryEventParams = | ReportEntityDetailsClickedParams | ReportEntityAlertsClickedParams | ReportEntityRiskFilteredParams - | ReportMLJobUpdateParams; + | ReportMLJobUpdateParams + | ReportCellActionClickedParams + | ReportCellActionClickedParams + | ReportAnomaliesCountClickedParams; export interface TelemetryClientStart { reportAlertsGroupingChanged(params: ReportAlertsGroupingChangedParams): void; @@ -87,6 +105,10 @@ export interface TelemetryClientStart { reportEntityAlertsClicked(params: ReportEntityAlertsClickedParams): void; reportEntityRiskFiltered(params: ReportEntityRiskFilteredParams): void; reportMLJobUpdate(params: ReportMLJobUpdateParams): void; + + reportCellActionClicked(params: ReportCellActionClickedParams): void; + + reportAnomaliesCountClicked(params: ReportAnomaliesCountClickedParams): void; } export type TelemetryEvent = @@ -117,4 +139,12 @@ export type TelemetryEvent = | { eventType: TelemetryEventTypes.MLJobUpdate; schema: RootSchema; + } + | { + eventType: TelemetryEventTypes.CellActionClicked; + schema: RootSchema; + } + | { + eventType: TelemetryEventTypes.AnomaliesCountClicked; + schema: RootSchema; }; diff --git a/x-pack/plugins/security_solution/public/explore/components/risk_score/constants.ts b/x-pack/plugins/security_solution/public/explore/components/risk_score/constants.ts index 7f2a8e37b94b0..d6147328d6be2 100644 --- a/x-pack/plugins/security_solution/public/explore/components/risk_score/constants.ts +++ b/x-pack/plugins/security_solution/public/explore/components/risk_score/constants.ts @@ -7,3 +7,7 @@ export const RISKY_HOSTS_DASHBOARD_TITLE = 'Current Risk Score for Hosts'; export const RISKY_USERS_DASHBOARD_TITLE = 'Current Risk Score for Users'; + +export const CELL_ACTIONS_TELEMETRY = { + component: 'RiskScoreTable', +}; diff --git a/x-pack/plugins/security_solution/public/explore/hosts/components/host_risk_score_table/columns.tsx b/x-pack/plugins/security_solution/public/explore/hosts/components/host_risk_score_table/columns.tsx index f1a489ed83b73..1c95938c106ad 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/components/host_risk_score_table/columns.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/components/host_risk_score_table/columns.tsx @@ -21,6 +21,7 @@ import type { RiskSeverity } from '../../../../../common/search_strategy'; import { RiskScoreFields, RiskScoreEntity } from '../../../../../common/search_strategy'; import { RiskScore } from '../../../components/risk_score/severity/common'; import { ENTITY_RISK_CLASSIFICATION } from '../../../components/risk_score/translations'; +import { CELL_ACTIONS_TELEMETRY } from '../../../components/risk_score/constants'; export const getHostRiskScoreColumns = ({ dispatchSeverityUpdate, @@ -47,6 +48,9 @@ export const getHostRiskScoreColumns = ({ type: 'keyword', aggregatable: true, }} + metadata={{ + telemetry: CELL_ACTIONS_TELEMETRY, + }} > diff --git a/x-pack/plugins/security_solution/public/explore/users/components/user_risk_score_table/columns.tsx b/x-pack/plugins/security_solution/public/explore/users/components/user_risk_score_table/columns.tsx index cf122adc0c397..d8d5abb4981c0 100644 --- a/x-pack/plugins/security_solution/public/explore/users/components/user_risk_score_table/columns.tsx +++ b/x-pack/plugins/security_solution/public/explore/users/components/user_risk_score_table/columns.tsx @@ -22,6 +22,7 @@ import { RiskScoreEntity, RiskScoreFields } from '../../../../../common/search_s import { UserDetailsLink } from '../../../../common/components/links'; import { UsersTableType } from '../../store/model'; import { ENTITY_RISK_CLASSIFICATION } from '../../../components/risk_score/translations'; +import { CELL_ACTIONS_TELEMETRY } from '../../../components/risk_score/constants'; export const getUserRiskScoreColumns = ({ dispatchSeverityUpdate, @@ -50,6 +51,9 @@ export const getUserRiskScoreColumns = ({ type: 'keyword', aggregatable: true, }} + metadata={{ + telemetry: CELL_ACTIONS_TELEMETRY, + }} > diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.test.tsx new file mode 100644 index 0000000000000..4ce4b6810ec98 --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.test.tsx @@ -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 { fireEvent, render } from '@testing-library/react'; +import React from 'react'; +import { AnomalyEntity } from '../../../../common/components/ml/anomaly/use_anomalies_search'; +import { createTelemetryServiceMock } from '../../../../common/lib/telemetry/telemetry_service.mock'; +import { TestProviders } from '../../../../common/mock'; +import { AnomaliesCountLink } from './anomalies_count_link'; + +const mockedTelemetry = createTelemetryServiceMock(); +jest.mock('../../../../common/lib/kibana', () => { + const original = jest.requireActual('../../../../common/lib/kibana'); + + return { + ...original, + useKibana: () => ({ + services: { + telemetry: mockedTelemetry, + }, + }), + }; +}); + +describe('AnomaliesCountLink', () => { + it('reports telemetry when clicked', () => { + const count = 10; + const jobId = 'test-job-id'; + + const { getByRole } = render( + , + { wrapper: TestProviders } + ); + + fireEvent.click(getByRole('button')); + + expect(mockedTelemetry.reportAnomaliesCountClicked).toHaveBeenLastCalledWith({ jobId, count }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.tsx new file mode 100644 index 0000000000000..529f197c62e44 --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { useDispatch } from 'react-redux'; +import React, { useCallback } from 'react'; +import { AnomalyEntity } from '../../../../common/components/ml/anomaly/use_anomalies_search'; +import { SecuritySolutionLinkAnchor } from '../../../../common/components/links'; +import { SecurityPageName } from '../../../../app/types'; +import { usersActions } from '../../../../explore/users/store'; +import { hostsActions } from '../../../../explore/hosts/store'; +import { HostsType } from '../../../../explore/hosts/store/model'; +import { UsersType } from '../../../../explore/users/store/model'; + +import { useKibana } from '../../../../common/lib/kibana'; + +export const AnomaliesCountLink = ({ + count, + jobId, + entity, +}: { + count: number; + jobId?: string; + entity: AnomalyEntity; +}) => { + const dispatch = useDispatch(); + const { telemetry } = useKibana().services; + + const deepLinkId = + entity === AnomalyEntity.User + ? SecurityPageName.usersAnomalies + : SecurityPageName.hostsAnomalies; + + const onClick = useCallback(() => { + if (!jobId) return; + + telemetry.reportAnomaliesCountClicked({ + jobId, + count, + }); + + if (entity === AnomalyEntity.User) { + dispatch( + usersActions.updateUsersAnomaliesJobIdFilter({ + jobIds: [jobId], + usersType: UsersType.page, + }) + ); + + dispatch( + usersActions.updateUsersAnomaliesInterval({ + interval: 'second', + usersType: UsersType.page, + }) + ); + } else { + dispatch( + hostsActions.updateHostsAnomaliesJobIdFilter({ + jobIds: [jobId], + hostsType: HostsType.page, + }) + ); + + dispatch( + hostsActions.updateHostsAnomaliesInterval({ + interval: 'second', + hostsType: HostsType.page, + }) + ); + } + }, [jobId, telemetry, count, entity, dispatch]); + + return ( + + {count} + + ); +}; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx index 92f8751782fa6..1f8556dd27a98 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx @@ -8,24 +8,16 @@ import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import type { EuiBasicTableColumn } from '@elastic/eui'; import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; -import { useDispatch } from 'react-redux'; - import * as i18n from './translations'; import type { AnomaliesCount } from '../../../../common/components/ml/anomaly/use_anomalies_search'; -import { AnomalyEntity } from '../../../../common/components/ml/anomaly/use_anomalies_search'; - -import { LinkAnchor, SecuritySolutionLinkAnchor } from '../../../../common/components/links'; -import { SecurityPageName } from '../../../../app/types'; -import { usersActions } from '../../../../explore/users/store'; -import { hostsActions } from '../../../../explore/hosts/store'; -import { HostsType } from '../../../../explore/hosts/store/model'; -import { UsersType } from '../../../../explore/users/store/model'; +import { LinkAnchor } from '../../../../common/components/links'; import type { SecurityJob } from '../../../../common/components/ml_popover/types'; import { isJobFailed, isJobStarted, isJobLoading, } from '../../../../../common/machine_learning/helpers'; +import { AnomaliesCountLink } from './anomalies_count_link'; type AnomaliesColumns = Array>; @@ -76,7 +68,7 @@ export const useAnomaliesColumns = ( if (!job) return ''; if (count > 0 || isJobStarted(job.jobState, job.datafeedState)) { - return ; + return ; } else if (isJobFailed(job.jobState, job.datafeedState)) { return i18n.JOB_STATUS_FAILED; } else if (job.isCompatible) { @@ -111,60 +103,3 @@ const EnableJob = ({ ); }; - -const AnomaliesTabLink = ({ - count, - jobId, - entity, -}: { - count: number; - jobId?: string; - entity: AnomalyEntity; -}) => { - const dispatch = useDispatch(); - - const deepLinkId = - entity === AnomalyEntity.User - ? SecurityPageName.usersAnomalies - : SecurityPageName.hostsAnomalies; - - const onClick = useCallback(() => { - if (!jobId) return; - - if (entity === AnomalyEntity.User) { - dispatch( - usersActions.updateUsersAnomaliesJobIdFilter({ - jobIds: [jobId], - usersType: UsersType.page, - }) - ); - - dispatch( - usersActions.updateUsersAnomaliesInterval({ - interval: 'second', - usersType: UsersType.page, - }) - ); - } else { - dispatch( - hostsActions.updateHostsAnomaliesJobIdFilter({ - jobIds: [jobId], - hostsType: HostsType.page, - }) - ); - - dispatch( - hostsActions.updateHostsAnomaliesInterval({ - interval: 'second', - hostsType: HostsType.page, - }) - ); - } - }, [jobId, dispatch, entity]); - - return ( - - {count} - - ); -}; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx index 7cd355e967974..eee34e79d99c6 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx @@ -14,6 +14,7 @@ import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { HostDetailsLink, UserDetailsLink } from '../../../../common/components/links'; import { HostsTableType } from '../../../../explore/hosts/store/model'; import { RiskScore } from '../../../../explore/components/risk_score/severity/common'; +import { CELL_ACTIONS_TELEMETRY } from '../../../../explore/components/risk_score/constants'; import type { HostRiskScore, RiskSeverity, @@ -64,6 +65,9 @@ export const getRiskScoreColumns = ( SecurityCellActionType.FILTER, SecurityCellActionType.SHOW_TOP_N, ]} + metadata={{ + telemetry: CELL_ACTIONS_TELEMETRY, + }} /> ) : ( diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 9553b2695f95c..61ec29d956414 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -194,7 +194,7 @@ export class Plugin implements IPlugin Date: Fri, 21 Apr 2023 14:02:03 +0300 Subject: [PATCH 46/67] [Lens] Tests trendline data from inspector (#155479) ## Summary Part of https://github.com/elastic/kibana/issues/142708 Adds more tests for trendlines depending on the inspector table. The issue above mentions retrieving the information from EC debug data but I think fetching the data from the inspector is a good alternative and is used in general in our FTs. Test runner 50 times https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2152 ### Checklist - [ ] [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 --- test/functional/services/inspector.ts | 13 +++ .../functional/apps/lens/group3/metric.ts | 81 ++++++++++++++++++- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/test/functional/services/inspector.ts b/test/functional/services/inspector.ts index c3f357ea3875b..cee65bf1c4b52 100644 --- a/test/functional/services/inspector.ts +++ b/test/functional/services/inspector.ts @@ -238,6 +238,19 @@ export class InspectorService extends FtrService { }); } + public async getTableDataWithId(tableTestSubj: string): Promise { + const chooserDataTestId = 'inspectorTableChooser'; + if (!(await this.testSubjects.exists(chooserDataTestId))) { + return []; + } + + return await this.retry.try(async () => { + await this.testSubjects.click(chooserDataTestId); + await this.testSubjects.click(tableTestSubj); + return this.getTableData(); + }); + } + /** * Returns the selected option value from combobox */ diff --git a/x-pack/test/functional/apps/lens/group3/metric.ts b/x-pack/test/functional/apps/lens/group3/metric.ts index 843422e87c51f..f5ef312c39d2d 100644 --- a/x-pack/test/functional/apps/lens/group3/metric.ts +++ b/x-pack/test/functional/apps/lens/group3/metric.ts @@ -15,6 +15,52 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const inspector = getService('inspector'); + const inspectorTrendlineData = [ + ['2015-09-19 06:00', 'null'], + ['2015-09-19 09:00', 'null'], + ['2015-09-19 12:00', 'null'], + ['2015-09-19 15:00', 'null'], + ['2015-09-19 18:00', 'null'], + ['2015-09-19 21:00', 'null'], + ['2015-09-20 00:00', '6,011.351'], + ['2015-09-20 03:00', '5,849.901'], + ['2015-09-20 06:00', '5,722.622'], + ['2015-09-20 09:00', '5,769.092'], + ['2015-09-20 12:00', '5,740.875'], + ['2015-09-20 15:00', '5,520.429'], + ['2015-09-20 18:00', '5,153.053'], + ['2015-09-20 21:00', '6,656.581'], + ['2015-09-21 00:00', '5,139.357'], + ['2015-09-21 03:00', '5,884.891'], + ['2015-09-21 06:00', '5,683.283'], + ['2015-09-21 09:00', '5,863.661'], + ['2015-09-21 12:00', '5,657.531'], + ['2015-09-21 15:00', '5,841.935'], + ]; + + const inspectorExpectedTrenlineDataWithBreakdown = [ + ['97.220.3.248', '2015-09-19 06:00', 'null'], + ['97.220.3.248', '2015-09-19 09:00', 'null'], + ['97.220.3.248', '2015-09-19 12:00', 'null'], + ['97.220.3.248', '2015-09-19 15:00', 'null'], + ['97.220.3.248', '2015-09-19 18:00', 'null'], + ['97.220.3.248', '2015-09-19 21:00', 'null'], + ['97.220.3.248', '2015-09-20 00:00', 'null'], + ['97.220.3.248', '2015-09-20 03:00', 'null'], + ['97.220.3.248', '2015-09-20 06:00', 'null'], + ['97.220.3.248', '2015-09-20 09:00', 'null'], + ['97.220.3.248', '2015-09-20 12:00', 'null'], + ['97.220.3.248', '2015-09-20 15:00', 'null'], + ['97.220.3.248', '2015-09-20 18:00', 'null'], + ['97.220.3.248', '2015-09-20 21:00', 'null'], + ['97.220.3.248', '2015-09-21 00:00', 'null'], + ['97.220.3.248', '2015-09-21 03:00', 'null'], + ['97.220.3.248', '2015-09-21 06:00', 'null'], + ['97.220.3.248', '2015-09-21 09:00', '19,755'], + ['97.220.3.248', '2015-09-21 12:00', 'null'], + ['97.220.3.248', '2015-09-21 15:00', 'null'], + ]; + const clickMetric = async (title: string) => { const tiles = await PageObjects.lens.getMetricTiles(); for (const tile of tiles) { @@ -47,7 +93,33 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); expect((await PageObjects.lens.getMetricVisualizationData()).length).to.be.equal(1); + }); + + it('should enable trendlines', async () => { + // trendline data without the breakdown + await PageObjects.lens.openDimensionEditor( + 'lnsMetric_primaryMetricDimensionPanel > lns-dimensionTrigger' + ); + await testSubjects.click('lnsMetric_supporting_visualization_trendline'); + await PageObjects.lens.closeDimensionEditor(); + + await inspector.open('lnsApp_inspectButton'); + + const trendLineData = await inspector.getTableDataWithId('inspectorTableChooser1'); + expect(trendLineData).to.eql(inspectorTrendlineData); + await inspector.close(); + await PageObjects.lens.openDimensionEditor( + 'lnsMetric_primaryMetricDimensionPanel > lns-dimensionTrigger' + ); + await testSubjects.click('lnsMetric_supporting_visualization_none'); + await PageObjects.lens.closeDimensionEditor(); + + await PageObjects.lens.waitForVisualization('mtrVis'); + }); + + it('should enable metric with breakdown', async () => { + // trendline data without the breakdown await PageObjects.lens.configureDimension({ dimension: 'lnsMetric_breakdownByDimensionPanel > lns-empty-dimension', operation: 'terms', @@ -112,7 +184,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { showingBar: false, }, ]); + }); + it('should enable bar with max dimension', async () => { await PageObjects.lens.openDimensionEditor( 'lnsMetric_maxDimensionPanel > lns-empty-dimension' ); @@ -125,7 +199,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.lens.removeDimension('lnsMetric_maxDimensionPanel'); }); - it('should enable trendlines', async () => { + it('should enable trendlines with breakdown', async () => { await PageObjects.lens.openDimensionEditor( 'lnsMetric_primaryMetricDimensionPanel > lns-dimensionTrigger' ); @@ -139,11 +213,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { (datum) => datum.showingTrendline ) ).to.be(true); + await PageObjects.lens.closeDimensionEditor(); await inspector.open('lnsApp_inspectButton'); - expect(await inspector.getNumberOfTables()).to.equal(2); - + const trendLineData = await inspector.getTableDataWithId('inspectorTableChooser1'); + expect(trendLineData).to.eql(inspectorExpectedTrenlineDataWithBreakdown); await inspector.close(); await PageObjects.lens.openDimensionEditor( From 134af87114a9186ce453e5414e32e89946bf6d82 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Fri, 21 Apr 2023 13:56:21 +0200 Subject: [PATCH 47/67] [Enterprise Search] Add empty prompt for hidden indices (#155391) ## Summary This adds an empty prompt to the index overview page for hidden indices. Screenshot 2023-04-20 at 14 50 06 ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [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 - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### 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) --------- Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com> --- .../search_index/generate_api_key_panel.tsx | 115 +++++++++++------- .../search_index/index_view_logic.test.ts | 1 + .../search_index/index_view_logic.ts | 6 + 3 files changed, 76 insertions(+), 46 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/generate_api_key_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/generate_api_key_panel.tsx index 40cdda3aaae32..365593c4699c3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/generate_api_key_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/generate_api_key_panel.tsx @@ -9,7 +9,15 @@ import React, { useState } from 'react'; import { useActions, useValues } from 'kea'; -import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiSwitch, EuiTitle } from '@elastic/eui'; +import { + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiSwitch, + EuiTitle, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -27,7 +35,7 @@ import { OverviewLogic } from './overview.logic'; export const GenerateApiKeyPanel: React.FC = () => { const { apiKey, isGenerateModalOpen } = useValues(OverviewLogic); - const { indexName, ingestionMethod } = useValues(IndexViewLogic); + const { indexName, ingestionMethod, isHiddenIndex } = useValues(IndexViewLogic); const { closeGenerateModal } = useActions(OverviewLogic); const { defaultPipeline } = useValues(SettingsLogic); @@ -41,11 +49,29 @@ export const GenerateApiKeyPanel: React.FC = () => { - - - - - {indexName[0] !== '.' && ( + {isHiddenIndex ? ( + + {i18n.translate('xpack.enterpriseSearch.content.overview.emptyPrompt.body', { + defaultMessage: + 'We do not recommend adding documents to an externally managed index.', + })} +

+ } + title={ +

+ {i18n.translate('xpack.enterpriseSearch.content.overview.emptyPrompt.title', { + defaultMessage: 'Index managed externally', + })} +

+ } + /> + ) : ( + + + +

{i18n.translate( @@ -54,45 +80,42 @@ export const GenerateApiKeyPanel: React.FC = () => { )}

- )} -
- - setOptimizedRequest(event.target.checked)} - label={i18n.translate( - 'xpack.enterpriseSearch.content.overview.optimizedRequest.label', - { defaultMessage: 'View Enterprise Search optimized request' } - )} - checked={optimizedRequest} - /> - - - - - - - - - - - -
-
- {indexName[0] !== '.' && ( - <> - - - - - - )} -
+
+ + setOptimizedRequest(event.target.checked)} + label={i18n.translate( + 'xpack.enterpriseSearch.content.overview.optimizedRequest.label', + { defaultMessage: 'View Enterprise Search optimized request' } + )} + checked={optimizedRequest} + /> + + + + + + + + + + + +
+
+ + + + + +
+ )}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts index 5e4256f92a97b..e440947cdfb5c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts @@ -51,6 +51,7 @@ const DEFAULT_VALUES = { ingestionStatus: IngestionStatus.CONNECTED, isCanceling: false, isConnectorIndex: false, + isHiddenIndex: false, isInitialLoading: false, isSyncing: false, isWaitingForSync: false, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts index b89e122f03c7f..cd33656062fdb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts @@ -77,6 +77,7 @@ export interface IndexViewValues { ingestionMethod: IngestionMethod; ingestionStatus: IngestionStatus; isCanceling: boolean; + isHiddenIndex: boolean; isInitialLoading: typeof CachedFetchIndexApiLogic.values.isInitialLoading; isSyncing: boolean; isWaitingForSync: boolean; @@ -234,6 +235,11 @@ export const IndexViewLogic = kea [selectors.indexData], (data: FetchIndexApiResponse | undefined) => isConnectorIndex(data), ], + isHiddenIndex: [ + () => [selectors.indexData], + (data: FetchIndexApiResponse | undefined) => + data?.hidden || (data?.name ?? '').startsWith('.'), + ], isSyncing: [ () => [selectors.indexData, selectors.syncStatus], (indexData: FetchIndexApiResponse | null, syncStatus: SyncStatus) => From 9e712bb6fec3e2fe882cfa2342efbf0ee28d2441 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Fri, 21 Apr 2023 13:57:34 +0200 Subject: [PATCH 48/67] [Enterprise Search] Replace connectors ruby references with python (#155374) ## Summary This replaces all references to the Ruby repository with references to the Python repository for connectors. --------- Co-authored-by: Navarone Feekery <13634519+navarone-feekery@users.noreply.github.com> Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com> --- .../search_index/connector/connector_configuration.tsx | 8 ++++---- .../connector/custom_connector_configuration_config.tsx | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx index 90972bcfc3c51..0abd1ba5b4afe 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx @@ -124,7 +124,7 @@ export const ConnectorConfiguration: React.FC = () => { values={{ link: ( @@ -145,7 +145,7 @@ export const ConnectorConfiguration: React.FC = () => { values={{ link: ( @@ -356,7 +356,7 @@ export const ConnectorConfiguration: React.FC = () => {
{i18n.translate( @@ -382,7 +382,7 @@ export const ConnectorConfiguration: React.FC = () => { {i18n.translate( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/custom_connector_configuration_config.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/custom_connector_configuration_config.tsx index 58be80f2caa15..6eb991df1cdf9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/custom_connector_configuration_config.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/custom_connector_configuration_config.tsx @@ -18,16 +18,16 @@ export const ConnectorConfigurationConfig: React.FC = () => { {i18n.translate( 'xpack.enterpriseSearch.content.indices.configurationConnector.config.connectorClientLink', - { defaultMessage: 'example connector client' } + { defaultMessage: 'connectors' } )} ), @@ -56,7 +56,7 @@ export const ConnectorConfigurationConfig: React.FC = () => { ), issuesLink: ( - + {i18n.translate( 'xpack.enterpriseSearch.content.indices.configurationConnector.config.issuesLink', { defaultMessage: 'issue' } From 60c8b8fecd45bdcf833d17d1f7a2e1cab75f5e85 Mon Sep 17 00:00:00 2001 From: Achyut Jhunjhunwala Date: Fri, 21 Apr 2023 14:16:19 +0200 Subject: [PATCH 49/67] [APM] Implement Unified Search for APM (#153842) ## Summary This PR brings Unified Search to APM https://github.com/elastic/kibana/issues/152147 ## Scope of Implementation 1. We are only adding the search capability for now. 2. Filters and Saved queries are not part of this scope ### Pending Items - [x] Add Unit tests - [x] Fix existing broken Unit tests - [x] Fix existing broken Cy tests - https://github.com/elastic/kibana/pull/154059 - [x] Replace the search bar for mobile - [x] Work on feedback after deploying this branch - [x] Add validation for Free Text. Awaiting - https://github.com/elastic/kibana/issues/154239 - [x] Add logic to pass custom filters to Unified Search. Awaiting - https://github.com/elastic/kibana/issues/154437 ### Pages using Unified Search - [x] Service Inventory - [x] Service Map - [x] Service Overview - [x] Transactions Overview - [x] Errors - [x] Trace Overview - [x] Dependencies Inventory - [x] Agent Explorer - [x] Storage Explorer ### Pages still using old Custom Implementation - [ ] Trace Explorer - Out of scope for this PR - [ ] Service Group - Changing this logic could take some additional time as this would mean we allowing our SearchBar component to accept a custom submit function which does not updates the URL, as in every other implementation, we update the URL. I would mark this as a follow up ticket/stretch goal - https://github.com/elastic/kibana/issues/154320 - [x] Alerts - ~~It uses a Custom Search bar built by Actionable Obs team. Not sure if it's in this scope.~~ Seems they are already using US ## Things to consider - [x] ~~What do we do with the old components - `KueryBar` and `ApmDatePicker`. Should we delete them ?~~ The existing component will stay as its still used in Certain places, see `Pages still using old Custom Implementation` of this PR - [x] Other implementation are supporting Free Text Search, hence this one is too and is not checking for valid KQL. I hope my understanding is correct here - If needed, then awaiting - https://github.com/elastic/kibana/issues/154239 [Update] - We will add validations for free text to replicate the previous behaviour which we had with our own custom implementation - [ ] The UX of the search bar is a bit off when it comes to long placeholders. May be we need a shorter text for placeholder - @boriskirov ? - [x] ~~When navigating from Service Inventory page to Traces or Dependencies page, we are only persisting URL state for the selected time range. Shouldn't KQL query be also part of it. If yes, that would a stretch goal but good thing to consider.~~ @gbamparop @sqren - As discussed during the demo, we will keep this functionality as it is, which means while navigating to a different page, the search bar will be reset, but the when navigating between tabs on the same page, search bar will persist. - [x] ~~On the Initial page load, the Unified Search Bar Input box does not loads immediately. You only see the DateTimePicker and the button. Once the component has completely loaded, only then the text box appear. I see other pages like Log Streams brings up a Full Page loader unless the component has loaded and then once certain things have loaded, they remove the full page loader and start showing the search bar. Opinion ?~~ @boriskirov @gbamparop @sqren - Added a EUI Skeleton to handle this issue. https://user-images.githubusercontent.com/7416358/228291762-0ca55e9a-7de9-4312-aa58-f484441430ce.mov --------- Co-authored-by: Katerina Patticha --- x-pack/plugins/apm/common/dependencies.ts | 6 +- .../storage_explorer/storage_explorer.cy.ts | 9 - .../e2e/read_only_user/deep_links.cy.ts | 12 + .../read_only_user/errors/errors_page.cy.ts | 6 +- .../header_filters/header_filters.cy.ts | 2 +- .../service_inventory/service_inventory.cy.ts | 9 - .../service_overview/errors_table.cy.ts | 4 +- .../service_overview/header_filters.cy.ts | 24 +- .../service_overview/time_comparison.cy.ts | 2 +- .../transaction_details/span_links.cy.ts | 12 +- .../app/dependencies_inventory/index.tsx | 13 +- .../components/app/mobile/search_bar.tsx | 24 +- .../components/app/service_map/index.tsx | 2 +- .../app/settings/agent_explorer/index.tsx | 9 +- .../routing/mobile_service_detail/index.tsx | 2 +- .../routing/service_detail/index.tsx | 6 +- .../templates/dependency_detail_template.tsx | 15 +- .../shared/{kuery_bar => }/get_bool_filter.ts | 10 +- .../components/shared/kuery_bar/index.tsx | 4 +- .../shared/search_bar/search_bar.test.tsx | 14 + .../shared/search_bar/search_bar.tsx | 29 +- .../shared/unified_search_bar/index.tsx | 248 ++++++++++++++++++ .../unified_search_bar.test.tsx | 138 ++++++++++ .../apm_plugin/mock_apm_plugin_context.tsx | 7 + .../use_processor_event.ts | 2 +- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 28 files changed, 500 insertions(+), 112 deletions(-) rename x-pack/plugins/apm/public/components/shared/{kuery_bar => }/get_bool_filter.ts (85%) create mode 100644 x-pack/plugins/apm/public/components/shared/unified_search_bar/index.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/unified_search_bar/unified_search_bar.test.tsx rename x-pack/plugins/apm/public/{components/shared/kuery_bar => hooks}/use_processor_event.ts (94%) diff --git a/x-pack/plugins/apm/common/dependencies.ts b/x-pack/plugins/apm/common/dependencies.ts index 6228d4b9d279d..812bb33342ba5 100644 --- a/x-pack/plugins/apm/common/dependencies.ts +++ b/x-pack/plugins/apm/common/dependencies.ts @@ -13,14 +13,14 @@ import { } from './es_fields/apm'; import { environmentQuery } from './utils/environment_query'; -export const kueryBarPlaceholder = i18n.translate( - 'xpack.apm.dependencies.kueryBarPlaceholder', +export const unifiedSearchBarPlaceholder = i18n.translate( + 'xpack.apm.dependencies.unifiedSearchBarPlaceholder', { defaultMessage: `Search dependency metrics (e.g. span.destination.service.resource:elasticsearch)`, } ); -export const getKueryBarBoolFilter = ({ +export const getSearchBarBoolFilter = ({ dependencyName, environment, }: { diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts index 3fdeff2e406a8..88d4408d33152 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts @@ -140,12 +140,6 @@ describe('Storage Explorer', () => { }); }); - it('when clicking the refresh button', () => { - cy.wait(mainAliasNames); - cy.contains('Refresh').click(); - cy.wait(mainAliasNames); - }); - it('when selecting a different time range and clicking the update button', () => { cy.wait(mainAliasNames); @@ -155,9 +149,6 @@ describe('Storage Explorer', () => { ); cy.contains('Update').click(); cy.wait(mainAliasNames); - - cy.contains('Refresh').click(); - cy.wait(mainAliasNames); }); it('with the correct lifecycle phase when changing the lifecycle phase', () => { diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts index 2ae901be32fbf..5451a0df2d845 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts @@ -17,6 +17,8 @@ describe('APM deep links', () => { cy.contains('APM / Service groups'); cy.contains('APM / Traces'); cy.contains('APM / Service Map'); + cy.contains('APM / Dependencies'); + cy.contains('APM / Settings'); // navigates to home page // Force click because welcome screen changes @@ -43,5 +45,15 @@ describe('APM deep links', () => { // navigates to service maps cy.contains('APM / Service Map').click({ force: true }); cy.url().should('include', '/apm/service-map'); + + cy.getByTestSubj('nav-search-input').type('APM'); + // navigates to dependencies page + cy.contains('APM / Dependencies').click({ force: true }); + cy.url().should('include', '/apm/dependencies/inventory'); + + cy.getByTestSubj('nav-search-input').type('APM'); + // navigates to settings page + cy.contains('APM / Settings').click({ force: true }); + cy.url().should('include', '/apm/settings/general-settings'); }); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts index bc6f872d76fcc..a64dc157d4037 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts @@ -81,16 +81,16 @@ describe('Errors page', () => { cy.contains('div', 'Error 1'); }); - it('clicking on type adds a filter in the kuerybar', () => { + it('clicking on type adds a filter in the searchbar', () => { cy.visitKibana(javaServiceErrorsPageHref); - cy.getByTestSubj('headerFilterKuerybar') + cy.getByTestSubj('apmUnifiedSearchBar') .invoke('val') .should('be.empty'); // `force: true` because Cypress says the element is 0x0 cy.contains('exception 0').click({ force: true, }); - cy.getByTestSubj('headerFilterKuerybar') + cy.getByTestSubj('apmUnifiedSearchBar') .its('length') .should('be.gt', 0); cy.get('table') diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts index e689e126d4bfd..8e30f2784eb34 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts @@ -45,7 +45,7 @@ describe('Service inventory - header filters', () => { cy.contains('Services'); cy.contains('opbeans-node'); cy.contains('service 1'); - cy.getByTestSubj('headerFilterKuerybar') + cy.getByTestSubj('apmUnifiedSearchBar') .type(`service.name: "${specialServiceName}"`) .type('{enter}'); cy.contains('service 1'); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts index 2d40c690a8c92..032409ec56d40 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts @@ -103,12 +103,6 @@ describe('Service inventory', () => { }); }); - it('when clicking the refresh button', () => { - cy.wait(mainAliasNames); - cy.contains('Refresh').click(); - cy.wait(mainAliasNames); - }); - it('when selecting a different time range and clicking the update button', () => { cy.wait(mainAliasNames); @@ -118,9 +112,6 @@ describe('Service inventory', () => { ); cy.contains('Update').click(); cy.wait(mainAliasNames); - - cy.contains('Refresh').click(); - cy.wait(mainAliasNames); }); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts index d693148010c7e..2970aa3887e95 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts @@ -50,12 +50,12 @@ describe('Errors table', () => { it('clicking on type adds a filter in the kuerybar and navigates to errors page', () => { cy.visitKibana(serviceOverviewHref); - cy.getByTestSubj('headerFilterKuerybar').invoke('val').should('be.empty'); + cy.getByTestSubj('apmUnifiedSearchBar').invoke('val').should('be.empty'); // `force: true` because Cypress says the element is 0x0 cy.contains('Exception').click({ force: true, }); - cy.getByTestSubj('headerFilterKuerybar').its('length').should('be.gt', 0); + cy.getByTestSubj('apmUnifiedSearchBar').its('length').should('be.gt', 0); cy.get('table').find('td:contains("Exception")').should('have.length', 1); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts index 8a25024506696..05fe508092ff4 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts @@ -117,7 +117,7 @@ describe('Service overview - header filters', () => { }); }); - describe('Filtering by kuerybar', () => { + describe('Filtering by searchbar', () => { beforeEach(() => { cy.loginAsViewerUser(); }); @@ -129,13 +129,23 @@ describe('Service overview - header filters', () => { }) ); cy.contains('opbeans-java'); - cy.getByTestSubj('headerFilterKuerybar').type('transaction.n'); + cy.getByTestSubj('apmUnifiedSearchBar').type('transaction.n'); cy.contains('transaction.name'); - cy.getByTestSubj('suggestionContainer').find('li').first().click(); - cy.getByTestSubj('headerFilterKuerybar').type(':'); - cy.getByTestSubj('suggestionContainer').find('li').first().click(); - cy.getByTestSubj('headerFilterKuerybar').type('{enter}'); - cy.url().should('include', '&kuery=transaction.name'); + cy.getByTestSubj( + 'autocompleteSuggestion-field-transaction.name-' + ).click(); + cy.getByTestSubj('apmUnifiedSearchBar').type(':'); + cy.getByTestSubj('autoCompleteSuggestionText').should('have.length', 1); + cy.getByTestSubj( + Cypress.$.escapeSelector( + 'autocompleteSuggestion-value-"GET-/api/product"-' + ) + ).click(); + cy.getByTestSubj('apmUnifiedSearchBar').type('{enter}'); + cy.url().should( + 'include', + '&kuery=transaction.name%20:%22GET%20%2Fapi%2Fproduct%22%20' + ); }); }); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts index bce3da42d5a3f..ec511a0725aac 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts @@ -121,7 +121,7 @@ describe('Service overview: Time Comparison', () => { '2021-10-20T00:00:00.000Z' ); - cy.getByTestSubj('superDatePickerApplyTimeButton').click(); + cy.getByTestSubj('querySubmitButton').click(); cy.getByTestSubj('comparisonSelect').should('have.value', '864000000ms'); cy.getByTestSubj('comparisonSelect').should( diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts index 60b36b10ee4a3..0ec3c16e8658b 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts @@ -52,7 +52,7 @@ describe('Span links', () => { cy.contains('2 Span links'); cy.getByTestSubj( `spanLinksBadge_${ids.producerInternalOnlyIds.spanAId}` - ).realHover(); + ).trigger('mouseover'); cy.contains('2 Span links found'); cy.contains('2 incoming'); cy.contains('0 outgoing'); @@ -66,7 +66,7 @@ describe('Span links', () => { cy.contains('2 Span links'); cy.getByTestSubj( `spanLinksBadge_${ids.producerExternalOnlyIds.spanBId}` - ).realHover(); + ).trigger('mouseover'); cy.contains('2 Span links found'); cy.contains('1 incoming'); cy.contains('1 outgoing'); @@ -80,7 +80,7 @@ describe('Span links', () => { cy.contains('2 Span links'); cy.getByTestSubj( `spanLinksBadge_${ids.producerConsumerIds.transactionCId}` - ).realHover(); + ).trigger('mouseover'); cy.contains('2 Span links found'); cy.contains('1 incoming'); cy.contains('1 outgoing'); @@ -94,7 +94,7 @@ describe('Span links', () => { cy.contains('1 Span link'); cy.getByTestSubj( `spanLinksBadge_${ids.producerConsumerIds.spanCId}` - ).realHover(); + ).trigger('mouseover'); cy.contains('1 Span link found'); cy.contains('1 incoming'); cy.contains('0 outgoing'); @@ -108,7 +108,7 @@ describe('Span links', () => { cy.contains('2 Span links'); cy.getByTestSubj( `spanLinksBadge_${ids.producerMultipleIds.transactionDId}` - ).realHover(); + ).trigger('mouseover'); cy.contains('2 Span links found'); cy.contains('0 incoming'); cy.contains('2 outgoing'); @@ -122,7 +122,7 @@ describe('Span links', () => { cy.contains('2 Span links'); cy.getByTestSubj( `spanLinksBadge_${ids.producerMultipleIds.spanEId}` - ).realHover(); + ).trigger('mouseover'); cy.contains('2 Span links found'); cy.contains('0 incoming'); cy.contains('2 outgoing'); diff --git a/x-pack/plugins/apm/public/components/app/dependencies_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/dependencies_inventory/index.tsx index b4ae13619abb0..ff569f369ba4e 100644 --- a/x-pack/plugins/apm/public/components/app/dependencies_inventory/index.tsx +++ b/x-pack/plugins/apm/public/components/app/dependencies_inventory/index.tsx @@ -8,27 +8,26 @@ import { EuiSpacer } from '@elastic/eui'; import React from 'react'; import { - getKueryBarBoolFilter, - kueryBarPlaceholder, + unifiedSearchBarPlaceholder, + getSearchBarBoolFilter, } from '../../../../common/dependencies'; -import { useApmParams } from '../../../hooks/use_apm_params'; import { SearchBar } from '../../shared/search_bar/search_bar'; import { DependenciesInventoryTable } from './dependencies_inventory_table'; +import { useApmParams } from '../../../hooks/use_apm_params'; export function DependenciesInventory() { const { query: { environment }, } = useApmParams('/dependencies/inventory'); - const kueryBarBoolFilter = getKueryBarBoolFilter({ + const searchBarBoolFilter = getSearchBarBoolFilter({ environment, }); - return ( <> diff --git a/x-pack/plugins/apm/public/components/app/mobile/search_bar.tsx b/x-pack/plugins/apm/public/components/app/mobile/search_bar.tsx index 3039b69d094a9..7d41b8cf3ee01 100644 --- a/x-pack/plugins/apm/public/components/app/mobile/search_bar.tsx +++ b/x-pack/plugins/apm/public/components/app/mobile/search_bar.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { EuiFlexGroup, EuiFlexGroupProps, @@ -14,30 +13,27 @@ import { } from '@elastic/eui'; import React from 'react'; import { useBreakpoints } from '../../../hooks/use_breakpoints'; -import { ApmDatePicker } from '../../shared/date_picker/apm_date_picker'; -import { KueryBar } from '../../shared/kuery_bar'; import { TimeComparison } from '../../shared/time_comparison'; import { TransactionTypeSelect } from '../../shared/transaction_type_select'; import { MobileFilters } from './service_overview/filters'; +import { UnifiedSearchBar } from '../../shared/unified_search_bar'; interface Props { hidden?: boolean; - showKueryBar?: boolean; + showUnifiedSearchBar?: boolean; showTimeComparison?: boolean; showTransactionTypeSelector?: boolean; showMobileFilters?: boolean; - kueryBarPlaceholder?: string; - kueryBarBoolFilter?: QueryDslQueryContainer[]; + searchBarPlaceholder?: string; } export function MobileSearchBar({ hidden = false, - showKueryBar = true, + showUnifiedSearchBar = true, showTimeComparison = false, showTransactionTypeSelector = false, showMobileFilters = false, - kueryBarBoolFilter, - kueryBarPlaceholder, + searchBarPlaceholder, }: Props) { const { isSmall, isMedium, isLarge, isXl, isXXXL } = useBreakpoints(); @@ -66,17 +62,11 @@ export function MobileSearchBar({ )} - {showKueryBar && ( + {showUnifiedSearchBar && ( - + )} - - - diff --git a/x-pack/plugins/apm/public/components/app/service_map/index.tsx b/x-pack/plugins/apm/public/components/app/service_map/index.tsx index 2b3d8c8eaf15c..f9b848b816d4c 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/index.tsx @@ -40,7 +40,7 @@ import { DisabledPrompt } from './disabled_prompt'; function PromptContainer({ children }: { children: ReactNode }) { return ( <> - + - + diff --git a/x-pack/plugins/apm/public/components/routing/mobile_service_detail/index.tsx b/x-pack/plugins/apm/public/components/routing/mobile_service_detail/index.tsx index 0d7a6ade36140..52e127c63f805 100644 --- a/x-pack/plugins/apm/public/components/routing/mobile_service_detail/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/mobile_service_detail/index.tsx @@ -33,7 +33,7 @@ export function page({ tabKey: React.ComponentProps['selectedTabKey']; element: React.ReactElement; searchBarOptions?: { - showKueryBar?: boolean; + showUnifiedSearchBar?: boolean; showTransactionTypeSelector?: boolean; showTimeComparison?: boolean; showMobileFilters?: boolean; diff --git a/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx b/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx index 6158cf85d0a84..ed2b3f477ced0 100644 --- a/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx @@ -48,7 +48,7 @@ function page({ tab: React.ComponentProps['selectedTab']; element: React.ReactElement; searchBarOptions?: { - showKueryBar?: boolean; + showUnifiedSearchBar?: boolean; showTransactionTypeSelector?: boolean; showTimeComparison?: boolean; hidden?: boolean; @@ -320,7 +320,7 @@ export const serviceDetail = { }), element: , searchBarOptions: { - showKueryBar: false, + showUnifiedSearchBar: false, }, }), '/services/{serviceName}/infrastructure': { @@ -331,7 +331,7 @@ export const serviceDetail = { }), element: , searchBarOptions: { - showKueryBar: false, + showUnifiedSearchBar: false, }, }), params: t.partial({ diff --git a/x-pack/plugins/apm/public/components/routing/templates/dependency_detail_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/dependency_detail_template.tsx index 7b57dbade2216..ecc4435f092ed 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/dependency_detail_template.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/dependency_detail_template.tsx @@ -8,10 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { - getKueryBarBoolFilter, - kueryBarPlaceholder, -} from '../../../../common/dependencies'; +import { unifiedSearchBarPlaceholder } from '../../../../common/dependencies'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useApmRouter } from '../../../hooks/use_apm_router'; import { useApmRoutePath } from '../../../hooks/use_apm_route_path'; @@ -29,7 +26,7 @@ interface Props { export function DependencyDetailTemplate({ children }: Props) { const { query, - query: { dependencyName, rangeFrom, rangeTo, environment }, + query: { dependencyName, rangeFrom, rangeTo }, } = useApmParams('/dependencies'); const router = useApmRouter(); @@ -38,11 +35,6 @@ export function DependencyDetailTemplate({ children }: Props) { const path = useApmRoutePath(); - const kueryBarBoolFilter = getKueryBarBoolFilter({ - environment, - dependencyName, - }); - const dependencyMetadataFetch = useFetcher( (callApmApi) => { if (!start || !end) { @@ -113,8 +105,7 @@ export function DependencyDetailTemplate({ children }: Props) { > {children} diff --git a/x-pack/plugins/apm/public/components/shared/kuery_bar/get_bool_filter.ts b/x-pack/plugins/apm/public/components/shared/get_bool_filter.ts similarity index 85% rename from x-pack/plugins/apm/public/components/shared/kuery_bar/get_bool_filter.ts rename to x-pack/plugins/apm/public/components/shared/get_bool_filter.ts index 92a8a9172ff9a..42575126aaab5 100644 --- a/x-pack/plugins/apm/public/components/shared/kuery_bar/get_bool_filter.ts +++ b/x-pack/plugins/apm/public/components/shared/get_bool_filter.ts @@ -12,11 +12,11 @@ import { SERVICE_NAME, TRANSACTION_NAME, TRANSACTION_TYPE, -} from '../../../../common/es_fields/apm'; -import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; -import { UIProcessorEvent } from '../../../../common/processor_event'; -import { environmentQuery } from '../../../../common/utils/environment_query'; -import { ApmUrlParams } from '../../../context/url_params_context/types'; +} from '../../../common/es_fields/apm'; +import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; +import { UIProcessorEvent } from '../../../common/processor_event'; +import { environmentQuery } from '../../../common/utils/environment_query'; +import { ApmUrlParams } from '../../context/url_params_context/types'; export function getBoolFilter({ groupId, diff --git a/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx b/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx index 825fa8e3ef7f4..d166c7d283eda 100644 --- a/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx @@ -18,9 +18,9 @@ import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_ import { useApmParams } from '../../../hooks/use_apm_params'; import { useApmDataView } from '../../../hooks/use_apm_data_view'; import { fromQuery, toQuery } from '../links/url_helpers'; -import { getBoolFilter } from './get_bool_filter'; +import { getBoolFilter } from '../get_bool_filter'; import { Typeahead } from './typeahead'; -import { useProcessorEvent } from './use_processor_event'; +import { useProcessorEvent } from '../../../hooks/use_processor_event'; interface State { suggestions: QuerySuggestion[]; diff --git a/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx b/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx index 27eecaf573bf6..d9efbe0403b41 100644 --- a/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx @@ -39,6 +39,20 @@ function setup({ const KibanaReactContext = createKibanaReactContext({ usageCollection: { reportUiCounter: () => {} }, dataViews: { get: async () => {} }, + data: { + query: { + queryString: { + setQuery: () => {}, + getQuery: () => {}, + clearQuery: () => {}, + }, + timefilter: { + timefilter: { + setTime: () => {}, + }, + }, + }, + }, } as Partial); // mock transaction types diff --git a/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.tsx b/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.tsx index edff90281b9ec..d2f25880ade33 100644 --- a/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.tsx +++ b/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.tsx @@ -4,8 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { EuiFlexGroup, EuiFlexGroupProps, @@ -13,30 +11,30 @@ import { EuiSpacer, } from '@elastic/eui'; import React from 'react'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { isMobileAgentName } from '../../../../common/agent_name'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useBreakpoints } from '../../../hooks/use_breakpoints'; -import { ApmDatePicker } from '../date_picker/apm_date_picker'; -import { KueryBar } from '../kuery_bar'; import { TimeComparison } from '../time_comparison'; import { TransactionTypeSelect } from '../transaction_type_select'; +import { UnifiedSearchBar } from '../unified_search_bar'; interface Props { hidden?: boolean; - showKueryBar?: boolean; + showUnifiedSearchBar?: boolean; showTimeComparison?: boolean; showTransactionTypeSelector?: boolean; - kueryBarPlaceholder?: string; - kueryBarBoolFilter?: QueryDslQueryContainer[]; + searchBarPlaceholder?: string; + searchBarBoolFilter?: QueryDslQueryContainer[]; } export function SearchBar({ hidden = false, - showKueryBar = true, + showUnifiedSearchBar = true, showTimeComparison = false, showTransactionTypeSelector = false, - kueryBarBoolFilter, - kueryBarPlaceholder, + searchBarPlaceholder, + searchBarBoolFilter, }: Props) { const { agentName } = useApmServiceContext(); const isMobileAgent = isMobileAgentName(agentName); @@ -69,11 +67,11 @@ export function SearchBar({ )} - {showKueryBar && ( + {showUnifiedSearchBar && ( - )} @@ -91,9 +89,6 @@ export function SearchBar({
)} - - - diff --git a/x-pack/plugins/apm/public/components/shared/unified_search_bar/index.tsx b/x-pack/plugins/apm/public/components/shared/unified_search_bar/index.tsx new file mode 100644 index 0000000000000..83e080c8c0df3 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/unified_search_bar/index.tsx @@ -0,0 +1,248 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useCallback, useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + Filter, + fromKueryExpression, + Query, + TimeRange, + toElasticsearchQuery, +} from '@kbn/es-query'; +import { useHistory, useLocation } from 'react-router-dom'; +import deepEqual from 'fast-deep-equal'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { EuiSkeletonRectangle } from '@elastic/eui'; +import qs from 'query-string'; +import { DataView, UI_SETTINGS } from '@kbn/data-plugin/common'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { UIProcessorEvent } from '../../../../common/processor_event'; +import { TimePickerTimeDefaults } from '../date_picker/typings'; +import { ApmPluginStartDeps } from '../../../plugin'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useApmDataView } from '../../../hooks/use_apm_data_view'; +import { useProcessorEvent } from '../../../hooks/use_processor_event'; +import { fromQuery, toQuery } from '../links/url_helpers'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { getBoolFilter } from '../get_bool_filter'; +import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; + +function useSearchBarParams(defaultKuery?: string) { + const { path, query } = useApmParams('/*'); + const urlKuery = 'kuery' in query ? query.kuery : undefined; + const serviceName = 'serviceName' in path ? path.serviceName : undefined; + const groupId = 'groupId' in path ? path.groupId : undefined; + const environment = 'environment' in query ? query.environment : undefined; + + return { + kuery: urlKuery + ? { + query: defaultKuery || urlKuery, + language: 'kuery', + } + : undefined, + serviceName, + groupId, + environment, + }; +} + +function useUrlTimeRange(defaultTimeRange: TimeRange) { + const location = useLocation(); + const query = qs.parse(location.search); + + const isDateRangeSet = 'rangeFrom' in query && 'rangeTo' in query; + + if (isDateRangeSet) { + return { + from: query.rangeFrom, + to: query.rangeTo, + }; + } + return defaultTimeRange; +} + +function getSearchBarPlaceholder( + searchbarPlaceholder?: string, + processorEvent?: UIProcessorEvent +) { + const examples = { + transaction: 'transaction.duration.us > 300000', + error: 'http.response.status_code >= 400', + metric: 'process.pid = "1234"', + defaults: + 'transaction.duration.us > 300000 AND http.response.status_code >= 400', + }; + const example = examples[processorEvent || 'defaults']; + + return ( + searchbarPlaceholder ?? + i18n.translate('xpack.apm.unifiedSearchBar.placeholder', { + defaultMessage: `Search {event, select, + transaction {transactions} + metric {metrics} + error {errors} + other {transactions, errors and metrics} + } (E.g. {queryExample})`, + values: { + queryExample: example, + event: processorEvent, + }, + }) + ); +} + +function convertKueryToEsQuery(kuery: string, dataView: DataView) { + const ast = fromKueryExpression(kuery); + return toElasticsearchQuery(ast, dataView); +} + +export function UnifiedSearchBar({ + placeholder, + value, + showDatePicker = true, + showSubmitButton = true, + isClearable = true, + boolFilter, +}: { + placeholder?: string; + value?: string; + showDatePicker?: boolean; + showSubmitButton?: boolean; + isClearable?: boolean; + boolFilter?: QueryDslQueryContainer[]; +}) { + const { + unifiedSearch: { + ui: { SearchBar }, + }, + core, + } = useApmPluginContext(); + const { services } = useKibana(); + const { + data: { + query: { queryString: queryStringService, timefilter: timeFilterService }, + }, + } = services; + + const { kuery, serviceName, environment, groupId } = + useSearchBarParams(value); + const timePickerTimeDefaults = core.uiSettings.get( + UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS + ); + const urlTimeRange = useUrlTimeRange(timePickerTimeDefaults); + const [displaySearchBar, setDisplaySearchBar] = useState(false); + + const syncSearchBarWithUrl = useCallback(() => { + // Sync Kuery params with Search Bar + if (kuery && !deepEqual(queryStringService.getQuery(), kuery)) { + queryStringService.setQuery(kuery); + } + // On page navigation the search bar persists the state where as the url is cleared, hence we need to clear the search bar + if (!kuery) { + queryStringService.clearQuery(); + } + // Sync Time Range with Search Bar + timeFilterService.timefilter.setTime(urlTimeRange as TimeRange); + }, [kuery, queryStringService, timeFilterService, urlTimeRange]); + + useEffect(() => { + syncSearchBarWithUrl(); + }, [syncSearchBarWithUrl]); + + const location = useLocation(); + const history = useHistory(); + const { dataView } = useApmDataView(); + const { urlParams } = useLegacyUrlParams(); + const processorEvent = useProcessorEvent(); + const searchbarPlaceholder = getSearchBarPlaceholder( + placeholder, + processorEvent + ); + + useEffect(() => { + if (dataView) setDisplaySearchBar(true); + }, [dataView]); + + const customFilters = + boolFilter ?? + getBoolFilter({ + groupId, + processorEvent, + serviceName, + environment, + urlParams, + }); + + const filtersForSearchBarSuggestions = customFilters.map((filter) => { + return { + query: filter, + } as Filter; + }); + const handleSubmit = (payload: { dateRange: TimeRange; query?: Query }) => { + if (dataView == null) { + return; + } + + const { dateRange, query } = payload; + const { from: rangeFrom, to: rangeTo } = dateRange; + + try { + const res = convertKueryToEsQuery( + query?.query as string, + dataView as DataView + ); + if (!res) { + return; + } + + const existingQueryParams = toQuery(location.search); + const updatedQueryWithTime = { + ...existingQueryParams, + rangeFrom, + rangeTo, + }; + history.push({ + ...location, + search: fromQuery({ + ...updatedQueryWithTime, + kuery: query?.query, + }), + }); + } catch (e) { + console.log('Invalid kuery syntax'); // eslint-disable-line no-console + } + }; + + return ( + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/unified_search_bar/unified_search_bar.test.tsx b/x-pack/plugins/apm/public/components/shared/unified_search_bar/unified_search_bar.test.tsx new file mode 100644 index 0000000000000..25d3ecbc41b0f --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/unified_search_bar/unified_search_bar.test.tsx @@ -0,0 +1,138 @@ +/* + * 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 { createMemoryHistory, MemoryHistory } from 'history'; +import React from 'react'; +import { Router, useLocation } from 'react-router-dom'; +import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import * as useFetcherHook from '../../../hooks/use_fetcher'; +import * as useApmDataViewHook from '../../../hooks/use_apm_data_view'; +import * as useApmParamsHook from '../../../hooks/use_apm_params'; +import * as useProcessorEventHook from '../../../hooks/use_processor_event'; +import { fromQuery } from '../links/url_helpers'; +import { CoreStart } from '@kbn/core/public'; +import { UnifiedSearchBar } from '.'; +import { UrlParams } from '../../../context/url_params_context/types'; +import { mount } from 'enzyme'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn(), +})); + +function setup({ + urlParams, + history, +}: { + urlParams: UrlParams; + history: MemoryHistory; +}) { + history.replace({ + pathname: '/services', + search: fromQuery(urlParams), + }); + + const setQuerySpy = jest.fn(); + const getQuerySpy = jest.fn(); + const clearQuerySpy = jest.fn(); + const setTimeSpy = jest.fn(); + + const KibanaReactContext = createKibanaReactContext({ + usageCollection: { + reportUiCounter: () => {}, + }, + dataViews: { + get: async () => {}, + }, + data: { + query: { + queryString: { + setQuery: setQuerySpy, + getQuery: getQuerySpy, + clearQuery: clearQuerySpy, + }, + timefilter: { + timefilter: { + setTime: setTimeSpy, + }, + }, + }, + }, + } as Partial); + + // mock transaction types + jest + .spyOn(useApmDataViewHook, 'useApmDataView') + .mockReturnValue({ dataView: undefined }); + + jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any); + + const wrapper = mount( + + + + + + + + ); + + return { wrapper, setQuerySpy, getQuerySpy, clearQuerySpy, setTimeSpy }; +} + +describe('when kuery is already present in the url, the search bar must reflect the same', () => { + let history: MemoryHistory; + beforeEach(() => { + history = createMemoryHistory(); + jest.spyOn(history, 'push'); + jest.spyOn(history, 'replace'); + }); + jest + .spyOn(useProcessorEventHook, 'useProcessorEvent') + .mockReturnValue(undefined); + + const search = '?method=json'; + const pathname = '/services'; + (useLocation as jest.Mock).mockImplementationOnce(() => ({ + search, + pathname, + })); + + it('sets the searchbar value based on URL', () => { + const expectedQuery = { + query: 'service.name:"opbeans-android"', + language: 'kuery', + }; + + const expectedTimeRange = { + from: 'now-15m', + to: 'now', + }; + + const urlParams = { + kuery: expectedQuery.query, + rangeFrom: expectedTimeRange.from, + rangeTo: expectedTimeRange.to, + environment: 'ENVIRONMENT_ALL', + comparisonEnabled: true, + serviceGroup: '', + offset: '1d', + }; + jest + .spyOn(useApmParamsHook, 'useApmParams') + .mockReturnValue({ query: urlParams, path: {} }); + + const { setQuerySpy, setTimeSpy } = setup({ + history, + urlParams, + }); + + expect(setQuerySpy).toBeCalledWith(expectedQuery); + expect(setTimeSpy).toBeCalledWith(expectedTimeRange); + }); +}); diff --git a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx index 6924277f229c0..9ba943b79b12c 100644 --- a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx @@ -98,6 +98,12 @@ const mockCorePlugins = { data: {}, }; +const mockUnifiedSearch = { + ui: { + SearchBar: () =>
, + }, +}; + export const mockApmPluginContextValue = { appMountParameters: coreMock.createAppMountParameters('/basepath'), config: mockConfig, @@ -106,6 +112,7 @@ export const mockApmPluginContextValue = { observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), corePlugins: mockCorePlugins, deps: {}, + unifiedSearch: mockUnifiedSearch, }; export function MockApmPluginContextWrapper({ diff --git a/x-pack/plugins/apm/public/components/shared/kuery_bar/use_processor_event.ts b/x-pack/plugins/apm/public/hooks/use_processor_event.ts similarity index 94% rename from x-pack/plugins/apm/public/components/shared/kuery_bar/use_processor_event.ts rename to x-pack/plugins/apm/public/hooks/use_processor_event.ts index d8abb49e42277..deffd7eae1517 100644 --- a/x-pack/plugins/apm/public/components/shared/kuery_bar/use_processor_event.ts +++ b/x-pack/plugins/apm/public/hooks/use_processor_event.ts @@ -7,7 +7,7 @@ import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { useLocation } from 'react-router-dom'; -import { UIProcessorEvent } from '../../../../common/processor_event'; +import { UIProcessorEvent } from '../../common/processor_event'; /** * Infer the processor.event to used based on the route path diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 7a86a2d49d1ba..3e5b85ffcf0b4 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -7546,7 +7546,6 @@ "xpack.apm.dataView.autoCreateDisabled": "La création automatique des vues de données a été désactivée via l'option de configuration \"autoCreateApmDataView\"", "xpack.apm.dataView.noApmData": "Aucune donnée APM", "xpack.apm.dependecyOperationDetailView.header.backLinkLabel": "Toutes les opérations", - "xpack.apm.dependencies.kueryBarPlaceholder": "Rechercher dans les indicateurs de dépendance (par ex., span.destination.service.resource:elasticsearch)", "xpack.apm.dependenciesInventory.dependencyTableColumn": "Dépendance", "xpack.apm.dependenciesTable.columnErrorRate": "Taux de transactions ayant échoué", "xpack.apm.dependenciesTable.columnErrorRateTip": "Le pourcentage de transactions ayant échoué pour le service sélectionné. Les transactions du serveur HTTP avec un code du statut 4xx (erreur du client) ne sont pas considérées comme des échecs, car l'appelant, et non le serveur, a provoqué l'échec.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a8c59ef19152c..ef49ea6ad1039 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -7547,7 +7547,6 @@ "xpack.apm.dataView.autoCreateDisabled": "データビューの自動作成は、「autoCreateApmDataView」構成オプションによって無効化されています", "xpack.apm.dataView.noApmData": "APMデータがありません", "xpack.apm.dependecyOperationDetailView.header.backLinkLabel": "すべての演算", - "xpack.apm.dependencies.kueryBarPlaceholder": "依存関係メトリックを検索(例:span.destination.service.resource:elasticsearch)", "xpack.apm.dependenciesInventory.dependencyTableColumn": "依存関係", "xpack.apm.dependenciesTable.columnErrorRate": "失敗したトランザクション率", "xpack.apm.dependenciesTable.columnErrorRateTip": "選択したサービスの失敗したトランザクションの割合。4xxステータスコード(クライアントエラー)のHTTPサーバートランザクションは、サーバーではなく呼び出し側が失敗の原因であるため、失敗と見なされません。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 9ad3258100c35..589d923ae8ce1 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7546,7 +7546,6 @@ "xpack.apm.dataView.autoCreateDisabled": "已通过“autoCreateApmDataView”配置选项禁止自动创建数据视图", "xpack.apm.dataView.noApmData": "无 APM 数据", "xpack.apm.dependecyOperationDetailView.header.backLinkLabel": "所有操作", - "xpack.apm.dependencies.kueryBarPlaceholder": "搜索依赖项指标(例如,span.destination.service.resource:elasticsearch)", "xpack.apm.dependenciesInventory.dependencyTableColumn": "依赖项", "xpack.apm.dependenciesTable.columnErrorRate": "失败事务率", "xpack.apm.dependenciesTable.columnErrorRateTip": "选定服务的失败事务百分比。状态代码为 4xx 的 HTTP 服务器事务(客户端错误)不会视为失败,因为是调用方而不是服务器造成了失败。", From 656f824a614e8331fae89877caa733f9a6a2eb5d Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 21 Apr 2023 08:23:48 -0400 Subject: [PATCH 50/67] skip failing test suite (#155447) --- .../test/functional/apps/security/doc_level_security_roles.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/security/doc_level_security_roles.ts b/x-pack/test/functional/apps/security/doc_level_security_roles.ts index 56feb76394b61..b1af9ec3350b7 100644 --- a/x-pack/test/functional/apps/security/doc_level_security_roles.ts +++ b/x-pack/test/functional/apps/security/doc_level_security_roles.ts @@ -19,7 +19,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['security', 'common', 'header', 'discover', 'settings']); const kibanaServer = getService('kibanaServer'); - describe('dls', function () { + // Failing: See https://github.com/elastic/kibana/issues/155447 + describe.skip('dls', function () { before('initialize tests', async () => { await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/security/dlstest'); From d401b4428851414a18af81b78869d8d94b5eb528 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Fri, 21 Apr 2023 14:41:37 +0200 Subject: [PATCH 51/67] [APM] Add error grouping key filter in error count rule type (#155410) part of https://github.com/elastic/kibana/issues/152329 Introduces the error grouping key filter in the error count rule type https://user-images.githubusercontent.com/3369346/233397481-899e32e0-f26d-4335-84fe-e18c5264f3d2.mov --- x-pack/plugins/apm/common/rules/schema.ts | 1 + .../error_count_rule_type/index.tsx | 11 +++++ .../components/alerting/utils/fields.tsx | 40 +++++++++++++++++++ .../server/routes/alerts/action_variables.ts | 9 +++++ .../plugins/apm/server/routes/alerts/route.ts | 1 + .../get_error_count_chart_preview.ts | 9 ++++- .../register_error_count_rule_type.ts | 12 +++++- .../tests/alerts/chart_preview.spec.ts | 27 +++++++++++++ 8 files changed, 107 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/apm/common/rules/schema.ts b/x-pack/plugins/apm/common/rules/schema.ts index 7e48ad989b606..698b4507c5b3f 100644 --- a/x-pack/plugins/apm/common/rules/schema.ts +++ b/x-pack/plugins/apm/common/rules/schema.ts @@ -15,6 +15,7 @@ export const errorCountParamsSchema = schema.object({ threshold: schema.number(), serviceName: schema.maybe(schema.string()), environment: schema.string(), + errorGroupingKey: schema.maybe(schema.string()), }); export const transactionDurationParamsSchema = schema.object({ diff --git a/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx b/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx index 09ca441cd26e9..f23dc6f4fb362 100644 --- a/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx @@ -21,6 +21,7 @@ import { createCallApmApi } from '../../../../services/rest/create_call_apm_api' import { ChartPreview } from '../../ui_components/chart_preview'; import { EnvironmentField, + ErrorGroupingKeyField, IsAboveField, ServiceField, } from '../../utils/fields'; @@ -33,6 +34,7 @@ export interface RuleParams { threshold?: number; serviceName?: string; environment?: string; + errorGroupingKey?: string; } interface Props { @@ -74,6 +76,7 @@ export function ErrorCountRuleType(props: Props) { query: { environment: params.environment, serviceName: params.serviceName, + errorGroupingKey: params.errorGroupingKey, interval, start, end, @@ -88,6 +91,7 @@ export function ErrorCountRuleType(props: Props) { params.windowUnit, params.environment, params.serviceName, + params.errorGroupingKey, ] ); @@ -98,6 +102,7 @@ export function ErrorCountRuleType(props: Props) { if (value !== params.serviceName) { setRuleParams('serviceName', value); setRuleParams('environment', ENVIRONMENT_ALL.value); + setRuleParams('errorGroupingKey', undefined); } }} />, @@ -106,6 +111,12 @@ export function ErrorCountRuleType(props: Props) { onChange={(value) => setRuleParams('environment', value)} serviceName={params.serviceName} />, + setRuleParams('errorGroupingKey', value)} + serviceName={params.serviceName} + />, + void; + serviceName?: string; +}) { + const label = i18n.translate('xpack.apm.alerting.fields.error.group.id', { + defaultMessage: 'Error grouping key', + }); + return ( + + + + ); +} + export function IsAboveField({ value, unit, diff --git a/x-pack/plugins/apm/server/routes/alerts/action_variables.ts b/x-pack/plugins/apm/server/routes/alerts/action_variables.ts index 471d2ff3141b0..1bb9c41546cf8 100644 --- a/x-pack/plugins/apm/server/routes/alerts/action_variables.ts +++ b/x-pack/plugins/apm/server/routes/alerts/action_variables.ts @@ -94,4 +94,13 @@ export const apmActionVariables = { name: 'viewInAppUrl' as const, usesPublicBaseUrl: true, }, + errorGroupingKey: { + description: i18n.translate( + 'xpack.apm.alerts.action_variables.errorGroupingKey', + { + defaultMessage: 'The error grouping key the alert is created for', + } + ), + name: 'errorGroupingKey' as const, + }, }; diff --git a/x-pack/plugins/apm/server/routes/alerts/route.ts b/x-pack/plugins/apm/server/routes/alerts/route.ts index 314e05fed539a..13b790155520f 100644 --- a/x-pack/plugins/apm/server/routes/alerts/route.ts +++ b/x-pack/plugins/apm/server/routes/alerts/route.ts @@ -28,6 +28,7 @@ const alertParamsRt = t.intersection([ t.literal(AggregationType.P99), ]), serviceName: t.string, + errorGroupingKey: t.string, transactionType: t.string, transactionName: t.string, }), diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts index 6175b2578a236..59ae52eab6a5a 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts @@ -7,7 +7,10 @@ import { rangeQuery, termQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { SERVICE_NAME } from '../../../../../common/es_fields/apm'; +import { + ERROR_GROUP_ID, + SERVICE_NAME, +} from '../../../../../common/es_fields/apm'; import { AlertParams } from '../../route'; import { environmentQuery } from '../../../../../common/utils/environment_query'; import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; @@ -24,12 +27,14 @@ export async function getTransactionErrorCountChartPreview({ apmEventClient: APMEventClient; alertParams: AlertParams; }): Promise { - const { serviceName, environment, interval, start, end } = alertParams; + const { serviceName, environment, errorGroupingKey, interval, start, end } = + alertParams; const query = { bool: { filter: [ ...termQuery(SERVICE_NAME, serviceName), + ...termQuery(ERROR_GROUP_ID, errorGroupingKey), ...rangeQuery(start, end), ...environmentQuery(environment), ], diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts index a008dd0d23cb3..2527a11e3f001 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts @@ -25,6 +25,7 @@ import { getEnvironmentLabel, } from '../../../../../common/environment_filter_values'; import { + ERROR_GROUP_ID, PROCESSOR_EVENT, SERVICE_ENVIRONMENT, SERVICE_NAME, @@ -77,6 +78,7 @@ export function registerErrorCountRuleType({ apmActionVariables.interval, apmActionVariables.reason, apmActionVariables.serviceName, + apmActionVariables.errorGroupingKey, apmActionVariables.threshold, apmActionVariables.triggerValue, apmActionVariables.viewInAppUrl, @@ -112,6 +114,7 @@ export function registerErrorCountRuleType({ }, { term: { [PROCESSOR_EVENT]: ProcessorEvent.error } }, ...termQuery(SERVICE_NAME, ruleParams.serviceName), + ...termQuery(ERROR_GROUP_ID, ruleParams.errorGroupingKey), ...environmentQuery(ruleParams.environment), ], }, @@ -164,7 +167,12 @@ export function registerErrorCountRuleType({ windowUnit: ruleParams.windowUnit, }); - const id = [ApmRuleType.ErrorCount, serviceName, environment] + const id = [ + ApmRuleType.ErrorCount, + serviceName, + environment, + ruleParams.errorGroupingKey, + ] .filter((name) => name) .join('_'); @@ -188,6 +196,7 @@ export function registerErrorCountRuleType({ [PROCESSOR_EVENT]: ProcessorEvent.error, [ALERT_EVALUATION_VALUE]: errorCount, [ALERT_EVALUATION_THRESHOLD]: ruleParams.threshold, + [ERROR_GROUP_ID]: ruleParams.errorGroupingKey, [ALERT_REASON]: alertReason, ...sourceFields, }, @@ -201,6 +210,7 @@ export function registerErrorCountRuleType({ reason: alertReason, serviceName, threshold: ruleParams.threshold, + errorGroupingKey: ruleParams.errorGroupingKey, triggerValue: errorCount, viewInAppUrl, }); diff --git a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.spec.ts index f070098a66661..7ec09849b7ff2 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.spec.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.spec.ts @@ -100,6 +100,33 @@ export default function ApiTest({ getService }: FtrProviderContext) { ).to.equal(true); }); + it('error_count with error grouping key', async () => { + const options = { + params: { + query: { + start, + end, + serviceName: 'opbeans-java', + errorGroupingKey: 'd16d39e7fa133b8943cea035430a7b4e', + environment: 'ENVIRONMENT_ALL', + interval: '5m', + }, + }, + }; + + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', + ...options, + }); + + expect(response.status).to.be(200); + expect(response.body.errorCountChartPreview).to.eql([ + { x: 1627974600000, y: 4 }, + { x: 1627974900000, y: 2 }, + { x: 1627975200000, y: 0 }, + ]); + }); + it('transaction_duration (with data)', async () => { const options = getOptions(); const response = await apmApiClient.readUser({ From e41936662e89c3050c8988c909792333bb58fa11 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 21 Apr 2023 14:46:47 +0200 Subject: [PATCH 52/67] [ML] Fix indentation in the API content page template (#155498) ## Summary Fixes indentation in the API content page template --- .../content_page/content_page.ts | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/ml/scripts/apidoc_scripts/content_page/content_page.ts b/x-pack/plugins/ml/scripts/apidoc_scripts/content_page/content_page.ts index 402a68c07e2d5..8b992817c843c 100644 --- a/x-pack/plugins/ml/scripts/apidoc_scripts/content_page/content_page.ts +++ b/x-pack/plugins/ml/scripts/apidoc_scripts/content_page/content_page.ts @@ -24,27 +24,26 @@ const getContent = (groups: Group[]) => { .join('\n'); return `--- - id: uiMlKibanaRestApi - slug: /ml-team/docs/ui/rest-api/ml-kibana-rest-api - title: Machine Learning Kibana REST API - image: https://source.unsplash.com/400x175/?Nature - description: This page contains documentation for the ML Kibana REST API. - date: ${moment().format('YYYY-MM-DD')} - tags: ['machine learning','internal docs', 'UI'] - --- +id: uiMlKibanaRestApi +slug: /ml-team/docs/ui/rest-api/ml-kibana-rest-api +title: Machine Learning Kibana REST API +image: https://source.unsplash.com/400x175/?Nature +description: This page contains documentation for the ML Kibana REST API. +date: ${moment().format('YYYY-MM-DD')} +tags: ['machine learning','internal docs', 'UI'] +--- - _Updated for ${kibanaPackageJson.version}_ +_Updated for ${kibanaPackageJson.version}_ - Some of the features of the Machine Learning (ML) Kibana plugin are provided via a REST API, which is ideal for creating an integration with the ML plugin. +Some of the features of the Machine Learning (ML) Kibana plugin are provided via a REST API, which is ideal for creating an integration with the ML plugin. - Each API is experimental and can include breaking changes in any version of the ML plugin, or might have been entirely removed from the plugin. +Each API is experimental and can include breaking changes in any version of the ML plugin, or might have been entirely removed from the plugin. - - +- - The following APIs are available: +The following APIs are available: -${groupsStr} - `; +${groupsStr}`; }; export const generateContentPage = () => { From cd90b11e97a2b12c8a4e81ecfadee83933cde1d4 Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Fri, 21 Apr 2023 15:08:42 +0200 Subject: [PATCH 53/67] [Observability:RulesPage] [TriggerActions:RulesList] Allow filtering of Rules via Rule Param (#154258) Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../public/pages/rules/rules.tsx | 21 ++- .../hooks/use_load_rule_aggregations.test.tsx | 3 + .../application/hooks/use_load_rules.test.tsx | 7 + .../application/hooks/use_load_rules_query.ts | 2 + .../lib/rule_api/map_filters_to_kuery_node.ts | 15 +++ .../application/lib/rule_api/rules_helpers.ts | 1 + .../lib/rule_api/rules_kuery_filter.ts | 2 + .../rules_list/components/rules_list.tsx | 121 +++++++++++------- .../rules_list_clear_rule_filter_banner.tsx | 39 ++++++ .../components/rules_list_filters_bar.tsx | 73 ++++++----- .../triggers_actions_ui/public/types.ts | 14 +- .../plugins/triggers_actions_ui/tsconfig.json | 3 +- 12 files changed, 210 insertions(+), 91 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_clear_rule_filter_banner.tsx diff --git a/x-pack/plugins/observability/public/pages/rules/rules.tsx b/x-pack/plugins/observability/public/pages/rules/rules.tsx index e7a63cfbdb81d..40e4c2192a0c0 100644 --- a/x-pack/plugins/observability/public/pages/rules/rules.tsx +++ b/x-pack/plugins/observability/public/pages/rules/rules.tsx @@ -62,14 +62,16 @@ export function RulesPage() { useHashQuery: false, }); - const { lastResponse, search, status, type } = urlStateStorage.get<{ + const { lastResponse, params, search, status, type } = urlStateStorage.get<{ lastResponse: string[]; + params: Record; search: string; status: RuleStatus[]; type: string[]; - }>('_a') || { lastResponse: [], search: '', status: [], type: [] }; + }>('_a') || { lastResponse: [], params: {}, search: '', status: [], type: [] }; const [stateLastResponse, setLastResponse] = useState(lastResponse); + const [stateParams, setParams] = useState>(params); const [stateSearch, setSearch] = useState(search); const [stateStatus, setStatus] = useState(status); const [stateType, setType] = useState(type); @@ -80,23 +82,28 @@ export function RulesPage() { const handleStatusFilterChange = (newStatus: RuleStatus[]) => { setStatus(newStatus); - urlStateStorage.set('_a', { lastResponse, search, status: newStatus, type }); + urlStateStorage.set('_a', { lastResponse, params, search, status: newStatus, type }); }; const handleLastRunOutcomeFilterChange = (newLastResponse: string[]) => { setRefresh(new Date()); setLastResponse(newLastResponse); - urlStateStorage.set('_a', { lastResponse: newLastResponse, search, status, type }); + urlStateStorage.set('_a', { lastResponse: newLastResponse, params, search, status, type }); }; const handleTypeFilterChange = (newType: string[]) => { setType(newType); - urlStateStorage.set('_a', { lastResponse, search, status, type: newType }); + urlStateStorage.set('_a', { lastResponse, params, search, status, type: newType }); }; const handleSearchFilterChange = (newSearch: string) => { setSearch(newSearch); - urlStateStorage.set('_a', { lastResponse, search: newSearch, status, type }); + urlStateStorage.set('_a', { lastResponse, params, search: newSearch, status, type }); + }; + + const handleRuleParamFilterChange = (newParams: Record) => { + setParams(newParams); + urlStateStorage.set('_a', { lastResponse, params: newParams, search, status, type }); }; return ( @@ -143,6 +150,7 @@ export function RulesPage() { refresh={stateRefresh} ruleDetailsRoute="alerts/rules/:ruleId" rulesListKey="observability_rulesListColumns" + ruleParamFilter={stateParams} showActionFilter={false} statusFilter={stateStatus} searchFilter={stateSearch} @@ -155,6 +163,7 @@ export function RulesPage() { 'ruleExecutionState', ]} onLastRunOutcomeFilterChange={handleLastRunOutcomeFilterChange} + onRuleParamFilterChange={handleRuleParamFilterChange} onSearchFilterChange={handleSearchFilterChange} onStatusFilterChange={handleStatusFilterChange} onTypeFilterChange={handleTypeFilterChange} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx index df067b43c32c7..b9f33ab40c6fc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx @@ -68,6 +68,7 @@ describe('useLoadRuleAggregations', () => { ruleExecutionStatuses: [], ruleLastRunOutcomes: [], ruleStatuses: [], + ruleParams: {}, tags: [], }, enabled: true, @@ -105,6 +106,7 @@ describe('useLoadRuleAggregations', () => { types: ['type1', 'type2'], actionTypes: ['action1', 'action2'], ruleExecutionStatuses: ['status1', 'status2'], + ruleParams: {}, ruleStatuses: ['enabled', 'snoozed'] as RuleStatus[], tags: ['tag1', 'tag2'], ruleLastRunOutcomes: ['outcome1', 'outcome2'], @@ -145,6 +147,7 @@ describe('useLoadRuleAggregations', () => { types: [], actionTypes: [], ruleExecutionStatuses: [], + ruleParams: {}, ruleLastRunOutcomes: [], ruleStatuses: [], tags: [], diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx index ff674f677783e..1342746223889 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx @@ -272,6 +272,7 @@ describe('useLoadRules', () => { types: [], actionTypes: [], ruleExecutionStatuses: [], + ruleParams: {}, ruleLastRunOutcomes: [], ruleStatuses: [], tags: [], @@ -328,6 +329,7 @@ describe('useLoadRules', () => { types: ['type1', 'type2'], actionTypes: ['action1', 'action2'], ruleExecutionStatuses: ['status1', 'status2'], + ruleParams: {}, ruleLastRunOutcomes: ['outcome1', 'outcome2'], ruleStatuses: ['enabled', 'snoozed'] as RuleStatus[], tags: ['tag1', 'tag2'], @@ -355,6 +357,7 @@ describe('useLoadRules', () => { actionTypesFilter: ['action1', 'action2'], ruleExecutionStatusesFilter: ['status1', 'status2'], ruleLastRunOutcomesFilter: ['outcome1', 'outcome2'], + ruleParamsFilter: {}, ruleStatusesFilter: ['enabled', 'snoozed'], tagsFilter: ['tag1', 'tag2'], sort: { field: 'name', direction: 'asc' }, @@ -378,6 +381,7 @@ describe('useLoadRules', () => { types: [], actionTypes: [], ruleExecutionStatuses: [], + ruleParams: {}, ruleLastRunOutcomes: [], ruleStatuses: [], tags: [], @@ -415,6 +419,7 @@ describe('useLoadRules', () => { types: [], actionTypes: [], ruleExecutionStatuses: [], + ruleParams: {}, ruleLastRunOutcomes: [], ruleStatuses: [], tags: [], @@ -444,6 +449,7 @@ describe('useLoadRules', () => { types: [], actionTypes: [], ruleExecutionStatuses: [], + ruleParams: {}, ruleLastRunOutcomes: [], ruleStatuses: [], tags: [], @@ -477,6 +483,7 @@ describe('useLoadRules', () => { types: ['some-kind-of-filter'], actionTypes: [], ruleExecutionStatuses: [], + ruleParams: {}, ruleLastRunOutcomes: [], ruleStatuses: [], tags: [], diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules_query.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules_query.ts index 8ffb59a7806ad..7a30b00b94c94 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules_query.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules_query.ts @@ -44,6 +44,7 @@ export const useLoadRulesQuery = (props: UseLoadRulesQueryProps) => { filters.actionTypes, filters.ruleStatuses, filters.ruleLastRunOutcomes, + filters.ruleParams, page, sort, { @@ -59,6 +60,7 @@ export const useLoadRulesQuery = (props: UseLoadRulesQueryProps) => { actionTypesFilter: filters.actionTypes, ruleExecutionStatusesFilter: filters.ruleExecutionStatuses, ruleLastRunOutcomesFilter: filters.ruleLastRunOutcomes, + ruleParamsFilter: filters.ruleParams, ruleStatusesFilter: filters.ruleStatuses, tagsFilter: filters.tags, sort, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.ts index 8159501b3d4b4..ea704891523b8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.ts @@ -13,6 +13,7 @@ export const mapFiltersToKueryNode = ({ actionTypesFilter, ruleExecutionStatusesFilter, ruleLastRunOutcomesFilter, + ruleParamsFilter, ruleStatusesFilter, tagsFilter, searchText, @@ -22,6 +23,7 @@ export const mapFiltersToKueryNode = ({ tagsFilter?: string[]; ruleExecutionStatusesFilter?: string[]; ruleLastRunOutcomesFilter?: string[]; + ruleParamsFilter?: Record; ruleStatusesFilter?: RuleStatus[]; searchText?: string; }): KueryNode | null => { @@ -63,6 +65,19 @@ export const mapFiltersToKueryNode = ({ ); } + if (ruleParamsFilter && Object.keys(ruleParamsFilter).length) { + filterKueryNode.push( + nodeBuilder.and( + Object.keys(ruleParamsFilter).map((ruleParam) => + nodeBuilder.is( + `alert.attributes.params.${ruleParam}`, + String(ruleParamsFilter[ruleParam]) + ) + ) + ) + ); + } + if (ruleStatusesFilter && ruleStatusesFilter.length) { const snoozedFilter = nodeBuilder.or([ fromKueryExpression('alert.attributes.muteAll: true'), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts index cce9fd3e5ac97..989bd49289dde 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts @@ -19,6 +19,7 @@ export interface LoadRulesProps { tagsFilter?: string[]; ruleExecutionStatusesFilter?: string[]; ruleLastRunOutcomesFilter?: string[]; + ruleParamsFilter?: Record; ruleStatusesFilter?: RuleStatus[]; sort?: Sorting; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts index d7c56388b4e9e..f7e2bd6a8177e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts @@ -19,6 +19,7 @@ export async function loadRulesWithKueryFilter({ actionTypesFilter, ruleExecutionStatusesFilter, ruleLastRunOutcomesFilter, + ruleParamsFilter, ruleStatusesFilter, tagsFilter, sort = { field: 'name', direction: 'asc' }, @@ -34,6 +35,7 @@ export async function loadRulesWithKueryFilter({ tagsFilter, ruleExecutionStatusesFilter, ruleLastRunOutcomesFilter, + ruleParamsFilter, ruleStatusesFilter, searchText, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx index 0bf80a0eb83cd..0fbb367e1b9ed 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx @@ -8,7 +8,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { i18n } from '@kbn/i18n'; -import { capitalize, isEmpty, sortBy } from 'lodash'; +import { capitalize, isEmpty, isEqual, sortBy } from 'lodash'; import { KueryNode } from '@kbn/es-query'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { @@ -75,6 +75,7 @@ import './rules_list.scss'; import { CreateRuleButton } from './create_rule_button'; import { ManageLicenseModal } from './manage_license_modal'; import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; +import { RulesListClearRuleFilterBanner } from './rules_list_clear_rule_filter_banner'; import { RulesListTable, convertRulesToTableItems } from './rules_list_table'; import { RulesListDocLink } from './rules_list_doc_link'; import { UpdateApiKeyModalConfirmation } from '../../../components/update_api_key_modal_confirmation'; @@ -107,23 +108,26 @@ const RuleEdit = lazy(() => import('../../rule_form/rule_edit')); export interface RulesListProps { filteredRuleTypes?: string[]; - showActionFilter?: boolean; + lastResponseFilter?: string[]; + lastRunOutcomeFilter?: string[]; + refresh?: Date; ruleDetailsRoute?: string; + ruleParamFilter?: Record; + rulesListKey?: string; + searchFilter?: string; + showActionFilter?: boolean; showCreateRuleButtonInPrompt?: boolean; - setHeaderActions?: (components?: React.ReactNode[]) => void; + showSearchBar?: boolean; statusFilter?: RuleStatus[]; - onStatusFilterChange?: (status: RuleStatus[]) => void; - lastResponseFilter?: string[]; + typeFilter?: string[]; + visibleColumns?: string[]; onLastResponseFilterChange?: (lastResponse: string[]) => void; - lastRunOutcomeFilter?: string[]; onLastRunOutcomeFilterChange?: (lastRunOutcome: string[]) => void; - typeFilter?: string[]; - onTypeFilterChange?: (type: string[]) => void; - searchFilter?: string; + onRuleParamFilterChange?: (ruleParams: Record) => void; onSearchFilterChange?: (search: string) => void; - refresh?: Date; - rulesListKey?: string; - visibleColumns?: string[]; + onStatusFilterChange?: (status: RuleStatus[]) => void; + onTypeFilterChange?: (type: string[]) => void; + setHeaderActions?: (components?: React.ReactNode[]) => void; } export const percentileFields = { @@ -142,47 +146,51 @@ const EMPTY_ARRAY: string[] = []; export const RulesList = ({ filteredRuleTypes = EMPTY_ARRAY, - showActionFilter = true, + lastResponseFilter, + lastRunOutcomeFilter, + refresh, ruleDetailsRoute, + ruleParamFilter, + rulesListKey, + searchFilter = '', + showActionFilter = true, showCreateRuleButtonInPrompt = false, + showSearchBar = true, statusFilter, - onStatusFilterChange, - lastResponseFilter, + typeFilter, + visibleColumns, onLastResponseFilterChange, - lastRunOutcomeFilter, onLastRunOutcomeFilterChange, - searchFilter = '', + onRuleParamFilterChange, onSearchFilterChange, - typeFilter, + onStatusFilterChange, onTypeFilterChange, setHeaderActions, - refresh, - rulesListKey, - visibleColumns, }: RulesListProps) => { const history = useHistory(); const { + actionTypeRegistry, + application: { capabilities }, http, + kibanaFeatures, notifications: { toasts }, - application: { capabilities }, ruleTypeRegistry, - actionTypeRegistry, - kibanaFeatures, } = useKibana().services; const canExecuteActions = hasExecuteActionsCapability(capabilities); const [isPerformingAction, setIsPerformingAction] = useState(false); const [page, setPage] = useState({ index: 0, size: DEFAULT_SEARCH_PAGE_SIZE }); const [inputText, setInputText] = useState(searchFilter); - const [filters, setFilters] = useState(() => ({ - searchText: searchFilter || '', - types: typeFilter || [], + const [filters, setFilters] = useState({ actionTypes: [], ruleExecutionStatuses: lastResponseFilter || [], ruleLastRunOutcomes: lastRunOutcomeFilter || [], + ruleParams: ruleParamFilter || {}, ruleStatuses: statusFilter || [], + searchText: searchFilter || '', tags: [], - })); + types: typeFilter || [], + }); const [ruleFlyoutVisible, setRuleFlyoutVisibility] = useState(false); const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false); @@ -357,6 +365,9 @@ export const RulesList = ({ case 'ruleLastRunOutcomes': onLastRunOutcomeFilterChange?.(value as string[]); break; + case 'ruleParams': + onRuleParamFilterChange?.(value as Record); + break; case 'searchText': onSearchFilterChange?.(value as string); break; @@ -389,6 +400,8 @@ export const RulesList = ({ [setFilters, handleUpdateFiltersEffect] ); + const handleClearRuleParamFilter = () => updateFilters({ filter: 'ruleParams', value: {} }); + useEffect(() => { if (statusFilter) { updateFilters({ filter: 'ruleStatuses', value: statusFilter }); @@ -407,6 +420,12 @@ export const RulesList = ({ } }, [lastRunOutcomeFilter]); + useEffect(() => { + if (ruleParamFilter && !isEqual(ruleParamFilter, filters.ruleParams)) { + updateFilters({ filter: 'ruleParams', value: ruleParamFilter }); + } + }, [ruleParamFilter]); + useEffect(() => { if (typeof searchFilter === 'string') { updateFilters({ filter: 'searchText', value: searchFilter }); @@ -783,26 +802,36 @@ export const RulesList = ({ /> )} + + {showSearchBar && !isEmpty(filters.ruleParams) ? ( + + ) : null} + {showRulesList && ( <> - - + {showSearchBar ? ( + <> + + + + ) : null} + void; +} + +export const RulesListClearRuleFilterBanner = ({ + onClickClearFilter, +}: RulesListClearRuleFilterProps) => { + return ( + <> + +

+ {' '} + {' '} + + + +

+
+ + + ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_filters_bar.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_filters_bar.tsx index 7945f8b328623..a429f3abc06c6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_filters_bar.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_filters_bar.tsx @@ -6,16 +6,16 @@ */ import React from 'react'; -import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiFilterGroup, - EuiFieldSearch, EuiSpacer, EuiLink, + EuiFieldSearch, } from '@elastic/eui'; import { ActionType, RulesListFilters, UpdateFiltersProps } from '../../../../types'; import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; @@ -29,43 +29,42 @@ import { ActionTypeFilter } from './action_type_filter'; import { RuleTagFilter } from './rule_tag_filter'; import { RuleStatusFilter } from './rule_status_filter'; -const ENTER_KEY = 13; - interface RulesListFiltersBarProps { - inputText: string; - filters: RulesListFilters; - showActionFilter: boolean; - rulesStatusesTotal: Record; - rulesLastRunOutcomesTotal: Record; - tags: string[]; - filterOptions: TypeFilterProps['options']; actionTypes: ActionType[]; + filterOptions: TypeFilterProps['options']; + filters: RulesListFilters; + inputText: string; lastUpdate: string; + rulesLastRunOutcomesTotal: Record; + rulesStatusesTotal: Record; + showActionFilter: boolean; showErrors: boolean; - updateFilters: (updateFiltersProps: UpdateFiltersProps) => void; - setInputText: (text: string) => void; + tags: string[]; onClearSelection: () => void; onRefreshRules: () => void; onToggleRuleErrors: () => void; + setInputText: (text: string) => void; + updateFilters: (updateFiltersProps: UpdateFiltersProps) => void; } +const ENTER_KEY = 13; export const RulesListFiltersBar = React.memo((props: RulesListFiltersBarProps) => { const { - filters, - inputText, - showActionFilter = true, - rulesStatusesTotal, - rulesLastRunOutcomesTotal, - tags, actionTypes, filterOptions, + filters, + inputText, lastUpdate, - showErrors, - updateFilters, - setInputText, onClearSelection, onRefreshRules, onToggleRuleErrors, + rulesLastRunOutcomesTotal, + rulesStatusesTotal, + setInputText, + showActionFilter = true, + showErrors, + tags, + updateFilters, } = props; const isRuleTagFilterEnabled = getIsExperimentalFeatureEnabled('ruleTagFilter'); @@ -136,6 +135,19 @@ export const RulesListFiltersBar = React.memo((props: RulesListFiltersBarProps) ...getRuleTagFilter(), ]; + const handleChange = (e: React.ChangeEvent) => { + setInputText(e.target.value); + if (e.target.value === '') { + updateFilters({ filter: 'searchText', value: e.target.value }); + } + }; + + const handleKeyup = (e: React.KeyboardEvent) => { + if (e.keyCode === ENTER_KEY) { + updateFilters({ filter: 'searchText', value: inputText }); + } + }; + return ( <> { - setInputText(e.target.value); - if (e.target.value === '') { - updateFilters({ filter: 'searchText', value: e.target.value }); - } - }} - onKeyUp={(e) => { - if (e.keyCode === ENTER_KEY) { - updateFilters({ filter: 'searchText', value: inputText }); - } - }} placeholder={i18n.translate( 'xpack.triggersActionsUI.sections.rulesList.searchPlaceholderTitle', { defaultMessage: 'Search' } )} + value={inputText} + onChange={handleChange} + onKeyUp={handleKeyup} /> {renderRuleStatusFilter()} diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 83f13dcbe18e5..b5278bde4e175 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -374,6 +374,11 @@ export interface RuleTypeParamsExpressionProps< unifiedSearch: UnifiedSearchPublicPluginStart; } +export type RuleParamsForRules = Record< + string, + Array<{ label: string; value: string | number | object }> +>; + export interface RuleTypeModel { id: string; description: string; @@ -688,13 +693,14 @@ export interface ConnectorServices { } export interface RulesListFilters { - searchText: string; - types: string[]; actionTypes: string[]; ruleExecutionStatuses: string[]; ruleLastRunOutcomes: string[]; + ruleParams: Record; ruleStatuses: RuleStatus[]; + searchText: string; tags: string[]; + types: string[]; } export type UpdateFiltersProps = @@ -709,6 +715,10 @@ export type UpdateFiltersProps = | { filter: 'types' | 'actionTypes' | 'ruleExecutionStatuses' | 'ruleLastRunOutcomes' | 'tags'; value: string[]; + } + | { + filter: 'ruleParams'; + value: Record; }; export interface RulesPageContainerState { diff --git a/x-pack/plugins/triggers_actions_ui/tsconfig.json b/x-pack/plugins/triggers_actions_ui/tsconfig.json index d2ff5b039022e..3222559bf2b9f 100644 --- a/x-pack/plugins/triggers_actions_ui/tsconfig.json +++ b/x-pack/plugins/triggers_actions_ui/tsconfig.json @@ -44,7 +44,6 @@ "@kbn/ui-theme", "@kbn/datemath", "@kbn/core-capabilities-common", - "@kbn/safer-lodash-set", "@kbn/shared-ux-router", "@kbn/alerts-ui-shared", "@kbn/safer-lodash-set", @@ -52,7 +51,7 @@ "@kbn/field-types", "@kbn/ecs", "@kbn/alerts-as-data-utils", - "@kbn/core-ui-settings-common" + "@kbn/core-ui-settings-common", ], "exclude": ["target/**/*"] } From b97b18eecd321cf8a96a505c0bc35edaa2b89692 Mon Sep 17 00:00:00 2001 From: Jill Guyonnet Date: Fri, 21 Apr 2023 15:19:38 +0200 Subject: [PATCH 54/67] [Fleet] Add raw status to Agent details UI (#154826) ## Summary Make raw agent status discoverable in Fleet UI, under `Agent details` tab. Closes https://github.com/elastic/kibana/issues/154067 ### Screenshots Screenshot 2023-04-19 at 12 14 48 Screenshot 2023-04-19 at 13 04 06 ### UX checklist - [ ] Action link title (`View agent JSON`) - [ ] Flyout title (`{agentName} agent details`) - [ ] Download button - [ ] Download button label (`Download JSON`) - [ ] Downloaded file name (`{agentName}-agent-details.json`) ### Testing steps 1. Run Kibana in dev on this branch. 2. In Fleet, click on an agent to get to the agent details page. 3. There should be a new `View agent JSON` item in the `Actions` menu. Click it. 4. A new flyout should open with the agent details in JSON format. Clicking outside of the flyout or on the `Close` button should close the flyout. 5. The `Download JSON` button should download the JSON correctly. ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [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 - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../components/actions_menu.tsx | 39 ++++++- .../agent_details_json_flyout.test.tsx | 74 ++++++++++++ .../components/agent_details_json_flyout.tsx | 106 ++++++++++++++++++ 3 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.test.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx 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 2a3d29226371c..d4710e71de9c2 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 @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useState, useMemo } from 'react'; +import React, { memo, useState, useMemo, useCallback } from 'react'; import { EuiPortal, EuiContextMenuItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -24,6 +24,8 @@ import { isAgentUpgradeable, policyHasFleetServer } from '../../../../services'; import { AgentRequestDiagnosticsModal } from '../../components/agent_request_diagnostics_modal'; import { ExperimentalFeaturesService } from '../../../../services'; +import { AgentDetailsJsonFlyout } from './agent_details_json_flyout'; + export const AgentDetailsActionMenu: React.FunctionComponent<{ agent: Agent; agentPolicy?: AgentPolicy; @@ -37,8 +39,17 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ const [isUnenrollModalOpen, setIsUnenrollModalOpen] = useState(false); const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false); const [isRequestDiagnosticsModalOpen, setIsRequestDiagnosticsModalOpen] = useState(false); + const [isAgentDetailsJsonFlyoutOpen, setIsAgentDetailsJsonFlyoutOpen] = useState(false); const isUnenrolling = agent.status === 'unenrolling'; + const [isContextMenuOpen, setIsContextMenuOpen] = useState(false); + const onContextMenuChange = useCallback( + (open: boolean) => { + setIsContextMenuOpen(open); + }, + [setIsContextMenuOpen] + ); + const hasFleetServer = agentPolicy && policyHasFleetServer(agentPolicy); const { diagnosticFileUploadEnabled } = ExperimentalFeaturesService.get(); @@ -70,6 +81,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ onClick={() => { setIsUnenrollModalOpen(true); }} + key="unenrollAgent" > {isUnenrolling ? ( { setIsUpgradeModalOpen(true); }} + key="upgradeAgent" > , + { + setIsContextMenuOpen(false); + setIsAgentDetailsJsonFlyoutOpen(!isAgentDetailsJsonFlyoutOpen); + }} + key="agentDetailsJson" + > + + , ]; if (diagnosticFileUploadEnabled) { @@ -105,6 +131,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ onClick={() => { setIsRequestDiagnosticsModalOpen(true); }} + key="requestDiagnostics" > )} + {isAgentDetailsJsonFlyoutOpen && ( + + setIsAgentDetailsJsonFlyoutOpen(false)} + /> + + )} { + const agent: Agent = { + id: '123', + packages: [], + type: 'PERMANENT', + active: true, + enrolled_at: `${Date.now()}`, + user_provided_metadata: {}, + local_metadata: {}, + }; + + beforeEach(() => { + mockUseStartServices.mockReturnValue({ + docLinks: { links: { fleet: { troubleshooting: 'https://elastic.co' } } }, + }); + }); + + const renderComponent = () => { + return render(); + }; + + it('renders a title with the agent id if host name is not defined', () => { + const result = renderComponent(); + expect(result.getByText("'123' agent details")).toBeInTheDocument(); + }); + + it('renders a title with the agent host name if defined', () => { + agent.local_metadata = { + host: { + hostname: '456', + }, + }; + const result = renderComponent(); + expect(result.getByText("'456' agent details")).toBeInTheDocument(); + }); + + it('does not add a link to the page after clicking Download', () => { + const result = renderComponent(); + const downloadButton = result.getByRole('button', { name: 'Download JSON' }); + const anchorMocked = { + href: '', + click: jest.fn(), + download: '', + setAttribute: jest.fn(), + } as any; + const createElementSpyOn = jest + .spyOn(document, 'createElement') + .mockReturnValueOnce(anchorMocked); + + downloadButton.click(); + expect(createElementSpyOn).toBeCalledWith('a'); + expect(result.queryAllByRole('link')).toHaveLength(1); // The only link is the one from the flyout's description. + expect(result.getByRole('link')).toHaveAttribute('href', 'https://elastic.co'); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx new file mode 100644 index 0000000000000..093823f108ac6 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiButton, + EuiButtonEmpty, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiLink, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +import type { Agent } from '../../../../types'; +import { useStartServices } from '../../../../hooks'; + +export const AgentDetailsJsonFlyout = memo<{ agent: Agent; onClose: () => void }>( + ({ agent, onClose }) => { + const agentToJson = JSON.stringify(agent, null, 2); + const agentName = + typeof agent.local_metadata?.host?.hostname === 'string' + ? agent.local_metadata.host.hostname + : agent.id; + + const downloadJson = () => { + const link = document.createElement('a'); + link.href = `data:text/json;charset=utf-8,${encodeURIComponent(agentToJson)}`; + link.download = `${agentName}-agent-details.json`; + link.click(); + }; + + const { docLinks } = useStartServices(); + + return ( + + + +

+ +

+
+
+ + +

+ + + + ), + }} + /> +

+
+ + {agentToJson} +
+ + + + + + + + + + + + + + +
+ ); + } +); From d32c8674178e9d31280e03599638861407a75241 Mon Sep 17 00:00:00 2001 From: Saarika Bhasi <55930906+saarikabhasi@users.noreply.github.com> Date: Fri, 21 Apr 2023 09:24:02 -0400 Subject: [PATCH 55/67] [Enterprise Search][Search Application] Update engines to be search application (#155299) ## Summary * Update 'engines' to be 'Search Application' in UI * Navigate to Search application from indices page rather than app search engine * Pre-select index and open Create Search Application Flyout when navigated from Index page https://user-images.githubusercontent.com/55930906/233170965-318cdc63-2953-45f8-89d3-b3f15f11ab11.mov ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [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 --- .../enterprise_search/common/constants.ts | 4 +- .../components/engine/add_indices_flyout.tsx | 7 +- .../engine/add_indices_logic.test.ts | 41 ++------- .../components/engine/add_indices_logic.ts | 12 ++- .../engine_connect/engine_api_integration.tsx | 2 +- .../generate_engine_api_key_modal.tsx | 2 +- .../engine_connect/search_application_api.tsx | 66 ++++++++------ .../components/engine/engine_error.test.tsx | 2 +- .../components/engine/engine_error.tsx | 9 +- .../components/engine/engine_indices.tsx | 8 +- .../engine/engine_view_header_actions.tsx | 87 ------------------- .../components/engine/header_docs_action.tsx | 2 +- .../components/indices_select_combobox.tsx | 7 +- .../components/tables/engines_table.tsx | 8 +- .../engines/create_engine_flyout.tsx | 41 +++++---- .../engines/create_engine_logic.test.ts | 17 ++-- .../components/engines/create_engine_logic.ts | 18 ++-- .../engines/delete_engine_modal.tsx | 6 +- .../components/engines/engines_list.tsx | 73 +++++++++------- .../engines/engines_list_logic.test.ts | 20 +---- .../components/engines/engines_list_logic.ts | 13 +-- .../components/engines/engines_router.tsx | 5 +- .../new_index/new_index_created_toast.tsx | 29 ------- .../create_engine_menu_item.tsx | 30 ++++--- .../header_actions/search_engines_popover.tsx | 19 ++-- .../search_indices/delete_index_modal.tsx | 2 +- .../translations/translations/fr-FR.json | 27 ------ .../translations/translations/ja-JP.json | 27 ------ .../translations/translations/zh-CN.json | 27 ------ 29 files changed, 205 insertions(+), 406 deletions(-) delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_header_actions.tsx diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index bdaf569d27143..34a48c7c76ccf 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -119,8 +119,8 @@ export const SEARCH_EXPERIENCES_PLUGIN = { }; export const ENGINES_PLUGIN = { - NAV_TITLE: i18n.translate('xpack.enterpriseSearch.engines.navTitle', { - defaultMessage: 'Engines', + NAV_TITLE: i18n.translate('xpack.enterpriseSearch.applications.navTitle', { + defaultMessage: 'Applications', }), }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_flyout.tsx index 22ab72ba3a405..7d504c65bb7ac 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_flyout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_flyout.tsx @@ -45,10 +45,13 @@ export const AddIndicesFlyout: React.FC = ({ onClose }) = const { selectedIndices, updateEngineStatus, updateEngineError } = useValues(AddIndicesLogic); const { setSelectedIndices, submitSelectedIndices } = useActions(AddIndicesLogic); - const selectedOptions = useMemo(() => selectedIndices.map(indexToOption), [selectedIndices]); + const selectedOptions = useMemo( + () => selectedIndices.map((index) => indexToOption(index)), + [selectedIndices] + ); const onIndicesChange = useCallback( (options: IndicesSelectComboBoxOption[]) => { - setSelectedIndices(options.map(({ value }) => value).filter(isNotNullish)); + setSelectedIndices(options.map(({ label }) => label).filter(isNotNullish)); }, [setSelectedIndices] ); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.test.ts index a60c6e9df756a..7b3e61940f001 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.test.ts @@ -8,7 +8,6 @@ import { LogicMounter } from '../../../__mocks__/kea_logic'; import { Status } from '../../../../../common/types/api'; -import { ElasticsearchIndexWithIngestion } from '../../../../../common/types/indices'; import { AddIndicesLogic, AddIndicesLogicValues } from './add_indices_logic'; @@ -18,16 +17,6 @@ const DEFAULT_VALUES: AddIndicesLogicValues = { updateEngineStatus: Status.IDLE, }; -const makeIndexData = (name: string): ElasticsearchIndexWithIngestion => ({ - count: 0, - hidden: false, - name, - total: { - docs: { count: 0, deleted: 0 }, - store: { size_in_bytes: 'n/a' }, - }, -}); - describe('AddIndicesLogic', () => { const { mount: mountAddIndicesLogic } = new LogicMounter(AddIndicesLogic); const { mount: mountEngineIndicesLogic } = new LogicMounter(AddIndicesLogic); @@ -47,31 +36,16 @@ describe('AddIndicesLogic', () => { describe('actions', () => { describe('setSelectedIndices', () => { it('adds the indices to selectedIndices', () => { - AddIndicesLogic.actions.setSelectedIndices([ - makeIndexData('index-001'), - makeIndexData('index-002'), - ]); + AddIndicesLogic.actions.setSelectedIndices(['index-001', 'index-002']); - expect(AddIndicesLogic.values.selectedIndices).toEqual([ - makeIndexData('index-001'), - makeIndexData('index-002'), - ]); + expect(AddIndicesLogic.values.selectedIndices).toEqual(['index-001', 'index-002']); }); it('replaces any existing indices', () => { - AddIndicesLogic.actions.setSelectedIndices([ - makeIndexData('index-001'), - makeIndexData('index-002'), - ]); - AddIndicesLogic.actions.setSelectedIndices([ - makeIndexData('index-003'), - makeIndexData('index-004'), - ]); + AddIndicesLogic.actions.setSelectedIndices(['index-001', 'index-002']); + AddIndicesLogic.actions.setSelectedIndices(['index-003', 'index-004']); - expect(AddIndicesLogic.values.selectedIndices).toEqual([ - makeIndexData('index-003'), - makeIndexData('index-004'), - ]); + expect(AddIndicesLogic.values.selectedIndices).toEqual(['index-003', 'index-004']); }); }); }); @@ -103,10 +77,7 @@ describe('AddIndicesLogic', () => { it('calls addIndicesToEngine when there are selectedIndices', () => { jest.spyOn(AddIndicesLogic.actions, 'addIndicesToEngine'); - AddIndicesLogic.actions.setSelectedIndices([ - makeIndexData('index-001'), - makeIndexData('index-002'), - ]); + AddIndicesLogic.actions.setSelectedIndices(['index-001', 'index-002']); AddIndicesLogic.actions.submitSelectedIndices(); expect(AddIndicesLogic.actions.addIndicesToEngine).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.ts index 37e5cf43ebd00..add950937b30a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.ts @@ -7,8 +7,6 @@ import { kea, MakeLogicType } from 'kea'; -import { ElasticsearchIndexWithIngestion } from '../../../../../common/types/indices'; - import { UpdateEngineApiLogic } from '../../api/engines/update_engine_api_logic'; import { EngineIndicesLogic, EngineIndicesLogicActions } from './engine_indices_logic'; @@ -17,21 +15,21 @@ export interface AddIndicesLogicActions { addIndicesToEngine: EngineIndicesLogicActions['addIndicesToEngine']; closeAddIndicesFlyout: EngineIndicesLogicActions['closeAddIndicesFlyout']; engineUpdated: EngineIndicesLogicActions['engineUpdated']; - setSelectedIndices: (indices: ElasticsearchIndexWithIngestion[]) => { - indices: ElasticsearchIndexWithIngestion[]; + setSelectedIndices: (indices: string[]) => { + indices: string[]; }; submitSelectedIndices: () => void; } export interface AddIndicesLogicValues { - selectedIndices: ElasticsearchIndexWithIngestion[]; + selectedIndices: string[]; updateEngineError: typeof UpdateEngineApiLogic.values.error | undefined; updateEngineStatus: typeof UpdateEngineApiLogic.values.status; } export const AddIndicesLogic = kea>({ actions: { - setSelectedIndices: (indices: ElasticsearchIndexWithIngestion[]) => ({ indices }), + setSelectedIndices: (indices: string[]) => ({ indices }), submitSelectedIndices: () => true, }, connect: { @@ -46,7 +44,7 @@ export const AddIndicesLogic = kea name)); + actions.addIndicesToEngine(selectedIndices); }, }), path: ['enterprise_search', 'content', 'add_indices_logic'], diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx index 778d69a5ed56b..b61614838d7a1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx @@ -73,7 +73,7 @@ export const EngineApiIntegrationStage: React.FC = () => {

{i18n.translate('xpack.enterpriseSearch.content.engine.api.step3.intro', { defaultMessage: - 'Learn how to integrate with your engine with the language clients maintained by Elastic to help build your search experience.', + 'Learn how to integrate with your search application with the language clients maintained by Elastic to help build your search experience.', })}

diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.tsx index c0cbcea37e93a..b4d03456ff5ec 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.tsx @@ -59,7 +59,7 @@ export const GenerateEngineApiKeyModal: React.FC {i18n.translate( 'xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.title', { - defaultMessage: 'Create Engine read-only API Key', + defaultMessage: 'Create Search application read-only API Key', } )} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx index c51654f0c557d..9d3c27895657f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx @@ -43,26 +43,32 @@ export const SearchApplicationAPI = () => { const steps = [ { - title: i18n.translate('xpack.enterpriseSearch.content.engine.api.step1.title', { + title: i18n.translate('xpack.enterpriseSearch.content.searchApplication.api.step1.title', { defaultMessage: 'Generate and save API key', }), children: ( <>

- {i18n.translate('xpack.enterpriseSearch.content.engine.api.step1.apiKeyWarning', { - defaultMessage: - "Elastic does not store API keys. Once generated, you'll only be able to view the key one time. Make sure you save it somewhere secure. If you lose access to it you'll need to generate a new API key from this screen.", - })}{' '} + {i18n.translate( + 'xpack.enterpriseSearch.content.searchApplication.api.step1.apiKeyWarning', + { + defaultMessage: + "Elastic does not store API keys. Once generated, you'll only be able to view the key one time. Make sure you save it somewhere secure. If you lose access to it you'll need to generate a new API key from this screen.", + } + )}{' '} - {i18n.translate('xpack.enterpriseSearch.content.engine.api.step1.learnMoreLink', { - defaultMessage: 'Learn more about API keys.', - })} + {i18n.translate( + 'xpack.enterpriseSearch.content.searchApplication.api.step1.learnMoreLink', + { + defaultMessage: 'Learn more about API keys.', + } + )}

@@ -73,10 +79,10 @@ export const SearchApplicationAPI = () => { iconSide="left" iconType="plusInCircleFilled" onClick={openGenerateModal} - data-telemetry-id="entSearchContent-engines-api-step1-createApiKeyButton" + data-telemetry-id="entSearchContent-searchApplications-api-step1-createApiKeyButton" > {i18n.translate( - 'xpack.enterpriseSearch.content.engine.api.step1.createAPIKeyButton', + 'xpack.enterpriseSearch.content.searchApplication.api.step1.createAPIKeyButton', { defaultMessage: 'Create API Key', } @@ -87,16 +93,19 @@ export const SearchApplicationAPI = () => { KibanaLogic.values.navigateToUrl('/app/management/security/api_keys', { shouldNotCreateHref: true, }) } > - {i18n.translate('xpack.enterpriseSearch.content.engine.api.step1.viewKeysButton', { - defaultMessage: 'View Keys', - })} + {i18n.translate( + 'xpack.enterpriseSearch.content.searchApplication.api.step1.viewKeysButton', + { + defaultMessage: 'View Keys', + } + )} @@ -104,17 +113,17 @@ export const SearchApplicationAPI = () => { ), }, { - title: i18n.translate('xpack.enterpriseSearch.content.engine.api.step2.title', { - defaultMessage: "Copy your engine's endpoint", + title: i18n.translate('xpack.enterpriseSearch.content.searchApplication.api.step2.title', { + defaultMessage: "Copy your search application's endpoint", }), children: ( <>

{i18n.translate( - 'xpack.enterpriseSearch.content.engine.api.step2.copyEndpointDescription', + 'xpack.enterpriseSearch.content.searchApplication.api.step2.copyEndpointDescription', { - defaultMessage: "Use this URL to access your engine's API endpoints.", + defaultMessage: "Use this URL to access your search application's API endpoints.", } )}

@@ -131,22 +140,22 @@ export const SearchApplicationAPI = () => { ), }, { - title: i18n.translate('xpack.enterpriseSearch.content.engine.api.step3.title', { + title: i18n.translate('xpack.enterpriseSearch.content.searchApplication.api.step3.title', { defaultMessage: 'Learn how to call your endpoints', }), children: , }, { - title: i18n.translate('xpack.enterpriseSearch.content.engine.api.step4.title', { + title: i18n.translate('xpack.enterpriseSearch.content.searchApplication.api.step4.title', { defaultMessage: '(Optional) Power up your analytics', }), children: ( <>

- {i18n.translate('xpack.enterpriseSearch.content.engine.api.step4.copy', { + {i18n.translate('xpack.enterpriseSearch.content.searchApplication.api.step4.copy', { defaultMessage: - 'Your engine provides basic analytics data as part of this installation. To receive more granular and custom metrics, integrate our Behavioral Analytics script on your platform.', + 'Your search application provides basic analytics data as part of this installation. To receive more granular and custom metrics, integrate our Behavioral Analytics script on your platform.', })}

@@ -154,7 +163,7 @@ export const SearchApplicationAPI = () => { navigateToUrl( generateEncodedPath(`${ANALYTICS_PLUGIN.URL}${COLLECTION_INTEGRATE_PATH}`, { @@ -166,9 +175,12 @@ export const SearchApplicationAPI = () => { iconSide="left" iconType="popout" > - {i18n.translate('xpack.enterpriseSearch.content.engine.api.step4.learnHowLink', { - defaultMessage: 'Learn how', - })} + {i18n.translate( + 'xpack.enterpriseSearch.content.searchApplication.api.step4.learnHowLink', + { + defaultMessage: 'Learn how', + } + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.test.tsx index 9c71c401fbaf0..0f47d2437e3dc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.test.tsx @@ -40,7 +40,7 @@ describe('EngineError', () => { const notFound = wrapper.find(NotFoundPrompt); expect(notFound.prop('backToLink')).toEqual('/engines'); - expect(notFound.prop('backToContent')).toEqual('Back to Engines'); + expect(notFound.prop('backToContent')).toEqual('Back to Search Applications'); const telemetry = wrapper.find(SendEnterpriseSearchTelemetry); expect(telemetry.prop('action')).toEqual('error'); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.tsx index b8be4c7652e1b..d36ff3e14e7a7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.tsx @@ -27,9 +27,12 @@ export const EngineError: React.FC<{ error?: HttpError; notFound?: boolean }> = <> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx index 116430e4d0017..4bcd3fcf4f215 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx @@ -64,7 +64,7 @@ export const EngineIndices: React.FC = () => { description: i18n.translate( 'xpack.enterpriseSearch.content.engine.indices.actions.removeIndex.title', { - defaultMessage: 'Remove this index from engine', + defaultMessage: 'Remove this index from search application', } ), icon: 'minusInCircle', @@ -215,7 +215,7 @@ export const EngineIndices: React.FC = () => { 'xpack.enterpriseSearch.content.engine.indices.unknownIndicesCallout.description', { defaultMessage: - 'Some data might be unreachable from this engine. Check for any pending operations or errors on affected indices, or remove those that should no longer be used by this engine.', + 'Some data might be unreachable from this search application. Check for any pending operations or errors on affected indices, or remove those that should no longer be used by this search application.', } )}

@@ -259,7 +259,7 @@ export const EngineIndices: React.FC = () => { }} title={i18n.translate( 'xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.title', - { defaultMessage: 'Remove this index from the engine' } + { defaultMessage: 'Remove this index from the Search Application' } )} buttonColor="danger" cancelButtonText={CANCEL_BUTTON_LABEL} @@ -278,7 +278,7 @@ export const EngineIndices: React.FC = () => { 'xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.description', { defaultMessage: - "This won't delete the index. You may add it back to this engine at a later time.", + "This won't delete the index. You may add it back to this search application at a later time.", } )}

diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_header_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_header_actions.tsx deleted file mode 100644 index bd48ee8d6d294..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_header_actions.tsx +++ /dev/null @@ -1,87 +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 } from 'react'; - -import { useValues, useActions } from 'kea'; - -import { EuiPopover, EuiButtonIcon, EuiText, EuiContextMenu, EuiIcon } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { TelemetryLogic } from '../../../shared/telemetry/telemetry_logic'; - -import { EngineViewLogic } from './engine_view_logic'; - -export const EngineViewHeaderActions: React.FC = () => { - const { engineData } = useValues(EngineViewLogic); - - const { openDeleteEngineModal } = useActions(EngineViewLogic); - const { sendEnterpriseSearchTelemetry } = useActions(TelemetryLogic); - - const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); - const toggleActionsPopover = () => setIsActionsPopoverOpen((isPopoverOpen) => !isPopoverOpen); - const closePopover = () => setIsActionsPopoverOpen(false); - return ( - <> - - } - isOpen={isActionsPopoverOpen} - panelPaddingSize="xs" - closePopover={closePopover} - display="block" - > - , - name: ( - - {i18n.translate( - 'xpack.enterpriseSearch.content.engine.headerActions.delete', - { defaultMessage: 'Delete this engine' } - )} - - ), - onClick: () => { - if (engineData) { - openDeleteEngineModal(); - sendEnterpriseSearchTelemetry({ - action: 'clicked', - metric: 'entSearchContent-engines-engineView-deleteEngine', - }); - } - }, - size: 's', - }, - ], - }, - ]} - /> - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/header_docs_action.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/header_docs_action.tsx index e20fbc81689a4..4e925b50c63b2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/header_docs_action.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/header_docs_action.tsx @@ -21,7 +21,7 @@ export const EngineHeaderDocsAction: React.FC = () => ( target="_blank" iconType="documents" > - Engine Docs + Search Application Docs diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/indices_select_combobox.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/indices_select_combobox.tsx index 5e6f7cebd89fc..200192d1e20a4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/indices_select_combobox.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/indices_select_combobox.tsx @@ -47,7 +47,7 @@ export const IndicesSelectComboBox = (props: IndicesSelectComboBoxProps) => { }, [searchQuery]); const options: Array> = - data?.indices?.map(indexToOption) ?? []; + data?.indices?.map((index) => indexToOption(index.name, index)) ?? []; const renderOption = (option: EuiComboBoxOptionOption) => ( @@ -84,8 +84,9 @@ export const IndicesSelectComboBox = (props: IndicesSelectComboBoxProps) => { }; export const indexToOption = ( - index: ElasticsearchIndexWithIngestion + indexName: string, + index?: ElasticsearchIndexWithIngestion ): IndicesSelectComboBoxOption => ({ - label: index.name, + label: indexName, value: index, }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/tables/engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/tables/engines_table.tsx index f07516313f6e8..de291752639fa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/tables/engines_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/tables/engines_table.tsx @@ -56,7 +56,7 @@ export const EnginesListTable: React.FC = ({ { field: 'name', name: i18n.translate('xpack.enterpriseSearch.content.enginesList.table.column.name', { - defaultMessage: 'Engine Name', + defaultMessage: 'Search Application Name', }), mobileOptions: { header: true, @@ -117,7 +117,7 @@ export const EnginesListTable: React.FC = ({ description: i18n.translate( 'xpack.enterpriseSearch.content.enginesList.table.column.actions.view.buttonDescription', { - defaultMessage: 'View this engine', + defaultMessage: 'View this search application', } ), type: 'icon', @@ -134,7 +134,7 @@ export const EnginesListTable: React.FC = ({ description: i18n.translate( 'xpack.enterpriseSearch.content.enginesList.table.column.action.delete.buttonDescription', { - defaultMessage: 'Delete this engine', + defaultMessage: 'Delete this search application', } ), type: 'icon', @@ -144,7 +144,7 @@ export const EnginesListTable: React.FC = ({ i18n.translate( 'xpack.enterpriseSearch.content.engineList.table.column.actions.deleteEngineLabel', { - defaultMessage: 'Delete this engine', + defaultMessage: 'Delete this search application', } ), onClick: (engine) => { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_flyout.tsx index ae7f7423be7ce..2df12a2b9bd8f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_flyout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_flyout.tsx @@ -5,13 +5,13 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; + +import { useLocation } from 'react-router-dom'; import { useActions, useValues } from 'kea'; import { - EuiButton, - EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiFieldText, @@ -26,6 +26,8 @@ import { EuiTitle, EuiComboBoxOptionOption, EuiCallOut, + EuiButton, + EuiButtonEmpty, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -33,14 +35,14 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { Status } from '../../../../../common/types/api'; import { ElasticsearchIndexWithIngestion } from '../../../../../common/types/indices'; -import { isNotNullish } from '../../../../../common/utils/is_not_nullish'; -import { CANCEL_BUTTON_LABEL } from '../../../shared/constants'; +import { CANCEL_BUTTON_LABEL, ESINDEX_QUERY_PARAMETER } from '../../../shared/constants'; import { docLinks } from '../../../shared/doc_links'; import { getErrorsFromHttpResponse } from '../../../shared/flash_messages/handle_api_errors'; -import { indexToOption, IndicesSelectComboBox } from './components/indices_select_combobox'; +import { parseQueryParams } from '../../../shared/query_params'; +import { indexToOption, IndicesSelectComboBox } from './components/indices_select_combobox'; import { CreateEngineLogic } from './create_engine_logic'; export interface CreateEngineFlyoutProps { @@ -60,11 +62,18 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => { selectedIndices, } = useValues(CreateEngineLogic); + const { search } = useLocation() as unknown as Location; + const { ...params } = parseQueryParams(search); + const indexName = params[ESINDEX_QUERY_PARAMETER]; + const onIndicesChange = ( selectedOptions: Array> ) => { - setSelectedIndices(selectedOptions.map((option) => option.value).filter(isNotNullish)); + setSelectedIndices(selectedOptions.map((option) => option.label)); }; + useEffect(() => { + if (indexName && typeof indexName === 'string') setSelectedIndices([indexName]); + }, []); return ( @@ -72,7 +81,7 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => {

{i18n.translate('xpack.enterpriseSearch.content.engines.createEngine.headerTitle', { - defaultMessage: 'Create an engine', + defaultMessage: 'Create a Search Application', })}

@@ -81,7 +90,7 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => {

{ > {i18n.translate( 'xpack.enterpriseSearch.content.engines.createEngine.header.docsLink', - { defaultMessage: 'Engines documentation' } + { defaultMessage: 'Search Application documentation' } )} ), @@ -107,7 +116,7 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => { color="danger" title={i18n.translate( 'xpack.enterpriseSearch.content.engines.createEngine.header.createError.title', - { defaultMessage: 'Error creating engine' } + { defaultMessage: 'Error creating search application' } )} > {getErrorsFromHttpResponse(createEngineError).map((errMessage, i) => ( @@ -126,7 +135,7 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => { fullWidth isDisabled={formDisabled} onChange={onIndicesChange} - selectedOptions={selectedIndices.map(indexToOption)} + selectedOptions={selectedIndices.map((index: string) => indexToOption(index))} /> ), status: indicesStatus, @@ -142,7 +151,7 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => { disabled={formDisabled} placeholder={i18n.translate( 'xpack.enterpriseSearch.content.engines.createEngine.nameEngine.placeholder', - { defaultMessage: 'Engine name' } + { defaultMessage: 'Search Application name' } )} value={engineName} onChange={(e) => setEngineName(e.target.value)} @@ -151,7 +160,7 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => { status: engineNameStatus, title: i18n.translate( 'xpack.enterpriseSearch.content.engines.createEngine.nameEngine.title', - { defaultMessage: 'Name your engine' } + { defaultMessage: 'Name your Search Application' } ), }, ]} @@ -171,7 +180,7 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => { { }} > {i18n.translate('xpack.enterpriseSearch.content.engines.createEngine.submit', { - defaultMessage: 'Create this engine', + defaultMessage: 'Create this Search Application', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.test.ts index 0d88ca44ff87d..93bdda9c4a2d0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.test.ts @@ -8,10 +8,12 @@ import { LogicMounter } from '../../../__mocks__/kea_logic'; import { Status } from '../../../../../common/types/api'; -import { ElasticsearchIndexWithIngestion } from '../../../../../common/types/indices'; +import { KibanaLogic } from '../../../shared/kibana'; import { CreateEngineApiLogic } from '../../api/engines/create_engine_api_logic'; +import { ENGINES_PATH } from '../../routes'; + import { CreateEngineLogic, CreateEngineLogicValues } from './create_engine_logic'; const DEFAULT_VALUES: CreateEngineLogicValues = { @@ -27,7 +29,7 @@ const DEFAULT_VALUES: CreateEngineLogicValues = { const VALID_ENGINE_NAME = 'unit-test-001'; const INVALID_ENGINE_NAME = 'TEST'; -const VALID_INDICES_DATA = [{ name: 'search-index-01' }] as ElasticsearchIndexWithIngestion[]; +const VALID_INDICES_DATA = ['search-index-01']; describe('CreateEngineLogic', () => { const { mount: apiLogicMount } = new LogicMounter(CreateEngineApiLogic); @@ -59,16 +61,17 @@ describe('CreateEngineLogic', () => { indices: ['search-index-01'], }); }); - it('engineCreated is handled', () => { + it('engineCreated is handled and is navigated to Search application list page', () => { jest.spyOn(CreateEngineLogic.actions, 'fetchEngines'); - jest.spyOn(CreateEngineLogic.actions, 'closeEngineCreate'); - + jest + .spyOn(KibanaLogic.values, 'navigateToUrl') + .mockImplementationOnce(() => Promise.resolve()); CreateEngineApiLogic.actions.apiSuccess({ result: 'created', }); + expect(KibanaLogic.values.navigateToUrl).toHaveBeenCalledWith(ENGINES_PATH); expect(CreateEngineLogic.actions.fetchEngines).toHaveBeenCalledTimes(1); - expect(CreateEngineLogic.actions.closeEngineCreate).toHaveBeenCalledTimes(1); }); }); describe('selectors', () => { @@ -114,7 +117,7 @@ describe('CreateEngineLogic', () => { it('returns true while create request in progress', () => { CreateEngineApiLogic.actions.makeRequest({ engineName: VALID_ENGINE_NAME, - indices: [VALID_INDICES_DATA[0].name], + indices: [VALID_INDICES_DATA[0]], }); expect(CreateEngineLogic.values.formDisabled).toEqual(true); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.ts index 266dab05c5441..c939dda096446 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.ts @@ -8,27 +8,27 @@ import { kea, MakeLogicType } from 'kea'; import { Status } from '../../../../../common/types/api'; -import { ElasticsearchIndexWithIngestion } from '../../../../../common/types/indices'; +import { KibanaLogic } from '../../../shared/kibana'; import { CreateEngineApiLogic, CreateEngineApiLogicActions, } from '../../api/engines/create_engine_api_logic'; +import { ENGINES_PATH } from '../../routes'; import { EnginesListLogic } from './engines_list_logic'; const NAME_VALIDATION = new RegExp(/^[a-z0-9\-]+$/); export interface CreateEngineLogicActions { - closeEngineCreate: () => void; createEngine: () => void; createEngineRequest: CreateEngineApiLogicActions['makeRequest']; engineCreateError: CreateEngineApiLogicActions['apiError']; engineCreated: CreateEngineApiLogicActions['apiSuccess']; fetchEngines: () => void; setEngineName: (engineName: string) => { engineName: string }; - setSelectedIndices: (indices: ElasticsearchIndexWithIngestion[]) => { - indices: ElasticsearchIndexWithIngestion[]; + setSelectedIndices: (indices: string[]) => { + indices: string[]; }; } @@ -40,7 +40,7 @@ export interface CreateEngineLogicValues { engineNameStatus: 'complete' | 'incomplete' | 'warning'; formDisabled: boolean; indicesStatus: 'complete' | 'incomplete'; - selectedIndices: ElasticsearchIndexWithIngestion[]; + selectedIndices: string[]; } export const CreateEngineLogic = kea< @@ -49,12 +49,12 @@ export const CreateEngineLogic = kea< actions: { createEngine: true, setEngineName: (engineName: string) => ({ engineName }), - setSelectedIndices: (indices: ElasticsearchIndexWithIngestion[]) => ({ indices }), + setSelectedIndices: (indices: string[]) => ({ indices }), }, connect: { actions: [ EnginesListLogic, - ['closeEngineCreate', 'fetchEngines'], + ['fetchEngines'], CreateEngineApiLogic, [ 'makeRequest as createEngineRequest', @@ -68,12 +68,12 @@ export const CreateEngineLogic = kea< createEngine: () => { actions.createEngineRequest({ engineName: values.engineName, - indices: values.selectedIndices.map((index) => index.name), + indices: values.selectedIndices, }); }, engineCreated: () => { actions.fetchEngines(); - actions.closeEngineCreate(); + KibanaLogic.values.navigateToUrl(ENGINES_PATH); }, }), path: ['enterprise_search', 'content', 'create_engine_logic'], diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/delete_engine_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/delete_engine_modal.tsx index 4203969bb74a2..46d7c8586ed80 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/delete_engine_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/delete_engine_modal.tsx @@ -28,7 +28,7 @@ export const DeleteEngineModal: React.FC = ({ engineName return ( { @@ -42,7 +42,7 @@ export const DeleteEngineModal: React.FC = ({ engineName confirmButtonText={i18n.translate( 'xpack.enterpriseSearch.content.engineList.deleteEngineModal.confirmButton.title', { - defaultMessage: 'Yes, delete this engine ', + defaultMessage: 'Yes, delete this search application', } )} buttonColor="danger" @@ -53,7 +53,7 @@ export const DeleteEngineModal: React.FC = ({ engineName 'xpack.enterpriseSearch.content.engineList.deleteEngineModal.delete.description', { defaultMessage: - 'Deleting your engine is not a reversible action. Your indices will not be affected. ', + 'Deleting your search application is not a reversible action. Your indices will not be affected. ', } )}

diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx index 7617467bba64f..48dce9f524164 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx @@ -31,6 +31,7 @@ import { docLinks } from '../../../shared/doc_links'; import { KibanaLogic } from '../../../shared/kibana'; import { LicensingLogic } from '../../../shared/licensing'; import { EXPLORE_PLATINUM_FEATURES_LINK } from '../../../workplace_search/constants'; +import { ENGINES_PATH, ENGINE_CREATION_PATH } from '../../routes'; import { EnterpriseSearchEnginesPageTemplate } from '../layout/engines_page_template'; import { LicensingCallout, LICENSING_FEATURE } from '../shared/licensing_callout/licensing_callout'; @@ -43,9 +44,12 @@ import { EngineListIndicesFlyout } from './engines_list_flyout'; import { EnginesListFlyoutLogic } from './engines_list_flyout_logic'; import { EnginesListLogic } from './engines_list_logic'; -export const CreateEngineButton: React.FC<{ disabled: boolean }> = ({ disabled }) => { +interface CreateEngineButtonProps { + disabled: boolean; +} +export const CreateEngineButton: React.FC = ({ disabled }) => { const [showPopover, setShowPopover] = useState(false); - const { openEngineCreate } = useActions(EnginesListLogic); + return ( = ({ disabled } iconType="plusInCircle" data-test-subj="enterprise-search-content-engines-creation-button" data-telemetry-id="entSearchContent-engines-list-createEngine" - disabled={disabled} - onClick={openEngineCreate} + isDisabled={disabled} + onClick={() => KibanaLogic.values.navigateToUrl(ENGINE_CREATION_PATH)} > - {i18n.translate('xpack.enterpriseSearch.content.engines.createEngineButtonLabel', { - defaultMessage: 'Create Search Application', - })} + {i18n.translate( + 'xpack.enterpriseSearch.content.searchApplications.createEngineButtonLabel', + { + defaultMessage: 'Create Search Application', + } + )}
} > @@ -82,7 +89,7 @@ export const CreateEngineButton: React.FC<{ disabled: boolean }> = ({ disabled } @@ -94,27 +101,27 @@ export const CreateEngineButton: React.FC<{ disabled: boolean }> = ({ disabled } ); }; +interface ListProps { + createEngineFlyoutOpen?: boolean; +} -export const EnginesList: React.FC = () => { +export const EnginesList: React.FC = ({ createEngineFlyoutOpen }) => { const { closeDeleteEngineModal, - closeEngineCreate, fetchEngines, onPaginate, openDeleteEngineModal, setSearchQuery, setIsFirstRequest, } = useActions(EnginesListLogic); - const { openFetchEngineFlyout } = useActions(EnginesListFlyoutLogic); - const { isCloud } = useValues(KibanaLogic); + const { isCloud, navigateToUrl } = useValues(KibanaLogic); const { hasPlatinumLicense } = useValues(LicensingLogic); const isGated = !isCloud && !hasPlatinumLicense; const { - createEngineFlyoutOpen, deleteModalEngineName, hasNoEngines, isDeleteModalVisible, @@ -127,7 +134,7 @@ export const EnginesList: React.FC = () => { const throttledSearchQuery = useThrottle(searchQuery, INPUT_THROTTLE_DELAY_MS); useEffect(() => { - // Don't fetch engines if we don't have a valid license + // Don't fetch search applications if we don't have a valid license if (!isGated) { fetchEngines(); } @@ -148,18 +155,18 @@ export const EnginesList: React.FC = () => { ) : null} - {createEngineFlyoutOpen && } + {createEngineFlyoutOpen && navigateToUrl(ENGINES_PATH)} />} { target="_blank" data-telemetry-id="entSearchContent-engines-documentation-viewDocumentaion" > - {i18n.translate('xpack.enterpriseSearch.content.engines.documentation', { - defaultMessage: 'explore our Search Applications documentation', - })} + {i18n.translate( + 'xpack.enterpriseSearch.content.searchApplications.documentation', + { + defaultMessage: 'explore our Search Applications documentation', + } + )} ), }} /> ), - pageTitle: i18n.translate('xpack.enterpriseSearch.content.engines.title', { + pageTitle: i18n.translate('xpack.enterpriseSearch.content.searchApplications.title', { defaultMessage: 'Search Applications', }), rightSideItems: isLoading @@ -185,7 +195,7 @@ export const EnginesList: React.FC = () => { ? [] : [], }} - pageViewTelemetry="Engines" + pageViewTelemetry="Search Applications" isLoading={isLoading && !isGated} > {isGated && ( @@ -200,15 +210,15 @@ export const EnginesList: React.FC = () => { { {i18n.translate( - 'xpack.enterpriseSearch.content.engines.searchPlaceholder.description', + 'xpack.enterpriseSearch.content.searchApplications.searchPlaceholder.description', { - defaultMessage: 'Locate an engine via name or by its included indices.', + defaultMessage: + 'Locate a search application via name or by its included indices.', } )} @@ -230,7 +241,7 @@ export const EnginesList: React.FC = () => { { }); }); }); - describe('openEngineCreate', () => { - it('set createEngineFlyoutOpen to true', () => { - EnginesListLogic.actions.openEngineCreate(); - expect(EnginesListLogic.values).toEqual({ - ...DEFAULT_VALUES, - createEngineFlyoutOpen: true, - }); - }); - }); - describe('closeEngineCreate', () => { - it('set createEngineFlyoutOpen to false', () => { - EnginesListLogic.actions.closeEngineCreate(); - expect(EnginesListLogic.values).toEqual({ - ...DEFAULT_VALUES, - createEngineFlyoutOpen: false, - }); - }); - }); + describe('setSearchQuery', () => { it('set setSearchQuery to search value', () => { EnginesListLogic.actions.setSearchQuery('my-search-query'); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list_logic.ts index 082747612698a..02ac44ae16c8d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list_logic.ts @@ -39,7 +39,7 @@ export type EnginesListActions = Pick< 'apiError' | 'apiSuccess' | 'makeRequest' > & { closeDeleteEngineModal(): void; - closeEngineCreate(): void; + deleteEngine: DeleteEnginesApiLogicActions['makeRequest']; deleteError: DeleteEnginesApiLogicActions['apiError']; deleteSuccess: DeleteEnginesApiLogicActions['apiSuccess']; @@ -50,13 +50,11 @@ export type EnginesListActions = Pick< openDeleteEngineModal: (engine: EnterpriseSearchEngine | EnterpriseSearchEngineDetails) => { engine: EnterpriseSearchEngine; }; - openEngineCreate(): void; setIsFirstRequest(): void; setSearchQuery(searchQuery: string): { searchQuery: string }; }; interface EngineListValues { - createEngineFlyoutOpen: boolean; data: typeof FetchEnginesAPILogic.values.data; deleteModalEngine: EnterpriseSearchEngine | null; deleteModalEngineName: string; @@ -76,11 +74,9 @@ interface EngineListValues { export const EnginesListLogic = kea>({ actions: { closeDeleteEngineModal: true, - closeEngineCreate: true, fetchEngines: true, onPaginate: (args: EuiBasicTableOnChange) => ({ pageNumber: args.page.index }), openDeleteEngineModal: (engine) => ({ engine }), - openEngineCreate: true, setIsFirstRequest: true, setSearchQuery: (searchQuery: string) => ({ searchQuery }), }, @@ -111,13 +107,6 @@ export const EnginesListLogic = kea ({ - createEngineFlyoutOpen: [ - false, - { - closeEngineCreate: () => false, - openEngineCreate: () => true, - }, - ], deleteModalEngine: [ null, { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_router.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_router.tsx index 662e1d7c21417..bcd5e5ec3bcd2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_router.tsx @@ -10,7 +10,7 @@ import { Switch } from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; -import { ENGINES_PATH, ENGINE_PATH } from '../../routes'; +import { ENGINES_PATH, ENGINE_CREATION_PATH, ENGINE_PATH } from '../../routes'; import { EngineRouter } from '../engine/engine_router'; import { NotFound } from '../not_found'; @@ -23,6 +23,9 @@ export const EnginesRouter: React.FC = () => { + + + diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_index_created_toast.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_index_created_toast.tsx index f1d120dee4e46..ac5bb69706146 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_index_created_toast.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_index_created_toast.tsx @@ -5,37 +5,9 @@ * 2.0. */ -import React from 'react'; - -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; - import { i18n } from '@kbn/i18n'; -import { APP_SEARCH_URL } from '../../../../../common/constants'; - import { flashSuccessToast } from '../../../shared/flash_messages'; -import { EuiButtonTo } from '../../../shared/react_router_helpers'; - -const SuccessToast = ( - <> - - {i18n.translate('xpack.enterpriseSearch.content.new_index.successToast.description', { - defaultMessage: - 'You can use App Search engines to build a search experience for your new Elasticsearch index.', - })} - - - - - - {i18n.translate('xpack.enterpriseSearch.content.new_index.successToast.button.label', { - defaultMessage: 'Create an engine', - })} - - - - -); export function flashIndexCreatedToast(): void { flashSuccessToast( @@ -44,7 +16,6 @@ export function flashIndexCreatedToast(): void { }), { iconType: 'cheer', - text: SuccessToast, } ); } diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/create_engine_menu_item.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/create_engine_menu_item.tsx index 61f090f8e20ad..059fa91c8ac0b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/create_engine_menu_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/create_engine_menu_item.tsx @@ -11,11 +11,11 @@ import { EuiContextMenuItem, EuiText, EuiFlexGroup, EuiFlexItem } from '@elastic import { i18n } from '@kbn/i18n'; -import { APP_SEARCH_PLUGIN } from '../../../../../../../common/constants'; -import { ENGINE_CREATION_PATH } from '../../../../../app_search/routes'; +import { ENTERPRISE_SEARCH_CONTENT_PLUGIN } from '../../../../../../../common/constants'; import { ESINDEX_QUERY_PARAMETER } from '../../../../../shared/constants'; import { generateEncodedPath } from '../../../../../shared/encode_path_params'; import { KibanaLogic } from '../../../../../shared/kibana'; +import { ENGINE_CREATION_PATH } from '../../../../routes'; export interface CreateEngineMenuItemProps { indexName?: string; @@ -28,12 +28,15 @@ export const CreateEngineMenuItem: React.FC = ({ ingestionMethod, isHiddenIndex, }) => { - const engineCreationPath = !indexName - ? `${APP_SEARCH_PLUGIN.URL}${ENGINE_CREATION_PATH}` - : generateEncodedPath(`${APP_SEARCH_PLUGIN.URL}${ENGINE_CREATION_PATH}?:indexKey=:indexName`, { - indexKey: ESINDEX_QUERY_PARAMETER, - indexName, - }); + const searchApplicationCreationPath = !indexName + ? `${ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL}${ENGINE_CREATION_PATH}` + : generateEncodedPath( + `${ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL}${ENGINE_CREATION_PATH}?:indexKey=:indexName`, + { + indexKey: ESINDEX_QUERY_PARAMETER, + indexName, + } + ); return ( @@ -43,7 +46,7 @@ export const CreateEngineMenuItem: React.FC = ({ size="s" icon="plusInCircle" onClick={() => { - KibanaLogic.values.navigateToUrl(engineCreationPath, { + KibanaLogic.values.navigateToUrl(searchApplicationCreationPath, { shouldNotCreateHref: true, }); }} @@ -51,9 +54,12 @@ export const CreateEngineMenuItem: React.FC = ({ >

- {i18n.translate('xpack.enterpriseSearch.content.index.searchEngines.createEngine', { - defaultMessage: 'Create an App Search engine', - })} + {i18n.translate( + 'xpack.enterpriseSearch.content.index.searchApplication.createSearchApplication', + { + defaultMessage: 'Create a Search Application', + } + )}

diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/search_engines_popover.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/search_engines_popover.tsx index 47584d3021ada..611c63c276750 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/search_engines_popover.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/search_engines_popover.tsx @@ -20,9 +20,11 @@ import { import { i18n } from '@kbn/i18n'; -import { APP_SEARCH_PLUGIN } from '../../../../../../../common/constants'; +import { ENTERPRISE_SEARCH_CONTENT_PLUGIN } from '../../../../../../../common/constants'; import { KibanaLogic } from '../../../../../shared/kibana'; +import { ENGINES_PATH } from '../../../../routes'; + import { CreateEngineMenuItem } from './create_engine_menu_item'; import { SearchEnginesPopoverLogic } from './search_engines_popover_logic'; @@ -52,7 +54,7 @@ export const SearchEnginesPopover: React.FC = ({ onClick={toggleSearchEnginesPopover} > {i18n.translate('xpack.enterpriseSearch.content.index.searchEngines.label', { - defaultMessage: 'Search engines', + defaultMessage: 'Search Applications', })} } @@ -64,15 +66,18 @@ export const SearchEnginesPopover: React.FC = ({ data-telemetry-id={`entSearchContent-${ingestionMethod}-header-searchEngines-viewEngines`} icon="eye" onClick={() => { - KibanaLogic.values.navigateToUrl(APP_SEARCH_PLUGIN.URL, { - shouldNotCreateHref: true, - }); + KibanaLogic.values.navigateToUrl( + ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + ENGINES_PATH, + { + shouldNotCreateHref: true, + } + ); }} >

{i18n.translate('xpack.enterpriseSearch.content.index.searchEngines.viewEngines', { - defaultMessage: 'View App Search engines', + defaultMessage: 'View Search Applications', })}

@@ -82,7 +87,7 @@ export const SearchEnginesPopover: React.FC = ({ content={i18n.translate( 'xpack.enterpriseSearch.content.index.searchEngines.createEngineDisabledTooltip', { - defaultMessage: 'You cannot create engines from hidden indices.', + defaultMessage: 'You cannot create search applications from hidden indices.', } )} > diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/delete_index_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/delete_index_modal.tsx index 90e1da7c26eb2..38875821aeccf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/delete_index_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/delete_index_modal.tsx @@ -80,7 +80,7 @@ export const DeleteIndexModal: React.FC = () => { 'xpack.enterpriseSearch.content.searchIndices.deleteModal.delete.description', { defaultMessage: - 'Deleting this index will also delete all of its data and its {ingestionMethod} configuration. Any associated search engines will no longer be able to access any data stored in this index.', + 'Deleting this index will also delete all of its data and its {ingestionMethod} configuration. Any associated search applications will no longer be able to access any data stored in this index.', values: { ingestionMethod: ingestionMethodToText(ingestionMethod), }, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 3e5b85ffcf0b4..cdfb1e09c6ae9 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -11131,8 +11131,6 @@ "xpack.enterpriseSearch.content.engine.indices.actions.viewIndex.caption": "Afficher l'index {indexName}", "xpack.enterpriseSearch.content.engineList.deleteEngine.successToast.title": "{engineName} a été supprimé", "xpack.enterpriseSearch.content.engines.createEngine.headerSubTitle": "Un moteur permet à vos utilisateurs d'interroger les données de vos index. Pour en savoir plus, explorez notre {enginesDocsLink} !", - "xpack.enterpriseSearch.content.engines.description": "Les moteurs vous permettent d'interroger les données indexées avec un ensemble complet d'outils de pertinence, d'analyse et de personnalisation. Pour en savoir plus sur le fonctionnement des moteurs dans Enterprise Search, {documentationUrl}", - "xpack.enterpriseSearch.content.engines.enginesList.description": "Affichage de {from}-{to} sur {total}", "xpack.enterpriseSearch.content.enginesList.indicesFlyout.subTitle": "Afficher les index associés à {engineName}", "xpack.enterpriseSearch.content.enginesList.table.column.view.indices": "{indicesCount, number} {indicesCount, plural, other {index}}", "xpack.enterpriseSearch.content.index.connector.syncRules.description": "Ajoutez une règle de synchronisation pour personnaliser les données qui sont synchronisées à partir de {indexName}. Tout est inclus par défaut, et les documents sont validés par rapport à l'ensemble des règles d'indexation configurées, en commençant par le haut de la liste.", @@ -12225,22 +12223,9 @@ "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.done": "Terminé", "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.generateButton": "Générer une clé en lecture seule", "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.title": "Créer une clé d'API en lecture seule pour le moteur", - "xpack.enterpriseSearch.content.engine.api.step1.apiKeyWarning": "Elastic ne stocke pas les clés d’API. Une fois la clé générée, vous ne pourrez la visualiser qu'une seule fois. Veillez à l'enregistrer dans un endroit sûr. Si vous n'y avez plus accès, vous devrez générer une nouvelle clé d’API à partir de cet écran.", - "xpack.enterpriseSearch.content.engine.api.step1.createAPIKeyButton": "Créer une clé d'API", - "xpack.enterpriseSearch.content.engine.api.step1.learnMoreLink": "Découvrez plus d'informations sur les clés d'API.", - "xpack.enterpriseSearch.content.engine.api.step1.title": "Générer et enregistrer la clé d'API", - "xpack.enterpriseSearch.content.engine.api.step1.viewKeysButton": "Afficher les clés", - "xpack.enterpriseSearch.content.engine.api.step2.copyEndpointDescription": "Utilisez cette URL pour accéder aux points de terminaison d'API de votre moteur.", - "xpack.enterpriseSearch.content.engine.api.step2.title": "Copier le point de terminaison de votre moteur", "xpack.enterpriseSearch.content.engine.api.step3.curlTitle": "cURL", "xpack.enterpriseSearch.content.engine.api.step3.intro": "Découvrez comment effectuer l'intégration avec votre moteur à l'aide des clients de langage gérés par Elastic pour vous aider à créer votre expérience de recherche.", "xpack.enterpriseSearch.content.engine.api.step3.searchUITitle": "Search UI", - "xpack.enterpriseSearch.content.engine.api.step3.title": "Découvrir comment appeler vos points de terminaison", - "xpack.enterpriseSearch.content.engine.api.step4.copy": "Votre moteur fournit des données d'analyse de base dans le cadre de cette installation. Pour recevoir des indicateurs plus granulaires et personnalisés, intégrez notre script Behavioral Analytics dans votre plateforme.", - "xpack.enterpriseSearch.content.engine.api.step4.learnHowLink": "Découvrez comment faire", - "xpack.enterpriseSearch.content.engine.api.step4.title": "(Facultatif) Booster vos analyses", - "xpack.enterpriseSearch.content.engine.headerActions.actionsButton.ariaLabel": "Bouton de menu d'actions du moteur", - "xpack.enterpriseSearch.content.engine.headerActions.delete": "Supprimer ce moteur", "xpack.enterpriseSearch.content.engine.indices.actions.columnTitle": "Actions", "xpack.enterpriseSearch.content.engine.indices.actions.removeIndex.title": "Retirer cet index du moteur", "xpack.enterpriseSearch.content.engine.indices.actions.viewIndex.title": "Afficher cet index", @@ -12265,7 +12250,6 @@ "xpack.enterpriseSearch.content.engineList.deleteEngineModal.delete.description": "La suppression de votre moteur ne pourra pas être annulée. Vos index ne seront pas affectés. ", "xpack.enterpriseSearch.content.engineList.deleteEngineModal.title": "Supprimer définitivement ce moteur ?", "xpack.enterpriseSearch.content.engineList.table.column.actions.deleteEngineLabel": "Supprimer ce moteur", - "xpack.enterpriseSearch.content.engines.breadcrumb": "Moteurs", "xpack.enterpriseSearch.content.engines.createEngine.header.createError.title": "Erreur lors de la création du moteur", "xpack.enterpriseSearch.content.engines.createEngine.header.docsLink": "Documentation sur les moteurs", "xpack.enterpriseSearch.content.engines.createEngine.headerTitle": "Créer un moteur", @@ -12273,15 +12257,9 @@ "xpack.enterpriseSearch.content.engines.createEngine.nameEngine.title": "Nommer votre moteur", "xpack.enterpriseSearch.content.engines.createEngine.selectIndices.title": "Sélectionner les index", "xpack.enterpriseSearch.content.engines.createEngine.submit": "Créer ce moteur", - "xpack.enterpriseSearch.content.engines.createEngineButtonLabel": "Créer un moteur", - "xpack.enterpriseSearch.content.engines.documentation": "explorer notre documentation sur les moteurs", "xpack.enterpriseSearch.content.engines.enginesList.empty.description": "Lançons-nous dans la création de votre premier moteur.", "xpack.enterpriseSearch.content.engines.enginesList.empty.title": "Créer votre premier moteur", "xpack.enterpriseSearch.content.engines.indices.addIndicesFlyout.updateError.title": "Erreur lors de la mise à jour du moteur", - "xpack.enterpriseSearch.content.engines.searchBar.ariaLabel": "Moteurs de recherche", - "xpack.enterpriseSearch.content.engines.searchPlaceholder": "Moteurs de recherche", - "xpack.enterpriseSearch.content.engines.searchPlaceholder.description": "Localisez un moteur en fonction de son nom ou de ses index inclus.", - "xpack.enterpriseSearch.content.engines.title": "Moteurs", "xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.docsCount.columnTitle": "Nombre de documents", "xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.health.columnTitle": "Intégrité des index", "xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.name.columnTitle": "Nom de l'index", @@ -12338,7 +12316,6 @@ "xpack.enterpriseSearch.content.index.pipelines.settings.reduceWhitespaceDescription": "Supprimer automatiquement l'espace supplémentaire de vos documents", "xpack.enterpriseSearch.content.index.pipelines.settings.reduceWhitespaceLabel": "Réduire l'espace", "xpack.enterpriseSearch.content.index.pipelines.settings.runMlInferenceDescrition": "Améliorer vos données à l'aide de modèles de ML entraînés compatibles", - "xpack.enterpriseSearch.content.index.searchEngines.createEngine": "Créer un moteur App Search", "xpack.enterpriseSearch.content.index.searchEngines.createEngineDisabledTooltip": "Vous ne pouvez pas créer de moteurs à partir d'index masqués.", "xpack.enterpriseSearch.content.index.searchEngines.label": "Moteurs de recherche", "xpack.enterpriseSearch.content.index.searchEngines.viewEngines": "Afficher les moteurs App Search", @@ -12617,8 +12594,6 @@ "xpack.enterpriseSearch.content.nativeConnectors.mongodb.name": "MongoDB", "xpack.enterpriseSearch.content.nativeConnectors.mysql.name": "MySQL", "xpack.enterpriseSearch.content.navTitle": "Contenu", - "xpack.enterpriseSearch.content.new_index.successToast.button.label": "Créer un moteur", - "xpack.enterpriseSearch.content.new_index.successToast.description": "Vous pouvez utiliser des moteurs App Search pour créer une expérience de recherche pour votre nouvel index Elasticsearch.", "xpack.enterpriseSearch.content.new_index.successToast.title": "Index créé avec succès", "xpack.enterpriseSearch.content.newIndex.breadcrumb": "Nouvel index de recherche", "xpack.enterpriseSearch.content.newIndex.emptyState.description": "Les données que vous ajoutez dans Enterprise Search sont appelées \"index de recherche\", et vous pouvez effectuer des recherches à l'intérieur à la fois dans App Search et dans Workplace Search. Maintenant, vous pouvez utiliser vos connecteurs dans App Search et vos robots d'indexation dans Workplace Search.", @@ -13039,8 +13014,6 @@ "xpack.enterpriseSearch.emailLabel": "E-mail", "xpack.enterpriseSearch.emptyState.description": "Votre contenu est stocké dans un index Elasticsearch. Commencez par créer un index Elasticsearch et sélectionnez une méthode d'ingestion. Les options comprennent le robot d'indexation Elastic, les intégrations de données tierces ou l'utilisation des points de terminaison d'API Elasticsearch.", "xpack.enterpriseSearch.emptyState.description.line2": "Qu’il s’agisse de créer une expérience de recherche avec App Search ou Elasticsearch, vous pouvez commencer ici.", - "xpack.enterpriseSearch.engines.engine.notFound.action1": "Retour aux moteurs", - "xpack.enterpriseSearch.engines.navTitle": "Moteurs", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.description": "Effectuez des recherches sur tout, partout. Offrez à vos équipes débordées une expérience de recherche innovante et puissante facilement mise en œuvre. Intégrez rapidement une fonction de recherche préréglée à votre site web, à votre application ou à votre lieu de travail. Effectuez des recherches simples sur tout.", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.notConfigured": "Enterprise Search n'est pas encore configuré dans votre instance Kibana.", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.videoAlt": "Premiers pas avec Enterprise Search", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index ef49ea6ad1039..c3aae5c75beb8 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -11130,8 +11130,6 @@ "xpack.enterpriseSearch.content.engine.indices.actions.viewIndex.caption": "インデックス{indexName}を表示", "xpack.enterpriseSearch.content.engineList.deleteEngine.successToast.title": "{engineName}が削除されました。", "xpack.enterpriseSearch.content.engines.createEngine.headerSubTitle": "エンジンは、ユーザーがインデックス内のデータを照会することを可能にします。詳細については、{enginesDocsLink}を探索してください。", - "xpack.enterpriseSearch.content.engines.description": "エンジンは、関連性、分析、パーソナライゼーションツールの完全なセットで、インデックスされたデータを照会することを可能にします。エンタープライズサーチにおけるエンジンの仕組みについて詳しく知るには{documentationUrl}", - "xpack.enterpriseSearch.content.engines.enginesList.description": "{total}件中{from}-{to}件を表示中", "xpack.enterpriseSearch.content.enginesList.indicesFlyout.subTitle": "{engineName}に関連付けられたインデックスを表示", "xpack.enterpriseSearch.content.enginesList.table.column.view.indices": "{indicesCount, number} {indicesCount, plural, other {インデックス}}", "xpack.enterpriseSearch.content.index.connector.syncRules.description": "同期ルールを追加して、{indexName}から同期されるデータをカスタマイズします。デフォルトではすべてが含まれます。ドキュメントは、リストの最上位から下に向かって、構成されたインデックスルールのセットに対して検証されます。", @@ -12224,22 +12222,9 @@ "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.done": "完了", "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.generateButton": "読み取り専用キーを生成", "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.title": "エンジン読み取り専用APIキーを作成", - "xpack.enterpriseSearch.content.engine.api.step1.apiKeyWarning": "ElasticはAPIキーを保存しません。生成後は、1回だけキーを表示できます。必ず安全に保管してください。アクセスできなくなった場合は、この画面から新しいAPIキーを生成する必要があります。", - "xpack.enterpriseSearch.content.engine.api.step1.createAPIKeyButton": "APIキーを作成", - "xpack.enterpriseSearch.content.engine.api.step1.learnMoreLink": "APIキーの詳細をご覧ください。", - "xpack.enterpriseSearch.content.engine.api.step1.title": "APIキーを生成して保存", - "xpack.enterpriseSearch.content.engine.api.step1.viewKeysButton": "キーを表示", - "xpack.enterpriseSearch.content.engine.api.step2.copyEndpointDescription": "このURLを使用して、エンジンのAPIエンドポイントにアクセスします。", - "xpack.enterpriseSearch.content.engine.api.step2.title": "エンジンのエンドポイントをコピー", "xpack.enterpriseSearch.content.engine.api.step3.curlTitle": "cURL", "xpack.enterpriseSearch.content.engine.api.step3.intro": "Elasticで管理されている言語クライアントが使用されたエンジンと統合し、検索エクスペリエンスを構築する方法をご覧ください。", "xpack.enterpriseSearch.content.engine.api.step3.searchUITitle": "Search UI", - "xpack.enterpriseSearch.content.engine.api.step3.title": "エンドポイントを呼び出す方法をご覧ください", - "xpack.enterpriseSearch.content.engine.api.step4.copy": "エンジンは、このインストールの一部として、基本分析データを提供します。さらに粒度の高いカスタムメトリックを利用するには、ご使用のプラットフォームで当社の行動分析を統合してください。", - "xpack.enterpriseSearch.content.engine.api.step4.learnHowLink": "方法を学習", - "xpack.enterpriseSearch.content.engine.api.step4.title": "(任意)分析を強化", - "xpack.enterpriseSearch.content.engine.headerActions.actionsButton.ariaLabel": "エンジンアクションメニューボタン", - "xpack.enterpriseSearch.content.engine.headerActions.delete": "このエンジンを削除", "xpack.enterpriseSearch.content.engine.indices.actions.columnTitle": "アクション", "xpack.enterpriseSearch.content.engine.indices.actions.removeIndex.title": "このインデックスをエンジンから削除", "xpack.enterpriseSearch.content.engine.indices.actions.viewIndex.title": "このインデックスを表示", @@ -12264,7 +12249,6 @@ "xpack.enterpriseSearch.content.engineList.deleteEngineModal.delete.description": "エンジンを削除すると、元に戻せません。インデックスには影響しません。", "xpack.enterpriseSearch.content.engineList.deleteEngineModal.title": "このエンジンを完全に削除しますか?", "xpack.enterpriseSearch.content.engineList.table.column.actions.deleteEngineLabel": "このエンジンを削除", - "xpack.enterpriseSearch.content.engines.breadcrumb": "エンジン", "xpack.enterpriseSearch.content.engines.createEngine.header.createError.title": "エンジンの作成エラー", "xpack.enterpriseSearch.content.engines.createEngine.header.docsLink": "エンジンドキュメント", "xpack.enterpriseSearch.content.engines.createEngine.headerTitle": "エンジンを作成", @@ -12272,15 +12256,9 @@ "xpack.enterpriseSearch.content.engines.createEngine.nameEngine.title": "エンジン名を指定", "xpack.enterpriseSearch.content.engines.createEngine.selectIndices.title": "インデックスを選択", "xpack.enterpriseSearch.content.engines.createEngine.submit": "このエンジンを削除", - "xpack.enterpriseSearch.content.engines.createEngineButtonLabel": "エンジンを作成", - "xpack.enterpriseSearch.content.engines.documentation": "エンジンドキュメントを読む", "xpack.enterpriseSearch.content.engines.enginesList.empty.description": "最初のエンジンの作成を説明します。", "xpack.enterpriseSearch.content.engines.enginesList.empty.title": "初めてのエンジンの作成", "xpack.enterpriseSearch.content.engines.indices.addIndicesFlyout.updateError.title": "エンジンの更新エラー", - "xpack.enterpriseSearch.content.engines.searchBar.ariaLabel": "検索エンジン", - "xpack.enterpriseSearch.content.engines.searchPlaceholder": "検索エンジン", - "xpack.enterpriseSearch.content.engines.searchPlaceholder.description": "名前または含まれているインデックスでエンジンを検索します。", - "xpack.enterpriseSearch.content.engines.title": "エンジン", "xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.docsCount.columnTitle": "ドキュメント数", "xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.health.columnTitle": "インデックス正常性", "xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.name.columnTitle": "インデックス名", @@ -12337,7 +12315,6 @@ "xpack.enterpriseSearch.content.index.pipelines.settings.reduceWhitespaceDescription": "ドキュメントの不要な空白を自動的に削除", "xpack.enterpriseSearch.content.index.pipelines.settings.reduceWhitespaceLabel": "空白の削除", "xpack.enterpriseSearch.content.index.pipelines.settings.runMlInferenceDescrition": "互換性がある学習済みMLモデルを使用してデータを強化", - "xpack.enterpriseSearch.content.index.searchEngines.createEngine": "App Searchエンジンの作成", "xpack.enterpriseSearch.content.index.searchEngines.createEngineDisabledTooltip": "非表示のインデックスからエンジンを作成することはできません。", "xpack.enterpriseSearch.content.index.searchEngines.label": "検索エンジン", "xpack.enterpriseSearch.content.index.searchEngines.viewEngines": "App Searchエンジンを表示", @@ -12616,8 +12593,6 @@ "xpack.enterpriseSearch.content.nativeConnectors.mongodb.name": "MongoDB", "xpack.enterpriseSearch.content.nativeConnectors.mysql.name": "MySQL", "xpack.enterpriseSearch.content.navTitle": "コンテンツ", - "xpack.enterpriseSearch.content.new_index.successToast.button.label": "エンジンを作成", - "xpack.enterpriseSearch.content.new_index.successToast.description": "App Searchエンジンを使用して、新しいElasticsearchインデックスの検索エクスペリエンスを構築できます。", "xpack.enterpriseSearch.content.new_index.successToast.title": "インデックスが正常に作成されました", "xpack.enterpriseSearch.content.newIndex.breadcrumb": "新しい検索インデックス", "xpack.enterpriseSearch.content.newIndex.emptyState.description": "エンタープライズ サーチで追加したデータは検索インデックスと呼ばれ、App SearchとWorkplace Searchの両方で検索可能です。App SearchのコネクターとWorkplace SearchのWebクローラーを使用できます。", @@ -13038,8 +13013,6 @@ "xpack.enterpriseSearch.emailLabel": "メール", "xpack.enterpriseSearch.emptyState.description": "コンテンツはElasticsearchインデックスに保存されます。まず、Elasticsearchインデックスを作成し、インジェスチョン方法を選択します。オプションには、Elastic Webクローラー、サードパーティデータ統合、Elasticsearch APIエンドポイントの使用があります。", "xpack.enterpriseSearch.emptyState.description.line2": "App SearchまたはElasticsearchのどちらで検索エクスペリエンスを構築しても、これが最初のステップです。", - "xpack.enterpriseSearch.engines.engine.notFound.action1": "エンジンに戻る", - "xpack.enterpriseSearch.engines.navTitle": "エンジン", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.description": "場所を問わず、何でも検索。組織を支える多忙なチームのために、パワフルでモダンな検索エクスペリエンスを簡単に導入できます。Webサイトやアプリ、ワークプレイスに事前調整済みの検索をすばやく追加しましょう。何でもシンプルに検索できます。", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.notConfigured": "エンタープライズサーチはまだKibanaインスタンスで構成されていません。", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.videoAlt": "エンタープライズ サーチの基本操作", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 589d923ae8ce1..84bdf7528d7ec 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -11131,8 +11131,6 @@ "xpack.enterpriseSearch.content.engine.indices.actions.viewIndex.caption": "查看索引 {indexName}", "xpack.enterpriseSearch.content.engineList.deleteEngine.successToast.title": "{engineName} 已删除", "xpack.enterpriseSearch.content.engines.createEngine.headerSubTitle": "引擎允许您的用户在索引中查询数据。请浏览我们的 {enginesDocsLink} 了解详情!", - "xpack.enterpriseSearch.content.engines.description": "引擎允许您通过一整套相关性、分析和个性化工具查询索引数据。有关引擎在 Enterprise Search 中的工作机制的详情,{documentationUrl}", - "xpack.enterpriseSearch.content.engines.enginesList.description": "正在显示第 {from}-{to} 个(共 {total} 个)", "xpack.enterpriseSearch.content.enginesList.indicesFlyout.subTitle": "查看与 {engineName} 关联的索引", "xpack.enterpriseSearch.content.enginesList.table.column.view.indices": "{indicesCount, number} 个{indicesCount, plural, other {索引}}", "xpack.enterpriseSearch.content.index.connector.syncRules.description": "添加同步规则以定制要从 {indexName} 同步哪些数据。默认包括所有内容,并根据从上到下列出的已配置索引规则集验证文档。", @@ -12225,22 +12223,9 @@ "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.done": "完成", "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.generateButton": "生成只读密钥", "xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.title": "创建引擎只读 API 密钥", - "xpack.enterpriseSearch.content.engine.api.step1.apiKeyWarning": "Elastic 不会存储 API 密钥。一旦生成,您只能查看密钥一次。请确保将其保存在某个安全位置。如果失去它的访问权限,您需要从此屏幕生成新的 API 密钥。", - "xpack.enterpriseSearch.content.engine.api.step1.createAPIKeyButton": "创建 API 密钥", - "xpack.enterpriseSearch.content.engine.api.step1.learnMoreLink": "详细了解 API 密钥。", - "xpack.enterpriseSearch.content.engine.api.step1.title": "生成并保存 API 密钥", - "xpack.enterpriseSearch.content.engine.api.step1.viewKeysButton": "查看密钥", - "xpack.enterpriseSearch.content.engine.api.step2.copyEndpointDescription": "使用此 URL 访问您引擎的 API 终端。", - "xpack.enterpriseSearch.content.engine.api.step2.title": "复制您引擎的终端", "xpack.enterpriseSearch.content.engine.api.step3.curlTitle": "cURL", "xpack.enterpriseSearch.content.engine.api.step3.intro": "了解如何将您的引擎与由 Elastic 维护的语言客户端进行集成,以帮助构建搜索体验。", "xpack.enterpriseSearch.content.engine.api.step3.searchUITitle": "搜索 UI", - "xpack.enterpriseSearch.content.engine.api.step3.title": "了解如何调用终端", - "xpack.enterpriseSearch.content.engine.api.step4.copy": "您的引擎作为此安装的一部分提供了基本分析数据。要接收更多粒度化的定制指标,请在您的平台上集成我们的行为分析脚本。", - "xpack.enterpriseSearch.content.engine.api.step4.learnHowLink": "了解操作方法", - "xpack.enterpriseSearch.content.engine.api.step4.title": "(可选)强化分析", - "xpack.enterpriseSearch.content.engine.headerActions.actionsButton.ariaLabel": "引擎操作菜单按钮", - "xpack.enterpriseSearch.content.engine.headerActions.delete": "删除此引擎", "xpack.enterpriseSearch.content.engine.indices.actions.columnTitle": "操作", "xpack.enterpriseSearch.content.engine.indices.actions.removeIndex.title": "从引擎中移除此索引", "xpack.enterpriseSearch.content.engine.indices.actions.viewIndex.title": "查看此索引", @@ -12265,7 +12250,6 @@ "xpack.enterpriseSearch.content.engineList.deleteEngineModal.delete.description": "删除引擎是不可逆操作。您的索引不会受到影响。", "xpack.enterpriseSearch.content.engineList.deleteEngineModal.title": "永久删除此引擎?", "xpack.enterpriseSearch.content.engineList.table.column.actions.deleteEngineLabel": "删除此引擎", - "xpack.enterpriseSearch.content.engines.breadcrumb": "引擎", "xpack.enterpriseSearch.content.engines.createEngine.header.createError.title": "创建引擎时出错", "xpack.enterpriseSearch.content.engines.createEngine.header.docsLink": "引擎文档", "xpack.enterpriseSearch.content.engines.createEngine.headerTitle": "创建引擎", @@ -12273,15 +12257,9 @@ "xpack.enterpriseSearch.content.engines.createEngine.nameEngine.title": "命名您的引擎", "xpack.enterpriseSearch.content.engines.createEngine.selectIndices.title": "选择索引", "xpack.enterpriseSearch.content.engines.createEngine.submit": "创建此引擎", - "xpack.enterpriseSearch.content.engines.createEngineButtonLabel": "创建引擎", - "xpack.enterpriseSearch.content.engines.documentation": "浏览我们的引擎文档", "xpack.enterpriseSearch.content.engines.enginesList.empty.description": "下面我们指导您创建首个引擎。", "xpack.enterpriseSearch.content.engines.enginesList.empty.title": "创建您的首个引擎", "xpack.enterpriseSearch.content.engines.indices.addIndicesFlyout.updateError.title": "更新引擎时出错", - "xpack.enterpriseSearch.content.engines.searchBar.ariaLabel": "搜索引擎", - "xpack.enterpriseSearch.content.engines.searchPlaceholder": "搜索引擎", - "xpack.enterpriseSearch.content.engines.searchPlaceholder.description": "通过名称或根据其包含的索引查找引擎。", - "xpack.enterpriseSearch.content.engines.title": "引擎", "xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.docsCount.columnTitle": "文档计数", "xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.health.columnTitle": "索引运行状况", "xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.name.columnTitle": "索引名称", @@ -12338,7 +12316,6 @@ "xpack.enterpriseSearch.content.index.pipelines.settings.reduceWhitespaceDescription": "自动剪裁文档中的额外空白", "xpack.enterpriseSearch.content.index.pipelines.settings.reduceWhitespaceLabel": "减少空白", "xpack.enterpriseSearch.content.index.pipelines.settings.runMlInferenceDescrition": "使用兼容的已训练 ML 模型增强您的数据", - "xpack.enterpriseSearch.content.index.searchEngines.createEngine": "创建 App Search 引擎", "xpack.enterpriseSearch.content.index.searchEngines.createEngineDisabledTooltip": "无法从隐藏的索引中创建引擎。", "xpack.enterpriseSearch.content.index.searchEngines.label": "搜索引擎", "xpack.enterpriseSearch.content.index.searchEngines.viewEngines": "查看 App Search 引擎", @@ -12617,8 +12594,6 @@ "xpack.enterpriseSearch.content.nativeConnectors.mongodb.name": "MongoDB", "xpack.enterpriseSearch.content.nativeConnectors.mysql.name": "MySQL", "xpack.enterpriseSearch.content.navTitle": "内容", - "xpack.enterpriseSearch.content.new_index.successToast.button.label": "创建引擎", - "xpack.enterpriseSearch.content.new_index.successToast.description": "您可以使用 App Search 引擎为您的新 Elasticsearch 索引构建搜索体验。", "xpack.enterpriseSearch.content.new_index.successToast.title": "已成功创建索引", "xpack.enterpriseSearch.content.newIndex.breadcrumb": "新搜索索引", "xpack.enterpriseSearch.content.newIndex.emptyState.description": "您在 Enterprise Search 中添加的数据称为搜索索引,您可在 App Search 和 Workplace Search 中搜索这些数据。现在,您可以在 App Search 中使用连接器,在 Workplace Search 中使用网络爬虫。", @@ -13039,8 +13014,6 @@ "xpack.enterpriseSearch.emailLabel": "电子邮件", "xpack.enterpriseSearch.emptyState.description": "您的内容存储在 Elasticsearch 索引中。通过创建 Elasticsearch 索引并选择采集方法开始使用。选项包括 Elastic 网络爬虫、第三方数据集成或使用 Elasticsearch API 终端。", "xpack.enterpriseSearch.emptyState.description.line2": "无论是使用 App Search 还是 Elasticsearch 构建搜索体验,您都可以从此处立即开始。", - "xpack.enterpriseSearch.engines.engine.notFound.action1": "返回到引擎", - "xpack.enterpriseSearch.engines.navTitle": "引擎", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.description": "随时随地进行全面搜索。为工作繁忙的团队轻松实现强大的现代搜索体验。将预先调整的搜索功能快速添加到您的网站、应用或工作区。全面搜索就是这么简单。", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.notConfigured": "企业搜索尚未在您的 Kibana 实例中配置。", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.videoAlt": "企业搜索入门", From 3c21b451b3d68d725980211e962b3d88aea16e97 Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Fri, 21 Apr 2023 15:47:28 +0200 Subject: [PATCH 56/67] [Ingest Pipelines] Add attachment processor (#155226) --- .../__jest__/processors/attachment.test.tsx | 122 +++++++++++ .../__jest__/processors/processor.helpers.tsx | 5 + .../processor_form/processors/attachment.tsx | 202 ++++++++++++++++++ .../processor_form/processors/index.ts | 1 + .../shared/map_processor_type_to_form.tsx | 18 ++ 5 files changed, 348 insertions(+) create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/attachment.test.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/attachment.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/attachment.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/attachment.test.tsx new file mode 100644 index 0000000000000..b4cfbf3046db0 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/attachment.test.tsx @@ -0,0 +1,122 @@ +/* + * 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 { act } from 'react-dom/test-utils'; +import { setup, SetupResult, getProcessorValue, setupEnvironment } from './processor.helpers'; + +const ATTACHMENT_TYPE = 'attachment'; + +describe('Processor: Attachment', () => { + let onUpdate: jest.Mock; + let testBed: SetupResult; + const { httpSetup } = setupEnvironment(); + + beforeAll(() => { + jest.useFakeTimers({ legacyFakeTimers: true }); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + beforeEach(async () => { + onUpdate = jest.fn(); + + await act(async () => { + testBed = await setup(httpSetup, { + value: { + processors: [], + }, + onFlyoutOpen: jest.fn(), + onUpdate, + }); + }); + + const { component, actions } = testBed; + + component.update(); + + // Open flyout to add new processor + actions.addProcessor(); + // Add type (the other fields are not visible until a type is selected) + await actions.addProcessorType(ATTACHMENT_TYPE); + }); + + test('prevents form submission if required fields are not provided', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Click submit button with only the type defined + await saveNewProcessor(); + + // Expect form error as "field" is a required parameter + expect(form.getErrorsMessages()).toEqual([ + 'A field value is required.', // "Field" input + ]); + }); + + test('saves with default parameter values', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Add "field" value + form.setInputValue('fieldNameField.input', 'test_attachment_processor'); + + // Save the field + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, ATTACHMENT_TYPE); + + expect(processors[0][ATTACHMENT_TYPE]).toEqual({ + field: 'test_attachment_processor', + }); + }); + + test('saves with optional parameter values', async () => { + const { + actions: { saveNewProcessor }, + form, + find, + component, + } = testBed; + + // Add required fields + form.setInputValue('fieldNameField.input', 'test_attachment_processor'); + + // Add optional fields + form.setInputValue('targetField.input', 'test_target'); + form.setInputValue('indexedCharsField.input', '123456'); + form.setInputValue('indexedCharsFieldField.input', 'indexed_chars_field'); + form.toggleEuiSwitch('removeBinaryField.input'); + form.setInputValue('resourceNameField.input', 'resource_name_field'); + + // Add "networkDirectionField" value (required) + await act(async () => { + find('propertiesField').simulate('change', [{ label: 'content' }]); + }); + component.update(); + + // Save the field + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, ATTACHMENT_TYPE); + + expect(processors[0][ATTACHMENT_TYPE]).toEqual({ + field: 'test_attachment_processor', + target_field: 'test_target', + properties: ['content'], + indexed_chars: '123456', + indexed_chars_field: 'indexed_chars_field', + remove_binary: true, + resource_name: 'resource_name_field', + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx index 43ebf84f6ef30..125d8758ca0d0 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx @@ -190,6 +190,11 @@ type TestSubject = | 'droppableList.input-2' | 'prefixField.input' | 'suffixField.input' + | 'indexedCharsField.input' + | 'indexedCharsFieldField.input' + | 'removeBinaryField.input' + | 'resourceNameField.input' + | 'propertiesField' | 'tileTypeField' | 'targetFormatField' | 'parentField.input' diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/attachment.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/attachment.tsx new file mode 100644 index 0000000000000..57f26bdaf204f --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/attachment.tsx @@ -0,0 +1,202 @@ +/* + * 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, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiCode, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; + +import { + ComboBoxField, + FIELD_TYPES, + UseField, + ToggleField, + Field, +} from '../../../../../../shared_imports'; + +import { FieldNameField } from './common_fields/field_name_field'; +import { TargetField } from './common_fields/target_field'; +import { IgnoreMissingField } from './common_fields/ignore_missing_field'; +import { FieldsConfig, to, from } from './shared'; + +const propertyValues: string[] = [ + 'content', + 'title', + 'author', + 'keywords', + 'date', + 'content_type', + 'content_length', + 'language', +]; + +const fieldsConfig: FieldsConfig = { + /* Optional field configs */ + indexed_chars: { + type: FIELD_TYPES.NUMBER, + serializer: from.emptyStringToUndefined, + label: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.attachment.indexedCharsFieldLabel', + { + defaultMessage: 'Indexed chars (optional)', + } + ), + helpText: ( + {'100000'} }} + /> + ), + }, + indexed_chars_field: { + type: FIELD_TYPES.TEXT, + serializer: from.emptyStringToUndefined, + label: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.attachment.indexedCharsFieldFieldLabel', + { + defaultMessage: 'Indexed chars field (optional)', + } + ), + helpText: ( + {'null'} }} + /> + ), + }, + properties: { + type: FIELD_TYPES.COMBO_BOX, + deserializer: to.arrayOfStrings, + serializer: from.optionalArrayOfStrings, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.attachment.propertiesFieldLabel', { + defaultMessage: 'Properties (optional)', + }), + helpText: ( + {'all'} }} + /> + ), + }, + remove_binary: { + type: FIELD_TYPES.TOGGLE, + defaultValue: false, + deserializer: to.booleanOrUndef, + serializer: from.undefinedIfValue(false), + label: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.attachment.removeBinaryFieldLabel', + { + defaultMessage: 'Remove binary', + } + ), + helpText: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.attachment.removeBinaryFieldHelpText', + { + defaultMessage: 'If enabled, the binary field will be removed from the document.', + } + ), + }, + resource_name: { + type: FIELD_TYPES.TEXT, + serializer: from.emptyStringToUndefined, + label: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.attachment.resourceNameFieldLabel', + { + defaultMessage: 'Resource name (optional)', + } + ), + helpText: ( + + ), + }, +}; + +export const Attachment: FunctionComponent = () => { + return ( + <> + + + + } + /> + + + {'attachment'} }} + /> + } + /> + + + + + + + + + + + + + + + + + ({ label })), + }} + path="fields.properties" + /> + + + + + + + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts index 3512dafdd2854..210d113bd2aba 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts @@ -8,6 +8,7 @@ // please try to keep this list sorted by module name (e.g. './bar' before './foo') export { Append } from './append'; +export { Attachment } from './attachment'; export { Bytes } from './bytes'; export { Circle } from './circle'; export { CommunityId } from './community_id'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx index 75bbd764097ad..60f66dbb415f3 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx @@ -14,6 +14,7 @@ import { LicenseType } from '../../../../../types'; import { Append, + Attachment, Bytes, Circle, CommunityId, @@ -100,6 +101,23 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { }, }), }, + attachment: { + FieldsComponent: Attachment, + docLinkPath: '/attachment.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.attachment', { + defaultMessage: 'Attachment', + }), + typeDescription: i18n.translate('xpack.ingestPipelines.processors.description.attachment', { + defaultMessage: 'Extract file attachments in common formats (such as PPT, XLS, and PDF).', + }), + getDefaultDescription: ({ field }) => + i18n.translate('xpack.ingestPipelines.processors.defaultDescription.attachment', { + defaultMessage: 'Extracts attachment from "{field}"', + values: { + field, + }, + }), + }, bytes: { FieldsComponent: Bytes, docLinkPath: '/bytes-processor.html', From b494716bdb5b5ef3171f65fd227543df2de517bf Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Fri, 21 Apr 2023 09:55:38 -0400 Subject: [PATCH 57/67] [Fleet] Create Synthetics migration for 8.8.0 (#154952) ## Summary Resolves https://github.com/elastic/kibana/issues/155215 Resolves https://github.com/elastic/kibana/issues/142653 Handles two primary migrations for Synthetics integration policies. 1. Rounds deprecated schedules to supported schedules 2. Transforms old deprecated throttling schema `5u/3u/20l` to support JSON schema `{ download: 5, upload: 3, latency: 20 }` Schedule migration --- Before 16m schedule Screen Shot 2023-04-19 at 6 17 35 PM After Screen Shot 2023-04-19 at 6 44 08 PM Before 4m schedule Screen Shot 2023-04-19 at 6 17 29 PM After Screen Shot 2023-04-19 at 6 44 30 PM Before 8m schedule Screen Shot 2023-04-19 at 6 17 23 PM After Screen Shot 2023-04-19 at 6 44 22 PM Before 2m schedule Screen Shot 2023-04-19 at 6 17 16 PM After Screen Shot 2023-04-19 at 6 43 55 PM Throttling migration --- Before throttling: false Screen Shot 2023-04-19 at 6 17 00 PM After Screen Shot 2023-04-19 at 6 49 50 PM Before custom throttling Screen Shot 2023-04-19 at 6 16 54 PM After Screen Shot 2023-04-19 at 6 49 44 PM Before default throttling Screen Shot 2023-04-19 at 6 16 48 PM After Screen Shot 2023-04-19 at 6 49 35 PM ### Testing 1. Check out the 8.7.0 branch 2. Create a Synthetics private location at `app/synthetics/settings/private-locations`. 3. Create a monitor, configured at that private location, with invalid schedules, ideally 1 or each type (http, icmp, browser, tcp) with an invalid schedule (for example, 2, 8, 11, 16, 333, etc) at `app/uptime/add-monitor`. Note: you must use Uptime to create monitors with arbitrary schedules. The Synthetics app will not let you. 4. Create a browser monitor, configured with your private location, with throttling turned off. 5. Create a browser monitor, configured for your private location, with a custom throttling profile. 6. Check out this PR and wait for saved object migration to run 7. Navigate to the agent policy for your monitor. Confirm the schedules were updated to supported schedules. Confirm the throttling configs now appear in yaml. Confirm that `throttling: false` remains for the throttling config that was turned off. --- .../fleet/server/saved_objects/index.ts | 6 +- .../migrations/synthetics/fixtures/8.7.0.ts | 1100 +++++++++++++++++ .../migrations/synthetics/index.ts | 8 + .../migrations/synthetics/to_v8_8_0.test.ts | 167 +++ .../migrations/synthetics/to_v8_8_0.ts | 107 ++ .../saved_objects/migrations/to_v8_8_0.ts | 35 + .../server/types/models/package_policy.ts | 1 + .../common/constants/monitor_defaults.ts | 4 +- .../formatters/browser/formatters.test.ts | 70 ++ .../common/formatters/browser/formatters.ts | 7 +- .../format_synthetics_policy.test.ts | 2 +- .../synthetics/services/add_monitor.ts | 14 +- .../synthetics/e2e/tasks/import_monitors.ts | 14 +- .../status_rule/status_rule_executor.test.ts | 14 +- .../synthetics_private_location.test.ts | 2 +- .../sample_data/test_browser_policy.ts | 7 +- .../test_project_monitor_policy.ts | 7 +- 17 files changed, 1538 insertions(+), 27 deletions(-) create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/fixtures/8.7.0.ts create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/index.ts create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.test.ts create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.ts create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_8_0.ts create mode 100644 x-pack/plugins/synthetics/common/formatters/browser/formatters.test.ts diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 763c6d6c69136..01d7bc1040368 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -54,10 +54,8 @@ import { migrateInstallationToV860, migratePackagePolicyToV860, } from './migrations/to_v8_6_0'; -import { - migratePackagePolicyToV870, - migratePackagePolicyToV880, -} from './migrations/security_solution'; +import { migratePackagePolicyToV870 } from './migrations/security_solution'; +import { migratePackagePolicyToV880 } from './migrations/to_v8_8_0'; /* * Saved object types and mappings diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/fixtures/8.7.0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/fixtures/8.7.0.ts new file mode 100644 index 0000000000000..2c24678184094 --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/fixtures/8.7.0.ts @@ -0,0 +1,1100 @@ +/* + * 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 { SavedObjectUnsanitizedDoc } from '@kbn/core/server'; + +import type { PackagePolicy } from '../../../../../common'; + +export const httpPolicy = { + type: 'ingest-package-policies', + id: 'ce2f0cc6-b082-4080-9ed3-82f38743a3ed-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + attributes: { + name: 'Invalid http monitor with 4 minute schedule-A private location-default', + namespace: 'default', + package: { name: 'synthetics', title: 'Elastic Synthetics', version: '0.11.8' }, + enabled: true, + policy_id: 'fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1', + inputs: [ + { + type: 'synthetics/http', + policy_template: 'synthetics', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { + type: 'synthetics', + dataset: 'http', + elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } }, + }, + vars: { + __ui: { + value: '{"is_tls_enabled":false,"is_zip_url_tls_enabled":false}', + type: 'yaml', + }, + enabled: { value: true, type: 'bool' }, + type: { value: 'http', type: 'text' }, + name: { value: 'Invalid http monitor with 4 minute schedule', type: 'text' }, + schedule: { value: '"@every 4m"', type: 'text' }, + urls: { value: 'https://elastic.co', type: 'text' }, + 'service.name': { value: '', type: 'text' }, + timeout: { value: '16s', type: 'text' }, + max_redirects: { value: '0', type: 'integer' }, + proxy_url: { value: '', type: 'text' }, + tags: { value: null, type: 'yaml' }, + username: { value: '', type: 'text' }, + password: { value: '', type: 'password' }, + 'response.include_headers': { value: true, type: 'bool' }, + 'response.include_body': { value: 'on_error', type: 'text' }, + 'check.request.method': { value: 'GET', type: 'text' }, + 'check.request.headers': { value: null, type: 'yaml' }, + 'check.request.body': { value: null, type: 'yaml' }, + 'check.response.status': { value: null, type: 'yaml' }, + 'check.response.headers': { value: null, type: 'yaml' }, + 'check.response.body.positive': { value: null, type: 'yaml' }, + 'check.response.body.negative': { value: null, type: 'yaml' }, + 'ssl.certificate_authorities': { value: null, type: 'yaml' }, + 'ssl.certificate': { value: null, type: 'yaml' }, + 'ssl.key': { value: null, type: 'yaml' }, + 'ssl.key_passphrase': { value: null, type: 'text' }, + 'ssl.verification_mode': { value: null, type: 'text' }, + 'ssl.supported_protocols': { value: null, type: 'yaml' }, + location_name: { value: 'A private location', type: 'text' }, + id: { value: 'ce2f0cc6-b082-4080-9ed3-82f38743a3ed', type: 'text' }, + config_id: { value: 'ce2f0cc6-b082-4080-9ed3-82f38743a3ed', type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { value: 'ui', type: 'text' }, + 'monitor.project.id': { value: null, type: 'text' }, + 'monitor.project.name': { value: null, type: 'text' }, + }, + id: 'synthetics/http-http-ce2f0cc6-b082-4080-9ed3-82f38743a3ed-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + compiled_stream: { + __ui: { is_tls_enabled: false, is_zip_url_tls_enabled: false }, + type: 'http', + name: 'Invalid http monitor with 4 minute schedule', + id: 'ce2f0cc6-b082-4080-9ed3-82f38743a3ed', + origin: 'ui', + 'run_from.id': 'A private location', + 'run_from.geo.name': 'A private location', + enabled: true, + urls: 'https://elastic.co', + schedule: '@every 4m', + timeout: '16s', + max_redirects: 0, + 'response.include_headers': true, + 'response.include_body': 'on_error', + 'check.request.method': 'GET', + processors: [ + { + add_fields: { + target: '', + fields: { + 'monitor.fleet_managed': true, + config_id: 'ce2f0cc6-b082-4080-9ed3-82f38743a3ed', + }, + }, + }, + ], + }, + }, + ], + }, + { + type: 'synthetics/tcp', + policy_template: 'synthetics', + enabled: false, + streams: [ + { + enabled: false, + data_stream: { type: 'synthetics', dataset: 'tcp' }, + vars: { + __ui: { type: 'yaml' }, + enabled: { value: true, type: 'bool' }, + type: { value: 'tcp', type: 'text' }, + name: { type: 'text' }, + schedule: { value: '"@every 3m"', type: 'text' }, + hosts: { type: 'text' }, + 'service.name': { type: 'text' }, + timeout: { type: 'text' }, + proxy_url: { type: 'text' }, + proxy_use_local_resolver: { value: false, type: 'bool' }, + tags: { type: 'yaml' }, + 'check.send': { type: 'text' }, + 'check.receive': { type: 'text' }, + 'ssl.certificate_authorities': { type: 'yaml' }, + 'ssl.certificate': { type: 'yaml' }, + 'ssl.key': { type: 'yaml' }, + 'ssl.key_passphrase': { type: 'text' }, + 'ssl.verification_mode': { type: 'text' }, + 'ssl.supported_protocols': { type: 'yaml' }, + location_name: { value: 'Fleet managed', type: 'text' }, + id: { type: 'text' }, + config_id: { type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { type: 'text' }, + 'monitor.project.id': { type: 'text' }, + 'monitor.project.name': { type: 'text' }, + }, + id: 'synthetics/tcp-tcp-ce2f0cc6-b082-4080-9ed3-82f38743a3ed-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + }, + ], + }, + { + type: 'synthetics/icmp', + policy_template: 'synthetics', + enabled: false, + streams: [ + { + enabled: false, + data_stream: { type: 'synthetics', dataset: 'icmp' }, + vars: { + __ui: { type: 'yaml' }, + enabled: { value: true, type: 'bool' }, + type: { value: 'icmp', type: 'text' }, + name: { type: 'text' }, + schedule: { value: '"@every 3m"', type: 'text' }, + wait: { value: '1s', type: 'text' }, + hosts: { type: 'text' }, + 'service.name': { type: 'text' }, + timeout: { type: 'text' }, + tags: { type: 'yaml' }, + location_name: { value: 'Fleet managed', type: 'text' }, + id: { type: 'text' }, + config_id: { type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { type: 'text' }, + 'monitor.project.id': { type: 'text' }, + 'monitor.project.name': { type: 'text' }, + }, + id: 'synthetics/icmp-icmp-ce2f0cc6-b082-4080-9ed3-82f38743a3ed-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + }, + ], + }, + { + type: 'synthetics/browser', + policy_template: 'synthetics', + enabled: false, + streams: [ + { + enabled: true, + data_stream: { + type: 'synthetics', + dataset: 'browser', + elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } }, + }, + vars: { + __ui: { type: 'yaml' }, + enabled: { value: true, type: 'bool' }, + type: { value: 'browser', type: 'text' }, + name: { type: 'text' }, + schedule: { value: '"@every 3m"', type: 'text' }, + 'service.name': { type: 'text' }, + timeout: { type: 'text' }, + tags: { type: 'yaml' }, + 'source.zip_url.url': { type: 'text' }, + 'source.zip_url.username': { type: 'text' }, + 'source.zip_url.folder': { type: 'text' }, + 'source.zip_url.password': { type: 'password' }, + 'source.inline.script': { type: 'yaml' }, + 'source.project.content': { type: 'text' }, + params: { type: 'yaml' }, + playwright_options: { type: 'yaml' }, + screenshots: { type: 'text' }, + synthetics_args: { type: 'text' }, + ignore_https_errors: { type: 'bool' }, + 'throttling.config': { type: 'text' }, + 'filter_journeys.tags': { type: 'yaml' }, + 'filter_journeys.match': { type: 'text' }, + 'source.zip_url.ssl.certificate_authorities': { type: 'yaml' }, + 'source.zip_url.ssl.certificate': { type: 'yaml' }, + 'source.zip_url.ssl.key': { type: 'yaml' }, + 'source.zip_url.ssl.key_passphrase': { type: 'text' }, + 'source.zip_url.ssl.verification_mode': { type: 'text' }, + 'source.zip_url.ssl.supported_protocols': { type: 'yaml' }, + 'source.zip_url.proxy_url': { type: 'text' }, + location_name: { value: 'Fleet managed', type: 'text' }, + id: { type: 'text' }, + config_id: { type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { type: 'text' }, + 'monitor.project.id': { type: 'text' }, + 'monitor.project.name': { type: 'text' }, + }, + id: 'synthetics/browser-browser-ce2f0cc6-b082-4080-9ed3-82f38743a3ed-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + compiled_stream: { + __ui: null, + type: 'browser', + name: null, + 'run_from.id': 'Fleet managed', + 'run_from.geo.name': 'Fleet managed', + enabled: true, + schedule: '@every 3m', + timeout: null, + throttling: null, + processors: [ + { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, + ], + }, + }, + { + enabled: true, + data_stream: { + type: 'synthetics', + dataset: 'browser.network', + elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } }, + }, + id: 'synthetics/browser-browser.network-ce2f0cc6-b082-4080-9ed3-82f38743a3ed-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + compiled_stream: { + processors: [ + { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, + ], + }, + }, + { + enabled: true, + data_stream: { + type: 'synthetics', + dataset: 'browser.screenshot', + elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } }, + }, + id: 'synthetics/browser-browser.screenshot-ce2f0cc6-b082-4080-9ed3-82f38743a3ed-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + compiled_stream: { + processors: [ + { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, + ], + }, + }, + ], + }, + ], + is_managed: true, + revision: 1, + created_at: '2023-04-19T15:34:51.655Z', + created_by: 'system', + updated_at: '2023-04-19T15:34:51.655Z', + updated_by: 'system', + }, + references: [], + coreMigrationVersion: '8.8.0', + updated_at: '2023-04-19T15:34:51.714Z', + created_at: '2023-04-19T15:34:51.714Z', + typeMigrationVersion: '8.7.0', +} as unknown as SavedObjectUnsanitizedDoc; +export const tcpPolicy = { + type: 'ingest-package-policies', + id: '77f25200-7cf3-450d-abfa-4f95faae1907-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + attributes: { + name: 'Invalid tcp monitor with 8 minute schedule-A private location-default', + namespace: 'default', + package: { name: 'synthetics', title: 'Elastic Synthetics', version: '0.11.8' }, + enabled: true, + policy_id: 'fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1', + inputs: [ + { + type: 'synthetics/http', + policy_template: 'synthetics', + enabled: false, + streams: [ + { + enabled: false, + data_stream: { type: 'synthetics', dataset: 'http' }, + vars: { + __ui: { type: 'yaml' }, + enabled: { value: true, type: 'bool' }, + type: { value: 'http', type: 'text' }, + name: { type: 'text' }, + schedule: { value: '"@every 3m"', type: 'text' }, + urls: { type: 'text' }, + 'service.name': { type: 'text' }, + timeout: { type: 'text' }, + max_redirects: { type: 'integer' }, + proxy_url: { type: 'text' }, + tags: { type: 'yaml' }, + username: { type: 'text' }, + password: { type: 'password' }, + 'response.include_headers': { type: 'bool' }, + 'response.include_body': { type: 'text' }, + 'check.request.method': { type: 'text' }, + 'check.request.headers': { type: 'yaml' }, + 'check.request.body': { type: 'yaml' }, + 'check.response.status': { type: 'yaml' }, + 'check.response.headers': { type: 'yaml' }, + 'check.response.body.positive': { type: 'yaml' }, + 'check.response.body.negative': { type: 'yaml' }, + 'ssl.certificate_authorities': { type: 'yaml' }, + 'ssl.certificate': { type: 'yaml' }, + 'ssl.key': { type: 'yaml' }, + 'ssl.key_passphrase': { type: 'text' }, + 'ssl.verification_mode': { type: 'text' }, + 'ssl.supported_protocols': { type: 'yaml' }, + location_name: { value: 'Fleet managed', type: 'text' }, + id: { type: 'text' }, + config_id: { type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { type: 'text' }, + 'monitor.project.id': { type: 'text' }, + 'monitor.project.name': { type: 'text' }, + }, + id: 'synthetics/http-http-77f25200-7cf3-450d-abfa-4f95faae1907-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + }, + ], + }, + { + type: 'synthetics/tcp', + policy_template: 'synthetics', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { + type: 'synthetics', + dataset: 'tcp', + elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } }, + }, + vars: { + __ui: { + value: '{"is_tls_enabled":false,"is_zip_url_tls_enabled":false}', + type: 'yaml', + }, + enabled: { value: true, type: 'bool' }, + type: { value: 'tcp', type: 'text' }, + name: { value: 'Invalid tcp monitor with 8 minute schedule', type: 'text' }, + schedule: { value: '"@every 8m"', type: 'text' }, + hosts: { value: 'localhost:5601', type: 'text' }, + 'service.name': { value: '', type: 'text' }, + timeout: { value: '16s', type: 'text' }, + proxy_url: { value: '', type: 'text' }, + proxy_use_local_resolver: { value: false, type: 'bool' }, + tags: { value: null, type: 'yaml' }, + 'check.send': { value: '', type: 'text' }, + 'check.receive': { value: '', type: 'text' }, + 'ssl.certificate_authorities': { value: null, type: 'yaml' }, + 'ssl.certificate': { value: null, type: 'yaml' }, + 'ssl.key': { value: null, type: 'yaml' }, + 'ssl.key_passphrase': { value: null, type: 'text' }, + 'ssl.verification_mode': { value: null, type: 'text' }, + 'ssl.supported_protocols': { value: null, type: 'yaml' }, + location_name: { value: 'A private location', type: 'text' }, + id: { value: '77f25200-7cf3-450d-abfa-4f95faae1907', type: 'text' }, + config_id: { value: '77f25200-7cf3-450d-abfa-4f95faae1907', type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { value: 'ui', type: 'text' }, + 'monitor.project.id': { value: null, type: 'text' }, + 'monitor.project.name': { value: null, type: 'text' }, + }, + id: 'synthetics/tcp-tcp-77f25200-7cf3-450d-abfa-4f95faae1907-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + compiled_stream: { + __ui: { is_tls_enabled: false, is_zip_url_tls_enabled: false }, + type: 'tcp', + name: 'Invalid tcp monitor with 8 minute schedule', + id: '77f25200-7cf3-450d-abfa-4f95faae1907', + origin: 'ui', + 'run_from.id': 'A private location', + 'run_from.geo.name': 'A private location', + enabled: true, + hosts: 'localhost:5601', + schedule: '@every 8m', + timeout: '16s', + proxy_use_local_resolver: false, + processors: [ + { + add_fields: { + target: '', + fields: { + 'monitor.fleet_managed': true, + config_id: '77f25200-7cf3-450d-abfa-4f95faae1907', + }, + }, + }, + ], + }, + }, + ], + }, + { + type: 'synthetics/icmp', + policy_template: 'synthetics', + enabled: false, + streams: [ + { + enabled: false, + data_stream: { type: 'synthetics', dataset: 'icmp' }, + vars: { + __ui: { type: 'yaml' }, + enabled: { value: true, type: 'bool' }, + type: { value: 'icmp', type: 'text' }, + name: { type: 'text' }, + schedule: { value: '"@every 3m"', type: 'text' }, + wait: { value: '1s', type: 'text' }, + hosts: { type: 'text' }, + 'service.name': { type: 'text' }, + timeout: { type: 'text' }, + tags: { type: 'yaml' }, + location_name: { value: 'Fleet managed', type: 'text' }, + id: { type: 'text' }, + config_id: { type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { type: 'text' }, + 'monitor.project.id': { type: 'text' }, + 'monitor.project.name': { type: 'text' }, + }, + id: 'synthetics/icmp-icmp-77f25200-7cf3-450d-abfa-4f95faae1907-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + }, + ], + }, + { + type: 'synthetics/browser', + policy_template: 'synthetics', + enabled: false, + streams: [ + { + enabled: true, + data_stream: { + type: 'synthetics', + dataset: 'browser', + elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } }, + }, + vars: { + __ui: { type: 'yaml' }, + enabled: { value: true, type: 'bool' }, + type: { value: 'browser', type: 'text' }, + name: { type: 'text' }, + schedule: { value: '"@every 3m"', type: 'text' }, + 'service.name': { type: 'text' }, + timeout: { type: 'text' }, + tags: { type: 'yaml' }, + 'source.zip_url.url': { type: 'text' }, + 'source.zip_url.username': { type: 'text' }, + 'source.zip_url.folder': { type: 'text' }, + 'source.zip_url.password': { type: 'password' }, + 'source.inline.script': { type: 'yaml' }, + 'source.project.content': { type: 'text' }, + params: { type: 'yaml' }, + playwright_options: { type: 'yaml' }, + screenshots: { type: 'text' }, + synthetics_args: { type: 'text' }, + ignore_https_errors: { type: 'bool' }, + 'throttling.config': { type: 'text' }, + 'filter_journeys.tags': { type: 'yaml' }, + 'filter_journeys.match': { type: 'text' }, + 'source.zip_url.ssl.certificate_authorities': { type: 'yaml' }, + 'source.zip_url.ssl.certificate': { type: 'yaml' }, + 'source.zip_url.ssl.key': { type: 'yaml' }, + 'source.zip_url.ssl.key_passphrase': { type: 'text' }, + 'source.zip_url.ssl.verification_mode': { type: 'text' }, + 'source.zip_url.ssl.supported_protocols': { type: 'yaml' }, + 'source.zip_url.proxy_url': { type: 'text' }, + location_name: { value: 'Fleet managed', type: 'text' }, + id: { type: 'text' }, + config_id: { type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { type: 'text' }, + 'monitor.project.id': { type: 'text' }, + 'monitor.project.name': { type: 'text' }, + }, + id: 'synthetics/browser-browser-77f25200-7cf3-450d-abfa-4f95faae1907-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + compiled_stream: { + __ui: null, + type: 'browser', + name: null, + 'run_from.id': 'Fleet managed', + 'run_from.geo.name': 'Fleet managed', + enabled: true, + schedule: '@every 3m', + timeout: null, + throttling: null, + processors: [ + { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, + ], + }, + }, + { + enabled: true, + data_stream: { + type: 'synthetics', + dataset: 'browser.network', + elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } }, + }, + id: 'synthetics/browser-browser.network-77f25200-7cf3-450d-abfa-4f95faae1907-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + compiled_stream: { + processors: [ + { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, + ], + }, + }, + { + enabled: true, + data_stream: { + type: 'synthetics', + dataset: 'browser.screenshot', + elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } }, + }, + id: 'synthetics/browser-browser.screenshot-77f25200-7cf3-450d-abfa-4f95faae1907-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + compiled_stream: { + processors: [ + { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, + ], + }, + }, + ], + }, + ], + is_managed: true, + revision: 1, + created_at: '2023-04-19T15:35:26.839Z', + created_by: 'system', + updated_at: '2023-04-19T15:35:26.839Z', + updated_by: 'system', + }, + references: [], + coreMigrationVersion: '8.8.0', + updated_at: '2023-04-19T15:35:26.884Z', + created_at: '2023-04-19T15:35:26.884Z', + typeMigrationVersion: '8.7.0', +} as unknown as SavedObjectUnsanitizedDoc; +export const icmpPolicy = { + type: 'ingest-package-policies', + id: 'e437c11c-5cb0-42d1-966f-d2ba231550fd-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + attributes: { + name: 'Invalid ICMP monitor with 11 minute schedule-A private location-default', + namespace: 'default', + package: { name: 'synthetics', title: 'Elastic Synthetics', version: '0.11.8' }, + enabled: true, + policy_id: 'fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1', + inputs: [ + { + type: 'synthetics/http', + policy_template: 'synthetics', + enabled: false, + streams: [ + { + enabled: false, + data_stream: { type: 'synthetics', dataset: 'http' }, + vars: { + __ui: { type: 'yaml' }, + enabled: { value: true, type: 'bool' }, + type: { value: 'http', type: 'text' }, + name: { type: 'text' }, + schedule: { value: '"@every 3m"', type: 'text' }, + urls: { type: 'text' }, + 'service.name': { type: 'text' }, + timeout: { type: 'text' }, + max_redirects: { type: 'integer' }, + proxy_url: { type: 'text' }, + tags: { type: 'yaml' }, + username: { type: 'text' }, + password: { type: 'password' }, + 'response.include_headers': { type: 'bool' }, + 'response.include_body': { type: 'text' }, + 'check.request.method': { type: 'text' }, + 'check.request.headers': { type: 'yaml' }, + 'check.request.body': { type: 'yaml' }, + 'check.response.status': { type: 'yaml' }, + 'check.response.headers': { type: 'yaml' }, + 'check.response.body.positive': { type: 'yaml' }, + 'check.response.body.negative': { type: 'yaml' }, + 'ssl.certificate_authorities': { type: 'yaml' }, + 'ssl.certificate': { type: 'yaml' }, + 'ssl.key': { type: 'yaml' }, + 'ssl.key_passphrase': { type: 'text' }, + 'ssl.verification_mode': { type: 'text' }, + 'ssl.supported_protocols': { type: 'yaml' }, + location_name: { value: 'Fleet managed', type: 'text' }, + id: { type: 'text' }, + config_id: { type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { type: 'text' }, + 'monitor.project.id': { type: 'text' }, + 'monitor.project.name': { type: 'text' }, + }, + id: 'synthetics/http-http-e437c11c-5cb0-42d1-966f-d2ba231550fd-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + }, + ], + }, + { + type: 'synthetics/tcp', + policy_template: 'synthetics', + enabled: false, + streams: [ + { + enabled: false, + data_stream: { type: 'synthetics', dataset: 'tcp' }, + vars: { + __ui: { type: 'yaml' }, + enabled: { value: true, type: 'bool' }, + type: { value: 'tcp', type: 'text' }, + name: { type: 'text' }, + schedule: { value: '"@every 3m"', type: 'text' }, + hosts: { type: 'text' }, + 'service.name': { type: 'text' }, + timeout: { type: 'text' }, + proxy_url: { type: 'text' }, + proxy_use_local_resolver: { value: false, type: 'bool' }, + tags: { type: 'yaml' }, + 'check.send': { type: 'text' }, + 'check.receive': { type: 'text' }, + 'ssl.certificate_authorities': { type: 'yaml' }, + 'ssl.certificate': { type: 'yaml' }, + 'ssl.key': { type: 'yaml' }, + 'ssl.key_passphrase': { type: 'text' }, + 'ssl.verification_mode': { type: 'text' }, + 'ssl.supported_protocols': { type: 'yaml' }, + location_name: { value: 'Fleet managed', type: 'text' }, + id: { type: 'text' }, + config_id: { type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { type: 'text' }, + 'monitor.project.id': { type: 'text' }, + 'monitor.project.name': { type: 'text' }, + }, + id: 'synthetics/tcp-tcp-e437c11c-5cb0-42d1-966f-d2ba231550fd-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + }, + ], + }, + { + type: 'synthetics/icmp', + policy_template: 'synthetics', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { + type: 'synthetics', + dataset: 'icmp', + elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } }, + }, + vars: { + __ui: { type: 'yaml' }, + enabled: { value: true, type: 'bool' }, + type: { value: 'icmp', type: 'text' }, + name: { value: 'Invalid ICMP monitor with 11 minute schedule', type: 'text' }, + schedule: { value: '"@every 16m"', type: 'text' }, + wait: { value: '1s', type: 'text' }, + hosts: { value: '1.1.1.1', type: 'text' }, + 'service.name': { value: '', type: 'text' }, + timeout: { value: '16s', type: 'text' }, + tags: { value: null, type: 'yaml' }, + location_name: { value: 'A private location', type: 'text' }, + id: { value: 'e437c11c-5cb0-42d1-966f-d2ba231550fd', type: 'text' }, + config_id: { value: 'e437c11c-5cb0-42d1-966f-d2ba231550fd', type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { value: 'ui', type: 'text' }, + 'monitor.project.id': { value: null, type: 'text' }, + 'monitor.project.name': { value: null, type: 'text' }, + }, + id: 'synthetics/icmp-icmp-e437c11c-5cb0-42d1-966f-d2ba231550fd-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + compiled_stream: { + __ui: null, + type: 'icmp', + name: 'Invalid ICMP monitor with 11 minute schedule', + id: 'e437c11c-5cb0-42d1-966f-d2ba231550fd', + origin: 'ui', + 'run_from.id': 'A private location', + 'run_from.geo.name': 'A private location', + enabled: true, + hosts: '1.1.1.1', + schedule: '@every 16m', + wait: '1s', + timeout: '16s', + processors: [ + { + add_fields: { + target: '', + fields: { + 'monitor.fleet_managed': true, + config_id: 'e437c11c-5cb0-42d1-966f-d2ba231550fd', + }, + }, + }, + ], + }, + }, + ], + }, + { + type: 'synthetics/browser', + policy_template: 'synthetics', + enabled: false, + streams: [ + { + enabled: true, + data_stream: { + type: 'synthetics', + dataset: 'browser', + elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } }, + }, + vars: { + __ui: { type: 'yaml' }, + enabled: { value: true, type: 'bool' }, + type: { value: 'browser', type: 'text' }, + name: { type: 'text' }, + schedule: { value: '"@every 3m"', type: 'text' }, + 'service.name': { type: 'text' }, + timeout: { type: 'text' }, + tags: { type: 'yaml' }, + 'source.zip_url.url': { type: 'text' }, + 'source.zip_url.username': { type: 'text' }, + 'source.zip_url.folder': { type: 'text' }, + 'source.zip_url.password': { type: 'password' }, + 'source.inline.script': { type: 'yaml' }, + 'source.project.content': { type: 'text' }, + params: { type: 'yaml' }, + playwright_options: { type: 'yaml' }, + screenshots: { type: 'text' }, + synthetics_args: { type: 'text' }, + ignore_https_errors: { type: 'bool' }, + 'throttling.config': { type: 'text' }, + 'filter_journeys.tags': { type: 'yaml' }, + 'filter_journeys.match': { type: 'text' }, + 'source.zip_url.ssl.certificate_authorities': { type: 'yaml' }, + 'source.zip_url.ssl.certificate': { type: 'yaml' }, + 'source.zip_url.ssl.key': { type: 'yaml' }, + 'source.zip_url.ssl.key_passphrase': { type: 'text' }, + 'source.zip_url.ssl.verification_mode': { type: 'text' }, + 'source.zip_url.ssl.supported_protocols': { type: 'yaml' }, + 'source.zip_url.proxy_url': { type: 'text' }, + location_name: { value: 'Fleet managed', type: 'text' }, + id: { type: 'text' }, + config_id: { type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { type: 'text' }, + 'monitor.project.id': { type: 'text' }, + 'monitor.project.name': { type: 'text' }, + }, + id: 'synthetics/browser-browser-e437c11c-5cb0-42d1-966f-d2ba231550fd-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + compiled_stream: { + __ui: null, + type: 'browser', + name: null, + 'run_from.id': 'Fleet managed', + 'run_from.geo.name': 'Fleet managed', + enabled: true, + schedule: '@every 3m', + timeout: null, + throttling: null, + processors: [ + { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, + ], + }, + }, + { + enabled: true, + data_stream: { + type: 'synthetics', + dataset: 'browser.network', + elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } }, + }, + id: 'synthetics/browser-browser.network-e437c11c-5cb0-42d1-966f-d2ba231550fd-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + compiled_stream: { + processors: [ + { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, + ], + }, + }, + { + enabled: true, + data_stream: { + type: 'synthetics', + dataset: 'browser.screenshot', + elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } }, + }, + id: 'synthetics/browser-browser.screenshot-e437c11c-5cb0-42d1-966f-d2ba231550fd-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default', + compiled_stream: { + processors: [ + { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, + ], + }, + }, + ], + }, + ], + is_managed: true, + revision: 1, + created_at: '2023-04-19T15:35:53.763Z', + created_by: 'system', + updated_at: '2023-04-19T15:35:53.763Z', + updated_by: 'system', + }, + references: [], + coreMigrationVersion: '8.8.0', + updated_at: '2023-04-19T15:35:53.793Z', + created_at: '2023-04-19T15:35:53.793Z', + typeMigrationVersion: '8.7.0', +} as unknown as SavedObjectUnsanitizedDoc; + +export const getBrowserPolicy = (throttling = '5d/3u/20l') => + ({ + type: 'ingest-package-policies', + id: '420754e9-40f2-486c-bc2e-265bafd735c5-fe200580-dee2-11ed-933e-0f85f8c5dd40-default', + attributes: { + name: 'https://elastic.co-A private location-default', + namespace: 'default', + package: { name: 'synthetics', title: 'Elastic Synthetics', version: '0.11.8' }, + enabled: true, + policy_id: 'fe200580-dee2-11ed-933e-0f85f8c5dd40', + inputs: [ + { + type: 'synthetics/http', + policy_template: 'synthetics', + enabled: false, + streams: [ + { + enabled: false, + data_stream: { type: 'synthetics', dataset: 'http' }, + vars: { + __ui: { type: 'yaml' }, + enabled: { value: true, type: 'bool' }, + type: { value: 'http', type: 'text' }, + name: { type: 'text' }, + schedule: { value: '"@every 3m"', type: 'text' }, + urls: { type: 'text' }, + 'service.name': { type: 'text' }, + timeout: { type: 'text' }, + max_redirects: { type: 'integer' }, + proxy_url: { type: 'text' }, + tags: { type: 'yaml' }, + username: { type: 'text' }, + password: { type: 'password' }, + 'response.include_headers': { type: 'bool' }, + 'response.include_body': { type: 'text' }, + 'check.request.method': { type: 'text' }, + 'check.request.headers': { type: 'yaml' }, + 'check.request.body': { type: 'yaml' }, + 'check.response.status': { type: 'yaml' }, + 'check.response.headers': { type: 'yaml' }, + 'check.response.body.positive': { type: 'yaml' }, + 'check.response.body.negative': { type: 'yaml' }, + 'ssl.certificate_authorities': { type: 'yaml' }, + 'ssl.certificate': { type: 'yaml' }, + 'ssl.key': { type: 'yaml' }, + 'ssl.key_passphrase': { type: 'text' }, + 'ssl.verification_mode': { type: 'text' }, + 'ssl.supported_protocols': { type: 'yaml' }, + location_name: { value: 'Fleet managed', type: 'text' }, + id: { type: 'text' }, + config_id: { type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { type: 'text' }, + 'monitor.project.id': { type: 'text' }, + 'monitor.project.name': { type: 'text' }, + }, + id: 'synthetics/http-http-420754e9-40f2-486c-bc2e-265bafd735c5-fe200580-dee2-11ed-933e-0f85f8c5dd40-default', + }, + ], + }, + { + type: 'synthetics/tcp', + policy_template: 'synthetics', + enabled: false, + streams: [ + { + enabled: false, + data_stream: { type: 'synthetics', dataset: 'tcp' }, + vars: { + __ui: { type: 'yaml' }, + enabled: { value: true, type: 'bool' }, + type: { value: 'tcp', type: 'text' }, + name: { type: 'text' }, + schedule: { value: '"@every 3m"', type: 'text' }, + hosts: { type: 'text' }, + 'service.name': { type: 'text' }, + timeout: { type: 'text' }, + proxy_url: { type: 'text' }, + proxy_use_local_resolver: { value: false, type: 'bool' }, + tags: { type: 'yaml' }, + 'check.send': { type: 'text' }, + 'check.receive': { type: 'text' }, + 'ssl.certificate_authorities': { type: 'yaml' }, + 'ssl.certificate': { type: 'yaml' }, + 'ssl.key': { type: 'yaml' }, + 'ssl.key_passphrase': { type: 'text' }, + 'ssl.verification_mode': { type: 'text' }, + 'ssl.supported_protocols': { type: 'yaml' }, + location_name: { value: 'Fleet managed', type: 'text' }, + id: { type: 'text' }, + config_id: { type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { type: 'text' }, + 'monitor.project.id': { type: 'text' }, + 'monitor.project.name': { type: 'text' }, + }, + id: 'synthetics/tcp-tcp-420754e9-40f2-486c-bc2e-265bafd735c5-fe200580-dee2-11ed-933e-0f85f8c5dd40-default', + }, + ], + }, + { + type: 'synthetics/icmp', + policy_template: 'synthetics', + enabled: false, + streams: [ + { + enabled: false, + data_stream: { type: 'synthetics', dataset: 'icmp' }, + vars: { + __ui: { type: 'yaml' }, + enabled: { value: true, type: 'bool' }, + type: { value: 'icmp', type: 'text' }, + name: { type: 'text' }, + schedule: { value: '"@every 3m"', type: 'text' }, + wait: { value: '1s', type: 'text' }, + hosts: { type: 'text' }, + 'service.name': { type: 'text' }, + timeout: { type: 'text' }, + tags: { type: 'yaml' }, + location_name: { value: 'Fleet managed', type: 'text' }, + id: { type: 'text' }, + config_id: { type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { type: 'text' }, + 'monitor.project.id': { type: 'text' }, + 'monitor.project.name': { type: 'text' }, + }, + id: 'synthetics/icmp-icmp-420754e9-40f2-486c-bc2e-265bafd735c5-fe200580-dee2-11ed-933e-0f85f8c5dd40-default', + }, + ], + }, + { + type: 'synthetics/browser', + policy_template: 'synthetics', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { + type: 'synthetics', + dataset: 'browser', + elasticsearch: { + privileges: { indices: ['auto_configure', 'create_doc', 'read'] }, + }, + }, + vars: { + __ui: { + value: + '{"script_source":{"is_generated_script":false,"file_name":""},"is_zip_url_tls_enabled":false,"is_tls_enabled":false}', + type: 'yaml', + }, + enabled: { value: true, type: 'bool' }, + type: { value: 'browser', type: 'text' }, + name: { value: 'https://elastic.co', type: 'text' }, + schedule: { value: '"@every 2m"', type: 'text' }, + 'service.name': { value: '', type: 'text' }, + timeout: { value: null, type: 'text' }, + tags: { value: null, type: 'yaml' }, + 'source.zip_url.url': { value: '', type: 'text' }, + 'source.zip_url.username': { value: '', type: 'text' }, + 'source.zip_url.folder': { value: '', type: 'text' }, + 'source.zip_url.password': { value: '', type: 'password' }, + 'source.inline.script': { + value: + "\"step('Go to https://elastic.co', async () => {\\n await page.goto('https://elastic.co');\\n});\"", + type: 'yaml', + }, + 'source.project.content': { value: '', type: 'text' }, + params: { value: '', type: 'yaml' }, + playwright_options: { value: '', type: 'yaml' }, + screenshots: { value: 'on', type: 'text' }, + synthetics_args: { value: null, type: 'text' }, + ignore_https_errors: { value: false, type: 'bool' }, + 'throttling.config': { value: throttling, type: 'text' }, + 'filter_journeys.tags': { value: null, type: 'yaml' }, + 'filter_journeys.match': { value: null, type: 'text' }, + 'source.zip_url.ssl.certificate_authorities': { value: null, type: 'yaml' }, + 'source.zip_url.ssl.certificate': { value: null, type: 'yaml' }, + 'source.zip_url.ssl.key': { value: null, type: 'yaml' }, + 'source.zip_url.ssl.key_passphrase': { value: null, type: 'text' }, + 'source.zip_url.ssl.verification_mode': { value: null, type: 'text' }, + 'source.zip_url.ssl.supported_protocols': { value: null, type: 'yaml' }, + 'source.zip_url.proxy_url': { value: '', type: 'text' }, + location_name: { value: 'A private location', type: 'text' }, + id: { value: '420754e9-40f2-486c-bc2e-265bafd735c5', type: 'text' }, + config_id: { value: '420754e9-40f2-486c-bc2e-265bafd735c5', type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { value: 'ui', type: 'text' }, + 'monitor.project.id': { value: null, type: 'text' }, + 'monitor.project.name': { value: null, type: 'text' }, + }, + id: 'synthetics/browser-browser-420754e9-40f2-486c-bc2e-265bafd735c5-fe200580-dee2-11ed-933e-0f85f8c5dd40-default', + compiled_stream: { + __ui: { + script_source: { is_generated_script: false, file_name: '' }, + is_zip_url_tls_enabled: false, + is_tls_enabled: false, + }, + type: 'browser', + name: 'https://elastic.co', + id: '420754e9-40f2-486c-bc2e-265bafd735c5', + origin: 'ui', + 'run_from.id': 'A private location', + 'run_from.geo.name': 'A private location', + enabled: true, + schedule: '@every 2m', + timeout: null, + throttling: throttling === 'false' ? false : throttling, + 'source.inline.script': + "step('Go to https://elastic.co', async () => {\n await page.goto('https://elastic.co');\n});", + screenshots: 'on', + processors: [ + { + add_fields: { + target: '', + fields: { + 'monitor.fleet_managed': true, + config_id: '420754e9-40f2-486c-bc2e-265bafd735c5', + }, + }, + }, + ], + }, + }, + { + enabled: true, + data_stream: { + type: 'synthetics', + dataset: 'browser.network', + elasticsearch: { + privileges: { indices: ['auto_configure', 'create_doc', 'read'] }, + }, + }, + id: 'synthetics/browser-browser.network-420754e9-40f2-486c-bc2e-265bafd735c5-fe200580-dee2-11ed-933e-0f85f8c5dd40-default', + compiled_stream: { + processors: [ + { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, + ], + }, + }, + { + enabled: true, + data_stream: { + type: 'synthetics', + dataset: 'browser.screenshot', + elasticsearch: { + privileges: { indices: ['auto_configure', 'create_doc', 'read'] }, + }, + }, + id: 'synthetics/browser-browser.screenshot-420754e9-40f2-486c-bc2e-265bafd735c5-fe200580-dee2-11ed-933e-0f85f8c5dd40-default', + compiled_stream: { + processors: [ + { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, + ], + }, + }, + ], + }, + ], + is_managed: true, + revision: 1, + created_at: '2023-04-19T18:55:35.126Z', + created_by: 'system', + updated_at: '2023-04-19T18:55:35.126Z', + updated_by: 'system', + }, + references: [], + coreMigrationVersion: '8.8.0', + updated_at: '2023-04-19T18:55:35.250Z', + created_at: '2023-04-19T18:55:35.250Z', + typeMigrationVersion: '8.7.0', + } as unknown as SavedObjectUnsanitizedDoc); diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/index.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/index.ts new file mode 100644 index 0000000000000..b38d03e10a104 --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/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 { migratePackagePolicyToV880 } from './to_v8_8_0'; diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.test.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.test.ts new file mode 100644 index 0000000000000..23d7462872c39 --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.test.ts @@ -0,0 +1,167 @@ +/* + * 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 { SavedObjectMigrationContext } from '@kbn/core/server'; + +import { getBrowserPolicy, httpPolicy, icmpPolicy, tcpPolicy } from './fixtures/8.7.0'; + +import { migratePackagePolicyToV880 as migration } from './to_v8_8_0'; + +describe('8.8.0 Synthetics Package Policy migration', () => { + describe('schedule migration', () => { + const testSchedules = [ + ['4', '3'], + ['4.5', '5'], + ['7', '5'], + ['8', '10'], + ['9.5', '10'], + ['12', '10'], + ['13', '15'], + ['16', '15'], + ['18', '20'], + ['21', '20'], + ['25', '20'], + ['26', '30'], + ['31', '30'], + ['45', '30'], + ['46', '60'], + ['61', '60'], + ['90', '60'], + ['91', '120'], + ['121', '120'], + ['195', '240'], + ['600', '240'], + ]; + + it.each(testSchedules)('handles a variety of schedules', (invalidSchedule, validSchedule) => { + const actual = migration( + { + ...httpPolicy, + attributes: { + ...httpPolicy.attributes, + inputs: [ + { + ...httpPolicy.attributes.inputs[0], + streams: [ + { + ...httpPolicy.attributes.inputs[0].streams[0], + vars: { + ...httpPolicy.attributes.inputs[0].streams[0].vars, + schedule: { + value: `"@every ${invalidSchedule}m"`, + type: 'text', + }, + }, + }, + ], + }, + ], + }, + }, + {} as SavedObjectMigrationContext + ); + expect(actual.attributes?.inputs[0]?.streams[0]?.vars?.schedule?.value).toEqual( + `"@every ${validSchedule}m"` + ); + expect(actual.attributes?.inputs[0]?.streams[0]?.compiled_stream?.schedule).toEqual( + `@every ${validSchedule}m` + ); + }); + + it('handles browserPolicy with 2 minute', () => { + const actual = migration(getBrowserPolicy(), {} as SavedObjectMigrationContext); + expect(actual.attributes?.inputs[3]?.streams[0]?.vars?.schedule?.value).toEqual( + '"@every 1m"' + ); + expect(actual.attributes?.inputs[3]?.streams[0]?.compiled_stream?.schedule).toEqual( + `@every 1m` + ); + }); + + it('handles httpPolicy with 4 minute schedule', () => { + const actual = migration(httpPolicy, {} as SavedObjectMigrationContext); + expect(actual.attributes?.inputs[0]?.streams[0]?.vars?.schedule?.value).toEqual( + '"@every 3m"' + ); + expect(actual.attributes?.inputs[0]?.streams[0]?.compiled_stream?.schedule).toEqual( + `@every 3m` + ); + }); + + it('handles tcp with 8 minute schedule', () => { + const actual = migration(tcpPolicy, {} as SavedObjectMigrationContext); + expect(actual.attributes?.inputs[1]?.streams[0]?.vars?.schedule?.value).toEqual( + '"@every 10m"' + ); + expect(actual.attributes?.inputs[1]?.streams[0]?.compiled_stream?.schedule).toEqual( + `@every 10m` + ); + }); + + it('handles icmpPolicy with 16 minute schedule', () => { + const actual = migration(icmpPolicy, {} as SavedObjectMigrationContext); + expect(actual.attributes?.inputs[2]?.streams[0]?.vars?.schedule?.value).toEqual( + '"@every 15m"' + ); + expect(actual.attributes?.inputs[2]?.streams[0]?.compiled_stream?.schedule).toEqual( + `@every 15m` + ); + }); + }); + + describe('throttling migration', () => { + it('handles throtling config for throttling: false', () => { + const actual = migration(getBrowserPolicy('false'), {} as SavedObjectMigrationContext); + expect(actual.attributes?.inputs[3]?.streams[0]?.vars?.['throttling.config']?.value).toEqual( + 'false' + ); + expect(actual.attributes?.inputs[3]?.streams[0]?.compiled_stream?.throttling).toEqual(false); + }); + + it('handles throttling config for default throttling', () => { + const actual = migration(getBrowserPolicy(), {} as SavedObjectMigrationContext); + expect(actual.attributes?.inputs[3]?.streams[0]?.vars?.['throttling.config']?.value).toEqual( + JSON.stringify({ download: 5, upload: 3, latency: 20 }) + ); + expect(actual.attributes?.inputs[3]?.streams[0]?.compiled_stream.throttling).toEqual({ + download: 5, + upload: 3, + latency: 20, + }); + }); + + it('handles throttling config for custom throttling', () => { + const actual = migration( + getBrowserPolicy('1.6d/0.75u/150l'), + {} as SavedObjectMigrationContext + ); + expect(actual.attributes?.inputs[3]?.streams[0]?.vars?.['throttling.config']?.value).toEqual( + JSON.stringify({ download: 1.6, upload: 0.75, latency: 150 }) + ); + expect(actual.attributes?.inputs[3]?.streams[0]?.compiled_stream.throttling).toEqual({ + download: 1.6, + upload: 0.75, + latency: 150, + }); + }); + + it('handles edge cases', () => { + const actual = migration( + getBrowserPolicy('not a valid value'), + {} as SavedObjectMigrationContext + ); + expect(actual.attributes?.inputs[3]?.streams[0]?.vars?.['throttling.config']?.value).toEqual( + JSON.stringify({ download: 5, upload: 3, latency: 20 }) + ); + expect(actual.attributes?.inputs[3]?.streams[0]?.compiled_stream.throttling).toEqual({ + download: 5, + upload: 3, + latency: 20, + }); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.ts new file mode 100644 index 0000000000000..872313471ae9b --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.ts @@ -0,0 +1,107 @@ +/* + * 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 { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from '@kbn/core/server'; + +import type { PackagePolicy } from '../../../../common'; + +export const ALLOWED_SCHEDULES_IN_MINUTES = [ + '1', + '3', + '5', + '10', + '15', + '20', + '30', + '60', + '120', + '240', +]; + +export const migratePackagePolicyToV880: SavedObjectMigrationFn = ( + packagePolicyDoc +) => { + if (packagePolicyDoc.attributes.package?.name !== 'synthetics') { + return packagePolicyDoc; + } + + const updatedPackagePolicyDoc: SavedObjectUnsanitizedDoc = packagePolicyDoc; + + const enabledInput = updatedPackagePolicyDoc.attributes.inputs.find( + (input) => input.enabled === true + ); + const enabledStream = enabledInput?.streams.find((stream) => { + return ['browser', 'http', 'icmp', 'tcp'].includes(stream.data_stream.dataset); + }); + if (!enabledStream) { + return updatedPackagePolicyDoc; + } + + if ( + enabledStream.vars && + enabledStream.vars.schedule?.value && + enabledStream.compiled_stream?.schedule + ) { + const schedule = enabledStream.vars.schedule.value.match(/\d+\.?\d*/g)?.[0]; + const updatedSchedule = getNearestSupportedSchedule(schedule); + const formattedUpdatedSchedule = `@every ${updatedSchedule}m`; + enabledStream.vars.schedule.value = `"${formattedUpdatedSchedule}"`; + enabledStream.compiled_stream.schedule = formattedUpdatedSchedule; + } + + if ( + enabledStream.data_stream.dataset === 'browser' && + enabledStream.vars?.['throttling.config'] && + enabledStream.compiled_stream?.throttling + ) { + const throttling = enabledStream.vars['throttling.config'].value; + if (throttling) { + const formattedThrottling = handleThrottling(throttling); + enabledStream.vars['throttling.config'].value = JSON.stringify(formattedThrottling); + enabledStream.compiled_stream.throttling = formattedThrottling; + } + } + + return updatedPackagePolicyDoc; +}; + +const handleThrottling = ( + throttling: string +): { download: number; upload: number; latency: number } => { + try { + const [download = 5, upload = 3, latency = 20] = throttling.match(/\d+\.?\d*/g) || []; + return { + download: Number(download), + upload: Number(upload), + latency: Number(latency), + }; + } catch { + return { + download: 5, + upload: 3, + latency: 20, + }; + } +}; + +const getNearestSupportedSchedule = (currentSchedule: string): string => { + try { + const closest = ALLOWED_SCHEDULES_IN_MINUTES.reduce(function (prev, curr) { + const supportedSchedule = parseFloat(curr); + const currSchedule = parseFloat(currentSchedule); + const prevSupportedSchedule = parseFloat(prev); + return Math.abs(supportedSchedule - currSchedule) < + Math.abs(prevSupportedSchedule - currSchedule) + ? curr + : prev; + }); + + return closest; + } catch { + return '10'; + } +}; diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_8_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_8_0.ts new file mode 100644 index 0000000000000..ab68e4e1c1429 --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_8_0.ts @@ -0,0 +1,35 @@ +/* + * 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 { SavedObjectMigrationFn } from '@kbn/core/server'; + +import type { PackagePolicy } from '../../../common'; + +import { migratePackagePolicyToV880 as SecSolMigratePackagePolicyToV880 } from './security_solution'; +import { migratePackagePolicyToV880 as SyntheticsMigratePackagePolicyToV880 } from './synthetics'; + +export const migratePackagePolicyToV880: SavedObjectMigrationFn = ( + packagePolicyDoc, + migrationContext +) => { + let updatedPackagePolicyDoc = packagePolicyDoc; + + // Endpoint specific migrations + if (packagePolicyDoc.attributes.package?.name === 'endpoint') { + updatedPackagePolicyDoc = SecSolMigratePackagePolicyToV880(packagePolicyDoc, migrationContext); + } + + // Synthetics specific migrations + if (packagePolicyDoc.attributes.package?.name === 'synthetics') { + updatedPackagePolicyDoc = SyntheticsMigratePackagePolicyToV880( + packagePolicyDoc, + migrationContext + ); + } + + return updatedPackagePolicyDoc; +}; diff --git a/x-pack/plugins/fleet/server/types/models/package_policy.ts b/x-pack/plugins/fleet/server/types/models/package_policy.ts index b94becf1fe449..8e60621102164 100644 --- a/x-pack/plugins/fleet/server/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/package_policy.ts @@ -99,6 +99,7 @@ const PackagePolicyBaseSchema = { namespace: NamespaceSchema, policy_id: schema.string(), enabled: schema.boolean(), + is_managed: schema.maybe(schema.boolean()), package: schema.maybe( schema.object({ name: schema.string(), diff --git a/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts b/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts index 6b9cc7913019a..adfa002e8e20a 100644 --- a/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts +++ b/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts @@ -48,9 +48,11 @@ export const CUSTOM_LABEL = i18n.translate('xpack.synthetics.connectionProfile.c defaultMessage: 'Custom', }); +export const DEFAULT_THROTTLING_VALUE = { download: '5', upload: '3', latency: '20' }; + export const PROFILE_VALUES: ThrottlingConfig[] = [ { - value: { download: '5', upload: '3', latency: '20' }, + value: DEFAULT_THROTTLING_VALUE, id: PROFILE_VALUES_ENUM.DEFAULT, label: i18n.translate('xpack.synthetics.connectionProfile.default', { defaultMessage: 'Default', diff --git a/x-pack/plugins/synthetics/common/formatters/browser/formatters.test.ts b/x-pack/plugins/synthetics/common/formatters/browser/formatters.test.ts new file mode 100644 index 0000000000000..7883e0aa09bca --- /dev/null +++ b/x-pack/plugins/synthetics/common/formatters/browser/formatters.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ConfigKey } from '../../runtime_types'; +import { throttlingFormatter } from './formatters'; + +describe('formatters', () => { + describe('throttling formatter', () => { + it('formats for no throttling', () => { + expect( + throttlingFormatter!( + { + [ConfigKey.THROTTLING_CONFIG]: { + value: { + download: '0', + upload: '0', + latency: '0', + }, + label: 'No throttling', + id: 'no-throttling', + }, + }, + ConfigKey.THROTTLING_CONFIG + ) + ).toEqual('false'); + }); + + it('formats for default throttling', () => { + expect( + throttlingFormatter!( + { + [ConfigKey.THROTTLING_CONFIG]: { + value: { + download: '5', + upload: '3', + latency: '20', + }, + label: 'Default', + id: 'default', + }, + }, + ConfigKey.THROTTLING_CONFIG + ) + ).toEqual(JSON.stringify({ download: 5, upload: 3, latency: 20 })); + }); + + it('formats for custom throttling', () => { + expect( + throttlingFormatter!( + { + [ConfigKey.THROTTLING_CONFIG]: { + value: { + download: '1.25', + upload: '0.75', + latency: '150', + }, + label: 'Custom', + id: 'custom', + }, + }, + ConfigKey.THROTTLING_CONFIG + ) + ).toEqual(JSON.stringify({ download: 1.25, upload: 0.75, latency: 150 })); + }); + }); +}); diff --git a/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts b/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts index 508464c48a5f2..9dfa027767851 100644 --- a/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts @@ -12,6 +12,7 @@ import { objectToJsonFormatter, stringToJsonFormatter, } from '../formatting_utils'; +import { DEFAULT_THROTTLING_VALUE } from '../../constants/monitor_defaults'; import { tlsFormatters } from '../tls/formatters'; @@ -24,7 +25,11 @@ export const throttlingFormatter: Formatter = (fields) => { return 'false'; } - return `${throttling.value.download}d/${throttling.value.upload}u/${throttling.value.latency}l`; + return JSON.stringify({ + download: Number(throttling?.value?.download || DEFAULT_THROTTLING_VALUE.download), + upload: Number(throttling?.value?.upload || DEFAULT_THROTTLING_VALUE.upload), + latency: Number(throttling?.value?.latency || DEFAULT_THROTTLING_VALUE), + }); }; export const browserFormatters: BrowserFormatMap = { diff --git a/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts b/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts index 6be823ee0a208..2706972456acd 100644 --- a/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts +++ b/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts @@ -422,7 +422,7 @@ describe('formatSyntheticsPolicy', () => { }, 'throttling.config': { type: 'text', - value: '5d/3u/20l', + value: JSON.stringify({ download: 5, upload: 3, latency: 20 }), }, timeout: { type: 'text', diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/add_monitor.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/add_monitor.ts index 44053aa29ed26..fb79e0ec94ff2 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/add_monitor.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/add_monitor.ts @@ -130,11 +130,15 @@ export const testDataMonitor = { 'filter_journeys.match': '', 'filter_journeys.tags': [], ignore_https_errors: false, - 'throttling.is_enabled': true, - 'throttling.download_speed': '5', - 'throttling.upload_speed': '3', - 'throttling.latency': '20', - 'throttling.config': '5d/3u/20l', + throttling: { + id: 'custom', + label: 'Custom', + value: { + download: '5', + upload: '3', + latency: '20', + }, + }, 'ssl.certificate_authorities': '', 'ssl.certificate': '', 'ssl.key': '', diff --git a/x-pack/plugins/synthetics/e2e/tasks/import_monitors.ts b/x-pack/plugins/synthetics/e2e/tasks/import_monitors.ts index f65d82a9933f4..f7143ee5b89e8 100644 --- a/x-pack/plugins/synthetics/e2e/tasks/import_monitors.ts +++ b/x-pack/plugins/synthetics/e2e/tasks/import_monitors.ts @@ -48,11 +48,15 @@ export const importMonitors = async ({ 'filter_journeys.match': '', 'filter_journeys.tags': [], ignore_https_errors: false, - 'throttling.is_enabled': true, - 'throttling.download_speed': '5', - 'throttling.upload_speed': '3', - 'throttling.latency': '20', - 'throttling.config': '5d/3u/20l', + throttling: { + id: 'custom', + label: 'Custom', + value: { + download: '5', + upload: '3', + latency: '20', + }, + }, }; const id = '1c215bd0-f580-11ec-89e5-694db461b7a5'; diff --git a/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.test.ts b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.test.ts index 367c27c4bca08..28d7018448374 100644 --- a/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.test.ts +++ b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.test.ts @@ -169,11 +169,15 @@ const testMonitors = [ 'filter_journeys.match': '', 'filter_journeys.tags': [], ignore_https_errors: false, - 'throttling.is_enabled': true, - 'throttling.download_speed': '5', - 'throttling.upload_speed': '3', - 'throttling.latency': '20', - 'throttling.config': '5d/3u/20l', + throttling: { + id: 'custom', + label: 'Custom', + value: { + download: '5', + upload: '3', + latency: '20', + }, + }, 'ssl.certificate_authorities': '', 'ssl.certificate': '', 'ssl.verification_mode': 'full', diff --git a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts index a334b6b406b3b..f1ebab30182ce 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts @@ -263,7 +263,7 @@ describe('SyntheticsPrivateLocation', () => { }, 'throttling.config': { type: 'text', - value: '5d/3u/20l', + value: JSON.stringify({ download: 5, upload: 3, latency: 20 }), }, timeout: { type: 'text', diff --git a/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts b/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts index 0cebf231cf787..eff1026cb4486 100644 --- a/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts +++ b/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts @@ -185,7 +185,10 @@ export const getTestBrowserSyntheticsPolicy = ({ screenshots: { value: 'on', type: 'text' }, synthetics_args: { value: null, type: 'text' }, ignore_https_errors: { value: false, type: 'bool' }, - 'throttling.config': { value: '5d/3u/20l', type: 'text' }, + 'throttling.config': { + value: JSON.stringify({ download: 5, upload: 3, latency: 20 }), + type: 'text', + }, 'filter_journeys.tags': { value: null, type: 'yaml' }, 'filter_journeys.match': { value: null, type: 'text' }, 'source.zip_url.ssl.certificate_authorities': { type: 'yaml' }, @@ -218,7 +221,7 @@ export const getTestBrowserSyntheticsPolicy = ({ enabled: true, schedule: '@every 3m', timeout: '16s', - throttling: '5d/3u/20l', + throttling: { download: 5, upload: 3, latency: 20 }, tags: ['cookie-test', 'browser'], 'source.inline.script': 'step("Visit /users api route", async () => {\\n const response = await page.goto(\'https://nextjs-test-synthetics.vercel.app/api/users\');\\n expect(response.status()).toEqual(200);\\n});', diff --git a/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts b/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts index cd5ea22451b92..ba48a26ff50fe 100644 --- a/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts +++ b/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts @@ -213,7 +213,10 @@ export const getTestProjectSyntheticsPolicy = ( screenshots: { value: 'on', type: 'text' }, synthetics_args: { value: null, type: 'text' }, ignore_https_errors: { value: false, type: 'bool' }, - 'throttling.config': { value: '5d/3u/20l', type: 'text' }, + 'throttling.config': { + value: JSON.stringify({ download: 5, upload: 3, latency: 20 }), + type: 'text', + }, 'filter_journeys.tags': { value: null, type: 'yaml' }, 'filter_journeys.match': { value: '"check if title is present"', type: 'text' }, 'source.zip_url.ssl.certificate_authorities': { type: 'yaml' }, @@ -246,7 +249,7 @@ export const getTestProjectSyntheticsPolicy = ( 'run_from.geo.name': locationName, 'run_from.id': locationName, timeout: null, - throttling: '5d/3u/20l', + throttling: { download: 5, upload: 3, latency: 20 }, 'source.project.content': 'UEsDBBQACAAIAON5qVQAAAAAAAAAAAAAAAAfAAAAZXhhbXBsZXMvdG9kb3MvYmFzaWMuam91cm5leS50c22Q0WrDMAxF3/sVF7MHB0LMXlc6RvcN+wDPVWNviW0sdUsp/fe5SSiD7UFCWFfHujIGlpnkybwxFTZfoY/E3hsaLEtwhs9RPNWKDU12zAOxkXRIbN4tB9d9pFOJdO6EN2HMqQguWN9asFBuQVMmJ7jiWNII9fIXrbabdUYr58l9IhwhQQZCYORCTFFUC31Btj21NRc7Mq4Nds+4bDD/pNVgT9F52Jyr2Fa+g75LAPttg8yErk+S9ELpTmVotlVwnfNCuh2lepl3+JflUmSBJ3uggt1v9INW/lHNLKze9dJe1J3QJK8pSvWkm6aTtCet5puq+x63+AFQSwcIAPQ3VfcAAACcAQAAUEsBAi0DFAAIAAgA43mpVAD0N1X3AAAAnAEAAB8AAAAAAAAAAAAgAKSBAAAAAGV4YW1wbGVzL3RvZG9zL2Jhc2ljLmpvdXJuZXkudHNQSwUGAAAAAAEAAQBNAAAARAEAAAAA', playwright_options: { headless: true, chromiumSandbox: false }, From f95ebdfb31fe3d154797c2617fff534f63325370 Mon Sep 17 00:00:00 2001 From: GitStart <1501599+gitstart@users.noreply.github.com> Date: Fri, 21 Apr 2023 17:02:58 +0300 Subject: [PATCH 58/67] [Synthetics] Filtering - location filter counts are off when filter is applied (#155437) Co-authored-by: Anjola Adeuyi <57623705+anjola-adeuyi@users.noreply.github.com> Co-authored-by: gitstart_bot Co-authored-by: shahzad31 --- .../common/monitor_filters/filter_group.tsx | 13 ++++++++++++- .../common/monitor_filters/use_filters.ts | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_group.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_group.tsx index 7e19abbf758ce..a59a26613a427 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_group.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_group.tsx @@ -61,7 +61,18 @@ export const FilterGroup = ({ label: LOCATION_LABEL, field: 'locations', values: getSyntheticsFilterDisplayValues( - mixUrlValues(data.locations, urlParams.locations), + mixUrlValues( + data.locations.map((locationData) => { + const matchingLocation = locations.find( + (location) => location.id === locationData.label + ); + return { + label: matchingLocation ? matchingLocation.label : locationData.label, + count: locationData.count, + }; + }), + urlParams.locations + ), 'locations', locations ), diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts index 014966f31bbc0..0e3fe38566f34 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts @@ -132,7 +132,7 @@ export const useFilters = (): FiltersList => { })) ?? [], schedules: schedules?.buckets?.map(({ key, doc_count: count }) => ({ - label: key, + label: String(key), count, })) ?? [], }; From cfc01d5444610b6d3d01d4178c0a5e5a1f04c5e3 Mon Sep 17 00:00:00 2001 From: Thom Heymann <190132+thomheymann@users.noreply.github.com> Date: Fri, 21 Apr 2023 15:19:25 +0100 Subject: [PATCH 59/67] Hide remote indices role configuration when not supported (#155490) Resolves #155389 ## Summary Adds feature flag to automatically hide remote index privileges section when not supported by cluster. --- .../role_mappings/role_mappings_api_client.ts | 3 +- .../roles/edit_role/edit_role_page.test.tsx | 36 +++++++- .../roles/edit_role/edit_role_page.tsx | 20 ++++- .../elasticsearch_privileges.test.tsx.snap | 84 ------------------- .../es/elasticsearch_privileges.test.tsx | 7 +- .../es/elasticsearch_privileges.tsx | 65 +++++++------- .../routes/role_mapping/feature_check.test.ts | 30 +++++++ .../routes/role_mapping/feature_check.ts | 4 + 8 files changed, 130 insertions(+), 119 deletions(-) diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.ts b/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.ts index 79e2335919b1a..5465bc24b7e31 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.ts +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.ts @@ -9,11 +9,12 @@ import type { HttpStart } from '@kbn/core/public'; import type { RoleMapping } from '../../../common/model'; -interface CheckRoleMappingFeaturesResponse { +export interface CheckRoleMappingFeaturesResponse { canManageRoleMappings: boolean; canUseInlineScripts: boolean; canUseStoredScripts: boolean; hasCompatibleRealms: boolean; + canUseRemoteIndices: boolean; } type DeleteRoleMappingsResponse = Array<{ diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx index 090c7e6854aeb..52e3d768ef07e 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx @@ -140,11 +140,13 @@ function getProps({ role, canManageSpaces = true, spacesEnabled = true, + canUseRemoteIndices = true, }: { action: 'edit' | 'clone'; role?: Role; canManageSpaces?: boolean; spacesEnabled?: boolean; + canUseRemoteIndices?: boolean; }) { const rolesAPIClient = rolesAPIClientMock.create(); rolesAPIClient.getRole.mockResolvedValue(role); @@ -171,12 +173,15 @@ function getProps({ const { fatalErrors } = coreMock.createSetup(); const { http, docLinks, notifications } = coreMock.createStart(); http.get.mockImplementation(async (path: any) => { - if (!spacesEnabled) { - throw { response: { status: 404 } }; // eslint-disable-line no-throw-literal - } if (path === '/api/spaces/space') { + if (!spacesEnabled) { + throw { response: { status: 404 } }; // eslint-disable-line no-throw-literal + } return buildSpaces(); } + if (path === '/internal/security/_check_role_mapping_features') { + return { canUseRemoteIndices }; + } }); return { @@ -265,6 +270,8 @@ describe('', () => { expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true); + expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1); + expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1); expectReadOnlyFormButtons(wrapper); }); @@ -291,6 +298,8 @@ describe('', () => { expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true); + expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1); + expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1); expectSaveFormButtons(wrapper); }); @@ -308,6 +317,8 @@ describe('', () => { expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe( false ); + expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1); + expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1); expectSaveFormButtons(wrapper); }); @@ -480,6 +491,8 @@ describe('', () => { expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true); + expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1); + expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1); expectReadOnlyFormButtons(wrapper); }); @@ -507,6 +520,8 @@ describe('', () => { expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true); + expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1); + expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1); expectSaveFormButtons(wrapper); }); @@ -524,6 +539,8 @@ describe('', () => { expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe( false ); + expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1); + expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1); expectSaveFormButtons(wrapper); }); @@ -612,6 +629,19 @@ describe('', () => { }); }); + it('hides remote index privileges section when not supported', async () => { + const wrapper = mountWithIntl( + + + + ); + + await waitForRender(wrapper); + + expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1); + expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(0); + }); + it('registers fatal error if features endpoint fails unexpectedly', async () => { const error = { response: { status: 500 } }; const getFeatures = jest.fn().mockRejectedValue(error); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx index e5e5736ca5833..9388ab92a0a76 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx @@ -21,6 +21,7 @@ import { } from '@elastic/eui'; import type { ChangeEvent, FocusEvent, FunctionComponent, HTMLProps } from 'react'; import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'; +import useAsync from 'react-use/lib/useAsync'; import type { IHttpFetchError } from '@kbn/core-http-browser'; import type { @@ -56,6 +57,7 @@ import { prepareRoleClone, } from '../../../../common/model'; import { useCapabilities } from '../../../components/use_capabilities'; +import type { CheckRoleMappingFeaturesResponse } from '../../role_mappings/role_mappings_api_client'; import type { UserAPIClient } from '../../users'; import type { IndicesAPIClient } from '../indices_api_client'; import { KibanaPrivileges } from '../model'; @@ -86,6 +88,12 @@ interface Props { spacesApiUi?: SpacesApiUi; } +function useFeatureCheck(http: HttpStart) { + return useAsync(() => + http.get('/internal/security/_check_role_mapping_features') + ); +} + function useRunAsUsers( userAPIClient: PublicMethodsOf, fatalErrors: FatalErrorsSetup @@ -311,6 +319,7 @@ export const EditRolePage: FunctionComponent = ({ const privileges = usePrivileges(privilegesAPIClient, fatalErrors); const spaces = useSpaces(http, fatalErrors); const features = useFeatures(getFeatures, fatalErrors); + const featureCheckState = useFeatureCheck(http); const [role, setRole] = useRole( rolesAPIClient, fatalErrors, @@ -329,7 +338,15 @@ export const EditRolePage: FunctionComponent = ({ } }, [hasReadOnlyPrivileges, isEditingExistingRole]); // eslint-disable-line react-hooks/exhaustive-deps - if (!role || !runAsUsers || !indexPatternsTitles || !privileges || !spaces || !features) { + if ( + !role || + !runAsUsers || + !indexPatternsTitles || + !privileges || + !spaces || + !features || + !featureCheckState.value + ) { return null; } @@ -457,6 +474,7 @@ export const EditRolePage: FunctionComponent = ({ builtinESPrivileges={builtInESPrivileges} license={license} docLinks={docLinks} + canUseRemoteIndices={featureCheckState.value?.canUseRemoteIndices} /> ); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap index 988f463f49fd1..8f160c6e57abc 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap @@ -200,89 +200,5 @@ exports[`it renders without crashing 1`] = ` } } /> - - - -

- -

-
- - -

- - - - -

-
- `; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.test.tsx index 13a4143890a86..1a1486f6d82e3 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.test.tsx @@ -63,8 +63,13 @@ test('it renders index privileges section', () => { expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1); }); -test('it renders remote index privileges section', () => { +test('it does not render remote index privileges section by default', () => { const wrapper = shallowWithIntl(); + expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(0); +}); + +test('it renders remote index privileges section when `canUseRemoteIndices` is enabled', () => { + const wrapper = shallowWithIntl(); expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1); }); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx index 88e0c953711fa..e963c4eda6d92 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx @@ -40,6 +40,7 @@ interface Props { validator: RoleValidator; builtinESPrivileges: BuiltinESPrivileges; indexPatterns: string[]; + canUseRemoteIndices?: boolean; } export class ElasticsearchPrivileges extends Component { @@ -62,6 +63,7 @@ export class ElasticsearchPrivileges extends Component { indexPatterns, license, builtinESPrivileges, + canUseRemoteIndices, } = this.props; return ( @@ -170,37 +172,42 @@ export class ElasticsearchPrivileges extends Component { availableIndexPrivileges={builtinESPrivileges.index} editable={editable} /> - - - -

- -

-
- - -

- + + + + +

+ +

+ + + +

+ + {this.learnMore(docLinks.links.security.indicesPrivileges)} +

+
+ - {this.learnMore(docLinks.links.security.indicesPrivileges)} -

-
- + + )} ); }; diff --git a/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts b/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts index 0efe93d21c1b2..ce0a38ef73039 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts @@ -21,6 +21,9 @@ interface TestOptions { } const defaultXpackUsageResponse = { + remote_clusters: { + size: 0, + }, security: { realms: { native: { @@ -94,6 +97,7 @@ describe('GET role mappings feature check', () => { canUseInlineScripts: true, canUseStoredScripts: true, hasCompatibleRealms: true, + canUseRemoteIndices: true, }, }, }); @@ -117,10 +121,31 @@ describe('GET role mappings feature check', () => { canUseInlineScripts: true, canUseStoredScripts: true, hasCompatibleRealms: true, + canUseRemoteIndices: true, }, }, }); + getFeatureCheckTest( + 'indicates canUseRemoteIndices=false when cluster does not support remote indices', + { + xpackUsageResponse: () => ({ + ...defaultXpackUsageResponse, + remote_clusters: undefined, + }), + asserts: { + statusCode: 200, + result: { + canManageRoleMappings: true, + canUseInlineScripts: true, + canUseStoredScripts: true, + hasCompatibleRealms: true, + canUseRemoteIndices: false, + }, + }, + } + ); + getFeatureCheckTest('disallows stored scripts when disabled', { nodeSettingsResponse: () => ({ nodes: { @@ -140,6 +165,7 @@ describe('GET role mappings feature check', () => { canUseInlineScripts: true, canUseStoredScripts: false, hasCompatibleRealms: true, + canUseRemoteIndices: true, }, }, }); @@ -163,12 +189,14 @@ describe('GET role mappings feature check', () => { canUseInlineScripts: false, canUseStoredScripts: true, hasCompatibleRealms: true, + canUseRemoteIndices: true, }, }, }); getFeatureCheckTest('indicates incompatible realms when only native and file are enabled', { xpackUsageResponse: () => ({ + ...defaultXpackUsageResponse, security: { realms: { native: { @@ -189,6 +217,7 @@ describe('GET role mappings feature check', () => { canUseInlineScripts: true, canUseStoredScripts: true, hasCompatibleRealms: false, + canUseRemoteIndices: true, }, }, }); @@ -219,6 +248,7 @@ describe('GET role mappings feature check', () => { canUseInlineScripts: true, canUseStoredScripts: true, hasCompatibleRealms: false, + canUseRemoteIndices: false, }, }, } diff --git a/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts b/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts index 9715f92cb5a37..309cdfbeab456 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts @@ -24,6 +24,9 @@ interface NodeSettingsResponse { } interface XPackUsageResponse { + remote_clusters?: { + size: number; + }; security: { realms: { [realmName: string]: { @@ -128,6 +131,7 @@ async function getEnabledRoleMappingsFeatures(esClient: ElasticsearchClient, log hasCompatibleRealms, canUseStoredScripts, canUseInlineScripts, + canUseRemoteIndices: !!xpackUsage.remote_clusters, }; } From 6aa1491c9ec766731fa505157038216c45fa6cfb Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Fri, 21 Apr 2023 16:41:08 +0200 Subject: [PATCH 60/67] [CM] Onboard `maps` to cross-type search (#155148) ## Summary Part of https://github.com/elastic/kibana/issues/152224 Follow up to https://github.com/elastic/kibana/issues/153256 This PR onboards maps CM integration into the multi-type search (`msearch`). It isn't actually used anywhere in the user-facing UI yet, as first other types need to be migrated to CM. This PR also adds an example app to test the `msearch` end-to-end. --- .../content_management_examples/kibana.jsonc | 4 +- .../public/examples/index.tsx | 63 ++++++++++++++++--- .../public/examples/msearch/index.tsx | 9 +++ .../public/examples/msearch/msearch_app.tsx | 42 +++++++++++++ .../public/examples/msearch/msearch_table.tsx | 62 ++++++++++++++++++ .../public/types.ts | 2 + .../content_management_examples/tsconfig.json | 6 ++ .../table_list/src/services.tsx | 4 +- .../content_management_services_schemas.ts | 15 +++++ ...ent_management_services_versioning.test.ts | 1 + .../content_management_services_versioning.ts | 6 ++ .../lib/content_management_types.ts | 10 +++ .../content_management/server/core/index.ts | 1 + .../server/core/registry.ts | 6 +- .../content_management/server/core/types.ts | 12 ++-- .../content_management/server/index.ts | 2 +- test/examples/content_management/index.ts | 1 + test/examples/content_management/msearch.ts | 47 ++++++++++++++ test/functional/page_objects/common_page.ts | 3 +- .../content_management/v1/cm_services.ts | 7 +++ .../server/content_management/maps_storage.ts | 36 ++++++++++- 21 files changed, 316 insertions(+), 23 deletions(-) create mode 100644 examples/content_management_examples/public/examples/msearch/index.tsx create mode 100644 examples/content_management_examples/public/examples/msearch/msearch_app.tsx create mode 100644 examples/content_management_examples/public/examples/msearch/msearch_table.tsx create mode 100644 test/examples/content_management/msearch.ts diff --git a/examples/content_management_examples/kibana.jsonc b/examples/content_management_examples/kibana.jsonc index 24759d7abdfb6..ae7cd95e4190a 100644 --- a/examples/content_management_examples/kibana.jsonc +++ b/examples/content_management_examples/kibana.jsonc @@ -9,7 +9,9 @@ "browser": true, "requiredPlugins": [ "contentManagement", - "developerExamples" + "developerExamples", + "kibanaReact", + "savedObjectsTaggingOss" ] } } diff --git a/examples/content_management_examples/public/examples/index.tsx b/examples/content_management_examples/public/examples/index.tsx index 715f3a6809581..611bcde59d445 100644 --- a/examples/content_management_examples/public/examples/index.tsx +++ b/examples/content_management_examples/public/examples/index.tsx @@ -8,22 +8,67 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { EuiPageTemplate } from '@elastic/eui'; +// eslint-disable-next-line no-restricted-imports +import { Router, Switch, Route, Redirect } from 'react-router-dom'; +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; +import { EuiPageTemplate, EuiSideNav } from '@elastic/eui'; import { AppMountParameters, CoreStart } from '@kbn/core/public'; import { StartDeps } from '../types'; import { TodoApp } from './todos'; +import { MSearchApp } from './msearch'; export const renderApp = ( - { notifications }: CoreStart, - { contentManagement }: StartDeps, - { element }: AppMountParameters + core: CoreStart, + { contentManagement, savedObjectsTaggingOss }: StartDeps, + { element, history }: AppMountParameters ) => { ReactDOM.render( - - - - - , + + + + + + + + + + + + + + + + + + + + + , element ); diff --git a/examples/content_management_examples/public/examples/msearch/index.tsx b/examples/content_management_examples/public/examples/msearch/index.tsx new file mode 100644 index 0000000000000..7caf5d200748a --- /dev/null +++ b/examples/content_management_examples/public/examples/msearch/index.tsx @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { MSearchApp } from './msearch_app'; diff --git a/examples/content_management_examples/public/examples/msearch/msearch_app.tsx b/examples/content_management_examples/public/examples/msearch/msearch_app.tsx new file mode 100644 index 0000000000000..aa2bc6c1a8b7b --- /dev/null +++ b/examples/content_management_examples/public/examples/msearch/msearch_app.tsx @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { ContentClientProvider, type ContentClient } from '@kbn/content-management-plugin/public'; +import { TableListViewKibanaProvider } from '@kbn/content-management-table-list'; +import type { CoreStart } from '@kbn/core/public'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { FormattedRelative, I18nProvider } from '@kbn/i18n-react'; +import { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public'; +import { MSearchTable } from './msearch_table'; + +export const MSearchApp = (props: { + contentClient: ContentClient; + core: CoreStart; + savedObjectsTagging: SavedObjectTaggingOssPluginStart; +}) => { + return ( + + + + + + + + ); +}; diff --git a/examples/content_management_examples/public/examples/msearch/msearch_table.tsx b/examples/content_management_examples/public/examples/msearch/msearch_table.tsx new file mode 100644 index 0000000000000..47eaab4ba602b --- /dev/null +++ b/examples/content_management_examples/public/examples/msearch/msearch_table.tsx @@ -0,0 +1,62 @@ +/* + * 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 { TableListView, UserContentCommonSchema } from '@kbn/content-management-table-list'; +import { useContentClient } from '@kbn/content-management-plugin/public'; +import React from 'react'; +import { SavedObjectsFindOptionsReference } from '@kbn/core-saved-objects-api-browser'; + +const LISTING_LIMIT = 1000; + +export const MSearchTable = () => { + const contentClient = useContentClient(); + + const findItems = async ( + searchQuery: string, + refs?: { + references?: SavedObjectsFindOptionsReference[]; + referencesToExclude?: SavedObjectsFindOptionsReference[]; + } + ) => { + const { hits, pagination } = await contentClient.mSearch({ + query: { + text: searchQuery, + limit: LISTING_LIMIT, + cursor: '1', + tags: { + included: refs?.references?.map((ref) => ref.id), + excluded: refs?.referencesToExclude?.map((ref) => ref.id), + }, + }, + contentTypes: [{ contentTypeId: 'map' }], // TODO: improve types to not require objects here? + }); + + // TODO: needs to have logic of extracting common schema from an unknown mSearch hit: hits.map(hit => cm.convertToCommonSchema(hit)) + // for now we just assume that mSearch hit satisfies UserContentCommonSchema + + return { hits, total: pagination.total }; + }; + + return ( + No data found. Try to install some sample data first.} + onClickTitle={(item) => { + alert(`Clicked item ${item.attributes.title} (${item.id})`); + }} + /> + ); +}; diff --git a/examples/content_management_examples/public/types.ts b/examples/content_management_examples/public/types.ts index 83e65c5455afd..747bfd961c434 100644 --- a/examples/content_management_examples/public/types.ts +++ b/examples/content_management_examples/public/types.ts @@ -11,6 +11,7 @@ import { ContentManagementPublicStart, } from '@kbn/content-management-plugin/public'; import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public'; +import { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public'; export interface SetupDeps { contentManagement: ContentManagementPublicSetup; @@ -19,4 +20,5 @@ export interface SetupDeps { export interface StartDeps { contentManagement: ContentManagementPublicStart; + savedObjectsTaggingOss: SavedObjectTaggingOssPluginStart; } diff --git a/examples/content_management_examples/tsconfig.json b/examples/content_management_examples/tsconfig.json index f383cd9d9b33e..7f07213ce82b6 100644 --- a/examples/content_management_examples/tsconfig.json +++ b/examples/content_management_examples/tsconfig.json @@ -19,5 +19,11 @@ "@kbn/developer-examples-plugin", "@kbn/content-management-plugin", "@kbn/core-application-browser", + "@kbn/shared-ux-link-redirect-app", + "@kbn/content-management-table-list", + "@kbn/kibana-react-plugin", + "@kbn/i18n-react", + "@kbn/saved-objects-tagging-oss-plugin", + "@kbn/core-saved-objects-api-browser", ] } diff --git a/packages/content-management/table_list/src/services.tsx b/packages/content-management/table_list/src/services.tsx index 5edc16d9a915d..d7752a270c86c 100644 --- a/packages/content-management/table_list/src/services.tsx +++ b/packages/content-management/table_list/src/services.tsx @@ -80,9 +80,7 @@ export interface TableListViewKibanaDependencies { core: { application: { capabilities: { - advancedSettings?: { - save: boolean; - }; + [key: string]: Readonly>>; }; getUrlForApp: (app: string, options: { path: string }) => string; currentAppId$: Observable; diff --git a/packages/kbn-object-versioning/lib/content_management_services_schemas.ts b/packages/kbn-object-versioning/lib/content_management_services_schemas.ts index 90e8400689dc2..c98a3f62ca3a0 100644 --- a/packages/kbn-object-versioning/lib/content_management_services_schemas.ts +++ b/packages/kbn-object-versioning/lib/content_management_services_schemas.ts @@ -103,6 +103,20 @@ const searchSchemas = getOptionalInOutSchemas({ ), }); +// Schema to validate the "msearch" service objects +const mSearchSchemas = schema.maybe( + schema.object({ + out: schema.maybe( + schema.object( + { + result: schema.maybe(versionableObjectSchema), + }, + { unknowns: 'forbid' } + ) + ), + }) +); + export const serviceDefinitionSchema = schema.object( { get: getSchemas, @@ -111,6 +125,7 @@ export const serviceDefinitionSchema = schema.object( update: createSchemas, delete: getSchemas, search: searchSchemas, + mSearch: mSearchSchemas, }, { unknowns: 'forbid' } ); diff --git a/packages/kbn-object-versioning/lib/content_management_services_versioning.test.ts b/packages/kbn-object-versioning/lib/content_management_services_versioning.test.ts index 49b55b0da44fc..08bd66609ab7e 100644 --- a/packages/kbn-object-versioning/lib/content_management_services_versioning.test.ts +++ b/packages/kbn-object-versioning/lib/content_management_services_versioning.test.ts @@ -240,6 +240,7 @@ describe('CM services getTransforms()', () => { 'delete.out.result', 'search.in.options', 'search.out.result', + 'mSearch.out.result', ].sort() ); }); diff --git a/packages/kbn-object-versioning/lib/content_management_services_versioning.ts b/packages/kbn-object-versioning/lib/content_management_services_versioning.ts index 5655a094f5e42..eb38de643f630 100644 --- a/packages/kbn-object-versioning/lib/content_management_services_versioning.ts +++ b/packages/kbn-object-versioning/lib/content_management_services_versioning.ts @@ -34,6 +34,7 @@ const serviceObjectPaths = [ 'delete.out.result', 'search.in.options', 'search.out.result', + 'mSearch.out.result', ]; const validateServiceDefinitions = (definitions: ServiceDefinitionVersioned) => { @@ -175,6 +176,11 @@ const getDefaultServiceTransforms = (): ServiceTransforms => ({ result: getDefaultTransforms(), }, }, + mSearch: { + out: { + result: getDefaultTransforms(), + }, + }, }); export const getTransforms = ( diff --git a/packages/kbn-object-versioning/lib/content_management_types.ts b/packages/kbn-object-versioning/lib/content_management_types.ts index 56d72013335cd..1594b450661ba 100644 --- a/packages/kbn-object-versioning/lib/content_management_types.ts +++ b/packages/kbn-object-versioning/lib/content_management_types.ts @@ -59,6 +59,11 @@ export interface ServicesDefinition { result?: VersionableObject; }; }; + mSearch?: { + out?: { + result?: VersionableObject; + }; + }; } export interface ServiceTransforms { @@ -112,6 +117,11 @@ export interface ServiceTransforms { result: ObjectTransforms; }; }; + mSearch: { + out: { + result: ObjectTransforms; + }; + }; } export interface ServiceDefinitionVersioned { diff --git a/src/plugins/content_management/server/core/index.ts b/src/plugins/content_management/server/core/index.ts index c765076f39e94..1ac7228c6f163 100644 --- a/src/plugins/content_management/server/core/index.ts +++ b/src/plugins/content_management/server/core/index.ts @@ -17,6 +17,7 @@ export type { ContentTypeDefinition, StorageContext, StorageContextGetTransformFn, + MSearchConfig, } from './types'; export type { ContentRegistry } from './registry'; diff --git a/src/plugins/content_management/server/core/registry.ts b/src/plugins/content_management/server/core/registry.ts index 7d36fa20fad1a..00adb4b04a403 100644 --- a/src/plugins/content_management/server/core/registry.ts +++ b/src/plugins/content_management/server/core/registry.ts @@ -9,7 +9,7 @@ import { validateVersion } from '@kbn/object-versioning/lib/utils'; import { ContentType } from './content_type'; import { EventBus } from './event_bus'; -import type { ContentStorage, ContentTypeDefinition } from './types'; +import type { ContentStorage, ContentTypeDefinition, MSearchConfig } from './types'; import type { ContentCrud } from './crud'; export class ContentRegistry { @@ -23,7 +23,9 @@ export class ContentRegistry { * @param contentType The content type to register * @param config The content configuration */ - register = ContentStorage>(definition: ContentTypeDefinition) { + register> = ContentStorage>( + definition: ContentTypeDefinition + ) { if (this.types.has(definition.id)) { throw new Error(`Content [${definition.id}] is already registered`); } diff --git a/src/plugins/content_management/server/core/types.ts b/src/plugins/content_management/server/core/types.ts index c9e66c2563b61..d26c6ac72fa41 100644 --- a/src/plugins/content_management/server/core/types.ts +++ b/src/plugins/content_management/server/core/types.ts @@ -41,7 +41,11 @@ export interface StorageContext { }; } -export interface ContentStorage { +export interface ContentStorage< + T = unknown, + U = T, + TMSearchConfig extends MSearchConfig = MSearchConfig +> { /** Get a single item */ get(ctx: StorageContext, id: string, options?: object): Promise>; @@ -69,7 +73,7 @@ export interface ContentStorage { * Opt-in to multi-type search. * Can only be supported if the content type is backed by a saved object since `mSearch` is using the `savedObjects.find` API. **/ - mSearch?: MSearchConfig; + mSearch?: TMSearchConfig; } export interface ContentTypeDefinition { @@ -87,7 +91,7 @@ export interface ContentTypeDefinition { +export interface MSearchConfig { /** * The saved object type that corresponds to this content type. */ @@ -98,7 +102,7 @@ export interface MSearchConfig { */ toItemResult: ( ctx: StorageContext, - savedObject: SavedObjectsFindResult + savedObject: SavedObjectsFindResult ) => T; /** diff --git a/src/plugins/content_management/server/index.ts b/src/plugins/content_management/server/index.ts index 659b59ed6880c..cdd69cb99b296 100644 --- a/src/plugins/content_management/server/index.ts +++ b/src/plugins/content_management/server/index.ts @@ -14,4 +14,4 @@ export function plugin(initializerContext: PluginInitializerContext) { } export type { ContentManagementServerSetup, ContentManagementServerStart } from './types'; -export type { ContentStorage, StorageContext } from './core'; +export type { ContentStorage, StorageContext, MSearchConfig } from './core'; diff --git a/test/examples/content_management/index.ts b/test/examples/content_management/index.ts index d5e3f14bfc0e6..c02b022781187 100644 --- a/test/examples/content_management/index.ts +++ b/test/examples/content_management/index.ts @@ -12,5 +12,6 @@ import { PluginFunctionalProviderContext } from '../../plugin_functional/service export default function ({ loadTestFile }: PluginFunctionalProviderContext) { describe('content management examples', function () { loadTestFile(require.resolve('./todo_app')); + loadTestFile(require.resolve('./msearch')); }); } diff --git a/test/examples/content_management/msearch.ts b/test/examples/content_management/msearch.ts new file mode 100644 index 0000000000000..0a583b455cd04 --- /dev/null +++ b/test/examples/content_management/msearch.ts @@ -0,0 +1,47 @@ +/* + * 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 { PluginFunctionalProviderContext } from '../../plugin_functional/services'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) { + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common', 'home', 'header']); + const listingTable = getService('listingTable'); + + describe('MSearch demo', () => { + before(async () => { + await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { + useActualUrl: true, + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.home.addSampleDataSet('flights'); + }); + after(async () => { + await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { + useActualUrl: true, + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.home.removeSampleDataSet('flights'); + }); + + it('MSearch demo works', async () => { + const appId = 'contentManagementExamples'; + await PageObjects.common.navigateToApp(appId, { + path: 'msearch', + }); + + await listingTable.waitUntilTableIsLoaded(); + await listingTable.searchForItemWithName('Origin Time Delayed'); + + await testSubjects.existOrFail( + `cm-msearch-tableListingTitleLink-[Flights]-Origin-Time-Delayed` + ); + }); + }); +} diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index d40e19e2526a9..3e2862bc58bce 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -221,6 +221,7 @@ export class CommonPageObject extends FtrService { { basePath = '', shouldLoginIfPrompted = true, + path = '', hash = '', search = '', disableWelcomePrompt = true, @@ -238,7 +239,7 @@ export class CommonPageObject extends FtrService { }); } else { appUrl = getUrl.noAuth(this.config.get('servers.kibana'), { - pathname: `${basePath}/app/${appName}`, + pathname: `${basePath}/app/${appName}` + (path ? `/${path}` : ''), hash, search, }); diff --git a/x-pack/plugins/maps/common/content_management/v1/cm_services.ts b/x-pack/plugins/maps/common/content_management/v1/cm_services.ts index 0e430b56ced98..5ea8008f53225 100644 --- a/x-pack/plugins/maps/common/content_management/v1/cm_services.ts +++ b/x-pack/plugins/maps/common/content_management/v1/cm_services.ts @@ -134,4 +134,11 @@ export const serviceDefinition: ServicesDefinition = { }, }, }, + mSearch: { + out: { + result: { + schema: mapSavedObjectSchema, + }, + }, + }, }; diff --git a/x-pack/plugins/maps/server/content_management/maps_storage.ts b/x-pack/plugins/maps/server/content_management/maps_storage.ts index 266ad81007345..5a031c675a32c 100644 --- a/x-pack/plugins/maps/server/content_management/maps_storage.ts +++ b/x-pack/plugins/maps/server/content_management/maps_storage.ts @@ -6,11 +6,16 @@ */ import Boom from '@hapi/boom'; import type { SearchQuery } from '@kbn/content-management-plugin/common'; -import type { ContentStorage, StorageContext } from '@kbn/content-management-plugin/server'; +import type { + ContentStorage, + StorageContext, + MSearchConfig, +} from '@kbn/content-management-plugin/server'; import type { SavedObject, SavedObjectReference, SavedObjectsFindOptions, + SavedObjectsFindResult, } from '@kbn/core-saved-objects-api-server'; import { CONTENT_ID } from '../../common/content_management'; @@ -86,7 +91,9 @@ function savedObjectToMapItem( const SO_TYPE: MapContentType = 'map'; -export class MapsStorage implements ContentStorage { +export class MapsStorage + implements ContentStorage> +{ constructor() {} async get(ctx: StorageContext, id: string): Promise { @@ -306,4 +313,29 @@ export class MapsStorage implements ContentStorage { return value; } + + // Configure `mSearch` to opt-in maps into the multi content type search API + mSearch = { + savedObjectType: SO_TYPE, + toItemResult: ( + ctx: StorageContext, + savedObject: SavedObjectsFindResult + ): MapItem => { + const { + utils: { getTransforms }, + } = ctx; + const transforms = getTransforms(cmServicesDefinition); + + // Validate DB response and DOWN transform to the request version + const { value, error: resultError } = transforms.mSearch.out.result.down( + savedObjectToMapItem(savedObject, false) + ); + + if (resultError) { + throw Boom.badRequest(`Invalid response. ${resultError.message}`); + } + + return value; + }, + }; } From a0aae1aa2325afc52a4df9e16ade49a73fab83dc Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Fri, 21 Apr 2023 07:51:28 -0700 Subject: [PATCH 61/67] [DOCS] Automate rule-flyout-rule-conditions.png (#155461) --- .../alerting/create-and-manage-rules.asciidoc | 1 + .../images/rule-flyout-rule-conditions.png | Bin 158056 -> 185264 bytes .../stack_alerting/list_view.ts | 18 ++++++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/docs/user/alerting/create-and-manage-rules.asciidoc b/docs/user/alerting/create-and-manage-rules.asciidoc index 7e35cd232566c..23bd7a9ae9fd6 100644 --- a/docs/user/alerting/create-and-manage-rules.asciidoc +++ b/docs/user/alerting/create-and-manage-rules.asciidoc @@ -49,6 +49,7 @@ Each rule type provides its own way of defining the conditions to detect, but an [role="screenshot"] image::images/rule-flyout-rule-conditions.png[UI for defining rule conditions on an index threshold rule,500] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. All rules must have a check interval, which defines how often to evaluate the rule conditions. Checks are queued; they run as close to the defined value as capacity allows. diff --git a/docs/user/alerting/images/rule-flyout-rule-conditions.png b/docs/user/alerting/images/rule-flyout-rule-conditions.png index ffdf870dfa001b8aa37ed2f963fc24514f4a9871..2810ac5e9a517af76c1f185e6d0abab9aed7be5e 100644 GIT binary patch literal 185264 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

~mShZ(4N2j@cTzy%sJ; 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�Lvb^lQ+`zMsidZi~yxyq$kFD*&EN0d?d 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 K=d#Wzp$PzME_B)e literal 158056 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

>y<7YuSNWB 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 %n3^!<+crA1)p7rPDKS z98BoLQO{3^e8ndUdbrE>a2w1AtVG{qJs)z-S9xjjS$8c8Q{7ZuyHUtvmXMDS!+8iN!TgR0+{z}l}l8}r3`;0~-@cgIHZ%T8o z9`%m;j-6blmsLnp^yf;%FvK719oQ{(z{=(38H-yQ)*2p{HtrSqim+Q}I+jH&eOX}$ z;fm4!l8P1#&k+31RPD>}NR0lzpiAq%Xq0Au&}rA*ybnx0EWyF`u6Aci2W2L_9+#z! zP6v6lx%=tmwE72rX&brgipZ#3W?Z)NP=_=t8JH2 zSB6L0mFN+D@A1Yj`Jq$hex=m|8h~=g?QY}#;r#QfTo%$49D>qcQNGS;sf=b8J|wz5 zINYE53D6@ao?|J>wrr@QpAvn}i9{u41Cjawc+0w1&*tzuDMy!a{{)yfSsJX3Pk*yB z=RXTrr!0RiHkf4$E*7>CP^bP!f4zK|ql7S>4)b~RYaRZVSDJXSSl>JYzWA5SI&B76 zpWG$0>;Fq60l)|9?#7qoh(E3W=n9loJ7zZ|`LAa9bpYRMjf$EU|AB!1To(!2!L-7C z{%Yo$HDGp69zg1D{O@(|@PT%SANLFW)y)5+`w~eUCGL1QUQ+2dj5Lw320-+5tkgj; zMVFZzhuvx#8zqQ;x`Km0v`=dd%qL_^8!nA+XNe1DW}c^aAiSyr`=ws)?fN~uZ+F`^ zT;z?5Ryv_j0`#Mkb!^pY%Q#BKY22jED4ELN{w?5wFIFNZmL-2E4g-m@JP;X_Y5 z8IxsPt{S8v3XluJeqZ7RuH$rJ>$0JiJT4S~%`uoc^!C?9xPL8}?^0_feP92HE)%-Yk(_&08Kd^_rim44#+j<+g=YVXiOBEDMN zLJz7aqzkH|2*JGVzY+Aq)R=M`^vH7s8R%so8i;6@ptU^w#$yk4VcLaQ{oQHqiC?%( zR7W=#oq463hN(N=q+jh0t@D&bc&UiSv0yszYk2_1?_cQK(_j7*;}4WN(6mlF4t5@U zKp&T2jXY;ojMb!n@5z7z!-#^4pDF|xPBl=7wPt3TQ}gWBqF?1F0 z_SaY53I1HMcXn-0;3?j6j2tfX5;NaT^K=T%OVetFiTjKoEFqB6fVee3FS|r0M z`-3MRDTVdPWccqO=#)M1he{dD^4QuHB_D;pboFGwdMncs8u6#E+H!gwbDK3&zEv_s zs9STrbqacNjL_0oDSfm0c6zDF@6F}{GeMcpIGO|JNvNMAo4>UlD|E;mRy3c!`gu}V z;6y&ZqNQcJ3dYa5DtPK;`KuE-7qJc$o$vr=YGi;YID8c+#$4Az~g9+0aBGP zXvb1-(Gv0gh}&~zS5muT_Myn|O493G+)vgEu08dM1KWpiGzLc1${Y`O*&0a}L}d5o zn{#QCL$a~6Wx=a2q*)7qnGn-$OXAK}6u}8m`irb&Kg-R8WG`$#Rq1nW&X(5c+dqW! zNkO;%3LH^PkEuUit=2Rk*;mQO)QH!}Fj{G}=cM)ONd9GfOe8qjPzojI;F@+m_qBcx z2g~(kMf6Arv*K?;WCZ>)K|3vg36g+_;QumxJbQr&3eZd8{Q2|$YC5qpK)kt=lwfu2 z_eZ~MGb`>#OVncLUnc7)FR-|sWb^#LdTR#c3N_)x+&|jWk8VDc*u80N`5ZRXeA0(~ P8+cj3kY;73p0WQ2C*N$d diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/list_view.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/list_view.ts index ac3be7d1814a5..dd4df322e5802 100644 --- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/list_view.ts +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/list_view.ts @@ -114,5 +114,23 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 1024 ); }); + + it('rule conditions screenshots', async () => { + await pageObjects.common.navigateToApp('triggersActions'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await testSubjects.setValue('ruleSearchField', ruleName); + const actionPanel = await testSubjects.find('collapsedItemActions'); + await actionPanel.click(); + const editRuleMenu = await testSubjects.find('editRule'); + await editRuleMenu.click(); + await testSubjects.scrollIntoView('intervalInput'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await commonScreenshots.takeScreenshot( + 'rule-flyout-rule-conditions', + screenshotDirectories, + 1400, + 1500 + ); + }); }); } From 3e81f8f09350f9a43fedac085b1194c4559a7439 Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Fri, 21 Apr 2023 17:02:14 +0200 Subject: [PATCH 62/67] [SecuritySolution] Fix flaky cypress timeline templates test (#155513) ## Summary The test was guessing the created template was on page `2` but, depending on the template data inserted, sometimes it is found on page `3`. This PR changes the test approach to search for the template name, so we don't rely on guessing the page. --- .../cypress/e2e/timeline_templates/export.cy.ts | 5 +++-- .../cypress/screens/table_pagination.ts | 2 ++ .../security_solution/cypress/tasks/table_pagination.ts | 9 +++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/timeline_templates/export.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/timeline_templates/export.cy.ts index b8fa0c1f5a80f..f015e08139cea 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/timeline_templates/export.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/timeline_templates/export.cy.ts @@ -15,7 +15,7 @@ import { import { TIMELINE_TEMPLATES_URL } from '../../urls/navigation'; import { createTimelineTemplate } from '../../tasks/api_calls/timelines'; import { cleanKibana } from '../../tasks/common'; -import { setRowsPerPageTo } from '../../tasks/table_pagination'; +import { searchByTitle } from '../../tasks/table_pagination'; describe('Export timelines', () => { before(() => { @@ -27,13 +27,14 @@ describe('Export timelines', () => { createTimelineTemplate(getTimelineTemplate()).then((response) => { cy.wrap(response).as('templateResponse'); cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('templateId'); + cy.wrap(response.body.data.persistTimeline.timeline.title).as('templateTitle'); }); login(); }); it('Exports a custom timeline template', function () { visitWithoutDateRange(TIMELINE_TEMPLATES_URL); - setRowsPerPageTo(20); + searchByTitle(this.templateTitle); exportTimeline(this.templateId); cy.wait('@export').then(({ response }) => { diff --git a/x-pack/plugins/security_solution/cypress/screens/table_pagination.ts b/x-pack/plugins/security_solution/cypress/screens/table_pagination.ts index e52194632954b..634cd6af9f0d1 100644 --- a/x-pack/plugins/security_solution/cypress/screens/table_pagination.ts +++ b/x-pack/plugins/security_solution/cypress/screens/table_pagination.ts @@ -19,3 +19,5 @@ export const TABLE_FIRST_PAGE = tablePageSelector(1); export const TABLE_SECOND_PAGE = tablePageSelector(2); export const TABLE_SORT_COLUMN_BTN = '[data-test-subj="tableHeaderSortButton"]'; + +export const TABLE_SEARCH_BAR = '[data-test-subj="search-bar"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/table_pagination.ts b/x-pack/plugins/security_solution/cypress/tasks/table_pagination.ts index ae62ca290e418..2e59383b39645 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/table_pagination.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/table_pagination.ts @@ -10,6 +10,7 @@ import { rowsPerPageSelector, tablePageSelector, TABLE_PER_PAGE_POPOVER_BTN, + TABLE_SEARCH_BAR, TABLE_SORT_COLUMN_BTN, } from '../screens/table_pagination'; @@ -33,6 +34,14 @@ export const setRowsPerPageTo = (rowsCount: number) => { .should('not.exist'); }; +export const searchByTitle = (title: string) => { + cy.get(LOADING_SPINNER).should('not.exist'); + cy.get(TABLE_PER_PAGE_POPOVER_BTN).should('exist'); + cy.get(TABLE_SEARCH_BAR).click({ force: true }); + // EuiSearchBox needs the "search" event to be triggered, {enter} doesn't work + cy.get(TABLE_SEARCH_BAR).type(`"${title}"`).trigger('search'); +}; + export const expectRowsPerPage = (rowsCount: number) => { cy.get(TABLE_PER_PAGE_POPOVER_BTN).contains(`Rows per page: ${rowsCount}`); }; From 0cd7973e1ff4693094e802b2cd3b6d711007bc5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=BCrkan=20YALAMAN?= Date: Fri, 21 Apr 2023 17:06:41 +0200 Subject: [PATCH 63/67] [Enterprise Search] Enterprise Search Cypress configuration (#155398) ## Summary Updated Test configurations to make Enterprise Search run with basic Cypress tests. In Enterprise Search root folder run `cypress.sh` or `cypress.sh open` to run tests via FTR servers running. You can run Cypress manually on a already running stack while developing with `cypress.sh dev`. ### Checklist Delete any items that are not applicable to this PR. - [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 --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .buildkite/ftr_configs.yml | 4 +- x-pack/plugins/enterprise_search/README.md | 35 +++----------- .../enterprise_search/cypress.config.ts | 38 +++++++++++++++ x-pack/plugins/enterprise_search/cypress.sh | 28 ++++++----- .../cypress/e2e/content/basic_crawler.cy.ts | 15 ++++++ .../enterprise_search/cypress/support/e2e.ts | 48 +++++++++++++++++++ .../enterprise_search/cypress/tasks/login.ts | 45 +++++++++++++++++ .../enterprise_search/cypress/tsconfig.json | 20 ++++++++ .../app_search/setup_guide.ts | 41 ---------------- .../without_host_configured/index.ts | 15 ------ .../workplace_search/setup_guide.ts | 41 ---------------- .../cli_config.ts | 34 +++++++++++++ .../cypress.config.ts | 7 ++- .../enterprise_search_server.ts | 6 +-- .../functional_enterprise_search/runner.ts | 8 +--- ..._configured.config.ts => visual_config.ts} | 2 +- .../without_host_configured.config.ts | 24 ---------- 17 files changed, 234 insertions(+), 177 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/cypress.config.ts mode change 100644 => 100755 x-pack/plugins/enterprise_search/cypress.sh create mode 100644 x-pack/plugins/enterprise_search/cypress/e2e/content/basic_crawler.cy.ts create mode 100644 x-pack/plugins/enterprise_search/cypress/support/e2e.ts create mode 100644 x-pack/plugins/enterprise_search/cypress/tasks/login.ts create mode 100644 x-pack/plugins/enterprise_search/cypress/tsconfig.json delete mode 100644 x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/app_search/setup_guide.ts delete mode 100644 x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/index.ts delete mode 100644 x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/workplace_search/setup_guide.ts create mode 100644 x-pack/test/functional_enterprise_search/cli_config.ts rename x-pack/test/functional_enterprise_search/{with_host_configured.config.ts => visual_config.ts} (95%) delete mode 100644 x-pack/test/functional_enterprise_search/without_host_configured.config.ts diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index ed9b81dba9667..1c466c001cb97 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -43,7 +43,8 @@ disabled: - x-pack/test/threat_intelligence_cypress/visual_config.ts - x-pack/test/threat_intelligence_cypress/cli_config_parallel.ts - x-pack/test/threat_intelligence_cypress/config.ts - - x-pack/test/functional_enterprise_search/with_host_configured.config.ts + - x-pack/test/functional_enterprise_search/visual_config.ts + - x-pack/test/functional_enterprise_search/cli_config.ts - x-pack/plugins/apm/ftr_e2e/ftr_config_open.ts - x-pack/plugins/apm/ftr_e2e/ftr_config_run.ts - x-pack/plugins/apm/ftr_e2e/ftr_config.ts @@ -224,7 +225,6 @@ enabled: - x-pack/test/functional_basic/apps/transform/feature_controls/config.ts - x-pack/test/functional_cors/config.ts - x-pack/test/functional_embedded/config.ts - - x-pack/test/functional_enterprise_search/without_host_configured.config.ts - x-pack/test/functional_execution_context/config.ts - x-pack/test/functional_with_es_ssl/apps/cases/group1/config.ts - x-pack/test/functional_with_es_ssl/apps/cases/group2/config.ts diff --git a/x-pack/plugins/enterprise_search/README.md b/x-pack/plugins/enterprise_search/README.md index 5694258001dd4..af0cdd43d97b8 100644 --- a/x-pack/plugins/enterprise_search/README.md +++ b/x-pack/plugins/enterprise_search/README.md @@ -89,29 +89,13 @@ Cypress tests can be run directly from the `x-pack/plugins/enterprise_search` fo ```bash # Basic syntax -sh cypress.sh {run|open} {suite} +sh cypress.sh {run|open|dev} # Examples -sh cypress.sh run overview # run Enterprise Search overview tests -sh cypress.sh open overview # open Enterprise Search overview tests +sh cypress.sh run # run Enterprise Search tests +sh cypress.sh open # open Enterprise Search tests +sh cypress.sh dev # run "cypress only" with Enterprise Search config -sh cypress.sh run as # run App Search tests -sh cypress.sh open as # open App Search tests - -sh cypress.sh run ws # run Workplace Search tests -sh cypress.sh open ws # open Workplace Search tests - -# Overriding env variables -sh cypress.sh open as --env username=enterprise_search password=123 - -# Overriding config settings, e.g. changing the base URL to a dev path, or enabling video recording -sh cypress.sh open as --config baseUrl=http://localhost:5601/xyz video=true - -# Only run a single specific test file -sh cypress.sh run ws --spec '**/example.spec.ts' - -# Opt to run Chrome headlessly -sh cypress.sh run ws --headless ``` There are 3 ways you can spin up the required environments to run our Cypress tests: @@ -126,13 +110,8 @@ There are 3 ways you can spin up the required environments to run our Cypress te - Enterprise Search: - Nothing extra is required to run Cypress tests, only what is already needed to run Kibana/Enterprise Search locally. 2. Running Cypress against Kibana's functional test server: - - :information_source: While we won't use the runner, we can still make use of Kibana's functional test server to help us spin up Elasticsearch and Kibana instances. - - NOTE: We recommend stopping any other local dev processes, to reduce issues with memory/performance - - From the `x-pack/` project folder, run `node scripts/functional_tests_server --config test/functional_enterprise_search/cypress.config.ts` - - Kibana: - - You will need to pass `--config baseUrl=http://localhost:5620` into your Cypress command. - - Enterprise Search: - - :warning: TODO: We _currently_ do not have a way of spinning up Enterprise Search from Kibana's FTR - for now, you can use local Enterprise Search (pointed at the FTR's `http://localhost:9220` Elasticsearch host instance) + - Make sure docker is up and running in you system + - From the `x-pack/` project folder, run `sh cypress.sh` which will spin up Kibana, Elasticsearch through functional test runners and Enterprise Search instance in Docker. 3. Running Cypress against Enterprise Search dockerized stack scripts - :warning: This is for Enterprise Search devs only, as this requires access to our closed source Enterprise Search repo - `stack_scripts/start-with-es-native-auth.sh --with-kibana` @@ -158,4 +137,4 @@ To track what Cypress is doing while running tests, you can pass in `--config vi See [our functional test runner README](../../test/functional_enterprise_search). -Our automated accessibility tests can be found in [x-pack/test/accessibility/apps](../../test/accessibility/apps/enterprise_search.ts). +Our automated accessibility tests can be found in [x-pack/test/accessibility/apps](../../test/accessibility/apps/enterprise_search.ts). \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/cypress.config.ts b/x-pack/plugins/enterprise_search/cypress.config.ts new file mode 100644 index 0000000000000..a08f3de4bf69b --- /dev/null +++ b/x-pack/plugins/enterprise_search/cypress.config.ts @@ -0,0 +1,38 @@ +/* + * 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 { defineCypressConfig } from '@kbn/cypress-config'; + +export default defineCypressConfig({ + defaultCommandTimeout: 60000, + execTimeout: 120000, + pageLoadTimeout: 120000, + requestTimeout: 60000, + responseTimeout: 60000, + retries: { + runMode: 2, + }, + screenshotOnRunFailure: false, + screenshotsFolder: '../../../target/kibana-fleet/cypress/screenshots', + trashAssetsBeforeRuns: false, + video: false, + videosFolder: '../../../target/kibana-fleet/cypress/videos', + viewportHeight: 900, + viewportWidth: 1440, + + // eslint-disable-next-line sort-keys + env: { + configport: '5601', + hostname: 'localhost', + protocol: 'http', + }, + // eslint-disable-next-line sort-keys + e2e: { + baseUrl: 'http://localhost:5601', + supportFile: './cypress/support/e2e.ts', + }, +}); diff --git a/x-pack/plugins/enterprise_search/cypress.sh b/x-pack/plugins/enterprise_search/cypress.sh old mode 100644 new mode 100755 index 9dbdd81ab788f..05f3ed07140fd --- a/x-pack/plugins/enterprise_search/cypress.sh +++ b/x-pack/plugins/enterprise_search/cypress.sh @@ -1,18 +1,22 @@ #! /bin/bash # Use either `cypress run` or `cypress open` - defaults to run -MODE="${1:-run}" +MODE="${1}" -# Choose which product folder to use, e.g. `yarn cypress open as` -PRODUCT="${2}" -# Provide helpful shorthands -if [ "$PRODUCT" == "as" ]; then PRODUCT='app_search'; fi -if [ "$PRODUCT" == "ws" ]; then PRODUCT='workplace_search'; fi -if [ "$PRODUCT" == "overview" ]; then PRODUCT='enterprise_search'; fi +if [ "$MODE" == "dev" ]; then + echo "Running dev mode. This will run cypress only" + node ../../../node_modules/.bin/cypress open --config-file ./cypress.config.ts ${2} +else + if ! docker info > /dev/null 2>&1; then + echo "This script needs docker to run. Start docker and try again." + echo "If you are testing against your own setup use ./cypress.sh dev" + exit 1 + fi -# Pass all remaining arguments (e.g., ...rest) from the 3rd arg onwards -# as an open-ended string. Appends onto to the end the Cypress command -# @see https://docs.cypress.io/guides/guides/command-line.html#Options -ARGS="${*:3}" + if [ "$MODE" == "open" ]; then + node ../../../scripts/functional_tests --config ../../test/functional_enterprise_search/visual_config.ts + else + node ../../../scripts/functional_tests --config ../../test/functional_enterprise_search/cli_config.ts + fi +fi -../../../node_modules/.bin/cypress "$MODE" --project "public/applications/$PRODUCT" --browser chrome $ARGS diff --git a/x-pack/plugins/enterprise_search/cypress/e2e/content/basic_crawler.cy.ts b/x-pack/plugins/enterprise_search/cypress/e2e/content/basic_crawler.cy.ts new file mode 100644 index 0000000000000..484aa6976072d --- /dev/null +++ b/x-pack/plugins/enterprise_search/cypress/e2e/content/basic_crawler.cy.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 { login } from '../../tasks/login'; + +describe('Enterprise Search Crawler', () => { + it('test', () => { + login(); + cy.visit('/app/enterprise_search/content/search_indices/new_index'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/cypress/support/e2e.ts b/x-pack/plugins/enterprise_search/cypress/support/e2e.ts new file mode 100644 index 0000000000000..1e2b130d59973 --- /dev/null +++ b/x-pack/plugins/enterprise_search/cypress/support/e2e.ts @@ -0,0 +1,48 @@ +/* + * 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. + */ + +// / + +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Enforce building this file. +export {}; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + getBySel(value: string, ...args: any[]): Chainable; + getKibanaVersion(): Chainable; + } + } +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function getBySel(selector: string, ...args: any[]) { + return cy.get(`[data-test-subj="${selector}"]`, ...args); +} + +Cypress.Commands.add('getBySel', getBySel); + +Cypress.on('uncaught:exception', () => { + return false; +}); diff --git a/x-pack/plugins/enterprise_search/cypress/tasks/login.ts b/x-pack/plugins/enterprise_search/cypress/tasks/login.ts new file mode 100644 index 0000000000000..bf4ad9b836def --- /dev/null +++ b/x-pack/plugins/enterprise_search/cypress/tasks/login.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. + */ +const ELASTICSEARCH_USERNAME = 'elastic'; +const ELASTICSEARCH_PASSWORD = 'changeme'; +const LOGIN_API_ENDPOINT = '/internal/security/login'; + +export const constructUrlWithUser = (route: string) => { + const url = Cypress.config().baseUrl; + const kibana = new URL(String(url)); + const hostname = kibana.hostname; + const username = ELASTICSEARCH_USERNAME; + const password = ELASTICSEARCH_PASSWORD; + const protocol = kibana.protocol.replace(':', ''); + const port = kibana.port; + + const path = `${route.startsWith('/') ? '' : '/'}${route}`; + const strUrl = `${protocol}://${username}:${password}@${hostname}:${port}${path}`; + const builtUrl = new URL(strUrl); + + cy.log(`origin: ${builtUrl.href}`); + return builtUrl.href; +}; + +export const login = () => { + cy.session('user', () => { + cy.request({ + body: { + currentURL: '/', + params: { + password: ELASTICSEARCH_PASSWORD, + username: ELASTICSEARCH_USERNAME, + }, + providerName: 'basic', + providerType: 'basic', + }, + headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, + method: 'POST', + url: constructUrlWithUser(LOGIN_API_ENDPOINT), + }); + }); +}; diff --git a/x-pack/plugins/enterprise_search/cypress/tsconfig.json b/x-pack/plugins/enterprise_search/cypress/tsconfig.json new file mode 100644 index 0000000000000..06640dca03363 --- /dev/null +++ b/x-pack/plugins/enterprise_search/cypress/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../../../tsconfig.base.json", + "include": ["**/*", "../cypress.config.ts"], + "exclude": ["target/**/*"], + "compilerOptions": { + "outDir": "target/types", + "types": ["cypress", "node", "cypress-react-selector"], + "resolveJsonModule": true + }, + "kbn_references": [ + "@kbn/cypress-config", + // cypress projects that are nested inside of other ts project use code + // from the parent ts project in ways that can't be automatically deteceted + // at this time so we have to force the inclusion of this reference + { + "path": "../tsconfig.json", + "force": true + } + ] +} diff --git a/x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/app_search/setup_guide.ts b/x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/app_search/setup_guide.ts deleted file mode 100644 index 2995150d73d07..0000000000000 --- a/x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/app_search/setup_guide.ts +++ /dev/null @@ -1,41 +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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../ftr_provider_context'; - -export default function enterpriseSearchSetupGuideTests({ - getService, - getPageObjects, -}: FtrProviderContext) { - const browser = getService('browser'); - const retry = getService('retry'); - const kibanaServer = getService('kibanaServer'); - const PageObjects = getPageObjects(['appSearch']); - - describe('Setup Guide', function () { - before(async () => { - await kibanaServer.savedObjects.cleanStandardList(); - }); - after(async () => { - await kibanaServer.savedObjects.cleanStandardList(); - }); - - describe('when no enterpriseSearch.host is configured', () => { - it('navigating to the plugin will redirect a user to the setup guide', async () => { - await PageObjects.appSearch.navigateToPage(); - await retry.try(async function () { - const currentUrl = await browser.getCurrentUrl(); - expect(currentUrl).to.contain('/app_search/setup_guide'); - - const documentTitle = await browser.getTitle(); - expect(documentTitle).to.contain('Setup Guide - App Search - Elastic'); - }); - }); - }); - }); -} diff --git a/x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/index.ts b/x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/index.ts deleted file mode 100644 index dda2e20745394..0000000000000 --- a/x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/index.ts +++ /dev/null @@ -1,15 +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 { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ loadTestFile }: FtrProviderContext) { - describe('Enterprise Search', function () { - loadTestFile(require.resolve('./app_search/setup_guide')); - loadTestFile(require.resolve('./workplace_search/setup_guide')); - }); -} diff --git a/x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/workplace_search/setup_guide.ts b/x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/workplace_search/setup_guide.ts deleted file mode 100644 index 0be3156d29c02..0000000000000 --- a/x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/workplace_search/setup_guide.ts +++ /dev/null @@ -1,41 +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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../ftr_provider_context'; - -export default function enterpriseSearchSetupGuideTests({ - getService, - getPageObjects, -}: FtrProviderContext) { - const browser = getService('browser'); - const retry = getService('retry'); - const kibanaServer = getService('kibanaServer'); - const PageObjects = getPageObjects(['workplaceSearch']); - - describe('Setup Guide', function () { - before(async () => { - await kibanaServer.savedObjects.cleanStandardList(); - }); - after(async () => { - await kibanaServer.savedObjects.cleanStandardList(); - }); - - describe('when no enterpriseSearch.host is configured', () => { - it('navigating to the plugin will redirect a user to the setup guide', async () => { - await PageObjects.workplaceSearch.navigateToPage(); - await retry.try(async function () { - const currentUrl = await browser.getCurrentUrl(); - expect(currentUrl).to.contain('/workplace_search/setup_guide'); - - const documentTitle = await browser.getTitle(); - expect(documentTitle).to.contain('Setup Guide - Workplace Search - Elastic'); - }); - }); - }); - }); -} diff --git a/x-pack/test/functional_enterprise_search/cli_config.ts b/x-pack/test/functional_enterprise_search/cli_config.ts new file mode 100644 index 0000000000000..ec7e75f01c288 --- /dev/null +++ b/x-pack/test/functional_enterprise_search/cli_config.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; +import { EnterpriseSearchCypressCliTestRunner } from './runner'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const kibanaCommonTestsConfig = await readConfigFile( + require.resolve('../../../test/common/config.js') + ); + const baseConfig = await readConfigFile(require.resolve('./cypress.config')); + + return { + ...kibanaCommonTestsConfig.getAll(), + // default to the xpack functional config + ...baseConfig.getAll(), + + junit: { + reportName: 'X-Pack Enterprise Search Functional Tests with Host Configured', + }, + kbnTestServer: { + ...baseConfig.get('kbnTestServer'), + serverArgs: [ + ...baseConfig.get('kbnTestServer.serverArgs'), + '--enterpriseSearch.host=http://localhost:3022', + ], + }, + testRunner: EnterpriseSearchCypressCliTestRunner, + }; +} diff --git a/x-pack/test/functional_enterprise_search/cypress.config.ts b/x-pack/test/functional_enterprise_search/cypress.config.ts index 9a6918ab0557d..099b92c429299 100644 --- a/x-pack/test/functional_enterprise_search/cypress.config.ts +++ b/x-pack/test/functional_enterprise_search/cypress.config.ts @@ -7,9 +7,6 @@ import { FtrConfigProviderContext } from '@kbn/test'; -// TODO: If Kibana CI doesn't end up using this (e.g., uses Dockerized containers -// instead of the functional test server), we can opt to delete this file later. - export default async function ({ readConfigFile }: FtrConfigProviderContext) { const baseConfig = await readConfigFile(require.resolve('./base_config')); @@ -32,7 +29,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...baseConfig.get('kbnTestServer.serverArgs'), '--csp.strict=false', '--csp.warnLegacyBrowsers=false', - '--enterpriseSearch.host=http://localhost:3002', + '--enterpriseSearch.host=http://localhost:3022', + '--usageCollection.uiCounters.enabled=false', + `--home.disableWelcomeScreen=true`, ], }, }; diff --git a/x-pack/test/functional_enterprise_search/enterprise_search_server.ts b/x-pack/test/functional_enterprise_search/enterprise_search_server.ts index ae588afc7de13..2f9eacff934d8 100644 --- a/x-pack/test/functional_enterprise_search/enterprise_search_server.ts +++ b/x-pack/test/functional_enterprise_search/enterprise_search_server.ts @@ -52,7 +52,7 @@ export async function setupEnterpriseSearch(logger: ToolingLog): Promise { `--name=enterprise-search-ftr`, `--rm`, `-p`, - `3002:3002`, + `3022:3022`, `-e`, `elasticsearch.host='http://host.docker.internal:9220'`, `-e`, @@ -66,9 +66,9 @@ export async function setupEnterpriseSearch(logger: ToolingLog): Promise { `-e`, `ENT_SEARCH_DEFAULT_PASSWORD=changeme`, `-e`, - `ent_search.listen_port=3002`, + `ent_search.listen_port=3022`, `-e`, - `ent_search.external_url='http://localhost:3002'`, + `ent_search.external_url='http://localhost:3022'`, `docker.elastic.co/enterprise-search/enterprise-search:${await getLatestVersion()}`, ]; diff --git a/x-pack/test/functional_enterprise_search/runner.ts b/x-pack/test/functional_enterprise_search/runner.ts index 8dfee33c5b2e7..b8ab1df8d7108 100644 --- a/x-pack/test/functional_enterprise_search/runner.ts +++ b/x-pack/test/functional_enterprise_search/runner.ts @@ -35,12 +35,8 @@ export async function runEnterpriseSearchTests( await withEnterpriseSearch(context, (runnerEnv) => withProcRunner(log, async (procs) => { await procs.run('cypress', { - cmd: 'sh', - args: [ - `${resolve(__dirname, '../../plugins/enterprise_search/cypress.sh')}`, - `${cypressCommand}`, - 'as', - ], + cmd: '../../../node_modules/.bin/cypress', + args: [cypressCommand, '--config-file', './cypress.config.ts', '--browser', 'chrome'], cwd: resolve(__dirname, '../../plugins/enterprise_search'), env: { FORCE_COLOR: '1', diff --git a/x-pack/test/functional_enterprise_search/with_host_configured.config.ts b/x-pack/test/functional_enterprise_search/visual_config.ts similarity index 95% rename from x-pack/test/functional_enterprise_search/with_host_configured.config.ts rename to x-pack/test/functional_enterprise_search/visual_config.ts index a1584b45da092..300d1b23b4191 100644 --- a/x-pack/test/functional_enterprise_search/with_host_configured.config.ts +++ b/x-pack/test/functional_enterprise_search/visual_config.ts @@ -26,7 +26,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...baseConfig.get('kbnTestServer'), serverArgs: [ ...baseConfig.get('kbnTestServer.serverArgs'), - '--enterpriseSearch.host=http://localhost:3002', + '--enterpriseSearch.host=http://localhost:3022', ], }, testRunner: EnterpriseSearchCypressVisualTestRunner, diff --git a/x-pack/test/functional_enterprise_search/without_host_configured.config.ts b/x-pack/test/functional_enterprise_search/without_host_configured.config.ts deleted file mode 100644 index f5af2bddd8531..0000000000000 --- a/x-pack/test/functional_enterprise_search/without_host_configured.config.ts +++ /dev/null @@ -1,24 +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 { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; - -export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const baseConfig = await readConfigFile(require.resolve('./base_config')); - - return { - // default to the xpack functional config - ...baseConfig.getAll(), - - testFiles: [resolve(__dirname, './apps/enterprise_search/without_host_configured')], - - junit: { - reportName: 'X-Pack Enterprise Search Functional Tests without Host Configured', - }, - }; -} From dcca5a77eef2e9b992825c50687b3abc0b34b15f Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 21 Apr 2023 17:20:12 +0200 Subject: [PATCH 64/67] [ML] Migrate SCSS to emotion for data grid (#155349) Migrate SCSS to emotion for data grid mini histograms. --- .../components/data_grid/column_chart.scss | 27 ------------- .../components/data_grid/column_chart.tsx | 38 +++++++++++++++---- .../components/data_grid/data_grid.scss | 18 --------- .../components/data_grid/data_grid.tsx | 22 +++++++++-- .../components/data_grid/use_column_chart.tsx | 27 ++++++++++--- 5 files changed, 70 insertions(+), 62 deletions(-) delete mode 100644 x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss delete mode 100644 x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss deleted file mode 100644 index 551734bc2fcdc..0000000000000 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss +++ /dev/null @@ -1,27 +0,0 @@ -.mlDataGridChart__histogram { - width: 100%; - height: $euiSizeXL + $euiSizeXXL; -} - -.mlDataGridChart__legend { - @include euiTextTruncate; - @include euiFontSizeXS; - - color: $euiColorMediumShade; - display: block; - overflow-x: hidden; - margin: $euiSizeXS 0 0 0; - font-style: italic; - font-weight: normal; - text-align: left; -} - -.mlDataGridChart__legend--numeric { - text-align: right; -} - -.mlDataGridChart__legendBoolean { - width: 100%; - min-width: $euiButtonMinWidth; - td { text-align: center } -} diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx index 06bb5a363d36a..f28d357f14869 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -5,18 +5,41 @@ * 2.0. */ -import React, { FC } from 'react'; -import classNames from 'classnames'; +import React, { type FC } from 'react'; +import { css } from '@emotion/react'; import { BarSeries, Chart, Settings, ScaleType } from '@elastic/charts'; -import { EuiDataGridColumn } from '@elastic/eui'; +import { euiTextTruncate, type EuiDataGridColumn } from '@elastic/eui'; -import './column_chart.scss'; +import { euiThemeVars } from '@kbn/ui-theme'; import { isUnsupportedChartData, ChartData } from '../../../../common/types/field_histograms'; import { useColumnChart } from './use_column_chart'; +const cssHistogram = css({ + width: '100%', + height: `calc(${euiThemeVars.euiSizeXL} + ${euiThemeVars.euiSizeXXL})`, +}); + +const cssHistogramLegend = css([ + css` + ${euiTextTruncate()} + `, + { + color: euiThemeVars.euiColorMediumShade, + display: 'block', + overflowX: 'hidden', + margin: `${euiThemeVars.euiSizeXS} 0 0 0`, + fontSize: euiThemeVars.euiFontSizeXS, + fontStyle: 'italic', + fontWeight: 'normal', + textAlign: 'left', + }, +]); + +const cssHistogramLegendNumeric = css([cssHistogramLegend, { textAlign: 'right' }]); + interface Props { chartData: ChartData; columnType: EuiDataGridColumn; @@ -41,6 +64,7 @@ const columnChartTheme = { }, scales: { barsPadding: 0.1 }, }; + export const ColumnChart: FC = ({ chartData, columnType, @@ -53,7 +77,7 @@ export const ColumnChart: FC = ({ return (

{!isUnsupportedChartData(chartData) && data.length > 0 && ( -
+
= ({
)}
{legendText} diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss deleted file mode 100644 index f9cc09ef8c425..0000000000000 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss +++ /dev/null @@ -1,18 +0,0 @@ -.mlDataGrid { - - .euiDataGridRowCell--boolean { - text-transform: none; - } - - // Overrides to align the sorting arrow, actions icon and the column header when no chart is available, - // to the bottom of the cell when histogram charts are enabled. - // Note that overrides have to be used as currently it is not possible to add a custom class name - // for the EuiDataGridHeaderCell - see https://github.com/elastic/eui/issues/5106 - .euiDataGridHeaderCell { - .euiDataGridHeaderCell__sortingArrow, - .euiDataGridHeaderCell__icon, - .euiPopover { - margin-top: auto; - } - } -} diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index e641410500611..8a290ee186a98 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -7,8 +7,7 @@ import { isEqual } from 'lodash'; import React, { memo, useEffect, useCallback, useRef, FC } from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; import { EuiButtonEmpty, @@ -27,8 +26,11 @@ import { EuiToolTip, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { CoreSetup } from '@kbn/core/public'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; + import { DEFAULT_SAMPLER_SHARD_SIZE } from '../../../../common/constants/field_histograms'; import { ANALYSIS_CONFIG_TYPE, INDEX_STATUS } from '../../data_frame_analytics/common'; @@ -49,10 +51,22 @@ import { import { DEFAULT_RESULTS_FIELD } from '../../../../common/constants/data_frame_analytics'; import { DataFrameAnalysisConfigType } from '../../../../common/types/data_frame_analytics'; -import './data_grid.scss'; // TODO Fix row hovering + bar highlighting // import { hoveredRow$ } from './column_chart'; +const cssOverride = css({ + '.euiDataGridRowCell--boolean': { textTransform: 'none' }, + // Overrides to align the sorting arrow, actions icon and the column header when no chart is available, + // to the bottom of the cell when histogram charts are enabled. + // Note that overrides have to be used as currently it is not possible to add a custom class name + // for the EuiDataGridHeaderCell - see https://github.com/elastic/eui/issues/5106 + '.euiDataGridHeaderCell': { + '.euiDataGridHeaderCell__sortingArrow,.euiDataGridHeaderCell__icon,.euiPopover': { + marginTop: 'auto', + }, + }, +}); + export const DataGridTitle: FC<{ title: string }> = ({ title }) => ( {title} @@ -337,7 +351,7 @@ export const DataGrid: FC = memo( onMutation={onMutation} > {(mutationRef) => ( -
+
{ diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx index f4c372d379357..e60c2f5b8f3ba 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx @@ -8,13 +8,14 @@ import moment from 'moment'; import { BehaviorSubject } from 'rxjs'; import React from 'react'; +import { css } from '@emotion/react'; import useObservable from 'react-use/lib/useObservable'; -import { euiPaletteColorBlind, EuiDataGridColumn } from '@elastic/eui'; +import { euiPaletteColorBlind, type EuiDataGridColumn } from '@elastic/eui'; +import { euiThemeVars } from '@kbn/ui-theme'; import { i18n } from '@kbn/i18n'; - import { KBN_FIELD_TYPES } from '@kbn/field-types'; import { @@ -28,6 +29,18 @@ import { import { NON_AGGREGATABLE } from './common'; +const cssHistogramLegendBoolean = css({ + width: '100%', + // This was originally $euiButtonMinWidth, but that + // is no longer exported from the EUI package, + // so we're replicating it here inline. + minWidth: `calc(${euiThemeVars.euiSize} * 7)`, +}); + +const cssTextAlignCenter = css({ + textAlign: 'center', +}); + export const hoveredRow$ = new BehaviorSubject(null); export const BAR_COLOR = euiPaletteColorBlind()[0]; @@ -94,11 +107,15 @@ export const getLegendText = ( if (chartData.type === 'boolean') { return ( - +
- {chartData.data[0] !== undefined && } - {chartData.data[1] !== undefined && } + {chartData.data[0] !== undefined && ( + + )} + {chartData.data[1] !== undefined && ( + + )}
{chartData.data[0].key_as_string}{chartData.data[1].key_as_string}{chartData.data[0].key_as_string}{chartData.data[1].key_as_string}
From 0d37f023f697c0e9d5c29d236b481e7803426f34 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Fri, 21 Apr 2023 17:22:24 +0200 Subject: [PATCH 65/67] [Lens] Apply default label on field change for counter rate (#155509) ## Summary Fix #154449 ![counter_rate_field_change](https://user-images.githubusercontent.com/924948/233646312-9d7b32af-20ed-426b-970f-6a58697deb2a.gif) ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [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 - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### 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) --- .../operations/layer_helpers.test.ts | 43 +++++++++++++++++++ .../form_based/operations/layer_helpers.ts | 16 ++++--- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.test.ts index 0490f3c479297..0bdf9aa0bc795 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.test.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.test.ts @@ -29,10 +29,12 @@ import { getFieldByNameFactory } from '../pure_helpers'; import { generateId } from '../../../id_generator'; import { createMockedFullReference, createMockedManagedReference } from './mocks'; import { + CounterRateIndexPatternColumn, FiltersIndexPatternColumn, FormulaIndexPatternColumn, GenericIndexPatternColumn, MathIndexPatternColumn, + MaxIndexPatternColumn, MovingAverageIndexPatternColumn, OperationDefinition, } from './definitions'; @@ -1356,6 +1358,47 @@ describe('state_helpers', () => { }).columns.col1 ).toEqual(expect.objectContaining({ label: 'Average of bytes' })); }); + + it('should update default label when referenced column gets a field change', () => { + expect( + replaceColumn({ + layer: { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'MyDefaultLabel', + dataType: 'number', + operationType: 'counter_rate', + isBucketed: false, + scale: 'ratio', + references: ['col2'], + timeScale: 's', + timeShift: '', + filter: undefined, + params: undefined, + } as CounterRateIndexPatternColumn, + col2: { + label: 'Max of bytes', + dataType: 'number', + operationType: 'max', + scale: 'ratio', + sourceField: indexPattern.fields[2].displayName, + } as MaxIndexPatternColumn, + }, + }, + indexPattern, + columnId: 'col2', + op: 'max', + field: indexPattern.fields[3], + visualizationGroups: [], + }).columns.col1 + ).toEqual( + expect.objectContaining({ + label: 'Counter rate of memory per second', + }) + ); + }); }); it('should execute adjustments for other columns', () => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.ts index efa523a5bd5da..e6ac80c439083 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.ts @@ -836,12 +836,16 @@ export function replaceColumn({ { ...layer, columns: { ...layer.columns, [columnId]: newColumn } }, columnId ); - return adjustColumnReferencesForChangedColumn( - { - ...newLayer, - columnOrder: getColumnOrder(newLayer), - }, - columnId + + return updateDefaultLabels( + adjustColumnReferencesForChangedColumn( + { + ...newLayer, + columnOrder: getColumnOrder(newLayer), + }, + columnId + ), + indexPattern ); } else if (operationDefinition.input === 'managedReference') { // Just changing a param in a formula column should trigger From 17c6b6ce338b1d2fbaf07bdcd48e8d58af80e0a9 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Fri, 21 Apr 2023 09:27:38 -0600 Subject: [PATCH 66/67] Properly handle NO DATA with multiple conditions with a mix of aggregations and document count thresholds (#154690) This PR fixes #154553, which arises when a rule containing aggregation-based conditions is combined with document count conditions. In such a case, the aggregation-based conditions may return "NO DATA". Unlike document count-based conditions, which do not typically generate a "NO DATA" response since this is equivalent to zero and only triggers if the user defines the condition as `Document count < 0`; aggregation-based conditions produce a `null` result that is interpreted as "NO DATA". This PR also improves the "NO DATA" reason message when a group-by field is not defined. The reason message before the change looked like `test.metric.3 reported no data in the last 1m for ` which I changed to `test.metric.3 reported no data in the last 1m` to be more readable. This also fixes the context variables for document count-based conditions by setting the `metric` context variable to `count`. --- .../server/lib/alerting/common/messages.ts | 2 +- .../metric_threshold_executor.test.ts | 70 ++++++++++++++++++- .../metric_threshold_executor.ts | 40 ++++++++--- 3 files changed, 98 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/infra/server/lib/alerting/common/messages.ts b/x-pack/plugins/infra/server/lib/alerting/common/messages.ts index 9a7c9d2764486..6bc48083d252b 100644 --- a/x-pack/plugins/infra/server/lib/alerting/common/messages.ts +++ b/x-pack/plugins/infra/server/lib/alerting/common/messages.ts @@ -138,7 +138,7 @@ export const buildNoDataAlertReason: (alertResult: { timeUnit: string; }) => string = ({ group, metric, timeSize, timeUnit }) => i18n.translate('xpack.infra.metrics.alerting.threshold.noDataAlertReason', { - defaultMessage: '{metric} reported no data in the last {interval} for {group}', + defaultMessage: '{metric} reported no data in the last {interval}{group}', values: { metric, interval: `${timeSize}${timeUnit}`, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index 9298b7583ef3d..eca33d020130d 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -70,9 +70,11 @@ const logger = { get: () => logger, } as unknown as Logger; +const STARTED_AT_MOCK_DATE = new Date(); + const mockOptions = { executionId: '', - startedAt: new Date(), + startedAt: STARTED_AT_MOCK_DATE, previousStartedAt: null, state: { wrapped: initialRuleState, @@ -1326,7 +1328,9 @@ describe('The metric threshold alert type', () => { }, ]); await execute(true); - expect(mostRecentAction(instanceID)).toBeNoDataAction(); + const recentAction = mostRecentAction(instanceID); + expect(recentAction.action.reason).toEqual('test.metric.3 reported no data in the last 1m'); + expect(recentAction).toBeNoDataAction(); }); test('does not send a No Data alert when not configured to do so', async () => { setEvaluationResults([ @@ -1350,6 +1354,68 @@ describe('The metric threshold alert type', () => { }); }); + describe('alerts with NO_DATA where one condtion is an aggregation and the other is a document count', () => { + afterAll(() => clearInstances()); + const instanceID = '*'; + const execute = (alertOnNoData: boolean, sourceId: string = 'default') => + executor({ + ...mockOptions, + services, + params: { + sourceId, + criteria: [ + { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [1], + metric: 'test.metric.3', + }, + { + ...baseCountCriterion, + comparator: Comparator.GT, + threshold: [30], + }, + ], + alertOnNoData, + }, + }); + test('sends a No Data alert when configured to do so', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.LT, + threshold: [1], + metric: 'test.metric.3', + currentValue: null, + timestamp: STARTED_AT_MOCK_DATE.toISOString(), + shouldFire: false, + shouldWarn: false, + isNoData: true, + bucketKey: { groupBy0: '*' }, + }, + }, + {}, + ]); + await execute(true); + const recentAction = mostRecentAction(instanceID); + expect(recentAction.action).toEqual({ + alertDetailsUrl: 'http://localhost:5601/app/observability/alerts/mock-alert-uuid', + alertState: 'NO DATA', + group: '*', + groupByKeys: undefined, + metric: { condition0: 'test.metric.3', condition1: 'count' }, + reason: 'test.metric.3 reported no data in the last 1m', + threshold: { condition0: ['1'], condition1: [30] }, + timestamp: STARTED_AT_MOCK_DATE.toISOString(), + value: { condition0: '[NO DATA]', condition1: 0 }, + viewInAppUrl: 'http://localhost:5601/app/metrics/explorer', + tags: [], + }); + expect(recentAction).toBeNoDataAction(); + }); + }); + describe('querying a groupBy alert that starts reporting no data, and then later reports data', () => { afterAll(() => clearInstances()); const instanceID = '*'; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index 7037e6398fc24..46d2766777d21 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -269,7 +269,7 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => // check to see if a No Data state has occurred if (nextState === AlertStates.NO_DATA) { reason = alertResults - .filter((result) => result[group].isNoData) + .filter((result) => result[group]?.isNoData) .map((result) => buildNoDataAlertReason({ ...result[group], group })) .join('\n'); } @@ -316,17 +316,30 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => alertState: stateToAlertMessage[nextState], group, groupByKeys: groupByKeysObjectMapping[group], - metric: mapToConditionsLookup(criteria, (c) => c.metric), + metric: mapToConditionsLookup(criteria, (c) => { + if (c.aggType === 'count') { + return 'count'; + } + return c.metric; + }), reason, - threshold: mapToConditionsLookup( - alertResults, - (result) => formatAlertResult(result[group]).threshold - ), + threshold: mapToConditionsLookup(alertResults, (result, index) => { + const evaluation = result[group]; + if (!evaluation) { + return criteria[index].threshold; + } + return formatAlertResult(evaluation).threshold; + }), timestamp, - value: mapToConditionsLookup( - alertResults, - (result) => formatAlertResult(result[group]).currentValue - ), + value: mapToConditionsLookup(alertResults, (result, index) => { + const evaluation = result[group]; + if (!evaluation && criteria[index].aggType === 'count') { + return 0; + } else if (!evaluation) { + return null; + } + return formatAlertResult(evaluation).currentValue; + }), viewInAppUrl: getViewInMetricsAppUrl(libs.basePath, spaceId), ...additionalContext, }); @@ -354,7 +367,12 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => alertState: stateToAlertMessage[AlertStates.OK], group: recoveredAlertId, groupByKeys: groupByKeysObjectForRecovered[recoveredAlertId], - metric: mapToConditionsLookup(criteria, (c) => c.metric), + metric: mapToConditionsLookup(criteria, (c) => { + if (criteria.aggType === 'count') { + return 'count'; + } + return c.metric; + }), timestamp: startedAt.toISOString(), threshold: mapToConditionsLookup(criteria, (c) => c.threshold), viewInAppUrl: getViewInMetricsAppUrl(libs.basePath, spaceId), From 29ffa177c1731e8a673e52e961e5ad5edbbab305 Mon Sep 17 00:00:00 2001 From: Hannah Mudge Date: Fri, 21 Apr 2023 09:30:13 -0600 Subject: [PATCH 67/67] [Dashboard] Add reset button (#154872) Closes https://github.com/elastic/kibana/issues/132457 ## Summary This PR adds a reset button to the dashboard top navigation in both edit and view mode - when clicked, if the dashboard has unsaved changes, it will revert the dashboard to the last saved state: https://user-images.githubusercontent.com/8698078/232918433-97bac4b0-7472-49e9-9eb3-2cb7c9e6edf6.mov > **Note** > The above video contains some old copy for the modals. Please refer to "All copy changes" below to see the updated copy. Note that, by adding more buttons to the top nav bar, we are increasing the risk of someone hitting [this accessibility issue](https://github.com/elastic/kibana/issues/154414) (where the breadcrumbs get completely overlapped before the browser is small enough for the navigation to collapse) - this will either be addressed on the Shared UX side or will be addressed as part of our [nav bar redesign](https://github.com/elastic/kibana/issues/154945) :+1: ### All Copy Changes - **Listing Page** | Before | After | |--------|-------| | ![Screenshot 2023-04-18 at 4 29 10 PM](https://user-images.githubusercontent.com/8698078/232919138-7be86e97-ebb4-48a9-b8b1-0b970131aa37.png) | ![image](https://user-images.githubusercontent.com/8698078/232919166-b7bc7b55-5074-485d-ad0a-2fb695367538.png) | | ![image](https://user-images.githubusercontent.com/8698078/232920038-6c8c463e-0c7d-49e7-99c8-86b2ae611844.png) | ![image](https://user-images.githubusercontent.com/8698078/233412233-a785d99d-9d07-4ee5-a121-646bbd839f7c.png) | - **Dashboard - _Edit Mode_** | Before | After | |--------|-------| | ![image](https://user-images.githubusercontent.com/8698078/232920992-2d1b61f4-dff2-4bdd-854b-9cd6fcae07ce.png) | ![image](https://user-images.githubusercontent.com/8698078/233412732-254a9503-5526-44bc-a2e0-067f8800ad26.png) | - **Dashboard - _View Mode_** | Before | After | |--------|-------| | N/A since you couldn't discard changes from view mode previously | ![image](https://user-images.githubusercontent.com/8698078/233413029-9a6b4256-3002-48c5-8620-7596d8f08153.png) | ### Flaky Test Runner - `test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts`
- `test/functional/apps/dashboard_elements/controls/options_list/options_list_dashboard_interaction.ts` ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [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 - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) (Refer above) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### 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) --- .../dashboard_app/_dashboard_app_strings.ts | 8 ++ .../top_nav/use_dashboard_menu_items.tsx | 74 +++++++++++++------ .../state/dashboard_container_reducers.ts | 5 +- .../_dashboard_listing_strings.ts | 41 +++++----- .../dashboard_listing/confirm_overlays.tsx | 18 +++-- .../customize_panel_action.tsx | 4 + .../group1/dashboard_unsaved_state.ts | 27 +++++-- .../options_list_dashboard_interaction.ts | 41 ++++++---- .../functional/page_objects/dashboard_page.ts | 21 ++++++ .../translations/translations/fr-FR.json | 5 -- .../translations/translations/ja-JP.json | 5 -- .../translations/translations/zh-CN.json | 5 -- 12 files changed, 172 insertions(+), 82 deletions(-) diff --git a/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts b/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts index e87a74d428f9a..dfc5a648f50aa 100644 --- a/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts +++ b/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts @@ -182,6 +182,14 @@ export const topNavStrings = { defaultMessage: 'Save as a new dashboard', }), }, + resetChanges: { + label: i18n.translate('dashboard.topNave.resetChangesButtonAriaLabel', { + defaultMessage: 'Reset', + }), + description: i18n.translate('dashboard.topNave.resetChangesConfigDescription', { + defaultMessage: 'Reset changes to dashboard', + }), + }, switchToViewMode: { label: i18n.translate('dashboard.topNave.cancelButtonAriaLabel', { defaultMessage: 'Switch to view mode', diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx index 73996719966a1..8ac32563d3e19 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx @@ -52,6 +52,7 @@ export const useDashboardMenuItems = ({ const hasOverlays = dashboard.select((state) => state.componentState.hasOverlays); const lastSavedId = dashboard.select((state) => state.componentState.lastSavedId); const dashboardTitle = dashboard.select((state) => state.explicitInput.title); + const viewMode = dashboard.select((state) => state.explicitInput.viewMode); /** * Show the Dashboard app's share menu @@ -109,21 +110,26 @@ export const useDashboardMenuItems = ({ }, [maybeRedirect, dashboard]); /** - * Returns to view mode. If the dashboard has unsaved changes shows a warning and resets to last saved state. + * Show the dashboard's "Confirm reset changes" modal. If confirmed: + * (1) reset the dashboard to the last saved state, and + * (2) if `switchToViewMode` is `true`, set the dashboard to view mode. */ - const returnToViewMode = useCallback(() => { - dashboard.clearOverlays(); - if (hasUnsavedChanges) { - confirmDiscardUnsavedChanges(() => { - batch(() => { - dashboard.resetToLastSavedState(); - dashboard.dispatch.setViewMode(ViewMode.VIEW); - }); - }); - return; - } - dashboard.dispatch.setViewMode(ViewMode.VIEW); - }, [dashboard, hasUnsavedChanges]); + const resetChanges = useCallback( + (switchToViewMode: boolean = false) => { + dashboard.clearOverlays(); + if (hasUnsavedChanges) { + confirmDiscardUnsavedChanges(() => { + batch(() => { + dashboard.resetToLastSavedState(); + if (switchToViewMode) dashboard.dispatch.setViewMode(ViewMode.VIEW); + }); + }, viewMode); + } else { + if (switchToViewMode) dashboard.dispatch.setViewMode(ViewMode.VIEW); + } + }, + [dashboard, hasUnsavedChanges, viewMode] + ); /** * Register all of the top nav configs that can be used by dashboard. @@ -184,7 +190,7 @@ export const useDashboardMenuItems = ({ id: 'cancel', disableButton: isSaveInProgress || !lastSavedId || hasOverlays, testId: 'dashboardViewOnlyMode', - run: () => returnToViewMode(), + run: () => resetChanges(true), } as TopNavMenuData, share: { @@ -215,9 +221,9 @@ export const useDashboardMenuItems = ({ quickSaveDashboard, hasUnsavedChanges, isSaveInProgress, - returnToViewMode, saveDashboardAs, setIsLabsShown, + resetChanges, hasOverlays, lastSavedId, isLabsShown, @@ -226,27 +232,53 @@ export const useDashboardMenuItems = ({ clone, ]); + const resetChangesMenuItem = useMemo(() => { + return { + ...topNavStrings.resetChanges, + id: 'reset', + testId: 'dashboardDiscardChangesMenuItem', + disableButton: + !hasUnsavedChanges || + hasOverlays || + (viewMode === ViewMode.EDIT && (isSaveInProgress || !lastSavedId)), + run: () => resetChanges(), + }; + }, [hasOverlays, lastSavedId, resetChanges, viewMode, isSaveInProgress, hasUnsavedChanges]); + /** * Build ordered menus for view and edit mode. */ const viewModeTopNavConfig = useMemo(() => { const labsMenuItem = isLabsEnabled ? [menuItems.labs] : []; const shareMenuItem = share ? [menuItems.share] : []; - const writePermissionsMenuItems = showWriteControls ? [menuItems.clone, menuItems.edit] : []; - return [...labsMenuItem, menuItems.fullScreen, ...shareMenuItem, ...writePermissionsMenuItems]; - }, [menuItems, share, showWriteControls, isLabsEnabled]); + const cloneMenuItem = showWriteControls ? [menuItems.clone] : []; + const editMenuItem = showWriteControls ? [menuItems.edit] : []; + return [ + ...labsMenuItem, + menuItems.fullScreen, + ...shareMenuItem, + ...cloneMenuItem, + resetChangesMenuItem, + ...editMenuItem, + ]; + }, [menuItems, share, showWriteControls, resetChangesMenuItem, isLabsEnabled]); const editModeTopNavConfig = useMemo(() => { const labsMenuItem = isLabsEnabled ? [menuItems.labs] : []; const shareMenuItem = share ? [menuItems.share] : []; const editModeItems: TopNavMenuData[] = []; if (lastSavedId) { - editModeItems.push(menuItems.saveAs, menuItems.switchToViewMode, menuItems.quickSave); + editModeItems.push( + menuItems.saveAs, + menuItems.switchToViewMode, + resetChangesMenuItem, + menuItems.quickSave + ); } else { editModeItems.push(menuItems.switchToViewMode, menuItems.saveAs); } return [...labsMenuItem, menuItems.settings, ...shareMenuItem, ...editModeItems]; - }, [lastSavedId, menuItems, share, isLabsEnabled]); + }, [lastSavedId, menuItems, share, resetChangesMenuItem, isLabsEnabled]); return { viewModeTopNavConfig, editModeTopNavConfig }; }; diff --git a/src/plugins/dashboard/public/dashboard_container/state/dashboard_container_reducers.ts b/src/plugins/dashboard/public/dashboard_container/state/dashboard_container_reducers.ts index dcf2167014f19..70bf3a7d65989 100644 --- a/src/plugins/dashboard/public/dashboard_container/state/dashboard_container_reducers.ts +++ b/src/plugins/dashboard/public/dashboard_container/state/dashboard_container_reducers.ts @@ -124,7 +124,10 @@ export const dashboardContainerReducers = { }, resetToLastSavedInput: (state: DashboardReduxState) => { - state.explicitInput = state.componentState.lastSavedInput; + state.explicitInput = { + ...state.componentState.lastSavedInput, + viewMode: state.explicitInput.viewMode, // keep current view mode when resetting + }; }, // ------------------------------------------------------------------------------ diff --git a/src/plugins/dashboard/public/dashboard_listing/_dashboard_listing_strings.ts b/src/plugins/dashboard/public/dashboard_listing/_dashboard_listing_strings.ts index df036a01fcf95..8887272da3d91 100644 --- a/src/plugins/dashboard/public/dashboard_listing/_dashboard_listing_strings.ts +++ b/src/plugins/dashboard/public/dashboard_listing/_dashboard_listing_strings.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { ViewMode } from '@kbn/embeddable-plugin/public'; import { i18n } from '@kbn/i18n'; export const dashboardListingErrorStrings = { @@ -91,32 +92,32 @@ export const dashboardUnsavedListingStrings = { defaultMessage: 'Continue editing', }), getDiscardAriaLabel: (title: string) => - i18n.translate('dashboard.listing.unsaved.discardAria', { - defaultMessage: 'Discard changes to {title}', + i18n.translate('dashboard.listing.unsaved.resetAria', { + defaultMessage: 'Reset changes to {title}', values: { title }, }), getDiscardTitle: () => - i18n.translate('dashboard.listing.unsaved.discardTitle', { - defaultMessage: 'Discard changes', + i18n.translate('dashboard.listing.unsaved.resetTitle', { + defaultMessage: 'Reset changes', }), }; -export const discardConfirmStrings = { - getDiscardTitle: () => - i18n.translate('dashboard.discardChangesConfirmModal.discardChangesTitle', { - defaultMessage: 'Discard changes to dashboard?', - }), - getDiscardSubtitle: () => - i18n.translate('dashboard.discardChangesConfirmModal.discardChangesDescription', { - defaultMessage: `Once you discard your changes, there's no getting them back.`, - }), - getDiscardConfirmButtonText: () => - i18n.translate('dashboard.discardChangesConfirmModal.confirmButtonLabel', { - defaultMessage: 'Discard changes', - }), - getDiscardCancelButtonText: () => - i18n.translate('dashboard.discardChangesConfirmModal.cancelButtonLabel', { - defaultMessage: 'Cancel', +export const resetConfirmStrings = { + getResetTitle: () => + i18n.translate('dashboard.resetChangesConfirmModal.resetChangesTitle', { + defaultMessage: 'Reset dashboard?', + }), + getResetSubtitle: (viewMode: ViewMode) => + viewMode === ViewMode.EDIT + ? i18n.translate('dashboard.discardChangesConfirmModal.discardChangesDescription', { + defaultMessage: `All unsaved changes will be lost.`, + }) + : i18n.translate('dashboard.resetChangesConfirmModal.resetChangesDescription', { + defaultMessage: `This dashboard will return to its last saved state. You might lose changes to filters and queries.`, + }), + getResetConfirmButtonText: () => + i18n.translate('dashboard.resetChangesConfirmModal.confirmButtonLabel', { + defaultMessage: 'Reset dashboard', }), }; diff --git a/src/plugins/dashboard/public/dashboard_listing/confirm_overlays.tsx b/src/plugins/dashboard/public/dashboard_listing/confirm_overlays.tsx index 03027cda242b9..40f4367059eff 100644 --- a/src/plugins/dashboard/public/dashboard_listing/confirm_overlays.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/confirm_overlays.tsx @@ -20,24 +20,28 @@ import { EuiText, EUI_MODAL_CANCEL_BUTTON, } from '@elastic/eui'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { pluginServices } from '../services/plugin_services'; -import { createConfirmStrings, discardConfirmStrings } from './_dashboard_listing_strings'; +import { createConfirmStrings, resetConfirmStrings } from './_dashboard_listing_strings'; export type DiscardOrKeepSelection = 'cancel' | 'discard' | 'keep'; -export const confirmDiscardUnsavedChanges = (discardCallback: () => void) => { +export const confirmDiscardUnsavedChanges = ( + discardCallback: () => void, + viewMode: ViewMode = ViewMode.EDIT // we want to show the danger modal on the listing page +) => { const { overlays: { openConfirm }, } = pluginServices.getServices(); - openConfirm(discardConfirmStrings.getDiscardSubtitle(), { - confirmButtonText: discardConfirmStrings.getDiscardConfirmButtonText(), - cancelButtonText: discardConfirmStrings.getDiscardCancelButtonText(), - buttonColor: 'danger', + openConfirm(resetConfirmStrings.getResetSubtitle(viewMode), { + confirmButtonText: resetConfirmStrings.getResetConfirmButtonText(), + buttonColor: viewMode === ViewMode.EDIT ? 'danger' : 'primary', + maxWidth: 500, defaultFocusedButton: EUI_MODAL_CANCEL_BUTTON, - title: discardConfirmStrings.getDiscardTitle(), + title: resetConfirmStrings.getResetTitle(), }).then((isConfirmed) => { if (isConfirmed) { discardCallback(); diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_panel/customize_panel_action.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_panel/customize_panel_action.tsx index ec6c2011ca53d..fc7a30efbe256 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_panel/customize_panel_action.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_panel/customize_panel_action.tsx @@ -129,6 +129,10 @@ export class CustomizePanelAction implements Action { size: 's', 'data-test-subj': 'customizePanel', + onClose: (overlayRef) => { + if (overlayTracker) overlayTracker.clearOverlays(); + overlayRef.close(); + }, } ); overlayTracker?.openOverlay(handle); diff --git a/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts b/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts index 7fbdc66313eba..51669b395b6f1 100644 --- a/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts +++ b/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts @@ -85,10 +85,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await validateQueryAndFilter(); }); - after(async () => { - // discard changes made in view mode - await PageObjects.dashboard.switchToEditMode(); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + it('can discard changes', async () => { + await PageObjects.dashboard.clickDiscardChanges(); + await PageObjects.dashboard.waitForRenderComplete(); + + const query = await queryBar.getQueryString(); + expect(query).to.eql(''); + const filterCount = await filterBar.getFilterCount(); + expect(filterCount).to.eql(0); }); }); @@ -140,8 +144,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(currentPanelCount).to.eql(unsavedPanelCount); }); - it('resets to original panel count after discarding changes', async () => { + it('can discard changes', async () => { + unsavedPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(unsavedPanelCount).to.eql(originalPanelCount + 2); + + await PageObjects.dashboard.clickDiscardChanges(); + const currentPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(currentPanelCount).to.eql(originalPanelCount); + }); + + it('resets to original panel count after switching to view mode and discarding changes', async () => { + await addPanels(); await PageObjects.header.waitUntilLoadingHasFinished(); + unsavedPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(unsavedPanelCount).to.eql(originalPanelCount + 2); + await PageObjects.dashboard.clickCancelOutOfEditMode(); await PageObjects.header.waitUntilLoadingHasFinished(); const currentPanelCount = await PageObjects.dashboard.getPanelCount(); diff --git a/test/functional/apps/dashboard_elements/controls/options_list/options_list_dashboard_interaction.ts b/test/functional/apps/dashboard_elements/controls/options_list/options_list_dashboard_interaction.ts index 1397de3712bb8..bda198fc16e8b 100644 --- a/test/functional/apps/dashboard_elements/controls/options_list/options_list_dashboard_interaction.ts +++ b/test/functional/apps/dashboard_elements/controls/options_list/options_list_dashboard_interaction.ts @@ -257,21 +257,36 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboard.clearUnsavedChanges(); }); - it('changes to selections can be discarded', async () => { - await dashboardControls.optionsListOpenPopover(controlId); - await dashboardControls.optionsListPopoverSelectOption('bark'); - await dashboardControls.optionsListEnsurePopoverIsClosed(controlId); - let selections = await dashboardControls.optionsListGetSelectionsString(controlId); - expect(selections).to.equal('hiss, grr, bark'); + describe('discarding changes', async () => { + describe('changes can be discarded', async () => { + let selections = ''; + + beforeEach(async () => { + await dashboardControls.optionsListOpenPopover(controlId); + await dashboardControls.optionsListPopoverSelectOption('bark'); + await dashboardControls.optionsListEnsurePopoverIsClosed(controlId); + selections = await dashboardControls.optionsListGetSelectionsString(controlId); + expect(selections).to.equal('hiss, grr, bark'); + }); - await dashboard.clickCancelOutOfEditMode(); - selections = await dashboardControls.optionsListGetSelectionsString(controlId); - expect(selections).to.equal('hiss, grr'); - }); + afterEach(async () => { + selections = await dashboardControls.optionsListGetSelectionsString(controlId); + expect(selections).to.equal('hiss, grr'); + }); - it('dashboard does not load with unsaved changes when changes are discarded', async () => { - await dashboard.switchToEditMode(); - await testSubjects.missingOrFail('dashboardUnsavedChangesBadge'); + it('by clicking the discard changes button', async () => { + await dashboard.clickDiscardChanges(); + }); + + it('by switching to view mode', async () => { + await dashboard.clickCancelOutOfEditMode(); + }); + }); + + it('dashboard does not load with unsaved changes when changes are discarded', async () => { + await dashboard.switchToEditMode(); + await testSubjects.missingOrFail('dashboardUnsavedChangesBadge'); + }); }); }); diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index 2fa2290a70a6c..28f30bd94aecf 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -309,6 +309,18 @@ export class DashboardPageObject extends FtrService { } } + public async clickDiscardChanges(accept = true) { + await this.retry.try(async () => { + await this.expectDiscardChangesButtonEnabled(); + this.log.debug('clickDiscardChanges'); + await this.testSubjects.click('dashboardDiscardChangesMenuItem'); + }); + await this.common.expectConfirmModalOpenState(true); + if (accept) { + await this.common.clickConfirmOnModal(); + } + } + public async clickQuickSave() { await this.retry.try(async () => { await this.expectQuickSaveButtonEnabled(); @@ -734,6 +746,15 @@ export class DashboardPageObject extends FtrService { await this.testSubjects.existOrFail('dashboardQuickSaveMenuItem'); } + public async expectDiscardChangesButtonEnabled() { + this.log.debug('expectDiscardChangesButtonEnabled'); + const quickSaveButton = await this.testSubjects.find('dashboardDiscardChangesMenuItem'); + const isDisabled = await quickSaveButton.getAttribute('disabled'); + if (isDisabled) { + throw new Error('Discard changes button disabled'); + } + } + public async expectQuickSaveButtonEnabled() { this.log.debug('expectQuickSaveButtonEnabled'); const quickSaveButton = await this.testSubjects.find('dashboardQuickSaveMenuItem'); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index cdfb1e09c6ae9..8d8b020e45426 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -1090,7 +1090,6 @@ "dashboard.addPanel.newEmbeddableAddedSuccessMessageTitle": "{savedObjectName} a été ajouté", "dashboard.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} a été ajouté", "dashboard.listing.createNewDashboard.newToKibanaDescription": "Vous êtes nouveau sur Kibana ? {sampleDataInstallLink} pour découvrir l'application.", - "dashboard.listing.unsaved.discardAria": "Ignorer les modifications apportées à {title}", "dashboard.listing.unsaved.editAria": "Continuer à modifier {title}", "dashboard.listing.unsaved.unsavedChangesTitle": "Vous avez des modifications non enregistrées dans le {dash} suivant :", "dashboard.loadingError.dashboardGridErrorMessage": "Impossible de charger le tableau de bord : {message}", @@ -1127,10 +1126,7 @@ "dashboard.dashboardPageTitle": "Tableaux de bord", "dashboard.dashboardWasSavedSuccessMessage": "Le tableau de bord \"{dashTitle}\" a été enregistré.", "dashboard.deleteError.toastDescription": "Erreur rencontrée lors de la suppression du tableau de bord", - "dashboard.discardChangesConfirmModal.cancelButtonLabel": "Annuler", - "dashboard.discardChangesConfirmModal.confirmButtonLabel": "Abandonner les modifications", "dashboard.discardChangesConfirmModal.discardChangesDescription": "Une fois les modifications ignorées, vous ne pourrez pas les récupérer.", - "dashboard.discardChangesConfirmModal.discardChangesTitle": "Ignorer les modifications apportées au tableau de bord ?", "dashboard.editingToolbar.addControlButtonTitle": "Ajouter un contrôle", "dashboard.editingToolbar.addTimeSliderControlButtonTitle": "Ajouter un contrôle de curseur temporel", "dashboard.editingToolbar.controlsButtonTitle": "Contrôles", @@ -1164,7 +1160,6 @@ "dashboard.listing.readonlyNoItemsTitle": "Aucun tableau de bord à afficher", "dashboard.listing.table.entityName": "tableau de bord", "dashboard.listing.table.entityNamePlural": "tableaux de bord", - "dashboard.listing.unsaved.discardTitle": "Abandonner les modifications", "dashboard.listing.unsaved.editTitle": "Poursuivre les modifications", "dashboard.listing.unsaved.loading": "Chargement", "dashboard.loadingError.dashboardNotFound": "Le tableau de bord demandé est introuvable.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c3aae5c75beb8..66dac0e4831d3 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1090,7 +1090,6 @@ "dashboard.addPanel.newEmbeddableAddedSuccessMessageTitle": "{savedObjectName}が追加されました", "dashboard.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName}が追加されました", "dashboard.listing.createNewDashboard.newToKibanaDescription": "Kibana は初心者ですか?{sampleDataInstallLink}してお試しください。", - "dashboard.listing.unsaved.discardAria": "{title}の変更を破棄", "dashboard.listing.unsaved.editAria": "{title}の編集を続行", "dashboard.listing.unsaved.unsavedChangesTitle": "次の{dash}には保存されていない変更があります:", "dashboard.loadingError.dashboardGridErrorMessage": "ダッシュボードが読み込めません:{message}", @@ -1127,10 +1126,7 @@ "dashboard.dashboardPageTitle": "ダッシュボード", "dashboard.dashboardWasSavedSuccessMessage": "ダッシュボード「{dashTitle}」が保存されました。", "dashboard.deleteError.toastDescription": "ダッシュボードの削除中にエラーが発生しました", - "dashboard.discardChangesConfirmModal.cancelButtonLabel": "キャンセル", - "dashboard.discardChangesConfirmModal.confirmButtonLabel": "変更を破棄", "dashboard.discardChangesConfirmModal.discardChangesDescription": "変更を破棄すると、元に戻すことはできません。", - "dashboard.discardChangesConfirmModal.discardChangesTitle": "ダッシュボードへの変更を破棄しますか?", "dashboard.editingToolbar.addControlButtonTitle": "コントロールを追加", "dashboard.editingToolbar.addTimeSliderControlButtonTitle": "時間スライダーコントロールを追加", "dashboard.editingToolbar.controlsButtonTitle": "コントロール", @@ -1164,7 +1160,6 @@ "dashboard.listing.readonlyNoItemsTitle": "表示するダッシュボードがありません", "dashboard.listing.table.entityName": "ダッシュボード", "dashboard.listing.table.entityNamePlural": "ダッシュボード", - "dashboard.listing.unsaved.discardTitle": "変更を破棄", "dashboard.listing.unsaved.editTitle": "編集を続行", "dashboard.listing.unsaved.loading": "読み込み中", "dashboard.loadingError.dashboardNotFound": "リクエストされたダッシュボードが見つかりませんでした。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 84bdf7528d7ec..6543233c2fbee 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1090,7 +1090,6 @@ "dashboard.addPanel.newEmbeddableAddedSuccessMessageTitle": "{savedObjectName} 已添加", "dashboard.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} 已添加", "dashboard.listing.createNewDashboard.newToKibanaDescription": "Kibana 新手?{sampleDataInstallLink}来试用一下。", - "dashboard.listing.unsaved.discardAria": "丢弃对 {title} 的更改", "dashboard.listing.unsaved.editAria": "继续编辑 {title}", "dashboard.listing.unsaved.unsavedChangesTitle": "在以下 {dash} 中有未保存更改:", "dashboard.loadingError.dashboardGridErrorMessage": "无法加载仪表板:{message}", @@ -1127,10 +1126,7 @@ "dashboard.dashboardPageTitle": "仪表板", "dashboard.dashboardWasSavedSuccessMessage": "仪表板“{dashTitle}”已保存", "dashboard.deleteError.toastDescription": "删除仪表板时发生错误", - "dashboard.discardChangesConfirmModal.cancelButtonLabel": "取消", - "dashboard.discardChangesConfirmModal.confirmButtonLabel": "放弃更改", "dashboard.discardChangesConfirmModal.discardChangesDescription": "放弃更改后,它们将无法恢复。", - "dashboard.discardChangesConfirmModal.discardChangesTitle": "放弃对仪表板所做的更改?", "dashboard.editingToolbar.addControlButtonTitle": "添加控件", "dashboard.editingToolbar.addTimeSliderControlButtonTitle": "添加时间滑块控件", "dashboard.editingToolbar.controlsButtonTitle": "控件", @@ -1164,7 +1160,6 @@ "dashboard.listing.readonlyNoItemsTitle": "没有可查看的仪表板", "dashboard.listing.table.entityName": "仪表板", "dashboard.listing.table.entityNamePlural": "仪表板", - "dashboard.listing.unsaved.discardTitle": "放弃更改", "dashboard.listing.unsaved.editTitle": "继续编辑", "dashboard.listing.unsaved.loading": "正在加载", "dashboard.loadingError.dashboardNotFound": "找不到请求的仪表板。",